diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/chat/protocols/matrix/lib/matrix-sdk/http-api | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/chat/protocols/matrix/lib/matrix-sdk/http-api')
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 |