UNPKG

matrix-js-sdk

Version:
255 lines (241 loc) 8.66 kB
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; /* Copyright 2024 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ import { logger } from "../../logger.js"; import { sleep } from "../../utils.js"; import { ClientRendezvousFailureReason, MSC4108FailureReason } from "../index.js"; import { Method } from "../../matrix.js"; import { ClientPrefix } from "../../http-api/index.js"; /** * Prototype of the unstable [MSC4108](https://github.com/matrix-org/matrix-spec-proposals/pull/4108) * insecure rendezvous session protocol. * @experimental Note that this is UNSTABLE and may have breaking changes without notice. */ export class MSC4108RendezvousSession { constructor(_ref) { var { fetchFn, onFailure, url, client, fallbackRzServer } = _ref; _defineProperty(this, "url", void 0); _defineProperty(this, "client", void 0); _defineProperty(this, "fallbackRzServer", void 0); _defineProperty(this, "fetchFn", void 0); _defineProperty(this, "onFailure", void 0); _defineProperty(this, "etag", void 0); _defineProperty(this, "expiresAt", void 0); _defineProperty(this, "expiresTimer", void 0); _defineProperty(this, "_cancelled", false); _defineProperty(this, "_ready", false); this.fetchFn = fetchFn; this.onFailure = onFailure; this.client = client; this.fallbackRzServer = fallbackRzServer; this.url = url; } /** * Returns whether the channel is ready to be used. */ get ready() { return this._ready; } /** * Returns whether the channel has been cancelled. */ get cancelled() { return this._cancelled; } fetch(resource, options) { if (this.fetchFn) { return this.fetchFn(resource, options); } return globalThis.fetch(resource, options); } getPostEndpoint() { var _this = this; return _asyncToGenerator(function* () { if (_this.client) { try { if (yield _this.client.doesServerSupportUnstableFeature("org.matrix.msc4108")) { return _this.client.http.getUrl("/org.matrix.msc4108/rendezvous", undefined, ClientPrefix.Unstable).toString(); } } catch (err) { logger.warn("Failed to get unstable features", err); } } return _this.fallbackRzServer; })(); } /** * Sends data via the rendezvous channel. * @param data the payload to send */ send(data) { var _this2 = this; return _asyncToGenerator(function* () { var _this2$url, _res$headers$get; if (_this2._cancelled) { return; } var method = _this2.url ? Method.Put : Method.Post; var uri = (_this2$url = _this2.url) !== null && _this2$url !== void 0 ? _this2$url : yield _this2.getPostEndpoint(); if (!uri) { throw new Error("Invalid rendezvous URI"); } var headers = { "content-type": "text/plain" }; // if we didn't create the rendezvous channel, we need to fetch the first etag if needed if (!_this2.etag && _this2.url) { yield _this2.receive(); } if (_this2.etag) { headers["if-match"] = _this2.etag; } logger.info("=> ".concat(method, " ").concat(uri, " with ").concat(data, " if-match: ").concat(_this2.etag)); var res = yield _this2.fetch(uri, { method, headers, body: data, redirect: "follow" }); if (res.status === 404) { return _this2.cancel(ClientRendezvousFailureReason.Unknown); } _this2.etag = (_res$headers$get = res.headers.get("etag")) !== null && _res$headers$get !== void 0 ? _res$headers$get : undefined; logger.info("Received etag: ".concat(_this2.etag)); if (method === Method.Post) { var expires = res.headers.get("expires"); if (expires) { if (_this2.expiresTimer) { clearTimeout(_this2.expiresTimer); _this2.expiresTimer = undefined; } _this2.expiresAt = new Date(expires); _this2.expiresTimer = setTimeout(() => { _this2.expiresTimer = undefined; _this2.cancel(ClientRendezvousFailureReason.Expired); }, _this2.expiresAt.getTime() - Date.now()); } // MSC4108: we expect a JSON response with a rendezvous URL var json = yield res.json(); if (typeof json.url !== "string") { throw new Error("No rendezvous URL given"); } _this2.url = json.url; _this2._ready = true; } })(); } /** * Receives data from the rendezvous channel. * @return the returned promise won't resolve until new data is acquired or the channel is closed either by the server or the other party. */ receive() { var _this3 = this; return _asyncToGenerator(function* () { if (!_this3.url) { throw new Error("Rendezvous not set up"); } // eslint-disable-next-line no-constant-condition while (true) { var _poll$headers$get; if (_this3._cancelled) { return undefined; } var headers = {}; if (_this3.etag) { headers["if-none-match"] = _this3.etag; } logger.info("=> GET ".concat(_this3.url, " if-none-match: ").concat(_this3.etag)); var poll = yield _this3.fetch(_this3.url, { method: Method.Get, headers }); if (poll.status === 404) { yield _this3.cancel(ClientRendezvousFailureReason.Unknown); return undefined; } // rely on server expiring the channel rather than checking ourselves var etag = (_poll$headers$get = poll.headers.get("etag")) !== null && _poll$headers$get !== void 0 ? _poll$headers$get : undefined; if (poll.headers.get("content-type") !== "text/plain") { _this3.etag = etag; } else if (poll.status === 200) { if (!etag) { // Some browsers & extensions block the ETag header for anti-tracking purposes // We try and detect this so the client can give the user a somewhat helpful message yield _this3.cancel(ClientRendezvousFailureReason.ETagMissing); return undefined; } _this3.etag = etag; var text = yield poll.text(); logger.info("Received: ".concat(text, " with etag ").concat(_this3.etag)); return text; } yield sleep(1000); } })(); } /** * Cancels the rendezvous channel. * If the reason is user_declined or user_cancelled then the channel will also be closed. * @param reason the reason to cancel with */ cancel(reason) { var _this4 = this; return _asyncToGenerator(function* () { var _this4$onFailure; if (_this4._cancelled) return; if (_this4.expiresTimer) { clearTimeout(_this4.expiresTimer); _this4.expiresTimer = undefined; } if (reason === ClientRendezvousFailureReason.Unknown && _this4.expiresAt && _this4.expiresAt.getTime() < Date.now()) { reason = ClientRendezvousFailureReason.Expired; } _this4._cancelled = true; _this4._ready = false; (_this4$onFailure = _this4.onFailure) === null || _this4$onFailure === void 0 || _this4$onFailure.call(_this4, reason); if (reason === ClientRendezvousFailureReason.UserDeclined || reason === MSC4108FailureReason.UserCancelled) { yield _this4.close(); } })(); } /** * Closes the rendezvous channel. */ close() { var _this5 = this; return _asyncToGenerator(function* () { if (_this5.expiresTimer) { clearTimeout(_this5.expiresTimer); _this5.expiresTimer = undefined; } if (!_this5.url) return; try { var method = Method.Delete; logger.info("=> ".concat(method, " ").concat(_this5.url)); yield _this5.fetch(_this5.url, { method }); } catch (e) { logger.warn(e); } })(); } } //# sourceMappingURL=MSC4108RendezvousSession.js.map