UNPKG

matrix-js-sdk

Version:
1,132 lines (1,078 loc) 303 kB
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties"; import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; var _excluded = ["server", "limit", "since"]; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /* Copyright 2015-2023 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 MatrixClient} for the public class. */ import { SyncApi, SyncState } from "./sync.js"; import { EventStatus, MatrixEvent, MatrixEventEvent } from "./models/event.js"; import { StubStore } from "./store/stub.js"; import { createNewMatrixCall, supportsMatrixCall } from "./webrtc/call.js"; import { Filter } from "./filter.js"; import { CallEventHandler } from "./webrtc/callEventHandler.js"; import { GroupCallEventHandler } from "./webrtc/groupCallEventHandler.js"; import * as utils from "./utils.js"; import { deepCompare, noUnsafeEventProps, replaceParam, safeSet, sleep } from "./utils.js"; import { Direction, EventTimeline } from "./models/event-timeline.js"; import { PushProcessor } from "./pushprocessor.js"; import { AutoDiscovery } from "./autodiscovery.js"; import { encodeUnpaddedBase64Url } from "./base64.js"; import { TypedReEmitter } from "./ReEmitter.js"; import { logger } from "./logger.js"; import { SERVICE_TYPES } from "./service-types.js"; import { ClientPrefix, IdentityPrefix, MatrixError, MatrixHttpApi, MediaPrefix, Method, retryNetworkOperation } from "./http-api/index.js"; import { User, UserEvent } from "./models/user.js"; import { getHttpUriForMxc } from "./content-repo.js"; import { SearchResult } from "./models/search-result.js"; import * as ContentHelpers from "./content-helpers.js"; import { NotificationCountType } from "./models/room.js"; import { RoomMemberEvent } from "./models/room-member.js"; import { RoomStateEvent } from "./models/room-state.js"; import { isSendDelayedEventRequestOpts, UpdateDelayedEventAction } from "./@types/requests.js"; import { EventType, LOCAL_NOTIFICATION_SETTINGS_PREFIX, MSC3912_RELATION_BASED_REDACTIONS_PROP, MsgType, PUSHER_ENABLED, RelationType, RoomCreateTypeField, RoomType, UNSTABLE_MSC3088_ENABLED, UNSTABLE_MSC3088_PURPOSE, UNSTABLE_MSC3089_TREE_SUBTYPE } from "./@types/event.js"; import { GuestAccess, HistoryVisibility, Preset } from "./@types/partials.js"; import { eventMapperFor } from "./event-mapper.js"; import { secureRandomString } from "./randomstring.js"; import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE, MSC3089TreeSpace } from "./models/MSC3089TreeSpace.js"; import { SearchOrderBy } from "./@types/search.js"; import { PushRuleActionName, PushRuleKind } from "./@types/PushRules.js"; import { GroupCall } from "./webrtc/groupCall.js"; import { MediaHandler } from "./webrtc/mediaHandler.js"; import { TypedEventEmitter } from "./models/typed-event-emitter.js"; import { MAIN_ROOM_TIMELINE, ReceiptType } from "./@types/read_receipts.js"; import { SlidingSyncSdk } from "./sliding-sync-sdk.js"; import { determineFeatureSupport, FeatureSupport, Thread, THREAD_RELATION_TYPE, ThreadFilterType, threadFilterTypeToFilter } from "./models/thread.js"; import { M_BEACON_INFO } from "./@types/beacon.js"; import { NamespacedValue, UnstableValue } from "./NamespacedValue.js"; import { ToDeviceMessageQueue } from "./ToDeviceMessageQueue.js"; import { IgnoredInvites } from "./models/invites-ignorer.js"; import { buildFeatureSupportMap, Feature, ServerSupport } from "./feature.js"; import { RUST_SDK_STORE_PREFIX } from "./rust-crypto/constants.js"; import { CryptoEvent } from "./crypto-api/index.js"; import { ServerSideSecretStorageImpl } from "./secret-storage.js"; import { MatrixRTCSessionManager } from "./matrixrtc/MatrixRTCSessionManager.js"; import { getRelationsThreadFilter } from "./thread-utils.js"; import { KnownMembership } from "./@types/membership.js"; import { ServerCapabilities } from "./serverCapabilities.js"; import { sha256 } from "./digest.js"; import { discoverAndValidateOIDCIssuerWellKnown, validateAuthMetadataAndKeys } from "./oidc/index.js"; import { UnsupportedDelayedEventsEndpointError, UnsupportedStickyEventsEndpointError } from "./errors.js"; var SCROLLBACK_DELAY_MS = 3000; var TURN_CHECK_INTERVAL = 10 * 60 * 1000; // poll for turn credentials every 10 minutes export var UNSTABLE_MSC3852_LAST_SEEN_UA = new UnstableValue("last_seen_user_agent", "org.matrix.msc3852.last_seen_user_agent"); export var PendingEventOrdering = /*#__PURE__*/function (PendingEventOrdering) { PendingEventOrdering["Chronological"] = "chronological"; PendingEventOrdering["Detached"] = "detached"; return PendingEventOrdering; }({}); export var GET_LOGIN_TOKEN_CAPABILITY = new NamespacedValue("m.get_login_token", "org.matrix.msc3882.get_login_token"); export var UNSTABLE_MSC2666_SHARED_ROOMS = "uk.half-shot.msc2666"; export var UNSTABLE_MSC2666_MUTUAL_ROOMS = "uk.half-shot.msc2666.mutual_rooms"; export var UNSTABLE_MSC2666_QUERY_MUTUAL_ROOMS = "uk.half-shot.msc2666.query_mutual_rooms"; export var UNSTABLE_MSC4140_DELAYED_EVENTS = "org.matrix.msc4140"; export var UNSTABLE_MSC4354_STICKY_EVENTS = "org.matrix.msc4354"; export var UNSTABLE_MSC4133_EXTENDED_PROFILES = "uk.tcpip.msc4133"; export var STABLE_MSC4133_EXTENDED_PROFILES = "uk.tcpip.msc4133.stable"; var CrossSigningKeyType = /*#__PURE__*/function (CrossSigningKeyType) { CrossSigningKeyType["MasterKey"] = "master_key"; CrossSigningKeyType["SelfSigningKey"] = "self_signing_key"; CrossSigningKeyType["UserSigningKey"] = "user_signing_key"; return CrossSigningKeyType; }(CrossSigningKeyType || {}); // Re-export for backwards compatibility /** * The summary of a room as defined by an initial version of MSC3266 and implemented in Synapse * Proposed at https://github.com/matrix-org/matrix-doc/pull/3266 */ /* eslint-enable camelcase */ // We're using this constant for methods overloading and inspect whether a variable // contains an eventId or not. This was required to ensure backwards compatibility // of methods for threads // Probably not the most graceful solution but does a good enough job for now var EVENT_ID_PREFIX = "$"; export var ClientEvent = /*#__PURE__*/function (ClientEvent) { /** * Fires whenever the SDK's syncing state is updated. The state can be one of: * <ul> * * <li>PREPARED: The client has synced with the server at least once and is * ready for methods to be called on it. This will be immediately followed by * a state of SYNCING. <i>This is the equivalent of "syncComplete" in the * previous API.</i></li> * * <li>CATCHUP: The client has detected the connection to the server might be * available again and will now try to do a sync again. As this sync might take * a long time (depending how long ago was last synced, and general server * performance) the client is put in this mode so the UI can reflect trying * to catch up with the server after losing connection.</li> * * <li>SYNCING : The client is currently polling for new events from the server. * This will be called <i>after</i> processing latest events from a sync.</li> * * <li>ERROR : The client has had a problem syncing with the server. If this is * called <i>before</i> PREPARED then there was a problem performing the initial * sync. If this is called <i>after</i> PREPARED then there was a problem polling * the server for updates. This may be called multiple times even if the state is * already ERROR. <i>This is the equivalent of "syncError" in the previous * API.</i></li> * * <li>RECONNECTING: The sync connection has dropped, but not (yet) in a way that * should be considered erroneous. * </li> * * <li>STOPPED: The client has stopped syncing with server due to stopClient * being called. * </li> * </ul> * State transition diagram: * ``` * +---->STOPPED * | * +----->PREPARED -------> SYNCING <--+ * | ^ | ^ | * | CATCHUP ----------+ | | | * | ^ V | | * null ------+ | +------- RECONNECTING | * | V V | * +------->ERROR ---------------------+ * * NB: 'null' will never be emitted by this event. * * ``` * Transitions: * <ul> * * <li>`null -> PREPARED` : Occurs when the initial sync is completed * first time. This involves setting up filters and obtaining push rules. * * <li>`null -> ERROR` : Occurs when the initial sync failed first time. * * <li>`ERROR -> PREPARED` : Occurs when the initial sync succeeds * after previously failing. * * <li>`PREPARED -> SYNCING` : Occurs immediately after transitioning * to PREPARED. Starts listening for live updates rather than catching up. * * <li>`SYNCING -> RECONNECTING` : Occurs when the live update fails. * * <li>`RECONNECTING -> RECONNECTING` : Can occur if the update calls * continue to fail, but the keepalive calls (to /versions) succeed. * * <li>`RECONNECTING -> ERROR` : Occurs when the keepalive call also fails * * <li>`ERROR -> SYNCING` : Occurs when the client has performed a * live update after having previously failed. * * <li>`ERROR -> ERROR` : Occurs when the client has failed to keepalive * for a second time or more.</li> * * <li>`SYNCING -> SYNCING` : Occurs when the client has performed a live * update. This is called <i>after</i> processing.</li> * * <li>`* -> STOPPED` : Occurs once the client has stopped syncing or * trying to sync after stopClient has been called.</li> * </ul> * * The payloads consits of the following 3 parameters: * * - state - An enum representing the syncing state. One of "PREPARED", * "SYNCING", "ERROR", "STOPPED". * * - prevState - An enum representing the previous syncing state. * One of "PREPARED", "SYNCING", "ERROR", "STOPPED" <b>or null</b>. * * - data - Data about this transition. * * @example * ``` * matrixClient.on("sync", function(state, prevState, data) { * switch (state) { * case "ERROR": * // update UI to say "Connection Lost" * break; * case "SYNCING": * // update UI to remove any "Connection Lost" message * break; * case "PREPARED": * // the client instance is ready to be queried. * var rooms = matrixClient.getRooms(); * break; * } * }); * ``` */ ClientEvent["Sync"] = "sync"; /** * Fires whenever the SDK receives a new event. * <p> * This is only fired for live events received via /sync - it is not fired for * events received over context, search, or pagination APIs. * * The payload is the matrix event which caused this event to fire. * @example * ``` * matrixClient.on("event", function(event){ * var sender = event.getSender(); * }); * ``` */ ClientEvent["Event"] = "event"; /** @deprecated Use {@link ReceivedToDeviceMessage}. * Fires whenever the SDK receives a new to-device event. * The payload is the matrix event ({@link MatrixEvent}) which caused this event to fire. * @example * ``` * matrixClient.on("toDeviceEvent", function(event){ * var sender = event.getSender(); * }); * ``` */ ClientEvent["ToDeviceEvent"] = "toDeviceEvent"; /** * Fires whenever the SDK receives a new (potentially decrypted) to-device message. * The payload is the to-device message and the encryption info for that message ({@link ReceivedToDeviceMessage}). * @example * ``` * matrixClient.on("receivedToDeviceMessage", function(payload){ * const { message, encryptionInfo } = payload; * var claimed_sender = encryptionInfo ? encryptionInfo.sender : message.sender; * var isVerified = encryptionInfo ? encryptionInfo.verified : false; * var type = message.type; * }); */ ClientEvent["ReceivedToDeviceMessage"] = "receivedToDeviceMessage"; /** * Fires whenever new user-scoped account_data is added. * The payload is a pair of event ({@link MatrixEvent}) describing the account_data just added, and the previous event, if known: * - event: The event describing the account_data just added * - oldEvent: The previous account data, if known. * @example * ``` * matrixClient.on("accountData", function(event, oldEvent){ * myAccountData[event.type] = event.content; * }); * ``` */ ClientEvent["AccountData"] = "accountData"; /** * Fires whenever a new Room is added. This will fire when you are invited to a * room, as well as when you join a room. <strong>This event is experimental and * may change.</strong> * * The payload is the newly created room, fully populated. * @example * ``` * matrixClient.on("Room", function(room){ * var roomId = room.roomId; * }); * ``` */ ClientEvent["Room"] = "Room"; /** * Fires whenever a Room is removed. This will fire when you forget a room. * <strong>This event is experimental and may change.</strong> * The payload is the roomId of the deleted room. * @example * ``` * matrixClient.on("deleteRoom", function(roomId){ * // update UI from getRooms() * }); * ``` */ ClientEvent["DeleteRoom"] = "deleteRoom"; ClientEvent["SyncUnexpectedError"] = "sync.unexpectedError"; /** * Fires when the client .well-known info is fetched. * The payload is the JSON object (see {@link IClientWellKnown}) returned by the server */ ClientEvent["ClientWellKnown"] = "WellKnown.client"; ClientEvent["ReceivedVoipEvent"] = "received_voip_event"; ClientEvent["TurnServers"] = "turnServers"; ClientEvent["TurnServersError"] = "turnServers.error"; return ClientEvent; }({}); var SSO_ACTION_PARAM = new UnstableValue("action", "org.matrix.msc3824.action"); /** * Represents a Matrix Client. Only directly construct this if you want to use * custom modules. Normally, {@link createClient} should be used * as it specifies 'sensible' defaults for these modules. */ export class MatrixClient extends TypedEventEmitter { constructor(opts) { var _opts$logger, _opts$usingExternalCr, _this, _opts$disableVoip, _opts$enableEncrypted, _opts$cryptoCallbacks; // If a custom logger is provided, use it. Otherwise, default to the global // one in logger.ts. super(); _this = this; _defineProperty(this, "logger", void 0); _defineProperty(this, "reEmitter", new TypedReEmitter(this)); _defineProperty(this, "olmVersion", null); // populated after initLegacyCrypto _defineProperty(this, "usingExternalCrypto", false); _defineProperty(this, "_store", void 0); _defineProperty(this, "deviceId", void 0); _defineProperty(this, "credentials", void 0); /** * Encryption key used for encrypting sensitive data (such as e2ee keys) in storage. * * As supplied in the constructor via {@link IMatrixClientCreateOpts#pickleKey}. * Used for migration from the legacy crypto to the rust crypto */ _defineProperty(this, "legacyPickleKey", void 0); _defineProperty(this, "scheduler", void 0); _defineProperty(this, "clientRunning", false); _defineProperty(this, "timelineSupport", false); _defineProperty(this, "urlPreviewCache", {}); _defineProperty(this, "identityServer", void 0); _defineProperty(this, "http", void 0); // XXX: Intended private, used in code. _defineProperty(this, "cryptoBackend", void 0); // one of crypto or rustCrypto /** * Support MSC4362: Simplified Encrypted State Events. * * The client must be recreated for changes to this setting to take effect * reliably. * * When this setting is true, if we find a state event that is encrypted * (within a room that supports encrypted state), we will attempt to decrypt * it as specified in MSC4362. If the user was in the room at the time an * encrypted state event was received (meaning we have the key), even if * this setting was set to false at the time it was received, recreating the * client with this setting set to true will allow decrypting that event. * * When this setting is false, any state event that is encrypted will not be * decrypted, meaning it will have no effect. This matched the behaviour of * a client that does not support MSC4362. */ _defineProperty(this, "enableEncryptedStateEvents", void 0); _defineProperty(this, "cryptoCallbacks", void 0); // XXX: Intended private, used in code. _defineProperty(this, "callEventHandler", void 0); // XXX: Intended private, used in code. _defineProperty(this, "groupCallEventHandler", void 0); _defineProperty(this, "supportsCallTransfer", false); // XXX: Intended private, used in code. _defineProperty(this, "forceTURN", false); // XXX: Intended private, used in code. _defineProperty(this, "iceCandidatePoolSize", 0); // XXX: Intended private, used in code. _defineProperty(this, "idBaseUrl", void 0); _defineProperty(this, "baseUrl", void 0); _defineProperty(this, "isVoipWithNoMediaAllowed", void 0); _defineProperty(this, "disableVoip", void 0); _defineProperty(this, "useLivekitForGroupCalls", void 0); // Note: these are all `protected` to let downstream consumers make mistakes if they want to. // We don't technically support this usage, but have reasons to do this. _defineProperty(this, "canSupportVoip", false); _defineProperty(this, "peekSync", null); _defineProperty(this, "isGuestAccount", false); _defineProperty(this, "ongoingScrollbacks", {}); _defineProperty(this, "notifTimelineSet", null); /** * Legacy crypto store used for migration from the legacy crypto to the rust crypto * @private */ _defineProperty(this, "legacyCryptoStore", void 0); _defineProperty(this, "verificationMethods", void 0); _defineProperty(this, "fallbackICEServerAllowed", false); _defineProperty(this, "syncApi", void 0); _defineProperty(this, "roomNameGenerator", void 0); _defineProperty(this, "pushRules", void 0); _defineProperty(this, "syncLeftRoomsPromise", void 0); _defineProperty(this, "syncedLeftRooms", false); _defineProperty(this, "clientOpts", void 0); _defineProperty(this, "clientWellKnownIntervalID", void 0); _defineProperty(this, "canResetTimelineCallback", void 0); _defineProperty(this, "canSupport", new Map()); // The pushprocessor caches useful things, so keep one and re-use it _defineProperty(this, "pushProcessor", new PushProcessor(this)); // Promise to a response of the server's /versions response // TODO: This should expire: https://github.com/matrix-org/matrix-js-sdk/issues/1020 _defineProperty(this, "serverVersionsPromise", void 0); _defineProperty(this, "clientWellKnown", void 0); _defineProperty(this, "clientWellKnownPromise", void 0); _defineProperty(this, "turnServers", []); _defineProperty(this, "turnServersExpiry", 0); _defineProperty(this, "checkTurnServersIntervalID", void 0); _defineProperty(this, "txnCtr", 0); _defineProperty(this, "mediaHandler", new MediaHandler(this)); _defineProperty(this, "sessionId", void 0); /** IDs of events which are currently being encrypted. * * This is part of the cancellation mechanism: if the event is no longer listed here when encryption completes, * that tells us that it has been cancelled, and we should not send it. */ _defineProperty(this, "eventsBeingEncrypted", new Set()); _defineProperty(this, "useE2eForGroupCall", true); _defineProperty(this, "toDeviceMessageQueue", void 0); _defineProperty(this, "livekitServiceURL", void 0); _defineProperty(this, "_secretStorage", void 0); // A manager for determining which invites should be ignored. _defineProperty(this, "ignoredInvites", void 0); _defineProperty(this, "matrixRTC", void 0); _defineProperty(this, "serverCapabilitiesService", void 0); _defineProperty(this, "startCallEventHandler", () => { if (this.isInitialSyncComplete()) { if (supportsMatrixCall()) { this.callEventHandler.start(); this.groupCallEventHandler.start(); } this.off(ClientEvent.Sync, this.startCallEventHandler); } }); _defineProperty(this, "startMatrixRTC", () => { if (this.isInitialSyncComplete()) { this.matrixRTC.start(); this.off(ClientEvent.Sync, this.startMatrixRTC); } }); /** * Once the client has been initialised, we want to clear notifications we * know for a fact should be here. * This issue should also be addressed on synapse's side and is tracked as part * of https://github.com/matrix-org/synapse/issues/14837 * * We consider a room or a thread as fully read if the current user has sent * the last event in the live timeline of that context and if the read receipt * we have on record matches. */ _defineProperty(this, "fixupRoomNotifications", () => { if (this.isInitialSyncComplete()) { var _this$getRooms; var unreadRooms = ((_this$getRooms = this.getRooms()) !== null && _this$getRooms !== void 0 ? _this$getRooms : []).filter(room => { return room.getUnreadNotificationCount(NotificationCountType.Total) > 0; }); for (var _room of unreadRooms) { var currentUserId = this.getSafeUserId(); _room.fixupNotifications(currentUserId); } this.off(ClientEvent.Sync, this.fixupRoomNotifications); } }); this.logger = (_opts$logger = opts.logger) !== null && _opts$logger !== void 0 ? _opts$logger : logger; opts.baseUrl = utils.ensureNoTrailingSlash(opts.baseUrl); opts.idBaseUrl = utils.ensureNoTrailingSlash(opts.idBaseUrl); this.baseUrl = opts.baseUrl; this.idBaseUrl = opts.idBaseUrl; this.identityServer = opts.identityServer; this.usingExternalCrypto = (_opts$usingExternalCr = opts.usingExternalCrypto) !== null && _opts$usingExternalCr !== void 0 ? _opts$usingExternalCr : false; this.store = opts.store || new StubStore(); this.deviceId = opts.deviceId || null; this.sessionId = secureRandomString(10); var userId = opts.userId || null; this.credentials = { userId }; this.http = new MatrixHttpApi(this, { fetchFn: opts.fetchFn, baseUrl: opts.baseUrl, idBaseUrl: opts.idBaseUrl, accessToken: opts.accessToken, refreshToken: opts.refreshToken, tokenRefreshFunction: opts.tokenRefreshFunction, prefix: ClientPrefix.V3, onlyData: true, extraParams: opts.queryParams, localTimeoutMs: opts.localTimeoutMs, useAuthorizationHeader: opts.useAuthorizationHeader, logger: this.logger }); if (opts.pickleKey) { this.legacyPickleKey = opts.pickleKey; } this.useLivekitForGroupCalls = Boolean(opts.useLivekitForGroupCalls); this.scheduler = opts.scheduler; if (this.scheduler) { this.scheduler.setProcessFunction(/*#__PURE__*/function () { var _ref = _asyncToGenerator(function* (eventToSend) { var room = _this.getRoom(eventToSend.getRoomId()); if (eventToSend.status !== EventStatus.SENDING) { _this.updatePendingEventStatus(room, eventToSend, EventStatus.SENDING); } var res = yield _this.sendEventHttpRequest(eventToSend); if (room) { // ensure we update pending event before the next scheduler run so that any listeners to event id // updates on the synchronous event emitter get a chance to run first. room.updatePendingEvent(eventToSend, EventStatus.SENT, res.event_id); } return res; }); return function (_x) { return _ref.apply(this, arguments); }; }()); } this.disableVoip = (_opts$disableVoip = opts.disableVoip) !== null && _opts$disableVoip !== void 0 ? _opts$disableVoip : false; if (!this.disableVoip && supportsMatrixCall()) { this.callEventHandler = new CallEventHandler(this); this.groupCallEventHandler = new GroupCallEventHandler(this); this.canSupportVoip = true; // Start listening for calls after the initial sync is done // We do not need to backfill the call event buffer // with encrypted events that might never get decrypted this.on(ClientEvent.Sync, this.startCallEventHandler); } // NB. We initialise MatrixRTC whether we have call support or not: this is just // the underlying session management and doesn't use any actual media capabilities this.matrixRTC = new MatrixRTCSessionManager(this.logger, this); this.serverCapabilitiesService = new ServerCapabilities(this.logger, this.http); this.on(ClientEvent.Sync, this.fixupRoomNotifications); this.timelineSupport = Boolean(opts.timelineSupport); this.legacyCryptoStore = opts.cryptoStore; this.verificationMethods = opts.verificationMethods; this.cryptoCallbacks = opts.cryptoCallbacks || {}; this.enableEncryptedStateEvents = (_opts$enableEncrypted = opts.enableEncryptedStateEvents) !== null && _opts$enableEncrypted !== void 0 ? _opts$enableEncrypted : false; this.forceTURN = opts.forceTURN || false; this.iceCandidatePoolSize = opts.iceCandidatePoolSize === undefined ? 0 : opts.iceCandidatePoolSize; this.supportsCallTransfer = opts.supportsCallTransfer || false; this.fallbackICEServerAllowed = opts.fallbackICEServerAllowed || false; this.isVoipWithNoMediaAllowed = opts.isVoipWithNoMediaAllowed || false; if (opts.useE2eForGroupCall !== undefined) this.useE2eForGroupCall = opts.useE2eForGroupCall; this.livekitServiceURL = opts.livekitServiceURL; this.roomNameGenerator = opts.roomNameGenerator; this.toDeviceMessageQueue = new ToDeviceMessageQueue(this, this.logger); // The SDK doesn't really provide a clean way for events to recalculate the push // actions for themselves, so we have to kinda help them out when they are encrypted. // We do this so that push rules are correctly executed on events in their decrypted // state, such as highlights when the user's name is mentioned. this.on(MatrixEventEvent.Decrypted, event => { fixNotificationCountOnDecryption(this, event); }); this.ignoredInvites = new IgnoredInvites(this); this._secretStorage = new ServerSideSecretStorageImpl(this, (_opts$cryptoCallbacks = opts.cryptoCallbacks) !== null && _opts$cryptoCallbacks !== void 0 ? _opts$cryptoCallbacks : {}); // having lots of event listeners is not unusual. 0 means "unlimited". this.setMaxListeners(0); } set store(newStore) { this._store = newStore; this._store.setUserCreator(userId => User.createUser(userId, this)); } get store() { return this._store; } /** * High level helper method to begin syncing and poll for new events. To listen for these * events, add a listener for {@link ClientEvent.Event} * via {@link MatrixClient#on}. Alternatively, listen for specific * state change events. * @param opts - Options to apply when syncing. */ startClient(opts) { var _this2 = this; return _asyncToGenerator(function* () { if (_this2.clientRunning) { // client is already running. return; } _this2.clientRunning = true; _this2.on(ClientEvent.Sync, _this2.startMatrixRTC); // Create our own user object artificially (instead of waiting for sync) // so it's always available, even if the user is not in any rooms etc. var userId = _this2.getUserId(); if (userId) { _this2.store.storeUser(new User(userId)); } // periodically poll for turn servers if we support voip if (_this2.supportsVoip()) { _this2.checkTurnServersIntervalID = setInterval(() => { _this2.checkTurnServers(); }, TURN_CHECK_INTERVAL); // noinspection ES6MissingAwait _this2.checkTurnServers(); } if (_this2.syncApi) { // This shouldn't happen since we thought the client was not running _this2.logger.error("Still have sync object whilst not running: stopping old one"); _this2.syncApi.stop(); } try { yield _this2.getVersions(); // This should be done with `canSupport` // TODO: https://github.com/vector-im/element-web/issues/23643 var { threads, list, fwdPagination } = yield _this2.doesServerSupportThread(); Thread.setServerSideSupport(threads); Thread.setServerSideListSupport(list); Thread.setServerSideFwdPaginationSupport(fwdPagination); } catch (e) { _this2.logger.error("Can't fetch server versions, continuing to initialise sync, this will be retried later", e); } _this2.clientOpts = opts !== null && opts !== void 0 ? opts : {}; if (_this2.clientOpts.slidingSync) { _this2.syncApi = new SlidingSyncSdk(_this2.clientOpts.slidingSync, _this2, _this2.clientOpts, _this2.buildSyncApiOptions()); } else { _this2.syncApi = new SyncApi(_this2, _this2.clientOpts, _this2.buildSyncApiOptions()); } _this2.syncApi.sync().catch(e => _this2.logger.info("Sync startup aborted with an error:", e)); if (_this2.clientOpts.clientWellKnownPollPeriod !== undefined) { _this2.clientWellKnownIntervalID = setInterval(() => { _this2.fetchClientWellKnown(); }, 1000 * _this2.clientOpts.clientWellKnownPollPeriod); _this2.fetchClientWellKnown(); } _this2.toDeviceMessageQueue.start(); _this2.serverCapabilitiesService.start(); })(); } /** * Construct a SyncApiOptions for this client, suitable for passing into the SyncApi constructor */ buildSyncApiOptions() { return { cryptoCallbacks: this.cryptoBackend, canResetEntireTimeline: roomId => { if (!this.canResetTimelineCallback) { return false; } return this.canResetTimelineCallback(roomId); }, logger: this.logger.getChild("sync") }; } /** * High level helper method to stop the client from polling and allow a * clean shutdown. */ stopClient() { var _this$cryptoBackend, _this$syncApi, _this$peekSync, _this$callEventHandle, _this$groupCallEventH; (_this$cryptoBackend = this.cryptoBackend) === null || _this$cryptoBackend === void 0 || _this$cryptoBackend.stop(); // crypto might have been initialised even if the client wasn't fully started this.off(ClientEvent.Sync, this.startMatrixRTC); if (!this.clientRunning) return; // already stopped this.logger.debug("stopping MatrixClient"); this.clientRunning = false; (_this$syncApi = this.syncApi) === null || _this$syncApi === void 0 || _this$syncApi.stop(); this.syncApi = undefined; (_this$peekSync = this.peekSync) === null || _this$peekSync === void 0 || _this$peekSync.stopPeeking(); (_this$callEventHandle = this.callEventHandler) === null || _this$callEventHandle === void 0 || _this$callEventHandle.stop(); (_this$groupCallEventH = this.groupCallEventHandler) === null || _this$groupCallEventH === void 0 || _this$groupCallEventH.stop(); this.callEventHandler = undefined; this.groupCallEventHandler = undefined; globalThis.clearInterval(this.checkTurnServersIntervalID); this.checkTurnServersIntervalID = undefined; if (this.clientWellKnownIntervalID !== undefined) { globalThis.clearInterval(this.clientWellKnownIntervalID); } this.toDeviceMessageQueue.stop(); this.matrixRTC.stop(); this.serverCapabilitiesService.stop(); } /** * Clear any data out of the persistent stores used by the client. * * @param args.cryptoDatabasePrefix - The database name to use for indexeddb, defaults to 'matrix-js-sdk'. * @returns Promise which resolves when the stores have been cleared. */ clearStores() { var _this3 = this; var args = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; if (this.clientRunning) { throw new Error("Cannot clear stores while client is running"); } var promises = []; promises.push(this.store.deleteAllData()); if (this.legacyCryptoStore) { promises.push(this.legacyCryptoStore.deleteAllData()); } // delete the stores used by the rust matrix-sdk-crypto, in case they were used var deleteRustSdkStore = /*#__PURE__*/function () { var _ref2 = _asyncToGenerator(function* () { var indexedDB; try { indexedDB = globalThis.indexedDB; if (!indexedDB) return; // No indexedDB support } catch (_unused) { // No indexedDB support return; } var _loop = function* _loop(dbname) { var prom = new Promise((resolve, reject) => { _this3.logger.info("Removing IndexedDB instance ".concat(dbname)); var req = indexedDB.deleteDatabase(dbname); req.onsuccess = _ => { _this3.logger.info("Removed IndexedDB instance ".concat(dbname)); resolve(0); }; req.onerror = e => { // In private browsing, Firefox has a globalThis.indexedDB, but attempts to delete an indexeddb // (even a non-existent one) fail with "DOMException: A mutation operation was attempted on a // database that did not allow mutations." // // it seems like the only thing we can really do is ignore the error. _this3.logger.warn("Failed to remove IndexedDB instance ".concat(dbname, ":"), e); resolve(0); }; req.onblocked = e => { _this3.logger.info("cannot yet remove IndexedDB instance ".concat(dbname)); }; }); yield prom; }; for (var dbname of ["".concat((_args$cryptoDatabaseP = args.cryptoDatabasePrefix) !== null && _args$cryptoDatabaseP !== void 0 ? _args$cryptoDatabaseP : RUST_SDK_STORE_PREFIX, "::matrix-sdk-crypto"), "".concat((_args$cryptoDatabaseP2 = args.cryptoDatabasePrefix) !== null && _args$cryptoDatabaseP2 !== void 0 ? _args$cryptoDatabaseP2 : RUST_SDK_STORE_PREFIX, "::matrix-sdk-crypto-meta")]) { var _args$cryptoDatabaseP, _args$cryptoDatabaseP2; yield* _loop(dbname); } }); return function deleteRustSdkStore() { return _ref2.apply(this, arguments); }; }(); promises.push(deleteRustSdkStore()); return Promise.all(promises).then(); // .then to fix types } /** * Get the user-id of the logged-in user * * @returns MXID for the logged-in user, or null if not logged in */ getUserId() { var _this$credentials$use, _this$credentials; return (_this$credentials$use = (_this$credentials = this.credentials) === null || _this$credentials === void 0 ? void 0 : _this$credentials.userId) !== null && _this$credentials$use !== void 0 ? _this$credentials$use : null; } /** * Get the user-id of the logged-in user * * @returns MXID for the logged-in user * @throws Error if not logged in */ getSafeUserId() { var userId = this.getUserId(); if (!userId) { throw new Error("Expected logged in user but found none."); } return userId; } /** * Get the domain for this client's MXID * @returns Domain of this MXID */ getDomain() { var _this$credentials2; if ((_this$credentials2 = this.credentials) !== null && _this$credentials2 !== void 0 && _this$credentials2.userId) { return this.credentials.userId.replace(/^.*?:/, ""); } return null; } /** * Get the local part of the current user ID e.g. "foo" in "\@foo:bar". * @returns The user ID localpart or null. */ getUserIdLocalpart() { var _this$credentials$use2, _this$credentials3; return (_this$credentials$use2 = (_this$credentials3 = this.credentials) === null || _this$credentials3 === void 0 || (_this$credentials3 = _this$credentials3.userId) === null || _this$credentials3 === void 0 ? void 0 : _this$credentials3.split(":")[0].substring(1)) !== null && _this$credentials$use2 !== void 0 ? _this$credentials$use2 : null; } /** * Get the device ID of this client * @returns device ID */ getDeviceId() { return this.deviceId; } /** * Get the session ID of this client * @returns session ID */ getSessionId() { return this.sessionId; } /** * Check if the runtime environment supports VoIP calling. * @returns True if VoIP is supported. */ supportsVoip() { return !this.disableVoip && this.canSupportVoip; } /** * @returns */ getMediaHandler() { return this.mediaHandler; } /** * Set whether VoIP calls are forced to use only TURN * candidates. This is the same as the forceTURN option * when creating the client. * @param force - True to force use of TURN servers */ setForceTURN(force) { this.forceTURN = force; } /** * Set whether to advertise transfer support to other parties on Matrix calls. * @param support - True to advertise the 'm.call.transferee' capability */ setSupportsCallTransfer(support) { this.supportsCallTransfer = support; } /** * Returns true if to-device signalling for group calls will be encrypted with Olm. * If false, it will be sent unencrypted. * @returns boolean Whether group call signalling will be encrypted */ getUseE2eForGroupCall() { return this.useE2eForGroupCall; } /** * Creates a new call. * The place*Call methods on the returned call can be used to actually place a call * * @param roomId - The room the call is to be placed in. * @returns the call or null if the browser doesn't support calling. */ createCall(roomId) { return createNewMatrixCall(this, roomId); } /** * Creates a new group call and sends the associated state event * to alert other members that the room now has a group call. * * @param roomId - The room the call is to be placed in. */ createGroupCall(roomId, type, isPtt, intent, dataChannelsEnabled, dataChannelOptions) { var _this4 = this; return _asyncToGenerator(function* () { if (_this4.getGroupCallForRoom(roomId)) { throw new Error("".concat(roomId, " already has an existing group call")); } var room = _this4.getRoom(roomId); if (!room) { throw new Error("Cannot find room ".concat(roomId)); } // Because without Media section a WebRTC connection is not possible, so need a RTCDataChannel to set up a // no media WebRTC connection anyway. return new GroupCall(_this4, room, type, isPtt, intent, undefined, dataChannelsEnabled || _this4.isVoipWithNoMediaAllowed, dataChannelOptions, _this4.isVoipWithNoMediaAllowed, _this4.useLivekitForGroupCalls, _this4.livekitServiceURL).create(); })(); } getLivekitServiceURL() { return this.livekitServiceURL; } // This shouldn't need to exist, but the widget API has startup ordering problems that // mean it doesn't know the livekit URL fast enough: remove this once this is fixed. setLivekitServiceURL(newURL) { this.livekitServiceURL = newURL; } /** * Wait until an initial state for the given room has been processed by the * client and the client is aware of any ongoing group calls. Awaiting on * the promise returned by this method before calling getGroupCallForRoom() * avoids races where getGroupCallForRoom is called before the state for that * room has been processed. It does not, however, fix other races, eg. two * clients both creating a group call at the same time. * @param roomId - The room ID to wait for * @returns A promise that resolves once existing group calls in the room * have been processed. */ waitUntilRoomReadyForGroupCalls(roomId) { return this.groupCallEventHandler.waitUntilRoomReadyForGroupCalls(roomId); } /** * Get an existing group call for the provided room. * @returns The group call or null if it doesn't already exist. */ getGroupCallForRoom(roomId) { return this.groupCallEventHandler.groupCalls.get(roomId) || null; } /** * Get the current sync state. * @returns the sync state, which may be null. * @see MatrixClient#event:"sync" */ getSyncState() { var _this$syncApi$getSync, _this$syncApi2; return (_this$syncApi$getSync = (_this$syncApi2 = this.syncApi) === null || _this$syncApi2 === void 0 ? void 0 : _this$syncApi2.getSyncState()) !== null && _this$syncApi$getSync !== void 0 ? _this$syncApi$getSync : null; } /** * Returns the additional data object associated with * the current sync state, or null if there is no * such data. * Sync errors, if available, are put in the 'error' key of * this object. */ getSyncStateData() { if (!this.syncApi) { return null; } return this.syncApi.getSyncStateData(); } /** * Whether the initial sync has completed. * @returns True if at least one sync has happened. */ isInitialSyncComplete() { var state = this.getSyncState(); if (!state) { return false; } return state === SyncState.Prepared || state === SyncState.Syncing; } /** * Return whether the client is configured for a guest account. * @returns True if this is a guest access_token (or no token is supplied). */ isGuest() { return this.isGuestAccount; } /** * Set whether this client is a guest account. <b>This method is experimental * and may change without warning.</b> * @param guest - True if this is a guest account. * @experimental if the token is a macaroon, it should be encoded in it that it is a 'guest' * access token, which means that the SDK can determine this entirely without * the dev manually flipping this flag. */ setGuest(guest) { this.isGuestAccount = guest; } /** * Return the provided scheduler, if any. * @returns The scheduler or undefined */ getScheduler() { return this.scheduler; } /** * Retry a backed off syncing request immediately. This should only be used when * the user <b>explicitly</b> attempts to retry their lost connection. * Will also retry any outbound to-device messages currently in the queue to be sent * (retries of regular outgoing events are handled separately, per-event). * @returns True if this resulted in a request being retried. */ retryImmediately() { var _this$syncApi$retryIm, _this$syncApi3; // don't await for this promise: we just want to kick it off this.toDeviceMessageQueue.sendQueue(); return (_this$syncApi$retryIm = (_this$syncApi3 = this.syncApi) === null || _this$syncApi3 === void 0 ? void 0 : _this$syncApi3.retryImmediately()) !== null && _this$syncApi$retryIm !== void 0 ? _this$syncApi$retryIm : false; } /** * Return the global notification EventTimelineSet, if any * * @returns the globl notification EventTimelineSet */ getNotifTimelineSet() { return this.notifTimelineSet; } /** * Set the global notification EventTimelineSet * */ setNotifTimelineSet(set) { this.notifTimelineSet = set; } /** * Gets the cached capabilities of the homeserver, returning cached ones if available. * If there are no cached capabilities and none can be fetched, throw an exception. * * @returns Promise resolving with The capabilities of the homeserver */ getCapabilities() { var _this5 = this; return _asyncToGenerator(function* () { var caps = _this5.serverCapabilitiesService.getCachedCapabilities(); if (caps) return caps; return _this5.serverCapabilitiesService.fetchCapabilities(); })(); } /** * Gets the cached capabilities of the homeserver. If none have been fetched yet, * return undefined. * * @returns The capabilities of the homeserver */ getCachedCapabilities() { return this.serverCapabilitiesService.getCachedCapabilities(); } /** * Fetches the latest capabilities from the homeserver, ignoring any cached * versions. The newly returned version is cached. * * @returns A promise which resolves to the capabilities of the homeserver */ fetchCapabilities() { return this.serverCapabilitiesService.fetchCapabilities(); } /** * Initialise support for end-to-end encryption in this client, using the rust matrix-sdk-crypto. * * **WARNING**: the cryptography stack is not thread-safe. Having multiple `MatrixClient` instances connected to * the same Indexed DB will cause data corruption and decryption failures. The application layer is responsible for * ensuring that only one `MatrixClient` issue is instantiated at a time. * * @param args.useIndexedDB - True to use an indexeddb store, false to use an in-memory store. Defaults to 'true'. * @param args.cryptoDatabasePrefix - The database name to use for indexeddb, defaults to 'matrix-js-sdk'. * Unused if useIndexedDB is 'false'. * @param args.storageKey - A key with which to encrypt the indexeddb store. If provided, it must be exactly * 32 bytes of data, and must be the same each time the client is initialised for a given device. * If both this and `storagePassword` are unspecified, the store will be unencrypted. * @param args.storagePassword - An alternative to `storageKey`. A password which will be used to derive a key to * encrypt the store with. Deriving a key from a password is (deliberately) a slow operation, so prefer * to pass a `storageKey` directly where possible. * * @returns a Promise which will resolve when the crypto layer has been * successfully initialised. */ initRustCrypto() { var _arguments = arguments, _this6 = this; return _asyncToGenerator(function* () { var _args$cryptoDatabaseP3, _this6$legacyPickleKe; var args = _arguments.length > 0 && _arguments[0] !== undefined ? _arguments[0] : {}; if (_this6.cryptoBackend) { _this6.logger.warn("Attempt to re-initialise e2e encryption on MatrixClient"); return; } var userId = _this6.getUserId(); if (userId === null) { throw new Error("Cannot enable encryption on MatrixClient with unknown userId: " + "ensure userId is passed in createClient()."); } var deviceId = _this6.getDeviceId(); if (deviceId === null) { throw new Error("Cannot enable encryption on MatrixClient with unknown deviceId: " + "ensure deviceId is passed in createClient()."); } // importing rust-crypto will download the webassembly, so we delay it until we know it will be // needed. _this6.logger.debug("Downloading Rust crypto library"); var RustCrypto = yield import("./rust-crypto/index.js"); var rustCrypto = yield RustCrypto.initRustCrypto({ logger: _this6.logger, http: _this6.http, userId: userId, deviceId: deviceId, secretStorage: _this6.secretStorage, cryptoCallbacks: _this6.cryptoCallbacks, storePrefix: args.useIndexedDB === false ? null : (_args$cryptoDatabaseP3 = args.cryptoDatabasePrefix) !== null && _args$cryptoDatabaseP3 !== void 0 ? _args$cryptoDatabaseP3 : RUST_SDK_STORE_PREFIX, storeKey: args.storageKey, storePassphrase: args.storagePassword, legacyCryptoStore: _this6.legacyCryptoStore, legacyPickleKey: (_this6$legacyPickleKe = _this6.legacyPickleKey) !== null && _this6$legacyPickleKe !== void 0 ? _this6$legacyPickleKe : "DEFAULT_KEY", legacyMigrationProgressListener: (progress, total) => { _this6.emit(CryptoEvent.LegacyCryptoStoreMigrationProgress, progress, total); }, enableEncryptedStateEvents: _this6.enableEncryptedStateEvents }); rustCrypto.setSupportedVerificationMethods(_this6.verificationMethods); _this6.cryptoBackend = rustCrypto; // attach the event listeners needed by RustCrypto _this6.on(RoomMemberEvent.Membership, rustCrypto.onRoomMembership.bind(rustCrypto)); _this6.on(RoomStateEvent.Events, rustCrypto.onRoomStateEvent.bind(rustCrypto)); _this6.on(ClientEvent.Event, event => { rustCrypto.onLiveEventFromSync(event); }); // re-emit the events emitted by the crypto impl _this6.reEmitter.reEmit(rustCrypto,