UNPKG

matrix-js-sdk

Version:
793 lines (753 loc) 320 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.UNSTABLE_MSC3882_CAPABILITY = exports.UNSTABLE_MSC3852_LAST_SEEN_UA = exports.UNSTABLE_MSC2666_SHARED_ROOMS = exports.UNSTABLE_MSC2666_QUERY_MUTUAL_ROOMS = exports.UNSTABLE_MSC2666_MUTUAL_ROOMS = exports.RoomVersionStability = exports.PendingEventOrdering = exports.MatrixClient = exports.M_AUTHENTICATION = exports.ClientEvent = exports.CRYPTO_ENABLED = void 0; exports.fixNotificationCountOnDecryption = fixNotificationCountOnDecryption; var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _sync = require("./sync"); var _event = require("./models/event"); var _stub = require("./store/stub"); var _call = require("./webrtc/call"); var _filter = require("./filter"); var _callEventHandler = require("./webrtc/callEventHandler"); var _groupCallEventHandler = require("./webrtc/groupCallEventHandler"); var utils = _interopRequireWildcard(require("./utils")); var _eventTimeline = require("./models/event-timeline"); var _pushprocessor = require("./pushprocessor"); var _autodiscovery = require("./autodiscovery"); var olmlib = _interopRequireWildcard(require("./crypto/olmlib")); var _ReEmitter = require("./ReEmitter"); var _RoomList = require("./crypto/RoomList"); var _logger = require("./logger"); var _serviceTypes = require("./service-types"); var _httpApi = require("./http-api"); var _crypto = require("./crypto"); var _recoverykey = require("./crypto/recoverykey"); var _key_passphrase = require("./crypto/key_passphrase"); var _user = require("./models/user"); var _contentRepo = require("./content-repo"); var _searchResult = require("./models/search-result"); var _dehydration = require("./crypto/dehydration"); var _api = require("./crypto/api"); var ContentHelpers = _interopRequireWildcard(require("./content-helpers")); var _room = require("./models/room"); var _roomMember = require("./models/room-member"); var _event2 = require("./@types/event"); var _partials = require("./@types/partials"); var _eventMapper = require("./event-mapper"); var _randomstring = require("./randomstring"); var _backup = require("./crypto/backup"); var _MSC3089TreeSpace = require("./models/MSC3089TreeSpace"); var _search = require("./@types/search"); var _PushRules = require("./@types/PushRules"); var _groupCall = require("./webrtc/groupCall"); var _mediaHandler = require("./webrtc/mediaHandler"); var _typedEventEmitter = require("./models/typed-event-emitter"); var _read_receipts = require("./@types/read_receipts"); var _slidingSyncSdk = require("./sliding-sync-sdk"); var _thread = require("./models/thread"); var _beacon = require("./@types/beacon"); var _NamespacedValue = require("./NamespacedValue"); var _ToDeviceMessageQueue = require("./ToDeviceMessageQueue"); var _invitesIgnorer = require("./models/invites-ignorer"); var _feature = require("./feature"); var _constants = require("./rust-crypto/constants"); var _secretStorage = require("./secret-storage"); const _excluded = ["server", "limit", "since"]; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } 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; } /* 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. */ const SCROLLBACK_DELAY_MS = 3000; const CRYPTO_ENABLED = (0, _crypto.isCryptoAvailable)(); exports.CRYPTO_ENABLED = CRYPTO_ENABLED; const CAPABILITIES_CACHE_MS = 21600000; // 6 hours - an arbitrary value const TURN_CHECK_INTERVAL = 10 * 60 * 1000; // poll for turn credentials every 10 minutes const UNSTABLE_MSC3852_LAST_SEEN_UA = new _NamespacedValue.UnstableValue("last_seen_user_agent", "org.matrix.msc3852.last_seen_user_agent"); exports.UNSTABLE_MSC3852_LAST_SEEN_UA = UNSTABLE_MSC3852_LAST_SEEN_UA; let PendingEventOrdering = /*#__PURE__*/function (PendingEventOrdering) { PendingEventOrdering["Chronological"] = "chronological"; PendingEventOrdering["Detached"] = "detached"; return PendingEventOrdering; }({}); exports.PendingEventOrdering = PendingEventOrdering; let RoomVersionStability = /*#__PURE__*/function (RoomVersionStability) { RoomVersionStability["Stable"] = "stable"; RoomVersionStability["Unstable"] = "unstable"; return RoomVersionStability; }({}); exports.RoomVersionStability = RoomVersionStability; const UNSTABLE_MSC3882_CAPABILITY = new _NamespacedValue.UnstableValue("m.get_login_token", "org.matrix.msc3882.get_login_token"); exports.UNSTABLE_MSC3882_CAPABILITY = UNSTABLE_MSC3882_CAPABILITY; const UNSTABLE_MSC2666_SHARED_ROOMS = "uk.half-shot.msc2666"; exports.UNSTABLE_MSC2666_SHARED_ROOMS = UNSTABLE_MSC2666_SHARED_ROOMS; const UNSTABLE_MSC2666_MUTUAL_ROOMS = "uk.half-shot.msc2666.mutual_rooms"; exports.UNSTABLE_MSC2666_MUTUAL_ROOMS = UNSTABLE_MSC2666_MUTUAL_ROOMS; const UNSTABLE_MSC2666_QUERY_MUTUAL_ROOMS = "uk.half-shot.msc2666.query_mutual_rooms"; /** * A representation of the capabilities advertised by a homeserver as defined by * [Capabilities negotiation](https://spec.matrix.org/v1.6/client-server-api/#get_matrixclientv3capabilities). */ /** @deprecated prefer {@link CrossSigningKeyInfo}. */ exports.UNSTABLE_MSC2666_QUERY_MUTUAL_ROOMS = UNSTABLE_MSC2666_QUERY_MUTUAL_ROOMS; var CrossSigningKeyType = /*#__PURE__*/function (CrossSigningKeyType) { CrossSigningKeyType["MasterKey"] = "master_key"; CrossSigningKeyType["SelfSigningKey"] = "self_signing_key"; CrossSigningKeyType["UserSigningKey"] = "user_signing_key"; return CrossSigningKeyType; }(CrossSigningKeyType || {}); const M_AUTHENTICATION = new _NamespacedValue.UnstableValue("m.authentication", "org.matrix.msc2965.authentication"); // Re-export for backwards compatibility exports.M_AUTHENTICATION = M_AUTHENTICATION; /* 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 const EVENT_ID_PREFIX = "$"; let ClientEvent = /*#__PURE__*/function (ClientEvent) { ClientEvent["Sync"] = "sync"; ClientEvent["Event"] = "event"; ClientEvent["ToDeviceEvent"] = "toDeviceEvent"; ClientEvent["AccountData"] = "accountData"; ClientEvent["Room"] = "Room"; ClientEvent["DeleteRoom"] = "deleteRoom"; ClientEvent["SyncUnexpectedError"] = "sync.unexpectedError"; ClientEvent["ClientWellKnown"] = "WellKnown.client"; ClientEvent["ReceivedVoipEvent"] = "received_voip_event"; ClientEvent["UndecryptableToDeviceEvent"] = "toDeviceEvent.undecryptable"; ClientEvent["TurnServers"] = "turnServers"; ClientEvent["TurnServersError"] = "turnServers.error"; return ClientEvent; }({}); exports.ClientEvent = ClientEvent; const SSO_ACTION_PARAM = new _NamespacedValue.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. */ class MatrixClient extends _typedEventEmitter.TypedEventEmitter { constructor(opts) { var _opts$usingExternalCr, _opts$cryptoCallbacks; super(); (0, _defineProperty2.default)(this, "reEmitter", new _ReEmitter.TypedReEmitter(this)); (0, _defineProperty2.default)(this, "olmVersion", null); // populated after initCrypto (0, _defineProperty2.default)(this, "usingExternalCrypto", false); (0, _defineProperty2.default)(this, "store", void 0); (0, _defineProperty2.default)(this, "deviceId", void 0); (0, _defineProperty2.default)(this, "credentials", void 0); (0, _defineProperty2.default)(this, "pickleKey", void 0); (0, _defineProperty2.default)(this, "scheduler", void 0); (0, _defineProperty2.default)(this, "clientRunning", false); (0, _defineProperty2.default)(this, "timelineSupport", false); (0, _defineProperty2.default)(this, "urlPreviewCache", {}); (0, _defineProperty2.default)(this, "identityServer", void 0); (0, _defineProperty2.default)(this, "http", void 0); // XXX: Intended private, used in code. /** * The libolm crypto implementation, if it is in use. * * @deprecated This should not be used. Instead, use the methods exposed directly on this class or * (where they are available) via {@link getCrypto}. */ (0, _defineProperty2.default)(this, "crypto", void 0); // XXX: Intended private, used in code. Being replaced by cryptoBackend (0, _defineProperty2.default)(this, "cryptoBackend", void 0); // one of crypto or rustCrypto (0, _defineProperty2.default)(this, "cryptoCallbacks", void 0); // XXX: Intended private, used in code. (0, _defineProperty2.default)(this, "callEventHandler", void 0); // XXX: Intended private, used in code. (0, _defineProperty2.default)(this, "groupCallEventHandler", void 0); (0, _defineProperty2.default)(this, "supportsCallTransfer", false); // XXX: Intended private, used in code. (0, _defineProperty2.default)(this, "forceTURN", false); // XXX: Intended private, used in code. (0, _defineProperty2.default)(this, "iceCandidatePoolSize", 0); // XXX: Intended private, used in code. (0, _defineProperty2.default)(this, "idBaseUrl", void 0); (0, _defineProperty2.default)(this, "baseUrl", void 0); (0, _defineProperty2.default)(this, "isVoipWithNoMediaAllowed", 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. (0, _defineProperty2.default)(this, "canSupportVoip", false); (0, _defineProperty2.default)(this, "peekSync", null); (0, _defineProperty2.default)(this, "isGuestAccount", false); (0, _defineProperty2.default)(this, "ongoingScrollbacks", {}); (0, _defineProperty2.default)(this, "notifTimelineSet", null); (0, _defineProperty2.default)(this, "cryptoStore", void 0); (0, _defineProperty2.default)(this, "verificationMethods", void 0); (0, _defineProperty2.default)(this, "fallbackICEServerAllowed", false); (0, _defineProperty2.default)(this, "roomList", void 0); (0, _defineProperty2.default)(this, "syncApi", void 0); (0, _defineProperty2.default)(this, "roomNameGenerator", void 0); (0, _defineProperty2.default)(this, "pushRules", void 0); (0, _defineProperty2.default)(this, "syncLeftRoomsPromise", void 0); (0, _defineProperty2.default)(this, "syncedLeftRooms", false); (0, _defineProperty2.default)(this, "clientOpts", void 0); (0, _defineProperty2.default)(this, "clientWellKnownIntervalID", void 0); (0, _defineProperty2.default)(this, "canResetTimelineCallback", void 0); (0, _defineProperty2.default)(this, "canSupport", new Map()); // The pushprocessor caches useful things, so keep one and re-use it (0, _defineProperty2.default)(this, "pushProcessor", new _pushprocessor.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 (0, _defineProperty2.default)(this, "serverVersionsPromise", void 0); (0, _defineProperty2.default)(this, "cachedCapabilities", void 0); (0, _defineProperty2.default)(this, "clientWellKnown", void 0); (0, _defineProperty2.default)(this, "clientWellKnownPromise", void 0); (0, _defineProperty2.default)(this, "turnServers", []); (0, _defineProperty2.default)(this, "turnServersExpiry", 0); (0, _defineProperty2.default)(this, "checkTurnServersIntervalID", void 0); (0, _defineProperty2.default)(this, "exportedOlmDeviceToImport", void 0); (0, _defineProperty2.default)(this, "txnCtr", 0); (0, _defineProperty2.default)(this, "mediaHandler", new _mediaHandler.MediaHandler(this)); (0, _defineProperty2.default)(this, "sessionId", void 0); (0, _defineProperty2.default)(this, "pendingEventEncryption", new Map()); (0, _defineProperty2.default)(this, "useE2eForGroupCall", true); (0, _defineProperty2.default)(this, "toDeviceMessageQueue", void 0); (0, _defineProperty2.default)(this, "_secretStorage", void 0); // A manager for determining which invites should be ignored. (0, _defineProperty2.default)(this, "ignoredInvites", void 0); (0, _defineProperty2.default)(this, "startCallEventHandler", () => { if (this.isInitialSyncComplete()) { this.callEventHandler.start(); this.groupCallEventHandler.start(); this.off(ClientEvent.Sync, this.startCallEventHandler); } }); /** * 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. */ (0, _defineProperty2.default)(this, "fixupRoomNotifications", () => { if (this.isInitialSyncComplete()) { var _this$getRooms; const unreadRooms = ((_this$getRooms = this.getRooms()) !== null && _this$getRooms !== void 0 ? _this$getRooms : []).filter(room => { return room.getUnreadNotificationCount(_room.NotificationCountType.Total) > 0; }); for (const room of unreadRooms) { const currentUserId = this.getSafeUserId(); room.fixupNotifications(currentUserId); } this.off(ClientEvent.Sync, this.fixupRoomNotifications); } }); 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 _stub.StubStore(); this.deviceId = opts.deviceId || null; this.sessionId = (0, _randomstring.randomString)(10); const userId = opts.userId || null; this.credentials = { userId }; this.http = new _httpApi.MatrixHttpApi(this, { fetchFn: opts.fetchFn, baseUrl: opts.baseUrl, idBaseUrl: opts.idBaseUrl, accessToken: opts.accessToken, prefix: _httpApi.ClientPrefix.R0, onlyData: true, extraParams: opts.queryParams, localTimeoutMs: opts.localTimeoutMs, useAuthorizationHeader: opts.useAuthorizationHeader }); if (opts.deviceToImport) { if (this.deviceId) { _logger.logger.warn("not importing device because device ID is provided to " + "constructor independently of exported data"); } else if (this.credentials.userId) { _logger.logger.warn("not importing device because user ID is provided to " + "constructor independently of exported data"); } else if (!opts.deviceToImport.deviceId) { _logger.logger.warn("not importing device because no device ID in exported data"); } else { this.deviceId = opts.deviceToImport.deviceId; this.credentials.userId = opts.deviceToImport.userId; // will be used during async initialization of the crypto this.exportedOlmDeviceToImport = opts.deviceToImport.olmDevice; } } else if (opts.pickleKey) { this.pickleKey = opts.pickleKey; } this.scheduler = opts.scheduler; if (this.scheduler) { this.scheduler.setProcessFunction(async eventToSend => { const room = this.getRoom(eventToSend.getRoomId()); if (eventToSend.status !== _event.EventStatus.SENDING) { this.updatePendingEventStatus(room, eventToSend, _event.EventStatus.SENDING); } const res = await 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, _event.EventStatus.SENT, res.event_id); } return res; }); } if ((0, _call.supportsMatrixCall)()) { this.callEventHandler = new _callEventHandler.CallEventHandler(this); this.groupCallEventHandler = new _groupCallEventHandler.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); } this.on(ClientEvent.Sync, this.fixupRoomNotifications); this.timelineSupport = Boolean(opts.timelineSupport); this.cryptoStore = opts.cryptoStore; this.verificationMethods = opts.verificationMethods; this.cryptoCallbacks = opts.cryptoCallbacks || {}; 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; // List of which rooms have encryption enabled: separate from crypto because // we still want to know which rooms are encrypted even if crypto is disabled: // we don't want to start sending unencrypted events to them. this.roomList = new _RoomList.RoomList(this.cryptoStore); this.roomNameGenerator = opts.roomNameGenerator; this.toDeviceMessageQueue = new _ToDeviceMessageQueue.ToDeviceMessageQueue(this); // 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(_event.MatrixEventEvent.Decrypted, event => { fixNotificationCountOnDecryption(this, event); }); // Like above, we have to listen for read receipts from ourselves in order to // correctly handle notification counts on encrypted rooms. // This fixes https://github.com/vector-im/element-web/issues/9421 this.on(_room.RoomEvent.Receipt, (event, room) => { if (room && this.isRoomEncrypted(room.roomId)) { // Figure out if we've read something or if it's just informational const content = event.getContent(); const isSelf = Object.keys(content).filter(eid => { for (const [key, value] of Object.entries(content[eid])) { if (!utils.isSupportedReceiptType(key)) continue; if (!value) continue; if (Object.keys(value).includes(this.getUserId())) return true; } return false; }).length > 0; if (!isSelf) return; // Work backwards to determine how many events are unread. We also set // a limit for how back we'll look to avoid spinning CPU for too long. // If we hit the limit, we assume the count is unchanged. const maxHistory = 20; const events = room.getLiveTimeline().getEvents(); let highlightCount = 0; for (let i = events.length - 1; i >= 0; i--) { var _pushActions$tweaks; if (i === events.length - maxHistory) return; // limit reached const event = events[i]; if (room.hasUserReadEvent(this.getUserId(), event.getId())) { // If the user has read the event, then the counting is done. break; } const pushActions = this.getPushActionsForEvent(event); highlightCount += pushActions !== null && pushActions !== void 0 && (_pushActions$tweaks = pushActions.tweaks) !== null && _pushActions$tweaks !== void 0 && _pushActions$tweaks.highlight ? 1 : 0; } // Note: we don't need to handle 'total' notifications because the counts // will come from the server. room.setUnreadNotificationCount(_room.NotificationCountType.Highlight, highlightCount); } }); this.ignoredInvites = new _invitesIgnorer.IgnoredInvites(this); this._secretStorage = new _secretStorage.ServerSideSecretStorageImpl(this, (_opts$cryptoCallbacks = opts.cryptoCallbacks) !== null && _opts$cryptoCallbacks !== void 0 ? _opts$cryptoCallbacks : {}); } /** * 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. */ async startClient(opts) { var _opts; if (this.clientRunning) { // client is already running. return; } this.clientRunning = true; // backwards compat for when 'opts' was 'historyLen'. if (typeof opts === "number") { opts = { initialSyncLimit: opts }; } // 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. const userId = this.getUserId(); if (userId) { this.store.storeUser(new _user.User(userId)); } // periodically poll for turn servers if we support voip if (this.canSupportVoip) { this.checkTurnServersIntervalID = setInterval(() => { this.checkTurnServers(); }, TURN_CHECK_INTERVAL); // noinspection ES6MissingAwait this.checkTurnServers(); } if (this.syncApi) { // This shouldn't happen since we thought the client was not running _logger.logger.error("Still have sync object whilst not running: stopping old one"); this.syncApi.stop(); } try { await this.getVersions(); // This should be done with `canSupport` // TODO: https://github.com/vector-im/element-web/issues/23643 const { threads, list, fwdPagination } = await this.doesServerSupportThread(); _thread.Thread.setServerSideSupport(threads); _thread.Thread.setServerSideListSupport(list); _thread.Thread.setServerSideFwdPaginationSupport(fwdPagination); } catch (e) { _logger.logger.error("Can't fetch server versions, continuing to initialise sync, this will be retried later", e); } this.clientOpts = (_opts = opts) !== null && _opts !== void 0 ? _opts : {}; if (this.clientOpts.slidingSync) { this.syncApi = new _slidingSyncSdk.SlidingSyncSdk(this.clientOpts.slidingSync, this, this.clientOpts, this.buildSyncApiOptions()); } else { this.syncApi = new _sync.SyncApi(this, this.clientOpts, this.buildSyncApiOptions()); } if (this.clientOpts.hasOwnProperty("experimentalThreadSupport")) { _logger.logger.warn("`experimentalThreadSupport` has been deprecated, use `threadSupport` instead"); } // If `threadSupport` is omitted and the deprecated `experimentalThreadSupport` has been passed // We should fallback to that value for backwards compatibility purposes if (!this.clientOpts.hasOwnProperty("threadSupport") && this.clientOpts.hasOwnProperty("experimentalThreadSupport")) { this.clientOpts.threadSupport = this.clientOpts.experimentalThreadSupport; } this.syncApi.sync(); if (this.clientOpts.clientWellKnownPollPeriod !== undefined) { this.clientWellKnownIntervalID = setInterval(() => { this.fetchClientWellKnown(); }, 1000 * this.clientOpts.clientWellKnownPollPeriod); this.fetchClientWellKnown(); } this.toDeviceMessageQueue.start(); } /** * Construct a SyncApiOptions for this client, suitable for passing into the SyncApi constructor */ buildSyncApiOptions() { return { crypto: this.crypto, cryptoCallbacks: this.cryptoBackend, canResetEntireTimeline: roomId => { if (!this.canResetTimelineCallback) { return false; } return this.canResetTimelineCallback(roomId); } }; } /** * 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 ? void 0 : _this$cryptoBackend.stop(); // crypto might have been initialised even if the client wasn't fully started if (!this.clientRunning) return; // already stopped _logger.logger.log("stopping MatrixClient"); this.clientRunning = false; (_this$syncApi = this.syncApi) === null || _this$syncApi === void 0 ? void 0 : _this$syncApi.stop(); this.syncApi = undefined; (_this$peekSync = this.peekSync) === null || _this$peekSync === void 0 ? void 0 : _this$peekSync.stopPeeking(); (_this$callEventHandle = this.callEventHandler) === null || _this$callEventHandle === void 0 ? void 0 : _this$callEventHandle.stop(); (_this$groupCallEventH = this.groupCallEventHandler) === null || _this$groupCallEventH === void 0 ? void 0 : _this$groupCallEventH.stop(); this.callEventHandler = undefined; this.groupCallEventHandler = undefined; global.clearInterval(this.checkTurnServersIntervalID); this.checkTurnServersIntervalID = undefined; if (this.clientWellKnownIntervalID !== undefined) { global.clearInterval(this.clientWellKnownIntervalID); } this.toDeviceMessageQueue.stop(); } /** * Try to rehydrate a device if available. The client must have been * initialized with a `cryptoCallback.getDehydrationKey` option, and this * function must be called before initCrypto and startClient are called. * * @returns Promise which resolves to undefined if a device could not be dehydrated, or * to the new device ID if the dehydration was successful. * @returns Rejects: with an error response. */ async rehydrateDevice() { if (this.crypto) { throw new Error("Cannot rehydrate device after crypto is initialized"); } if (!this.cryptoCallbacks.getDehydrationKey) { return; } const getDeviceResult = await this.getDehydratedDevice(); if (!getDeviceResult) { return; } if (!getDeviceResult.device_data || !getDeviceResult.device_id) { _logger.logger.info("no dehydrated device found"); return; } const account = new global.Olm.Account(); try { const deviceData = getDeviceResult.device_data; if (deviceData.algorithm !== _dehydration.DEHYDRATION_ALGORITHM) { _logger.logger.warn("Wrong algorithm for dehydrated device"); return; } _logger.logger.log("unpickling dehydrated device"); const key = await this.cryptoCallbacks.getDehydrationKey(deviceData, k => { // copy the key so that it doesn't get clobbered account.unpickle(new Uint8Array(k), deviceData.account); }); account.unpickle(key, deviceData.account); _logger.logger.log("unpickled device"); const rehydrateResult = await this.http.authedRequest(_httpApi.Method.Post, "/dehydrated_device/claim", undefined, { device_id: getDeviceResult.device_id }, { prefix: "/_matrix/client/unstable/org.matrix.msc2697.v2" }); if (rehydrateResult.success) { this.deviceId = getDeviceResult.device_id; _logger.logger.info("using dehydrated device"); const pickleKey = this.pickleKey || "DEFAULT_KEY"; this.exportedOlmDeviceToImport = { pickledAccount: account.pickle(pickleKey), sessions: [], pickleKey: pickleKey }; account.free(); return this.deviceId; } else { account.free(); _logger.logger.info("not using dehydrated device"); return; } } catch (e) { account.free(); _logger.logger.warn("could not unpickle", e); } } /** * Get the current dehydrated device, if any * @returns A promise of an object containing the dehydrated device */ async getDehydratedDevice() { try { return await this.http.authedRequest(_httpApi.Method.Get, "/dehydrated_device", undefined, undefined, { prefix: "/_matrix/client/unstable/org.matrix.msc2697.v2" }); } catch (e) { _logger.logger.info("could not get dehydrated device", e); return; } } /** * Set the dehydration key. This will also periodically dehydrate devices to * the server. * * @param key - the dehydration key * @param keyInfo - Information about the key. Primarily for * information about how to generate the key from a passphrase. * @param deviceDisplayName - The device display name for the * dehydrated device. * @returns A promise that resolves when the dehydrated device is stored. */ async setDehydrationKey(key, keyInfo, deviceDisplayName) { if (!this.crypto) { _logger.logger.warn("not dehydrating device if crypto is not enabled"); return; } return this.crypto.dehydrationManager.setKeyAndQueueDehydration(key, keyInfo, deviceDisplayName); } /** * Creates a new dehydrated device (without queuing periodic dehydration) * @param key - the dehydration key * @param keyInfo - Information about the key. Primarily for * information about how to generate the key from a passphrase. * @param deviceDisplayName - The device display name for the * dehydrated device. * @returns the device id of the newly created dehydrated device */ async createDehydratedDevice(key, keyInfo, deviceDisplayName) { if (!this.crypto) { _logger.logger.warn("not dehydrating device if crypto is not enabled"); return; } await this.crypto.dehydrationManager.setKey(key, keyInfo, deviceDisplayName); return this.crypto.dehydrationManager.dehydrateDevice(); } async exportDevice() { if (!this.crypto) { _logger.logger.warn("not exporting device if crypto is not enabled"); return; } return { userId: this.credentials.userId, deviceId: this.deviceId, // XXX: Private member access. olmDevice: await this.crypto.olmDevice.export() }; } /** * Clear any data out of the persistent stores used by the client. * * @returns Promise which resolves when the stores have been cleared. */ clearStores() { if (this.clientRunning) { throw new Error("Cannot clear stores while client is running"); } const promises = []; promises.push(this.store.deleteAllData()); if (this.cryptoStore) { promises.push(this.cryptoStore.deleteAllData()); } // delete the stores used by the rust matrix-sdk-crypto, in case they were used const deleteRustSdkStore = async () => { let indexedDB; try { indexedDB = global.indexedDB; } catch (e) { // No indexeddb support return; } for (const dbname of [`${_constants.RUST_SDK_STORE_PREFIX}::matrix-sdk-crypto`, `${_constants.RUST_SDK_STORE_PREFIX}::matrix-sdk-crypto-meta`]) { const prom = new Promise((resolve, reject) => { _logger.logger.info(`Removing IndexedDB instance ${dbname}`); const req = indexedDB.deleteDatabase(dbname); req.onsuccess = _ => { _logger.logger.info(`Removed IndexedDB instance ${dbname}`); resolve(0); }; req.onerror = e => { // In private browsing, Firefox has a global.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. _logger.logger.warn(`Failed to remove IndexedDB instance ${dbname}:`, e); resolve(0); }; req.onblocked = e => { _logger.logger.info(`cannot yet remove IndexedDB instance ${dbname}`); }; }); await prom; } }; 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() { if (this.credentials && this.credentials.userId) { return this.credentials.userId; } return null; } /** * Get the user-id of the logged-in user * * @returns MXID for the logged-in user * @throws Error if not logged in */ getSafeUserId() { const 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() { if (this.credentials && this.credentials.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() { if (this.credentials && this.credentials.userId) { return this.credentials.userId.split(":")[0].substring(1); } return 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.canSupportVoip; } /** * @returns */ getMediaHandler() { return this.mediaHandler; } /** * Set whether VoIP calls are forced to use on