summaryrefslogtreecommitdiffstats
path: root/comm/chat/protocols/matrix/lib/matrix-sdk/http-api
diff options
context:
space:
mode:
Diffstat (limited to 'comm/chat/protocols/matrix/lib/matrix-sdk/http-api')
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/http-api/errors.js83
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/http-api/fetch.js265
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/http-api/index.js240
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/http-api/interface.js27
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/http-api/method.js29
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/http-api/prefix.js39
-rw-r--r--comm/chat/protocols/matrix/lib/matrix-sdk/http-api/utils.js143
7 files changed, 826 insertions, 0 deletions
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/errors.js b/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/errors.js
new file mode 100644
index 0000000000..067053b548
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/errors.js
@@ -0,0 +1,83 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.MatrixError = exports.HTTPError = exports.ConnectionError = void 0;
+function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
+function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+/**
+ * Construct a generic HTTP error. This is a JavaScript Error with additional information
+ * specific to HTTP responses.
+ * @param msg - The error message to include.
+ * @param httpStatus - The HTTP response status code.
+ */
+class HTTPError extends Error {
+ constructor(msg, httpStatus) {
+ super(msg);
+ this.httpStatus = httpStatus;
+ }
+}
+exports.HTTPError = HTTPError;
+class MatrixError extends HTTPError {
+ /**
+ * Construct a Matrix error. This is a JavaScript Error with additional
+ * information specific to the standard Matrix error response.
+ * @param errorJson - The Matrix error JSON returned from the homeserver.
+ * @param httpStatus - The numeric HTTP status code given
+ */
+ constructor(errorJson = {}, httpStatus, url, event) {
+ let message = errorJson.error || "Unknown message";
+ if (httpStatus) {
+ message = `[${httpStatus}] ${message}`;
+ }
+ if (url) {
+ message = `${message} (${url})`;
+ }
+ super(`MatrixError: ${message}`, httpStatus);
+ this.httpStatus = httpStatus;
+ this.url = url;
+ this.event = event;
+ // The Matrix 'errcode' value, e.g. "M_FORBIDDEN".
+ _defineProperty(this, "errcode", void 0);
+ // The raw Matrix error JSON used to construct this object.
+ _defineProperty(this, "data", void 0);
+ this.errcode = errorJson.errcode;
+ this.name = errorJson.errcode || "Unknown error code";
+ this.data = errorJson;
+ }
+}
+
+/**
+ * Construct a ConnectionError. This is a JavaScript Error indicating
+ * that a request failed because of some error with the connection, either
+ * CORS was not correctly configured on the server, the server didn't response,
+ * the request timed out, or the internet connection on the client side went down.
+ */
+exports.MatrixError = MatrixError;
+class ConnectionError extends Error {
+ constructor(message, cause) {
+ super(message + (cause ? `: ${cause.message}` : ""));
+ }
+ get name() {
+ return "ConnectionError";
+ }
+}
+exports.ConnectionError = ConnectionError; \ No newline at end of file
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/fetch.js b/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/fetch.js
new file mode 100644
index 0000000000..5450392423
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/fetch.js
@@ -0,0 +1,265 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.FetchHttpApi = void 0;
+var _utils = require("../utils");
+var _method = require("./method");
+var _errors = require("./errors");
+var _interface = require("./interface");
+var _utils2 = require("./utils");
+function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
+function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } /*
+ Copyright 2022 The Matrix.org Foundation C.I.C.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */ /**
+ * This is an internal module. See {@link MatrixHttpApi} for the public class.
+ */
+class FetchHttpApi {
+ constructor(eventEmitter, opts) {
+ this.eventEmitter = eventEmitter;
+ this.opts = opts;
+ _defineProperty(this, "abortController", new AbortController());
+ (0, _utils.checkObjectHasKeys)(opts, ["baseUrl", "prefix"]);
+ opts.onlyData = !!opts.onlyData;
+ opts.useAuthorizationHeader = opts.useAuthorizationHeader ?? true;
+ }
+ abort() {
+ this.abortController.abort();
+ this.abortController = new AbortController();
+ }
+ fetch(resource, options) {
+ if (this.opts.fetchFn) {
+ return this.opts.fetchFn(resource, options);
+ }
+ return global.fetch(resource, options);
+ }
+
+ /**
+ * Sets the base URL for the identity server
+ * @param url - The new base url
+ */
+ setIdBaseUrl(url) {
+ this.opts.idBaseUrl = url;
+ }
+ idServerRequest(method, path, params, prefix, accessToken) {
+ if (!this.opts.idBaseUrl) {
+ throw new Error("No identity server base URL set");
+ }
+ let queryParams = undefined;
+ let body = undefined;
+ if (method === _method.Method.Get) {
+ queryParams = params;
+ } else {
+ body = params;
+ }
+ const fullUri = this.getUrl(path, queryParams, prefix, this.opts.idBaseUrl);
+ const opts = {
+ json: true,
+ headers: {}
+ };
+ if (accessToken) {
+ opts.headers.Authorization = `Bearer ${accessToken}`;
+ }
+ return this.requestOtherUrl(method, fullUri, body, opts);
+ }
+
+ /**
+ * Perform an authorised request to the homeserver.
+ * @param method - The HTTP method e.g. "GET".
+ * @param path - The HTTP path <b>after</b> the supplied prefix e.g.
+ * "/createRoom".
+ *
+ * @param queryParams - A dict of query params (these will NOT be
+ * urlencoded). If unspecified, there will be no query params.
+ *
+ * @param body - The HTTP JSON body.
+ *
+ * @param opts - additional options. If a number is specified,
+ * this is treated as `opts.localTimeoutMs`.
+ *
+ * @returns Promise which resolves to
+ * ```
+ * {
+ * data: {Object},
+ * headers: {Object},
+ * code: {Number},
+ * }
+ * ```
+ * If `onlyData` is set, this will resolve to the `data` object only.
+ * @returns Rejects with an error if a problem occurred.
+ * This includes network problems and Matrix-specific error JSON.
+ */
+ authedRequest(method, path, queryParams, body, opts = {}) {
+ if (!queryParams) queryParams = {};
+ if (this.opts.accessToken) {
+ if (this.opts.useAuthorizationHeader) {
+ if (!opts.headers) {
+ opts.headers = {};
+ }
+ if (!opts.headers.Authorization) {
+ opts.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;
+ }
+ }
+ const requestPromise = this.request(method, path, queryParams, body, opts);
+ requestPromise.catch(err => {
+ if (err.errcode == "M_UNKNOWN_TOKEN" && !opts?.inhibitLogoutEmit) {
+ this.eventEmitter.emit(_interface.HttpApiEvent.SessionLoggedOut, err);
+ } else if (err.errcode == "M_CONSENT_NOT_GIVEN") {
+ this.eventEmitter.emit(_interface.HttpApiEvent.NoConsent, err.message, err.data.consent_uri);
+ }
+ });
+
+ // 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.
+ * @param method - The HTTP method e.g. "GET".
+ * @param path - The HTTP path <b>after</b> the supplied prefix e.g.
+ * "/createRoom".
+ *
+ * @param queryParams - A dict of query params (these will NOT be
+ * urlencoded). If unspecified, there will be no query params.
+ *
+ * @param body - The HTTP JSON body.
+ *
+ * @param opts - additional options
+ *
+ * @returns Promise which resolves to
+ * ```
+ * {
+ * data: {Object},
+ * headers: {Object},
+ * code: {Number},
+ * }
+ * ```
+ * If `onlyData</code> is set, this will resolve to the <code>data`
+ * object only.
+ * @returns Rejects with an error if a problem
+ * occurred. This includes network problems and Matrix-specific error JSON.
+ */
+ request(method, path, queryParams, body, opts) {
+ const fullUri = this.getUrl(path, queryParams, opts?.prefix, opts?.baseUrl);
+ return this.requestOtherUrl(method, fullUri, body, opts);
+ }
+
+ /**
+ * Perform a request to an arbitrary URL.
+ * @param method - The HTTP method e.g. "GET".
+ * @param url - The HTTP URL object.
+ *
+ * @param body - The HTTP JSON body.
+ *
+ * @param opts - additional options
+ *
+ * @returns Promise which resolves to data unless `onlyData` is specified as false,
+ * where the resolved value will be a fetch Response object.
+ * @returns Rejects with an error if a problem
+ * occurred. This includes network problems and Matrix-specific error JSON.
+ */
+ async requestOtherUrl(method, url, body, opts = {}) {
+ const headers = Object.assign({}, opts.headers || {});
+ const json = opts.json ?? true;
+ // We can't use getPrototypeOf here as objects made in other contexts e.g. over postMessage won't have same ref
+ const jsonBody = json && body?.constructor?.name === Object.name;
+ if (json) {
+ if (jsonBody && !headers["Content-Type"]) {
+ headers["Content-Type"] = "application/json";
+ }
+ if (!headers["Accept"]) {
+ headers["Accept"] = "application/json";
+ }
+ }
+ const timeout = opts.localTimeoutMs ?? this.opts.localTimeoutMs;
+ const keepAlive = opts.keepAlive ?? false;
+ const signals = [this.abortController.signal];
+ if (timeout !== undefined) {
+ signals.push((0, _utils2.timeoutSignal)(timeout));
+ }
+ if (opts.abortSignal) {
+ signals.push(opts.abortSignal);
+ }
+ let data;
+ if (jsonBody) {
+ data = JSON.stringify(body);
+ } else {
+ data = body;
+ }
+ const {
+ signal,
+ cleanup
+ } = (0, _utils2.anySignal)(signals);
+ let res;
+ try {
+ res = await this.fetch(url, {
+ signal,
+ method,
+ body: data,
+ headers,
+ mode: "cors",
+ redirect: "follow",
+ referrer: "",
+ referrerPolicy: "no-referrer",
+ cache: "no-cache",
+ credentials: "omit",
+ // we send credentials via headers
+ keepalive: keepAlive
+ });
+ } catch (e) {
+ if (e.name === "AbortError") {
+ throw e;
+ }
+ throw new _errors.ConnectionError("fetch failed", e);
+ } finally {
+ cleanup();
+ }
+ if (!res.ok) {
+ throw (0, _utils2.parseErrorResponse)(res, await res.text());
+ }
+ if (this.opts.onlyData) {
+ return json ? res.json() : res.text();
+ }
+ return res;
+ }
+
+ /**
+ * Form and return a homeserver request URL based on the given path params and prefix.
+ * @param path - The HTTP path <b>after</b> the supplied prefix e.g. "/createRoom".
+ * @param queryParams - A dict of query params (these will NOT be urlencoded).
+ * @param prefix - The full prefix to use e.g. "/_matrix/client/v2_alpha", defaulting to this.opts.prefix.
+ * @param baseUrl - The baseUrl to use e.g. "https://matrix.org", defaulting to this.opts.baseUrl.
+ * @returns URL
+ */
+ getUrl(path, queryParams, prefix, baseUrl) {
+ const baseUrlWithFallback = baseUrl ?? this.opts.baseUrl;
+ const baseUrlWithoutTrailingSlash = baseUrlWithFallback.endsWith("/") ? baseUrlWithFallback.slice(0, -1) : baseUrlWithFallback;
+ const url = new URL(baseUrlWithoutTrailingSlash + (prefix ?? this.opts.prefix) + path);
+ if (queryParams) {
+ (0, _utils.encodeParams)(queryParams, url.searchParams);
+ }
+ return url;
+ }
+}
+exports.FetchHttpApi = FetchHttpApi; \ No newline at end of file
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/index.js b/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/index.js
new file mode 100644
index 0000000000..c9425eec60
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/index.js
@@ -0,0 +1,240 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+var _exportNames = {
+ MatrixHttpApi: true
+};
+exports.MatrixHttpApi = void 0;
+var _fetch = require("./fetch");
+var _prefix = require("./prefix");
+Object.keys(_prefix).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
+ if (key in exports && exports[key] === _prefix[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function () {
+ return _prefix[key];
+ }
+ });
+});
+var _utils = require("../utils");
+var callbacks = _interopRequireWildcard(require("../realtime-callbacks"));
+var _method = require("./method");
+Object.keys(_method).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
+ if (key in exports && exports[key] === _method[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function () {
+ return _method[key];
+ }
+ });
+});
+var _errors = require("./errors");
+Object.keys(_errors).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
+ if (key in exports && exports[key] === _errors[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function () {
+ return _errors[key];
+ }
+ });
+});
+var _utils2 = require("./utils");
+Object.keys(_utils2).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
+ if (key in exports && exports[key] === _utils2[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function () {
+ return _utils2[key];
+ }
+ });
+});
+var _interface = require("./interface");
+Object.keys(_interface).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
+ if (key in exports && exports[key] === _interface[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function () {
+ return _interface[key];
+ }
+ });
+});
+function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
+function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
+function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
+function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } /*
+ Copyright 2022 The Matrix.org Foundation C.I.C.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+class MatrixHttpApi extends _fetch.FetchHttpApi {
+ constructor(...args) {
+ super(...args);
+ _defineProperty(this, "uploads", []);
+ }
+ /**
+ * Upload content to the homeserver
+ *
+ * @param file - The object to upload. On a browser, something that
+ * can be sent to XMLHttpRequest.send (typically a File). Under node.js,
+ * a Buffer, String or ReadStream.
+ *
+ * @param opts - options object
+ *
+ * @returns Promise which resolves to response object, as
+ * determined by this.opts.onlyData, opts.rawResponse, and
+ * opts.onlyContentUri. Rejects with an error (usually a MatrixError).
+ */
+ uploadContent(file, opts = {}) {
+ const includeFilename = opts.includeFilename ?? true;
+ const abortController = opts.abortController ?? new AbortController();
+
+ // 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 upload = {
+ loaded: 0,
+ total: 0,
+ abortController
+ };
+ const deferred = (0, _utils.defer)();
+ if (global.XMLHttpRequest) {
+ const xhr = new global.XMLHttpRequest();
+ const timeoutFn = function () {
+ xhr.abort();
+ deferred.reject(new Error("Timeout"));
+ };
+
+ // 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 () {
+ switch (xhr.readyState) {
+ case global.XMLHttpRequest.DONE:
+ callbacks.clearTimeout(timeoutTimer);
+ try {
+ if (xhr.status === 0) {
+ throw new DOMException(xhr.statusText, "AbortError"); // mimic fetch API
+ }
+
+ if (!xhr.responseText) {
+ throw new Error("No response body.");
+ }
+ if (xhr.status >= 400) {
+ deferred.reject((0, _utils2.parseErrorResponse)(xhr, xhr.responseText));
+ } else {
+ deferred.resolve(JSON.parse(xhr.responseText));
+ }
+ } catch (err) {
+ if (err.name === "AbortError") {
+ deferred.reject(err);
+ return;
+ }
+ deferred.reject(new _errors.ConnectionError("request failed", err));
+ }
+ break;
+ }
+ };
+ xhr.upload.onprogress = ev => {
+ callbacks.clearTimeout(timeoutTimer);
+ upload.loaded = ev.loaded;
+ upload.total = ev.total;
+ timeoutTimer = callbacks.setTimeout(timeoutFn, 30000);
+ opts.progressHandler?.({
+ loaded: ev.loaded,
+ total: ev.total
+ });
+ };
+ const url = this.getUrl("/upload", undefined, _prefix.MediaPrefix.R0);
+ if (includeFilename && fileName) {
+ url.searchParams.set("filename", encodeURIComponent(fileName));
+ }
+ if (!this.opts.useAuthorizationHeader && this.opts.accessToken) {
+ url.searchParams.set("access_token", encodeURIComponent(this.opts.accessToken));
+ }
+ xhr.open(_method.Method.Post, url.href);
+ if (this.opts.useAuthorizationHeader && this.opts.accessToken) {
+ xhr.setRequestHeader("Authorization", "Bearer " + this.opts.accessToken);
+ }
+ xhr.setRequestHeader("Content-Type", contentType);
+ xhr.send(file);
+ abortController.signal.addEventListener("abort", () => {
+ xhr.abort();
+ });
+ } else {
+ const queryParams = {};
+ if (includeFilename && fileName) {
+ queryParams.filename = fileName;
+ }
+ const headers = {
+ "Content-Type": contentType
+ };
+ this.authedRequest(_method.Method.Post, "/upload", queryParams, file, {
+ prefix: _prefix.MediaPrefix.R0,
+ headers,
+ abortSignal: abortController.signal
+ }).then(response => {
+ return this.opts.onlyData ? response : response.json();
+ }).then(deferred.resolve, deferred.reject);
+ }
+
+ // remove the upload from the list on completion
+ upload.promise = deferred.promise.finally(() => {
+ (0, _utils.removeElement)(this.uploads, elem => elem === upload);
+ });
+ abortController.signal.addEventListener("abort", () => {
+ (0, _utils.removeElement)(this.uploads, elem => elem === upload);
+ deferred.reject(new DOMException("Aborted", "AbortError"));
+ });
+ this.uploads.push(upload);
+ return upload.promise;
+ }
+ cancelUpload(promise) {
+ const upload = this.uploads.find(u => u.promise === promise);
+ if (upload) {
+ upload.abortController.abort();
+ return true;
+ }
+ return false;
+ }
+ getCurrentUploads() {
+ return this.uploads;
+ }
+
+ /**
+ * Get the content repository url with query parameters.
+ * @returns An object with a 'base', 'path' and 'params' for base URL,
+ * path and query parameters respectively.
+ */
+ getContentUri() {
+ return {
+ base: this.opts.baseUrl,
+ path: _prefix.MediaPrefix.R0 + "/upload",
+ params: {
+ access_token: this.opts.accessToken
+ }
+ };
+ }
+}
+exports.MatrixHttpApi = MatrixHttpApi; \ No newline at end of file
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/interface.js b/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/interface.js
new file mode 100644
index 0000000000..4ee57a29b0
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/interface.js
@@ -0,0 +1,27 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.HttpApiEvent = void 0;
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+let HttpApiEvent = /*#__PURE__*/function (HttpApiEvent) {
+ HttpApiEvent["SessionLoggedOut"] = "Session.logged_out";
+ HttpApiEvent["NoConsent"] = "no_consent";
+ return HttpApiEvent;
+}({});
+exports.HttpApiEvent = HttpApiEvent; \ No newline at end of file
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/method.js b/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/method.js
new file mode 100644
index 0000000000..cab6c3e720
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/method.js
@@ -0,0 +1,29 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.Method = void 0;
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+let Method = /*#__PURE__*/function (Method) {
+ Method["Get"] = "GET";
+ Method["Put"] = "PUT";
+ Method["Post"] = "POST";
+ Method["Delete"] = "DELETE";
+ return Method;
+}({});
+exports.Method = Method; \ No newline at end of file
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/prefix.js b/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/prefix.js
new file mode 100644
index 0000000000..3bc37083b3
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/prefix.js
@@ -0,0 +1,39 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.MediaPrefix = exports.IdentityPrefix = exports.ClientPrefix = void 0;
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+let ClientPrefix = /*#__PURE__*/function (ClientPrefix) {
+ ClientPrefix["R0"] = "/_matrix/client/r0";
+ ClientPrefix["V1"] = "/_matrix/client/v1";
+ ClientPrefix["V3"] = "/_matrix/client/v3";
+ ClientPrefix["Unstable"] = "/_matrix/client/unstable";
+ return ClientPrefix;
+}({});
+exports.ClientPrefix = ClientPrefix;
+let IdentityPrefix = /*#__PURE__*/function (IdentityPrefix) {
+ IdentityPrefix["V2"] = "/_matrix/identity/v2";
+ return IdentityPrefix;
+}({});
+exports.IdentityPrefix = IdentityPrefix;
+let MediaPrefix = /*#__PURE__*/function (MediaPrefix) {
+ MediaPrefix["R0"] = "/_matrix/media/r0";
+ return MediaPrefix;
+}({});
+exports.MediaPrefix = MediaPrefix; \ No newline at end of file
diff --git a/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/utils.js b/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/utils.js
new file mode 100644
index 0000000000..a39b6dc2bd
--- /dev/null
+++ b/comm/chat/protocols/matrix/lib/matrix-sdk/http-api/utils.js
@@ -0,0 +1,143 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.anySignal = anySignal;
+exports.parseErrorResponse = parseErrorResponse;
+exports.retryNetworkOperation = retryNetworkOperation;
+exports.timeoutSignal = timeoutSignal;
+var _contentType = require("content-type");
+var _logger = require("../logger");
+var _utils = require("../utils");
+var _errors = require("./errors");
+/*
+Copyright 2022 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Ponyfill for https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout
+function timeoutSignal(ms) {
+ const controller = new AbortController();
+ setTimeout(() => {
+ controller.abort();
+ }, ms);
+ return controller.signal;
+}
+function anySignal(signals) {
+ const controller = new AbortController();
+ function cleanup() {
+ for (const signal of signals) {
+ signal.removeEventListener("abort", onAbort);
+ }
+ }
+ function onAbort() {
+ controller.abort();
+ cleanup();
+ }
+ for (const signal of signals) {
+ if (signal.aborted) {
+ onAbort();
+ break;
+ }
+ signal.addEventListener("abort", onAbort);
+ }
+ return {
+ signal: controller.signal,
+ cleanup
+ };
+}
+
+/**
+ * Attempt to turn an HTTP error response into a Javascript Error.
+ *
+ * If it is a JSON response, we will parse it into a MatrixError. Otherwise
+ * we return a generic Error.
+ *
+ * @param response - response object
+ * @param body - raw body of the response
+ * @returns
+ */
+function parseErrorResponse(response, body) {
+ let contentType;
+ try {
+ contentType = getResponseContentType(response);
+ } catch (e) {
+ return e;
+ }
+ if (contentType?.type === "application/json" && body) {
+ return new _errors.MatrixError(JSON.parse(body), response.status, isXhr(response) ? response.responseURL : response.url);
+ }
+ if (contentType?.type === "text/plain") {
+ return new _errors.HTTPError(`Server returned ${response.status} error: ${body}`, response.status);
+ }
+ return new _errors.HTTPError(`Server returned ${response.status} error`, response.status);
+}
+function isXhr(response) {
+ return "getResponseHeader" in response;
+}
+
+/**
+ * extract the Content-Type header from the response object, and
+ * parse it to a `{type, parameters}` object.
+ *
+ * returns null if no content-type header could be found.
+ *
+ * @param response - response object
+ * @returns parsed content-type header, or null if not found
+ */
+function getResponseContentType(response) {
+ let contentType;
+ if (isXhr(response)) {
+ contentType = response.getResponseHeader("Content-Type");
+ } else {
+ contentType = response.headers.get("Content-Type");
+ }
+ if (!contentType) return null;
+ try {
+ return (0, _contentType.parse)(contentType);
+ } catch (e) {
+ throw new Error(`Error parsing Content-Type '${contentType}': ${e}`);
+ }
+}
+
+/**
+ * Retries a network operation run in a callback.
+ * @param maxAttempts - maximum attempts to try
+ * @param callback - callback that returns a promise of the network operation. If rejected with ConnectionError, it will be retried by calling the callback again.
+ * @returns the result of the network operation
+ * @throws {@link ConnectionError} If after maxAttempts the callback still throws ConnectionError
+ */
+async function retryNetworkOperation(maxAttempts, callback) {
+ let attempts = 0;
+ let lastConnectionError = null;
+ while (attempts < maxAttempts) {
+ try {
+ if (attempts > 0) {
+ const timeout = 1000 * Math.pow(2, attempts);
+ _logger.logger.log(`network operation failed ${attempts} times, retrying in ${timeout}ms...`);
+ await (0, _utils.sleep)(timeout);
+ }
+ return await callback();
+ } catch (err) {
+ if (err instanceof _errors.ConnectionError) {
+ attempts += 1;
+ lastConnectionError = err;
+ } else {
+ throw err;
+ }
+ }
+ }
+ throw lastConnectionError;
+} \ No newline at end of file