"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var _a, _b;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MatrixClient = void 0;
const events_1 = require("events");
const MemoryStorageProvider_1 = require("./storage/MemoryStorageProvider");
const UnstableApis_1 = require("./UnstableApis");
const request_1 = require("./request");
const LogService_1 = require("./logging/LogService");
const htmlencode_1 = require("htmlencode");
const RichReply_1 = require("./helpers/RichReply");
const Metrics_1 = require("./metrics/Metrics");
const decorators_1 = require("./metrics/decorators");
const AdminApis_1 = require("./AdminApis");
const Presence_1 = require("./models/Presence");
const MembershipEvent_1 = require("./models/events/MembershipEvent");
const RoomEvent_1 = require("./models/events/RoomEvent");
const EventKind_1 = require("./models/events/EventKind");
const IdentityClient_1 = require("./identity/IdentityClient");
const http_1 = require("./http");
const html_to_text_1 = require("html-to-text");
const Spaces_1 = require("./models/Spaces");
const PowerLevelAction_1 = require("./models/PowerLevelAction");
const CryptoClient_1 = require("./e2ee/CryptoClient");
const Crypto_1 = require("./models/Crypto");
const decorators_2 = require("./e2ee/decorators");
const EncryptedRoomEvent_1 = require("./models/events/EncryptedRoomEvent");
const SYNC_BACKOFF_MIN_MS = 5000;
const SYNC_BACKOFF_MAX_MS = 15000;
/**
 * A client that is capable of interacting with a matrix homeserver.
 */
class MatrixClient extends events_1.EventEmitter {
    /**
     * Creates a new matrix client
     * @param {string} homeserverUrl The homeserver's client-server API URL
     * @param {string} accessToken The access token for the homeserver
     * @param {IStorageProvider} storage The storage provider to use. Defaults to MemoryStorageProvider.
     * @param {ICryptoStorageProvider} cryptoStore Optional crypto storage provider to use. If not supplied,
     * end-to-end encryption will not be functional in this client.
     */
    constructor(homeserverUrl, accessToken, storage = null, cryptoStore = null) {
        super();
        this.homeserverUrl = homeserverUrl;
        this.accessToken = accessToken;
        this.storage = storage;
        this.cryptoStore = cryptoStore;
        /**
         * The presence status to use while syncing. The valid values are "online" to set the account as online,
         * "offline" to set the user as offline, "unavailable" for marking the user away, and null for not setting
         * an explicit presence (the default).
         *
         * Has no effect if the client is not syncing. Does not apply until the next sync request.
         */
        this.syncingPresence = null;
        /**
         * The number of milliseconds to wait for new events for on the next sync.
         *
         * Has no effect if the client is not syncing. Does not apply until the next sync request.
         */
        this.syncingTimeout = 30000;
        this.requestId = 0;
        this.lastJoinedRoomIds = [];
        this.joinStrategy = null;
        this.eventProcessors = {};
        this.filterId = 0;
        this.stopSyncing = false;
        this.metricsInstance = new Metrics_1.Metrics();
        this.unstableApisInstance = new UnstableApis_1.UnstableApis(this);
        /**
         * Set this to true to have the client only persist the sync token after the sync
         * has been processed successfully. Note that if this is true then when the sync
         * loop throws an error the client will not persist a token.
         */
        this.persistTokenAfterSync = false;
        if (this.homeserverUrl.endsWith("/")) {
            this.homeserverUrl = this.homeserverUrl.substring(0, this.homeserverUrl.length - 1);
        }
        if (this.cryptoStore) {
            if (!this.storage || this.storage instanceof MemoryStorageProvider_1.MemoryStorageProvider) {
                LogService_1.LogService.warn("MatrixClientLite", "Starting an encryption-capable client with a memory store is not considered a good idea.");
            }
            this.crypto = new CryptoClient_1.CryptoClient(this);
            LogService_1.LogService.debug("MatrixClientLite", "End-to-end encryption client created");
        }
        else {
            // LogService.trace("MatrixClientLite", "Not setting up encryption");
        }
        if (!this.storage)
            this.storage = new MemoryStorageProvider_1.MemoryStorageProvider();
    }
    /**
     * The storage provider for this client. Direct access is usually not required.
     */
    get storageProvider() {
        return this.storage;
    }
    /**
     * The metrics instance for this client
     */
    get metrics() {
        return this.metricsInstance;
    }
    /**
     * Assigns a new metrics instance, overwriting the old one.
     * @param {Metrics} metrics The new metrics instance.
     */
    set metrics(metrics) {
        if (!metrics)
            throw new Error("Metrics cannot be null/undefined");
        this.metricsInstance = metrics;
    }
    /**
     * Gets the unstable API access class. This is generally not recommended to be
     * used by clients.
     * @return {UnstableApis} The unstable API access class.
     */
    get unstableApis() {
        return this.unstableApisInstance;
    }
    /**
     * Gets the admin API access class.
     * @return {AdminApis} The admin API access class.
     */
    get adminApis() {
        return new AdminApis_1.AdminApis(this);
    }
    /**
     * Sets a user ID to impersonate as. This will assume that the access token for this client
     * is for an application service, and that the userId given is within the reach of the
     * application service. Setting this to null will stop future impersonation. The user ID is
     * assumed to already be valid
     * @param {string} userId The user ID to masquerade as
     */
    impersonateUserId(userId) {
        this.impersonatedUserId = userId;
        this.userId = userId;
    }
    /**
     * Acquires an identity server client for communicating with an identity server. Note that
     * this will automatically do the login portion to establish a usable token with the identity
     * server provided, but it will not automatically accept any terms of service.
     *
     * The identity server name provided will in future be resolved to a server address - for now
     * that resolution is assumed to be prefixing the name with `https://`.
     * @param {string} identityServerName The domain of the identity server to connect to.
     * @returns {Promise<IdentityClient>} Resolves to a prepared identity client.
     */
    getIdentityServerClient(identityServerName) {
        return __awaiter(this, void 0, void 0, function* () {
            const oidcToken = yield this.getOpenIDConnectToken();
            return IdentityClient_1.IdentityClient.acquire(oidcToken, `https://${identityServerName}`, this);
        });
    }
    /**
     * Sets the strategy to use for when joinRoom is called on this client
     * @param {IJoinRoomStrategy} strategy The strategy to use, or null to use none
     */
    setJoinStrategy(strategy) {
        this.joinStrategy = strategy;
    }
    /**
     * Adds a preprocessor to the event pipeline. When this client encounters an event, it
     * will try to run it through the preprocessors it can in the order they were added.
     * @param {IPreprocessor} preprocessor the preprocessor to add
     */
    addPreprocessor(preprocessor) {
        if (!preprocessor)
            throw new Error("Preprocessor cannot be null");
        const eventTypes = preprocessor.getSupportedEventTypes();
        if (!eventTypes)
            return; // Nothing to do
        for (const eventType of eventTypes) {
            if (!this.eventProcessors[eventType])
                this.eventProcessors[eventType] = [];
            this.eventProcessors[eventType].push(preprocessor);
        }
    }
    processEvent(event) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!event)
                return event;
            if (!this.eventProcessors[event["type"]])
                return event;
            for (const processor of this.eventProcessors[event["type"]]) {
                yield processor.processEvent(event, this, EventKind_1.EventKind.RoomEvent);
            }
            return event;
        });
    }
    /**
     * Retrieves an OpenID Connect token from the homeserver for the current user.
     * @returns {Promise<OpenIDConnectToken>} Resolves to the token.
     */
    getOpenIDConnectToken() {
        return __awaiter(this, void 0, void 0, function* () {
            const userId = encodeURIComponent(yield this.getUserId());
            return this.doRequest("POST", "/_matrix/client/r0/user/" + userId + "/openid/request_token", null, {});
        });
    }
    /**
     * Retrieves content from account data.
     * @param {string} eventType The type of account data to retrieve.
     * @returns {Promise<any>} Resolves to the content of that account data.
     */
    getAccountData(eventType) {
        return __awaiter(this, void 0, void 0, function* () {
            const userId = encodeURIComponent(yield this.getUserId());
            eventType = encodeURIComponent(eventType);
            return this.doRequest("GET", "/_matrix/client/r0/user/" + userId + "/account_data/" + eventType);
        });
    }
    /**
     * Retrieves content from room account data.
     * @param {string} eventType The type of room account data to retrieve.
     * @param {string} roomId The room to read the account data from.
     * @returns {Promise<any>} Resolves to the content of that account data.
     */
    getRoomAccountData(eventType, roomId) {
        return __awaiter(this, void 0, void 0, function* () {
            const userId = encodeURIComponent(yield this.getUserId());
            eventType = encodeURIComponent(eventType);
            roomId = encodeURIComponent(roomId);
            return this.doRequest("GET", "/_matrix/client/r0/user/" + userId + "/rooms/" + roomId + "/account_data/" + eventType);
        });
    }
    /**
     * Retrieves content from account data. If the account data request throws an error,
     * this simply returns the default provided.
     * @param {string} eventType The type of account data to retrieve.
     * @param {any} defaultContent The default value. Defaults to null.
     * @returns {Promise<any>} Resolves to the content of that account data, or the default.
     */
    getSafeAccountData(eventType, defaultContent = null) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                return yield this.getAccountData(eventType);
            }
            catch (e) {
                LogService_1.LogService.warn("MatrixClient", `Error getting ${eventType} account data:`, LogService_1.extractRequestError(e));
                return defaultContent;
            }
        });
    }
    /**
     * Retrieves content from room account data. If the account data request throws an error,
     * this simply returns the default provided.
     * @param {string} eventType The type of room account data to retrieve.
     * @param {string} roomId The room to read the account data from.
     * @param {any} defaultContent The default value. Defaults to null.
     * @returns {Promise<any>} Resolves to the content of that room account data, or the default.
     */
    getSafeRoomAccountData(eventType, roomId, defaultContent = null) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                return yield this.getRoomAccountData(eventType, roomId);
            }
            catch (e) {
                LogService_1.LogService.warn("MatrixClient", `Error getting ${eventType} room account data in ${roomId}:`, LogService_1.extractRequestError(e));
                return defaultContent;
            }
        });
    }
    /**
     * Sets account data.
     * @param {string} eventType The type of account data to set
     * @param {any} content The content to set
     * @returns {Promise<any>} Resolves when updated
     */
    setAccountData(eventType, content) {
        return __awaiter(this, void 0, void 0, function* () {
            const userId = encodeURIComponent(yield this.getUserId());
            eventType = encodeURIComponent(eventType);
            return this.doRequest("PUT", "/_matrix/client/r0/user/" + userId + "/account_data/" + eventType, null, content);
        });
    }
    /**
     * Sets room account data.
     * @param {string} eventType The type of room account data to set
     * @param {string} roomId The room to set account data in
     * @param {any} content The content to set
     * @returns {Promise<any>} Resolves when updated
     */
    setRoomAccountData(eventType, roomId, content) {
        return __awaiter(this, void 0, void 0, function* () {
            const userId = encodeURIComponent(yield this.getUserId());
            eventType = encodeURIComponent(eventType);
            roomId = encodeURIComponent(roomId);
            return this.doRequest("PUT", "/_matrix/client/r0/user/" + userId + "/rooms/" + roomId + "/account_data/" + eventType, null, content);
        });
    }
    /**
     * Gets the presence information for the current user.
     * @returns {Promise<Presence>} Resolves to the presence status of the user.
     */
    getPresenceStatus() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.getPresenceStatusFor(yield this.getUserId());
        });
    }
    /**
     * Gets the presence information for a given user.
     * @param {string} userId The user ID to look up the presence of.
     * @returns {Promise<Presence>} Resolves to the presence status of the user.
     */
    getPresenceStatusFor(userId) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.doRequest("GET", "/_matrix/client/r0/presence/" + encodeURIComponent(userId) + "/status").then(r => new Presence_1.Presence(r));
        });
    }
    /**
     * Sets the presence status for the current user.
     * @param {"online"|"offline"|"unavailable"} presence The new presence state for the user.
     * @param {string} statusMessage Optional status message to include with the presence.
     * @returns {Promise<any>} Resolves when complete.
     */
    setPresenceStatus(presence, statusMessage = null) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.doRequest("PUT", "/_matrix/client/r0/presence/" + encodeURIComponent(yield this.getUserId()) + "/status", null, {
                presence: presence,
                status_msg: statusMessage,
            });
        });
    }
    /**
     * Gets a published alias for the given room. These are supplied by the room admins
     * and should point to the room, but may not. This is primarily intended to be used
     * in the context of rendering a mention (pill) for a room.
     * @param {string} roomIdOrAlias The room ID or alias to get an alias for.
     * @returns {Promise<string>} Resolves to a published room alias, or falsey if none found.
     */
    getPublishedAlias(roomIdOrAlias) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                const roomId = yield this.resolveRoom(roomIdOrAlias);
                const event = yield this.getRoomStateEvent(roomId, "m.room.canonical_alias", "");
                if (!event)
                    return null;
                const canonical = event['alias'];
                const alt = event['alt_aliases'] || [];
                return canonical || alt[0];
            }
            catch (e) {
                // Assume none
                return null;
            }
        });
    }
    /**
     * Adds a new room alias to the room directory
     * @param {string} alias The alias to add (eg: "#my-room:matrix.org")
     * @param {string} roomId The room ID to add the alias to
     * @returns {Promise} resolves when the alias has been added
     */
    createRoomAlias(alias, roomId) {
        alias = encodeURIComponent(alias);
        return this.doRequest("PUT", "/_matrix/client/r0/directory/room/" + alias, null, {
            "room_id": roomId,
        });
    }
    /**
     * Removes a room alias from the room directory
     * @param {string} alias The alias to remove
     * @returns {Promise} resolves when the alias has been deleted
     */
    deleteRoomAlias(alias) {
        alias = encodeURIComponent(alias);
        return this.doRequest("DELETE", "/_matrix/client/r0/directory/room/" + alias);
    }
    /**
     * Sets the visibility of a room in the directory.
     * @param {string} roomId The room ID to manipulate the visibility of
     * @param {"public" | "private"} visibility The visibility to set for the room
     * @return {Promise} resolves when the visibility has been updated
     */
    setDirectoryVisibility(roomId, visibility) {
        roomId = encodeURIComponent(roomId);
        return this.doRequest("PUT", "/_matrix/client/r0/directory/list/room/" + roomId, null, {
            "visibility": visibility,
        });
    }
    /**
     * Gets the visibility of a room in the directory.
     * @param {string} roomId The room ID to query the visibility of
     * @return {Promise<"public"|"private">} The visibility of the room
     */
    getDirectoryVisibility(roomId) {
        roomId = encodeURIComponent(roomId);
        return this.doRequest("GET", "/_matrix/client/r0/directory/list/room/" + roomId).then(response => {
            return response["visibility"];
        });
    }
    /**
     * Resolves a room ID or alias to a room ID. If the given ID or alias looks like a room ID
     * already, it will be returned as-is. If the room ID or alias looks like a room alias, it
     * will be resolved to a room ID if possible. If the room ID or alias is neither, an error
     * will be raised.
     * @param {string} roomIdOrAlias the room ID or alias to resolve to a room ID
     * @returns {Promise<string>} resolves to the room ID
     */
    resolveRoom(roomIdOrAlias) {
        return __awaiter(this, void 0, void 0, function* () {
            if (roomIdOrAlias.startsWith("!"))
                return roomIdOrAlias; // probably
            if (roomIdOrAlias.startsWith("#"))
                return this.lookupRoomAlias(roomIdOrAlias).then(r => r.roomId);
            throw new Error("Invalid room ID or alias");
        });
    }
    /**
     * Does a room directory lookup for a given room alias
     * @param {string} roomAlias the room alias to look up in the room directory
     * @returns {Promise<RoomDirectoryLookupResponse>} resolves to the room's information
     */
    lookupRoomAlias(roomAlias) {
        return this.doRequest("GET", "/_matrix/client/r0/directory/room/" + encodeURIComponent(roomAlias)).then(response => {
            return {
                roomId: response["room_id"],
                residentServers: response["servers"],
            };
        });
    }
    /**
     * Invites a user to a room.
     * @param {string} userId the user ID to invite
     * @param {string} roomId the room ID to invite the user to
     * @returns {Promise<any>} resolves when completed
     */
    inviteUser(userId, roomId) {
        return this.doRequest("POST", "/_matrix/client/r0/rooms/" + encodeURIComponent(roomId) + "/invite", null, {
            user_id: userId,
        });
    }
    /**
     * Kicks a user from a room.
     * @param {string} userId the user ID to kick
     * @param {string} roomId the room ID to kick the user in
     * @param {string?} reason optional reason for the kick
     * @returns {Promise<any>} resolves when completed
     */
    kickUser(userId, roomId, reason = null) {
        return this.doRequest("POST", "/_matrix/client/r0/rooms/" + encodeURIComponent(roomId) + "/kick", null, {
            user_id: userId,
            reason: reason,
        });
    }
    /**
     * Bans a user from a room.
     * @param {string} userId the user ID to ban
     * @param {string} roomId the room ID to set the ban in
     * @param {string?} reason optional reason for the ban
     * @returns {Promise<any>} resolves when completed
     */
    banUser(userId, roomId, reason = null) {
        return this.doRequest("POST", "/_matrix/client/r0/rooms/" + encodeURIComponent(roomId) + "/ban", null, {
            user_id: userId,
            reason: reason,
        });
    }
    /**
     * Unbans a user in a room.
     * @param {string} userId the user ID to unban
     * @param {string} roomId the room ID to lift the ban in
     * @returns {Promise<any>} resolves when completed
     */
    unbanUser(userId, roomId) {
        return this.doRequest("POST", "/_matrix/client/r0/rooms/" + encodeURIComponent(roomId) + "/unban", null, {
            user_id: userId,
        });
    }
    /**
     * Gets the current user ID for this client
     * @returns {Promise<string>} The user ID of this client
     */
    getUserId() {
        if (this.userId)
            return Promise.resolve(this.userId);
        return this.getWhoAmI().then(response => {
            this.userId = response["user_id"];
            return this.userId;
        });
    }
    /**
     * Gets the user's information from the server directly.
     * @returns {Promise<IWhoAmI>} The "who am I" response.
     */
    getWhoAmI() {
        return this.doRequest("GET", "/_matrix/client/r0/account/whoami");
    }
    /**
     * Stops the client from syncing.
     */
    stop() {
        this.stopSyncing = true;
    }
    /**
     * Starts syncing the client with an optional filter
     * @param {any} filter The filter to use, or null for none
     * @returns {Promise<any>} Resolves when the client has started syncing
     */
    start(filter = null) {
        return __awaiter(this, void 0, void 0, function* () {
            this.stopSyncing = false;
            if (!filter || typeof (filter) !== "object") {
                LogService_1.LogService.trace("MatrixClientLite", "No filter given or invalid object - using defaults.");
                filter = null;
            }
            LogService_1.LogService.trace("MatrixClientLite", "Populating joined rooms to avoid excessive join emits");
            this.lastJoinedRoomIds = yield this.getJoinedRooms();
            const userId = yield this.getUserId();
            if (this.crypto) {
                LogService_1.LogService.debug("MatrixClientLite", "Preparing end-to-end encryption");
                yield this.crypto.prepare(this.lastJoinedRoomIds);
                LogService_1.LogService.info("MatrixClientLite", "End-to-end encryption enabled");
            }
            let createFilter = false;
            // noinspection ES6RedundantAwait
            let existingFilter = yield Promise.resolve(this.storage.getFilter());
            if (existingFilter) {
                LogService_1.LogService.trace("MatrixClientLite", "Found existing filter. Checking consistency with given filter");
                if (JSON.stringify(existingFilter.filter) === JSON.stringify(filter)) {
                    LogService_1.LogService.trace("MatrixClientLite", "Filters match");
                    this.filterId = existingFilter.id;
                }
                else {
                    createFilter = true;
                }
            }
            else {
                createFilter = true;
            }
            if (createFilter && filter) {
                LogService_1.LogService.trace("MatrixClientLite", "Creating new filter");
                return this.doRequest("POST", "/_matrix/client/r0/user/" + encodeURIComponent(userId) + "/filter", null, filter).then((response) => __awaiter(this, void 0, void 0, function* () {
                    this.filterId = response["filter_id"];
                    // noinspection ES6RedundantAwait
                    yield Promise.resolve(this.storage.setSyncToken(null));
                    // noinspection ES6RedundantAwait
                    yield Promise.resolve(this.storage.setFilter({
                        id: this.filterId,
                        filter: filter,
                    }));
                }));
            }
            LogService_1.LogService.trace("MatrixClientLite", "Starting sync with filter ID " + this.filterId);
            return this.startSyncInternal();
        });
    }
    startSyncInternal() {
        return this.startSync();
    }
    startSync(emitFn = null) {
        return __awaiter(this, void 0, void 0, function* () {
            // noinspection ES6RedundantAwait
            let token = yield Promise.resolve(this.storage.getSyncToken());
            const promiseWhile = () => __awaiter(this, void 0, void 0, function* () {
                if (this.stopSyncing) {
                    LogService_1.LogService.info("MatrixClientLite", "Client stop requested - stopping sync");
                    return;
                }
                try {
                    const response = yield this.doSync(token);
                    token = response["next_batch"];
                    if (!this.persistTokenAfterSync) {
                        yield Promise.resolve(this.storage.setSyncToken(token));
                    }
                    LogService_1.LogService.debug("MatrixClientLite", "Received sync. Next token: " + token);
                    yield this.processSync(response, emitFn);
                    if (this.persistTokenAfterSync) {
                        yield Promise.resolve(this.storage.setSyncToken(token));
                    }
                }
                catch (e) {
                    LogService_1.LogService.error("MatrixClientLite", "Error handling sync " + LogService_1.extractRequestError(e));
                    const backoffTime = SYNC_BACKOFF_MIN_MS + Math.random() * (SYNC_BACKOFF_MAX_MS - SYNC_BACKOFF_MIN_MS);
                    LogService_1.LogService.info("MatrixClientLite", `Backing off for ${backoffTime}ms`);
                    yield new Promise((r) => setTimeout(r, backoffTime));
                }
                return promiseWhile();
            });
            promiseWhile(); // start the loop
        });
    }
    doSync(token) {
        LogService_1.LogService.debug("MatrixClientLite", "Performing sync with token " + token);
        const conf = {
            full_state: false,
            timeout: Math.max(0, this.syncingTimeout),
        };
        // synapse complains if the variables are null, so we have to have it unset instead
        if (token)
            conf["since"] = token;
        if (this.filterId)
            conf['filter'] = this.filterId;
        if (this.syncingPresence)
            conf['presence'] = this.syncingPresence;
        // timeout is 40s if we have a token, otherwise 10min
        return this.doRequest("GET", "/_matrix/client/r0/sync", conf, null, (token ? 40000 : 600000));
    }
    processSync(raw, emitFn = null) {
        var _a, _b, _c, _d, _e, _f, _g;
        return __awaiter(this, void 0, void 0, function* () {
            if (!emitFn)
                emitFn = (e, ...p) => Promise.resolve(this.emit(e, ...p));
            if (!raw)
                return; // nothing to process
            if (raw['device_one_time_keys_count']) {
                (_a = this.crypto) === null || _a === void 0 ? void 0 : _a.updateCounts(raw['device_one_time_keys_count']);
            }
            let unusedFallbacks = null;
            if (raw['org.matrix.msc2732.device_unused_fallback_key_types']) {
                unusedFallbacks = raw['org.matrix.msc2732.device_unused_fallback_key_types'];
            }
            else if (raw['device_unused_fallback_key_types']) {
                unusedFallbacks = raw['device_unused_fallback_key_types'];
            }
            // XXX: We should be able to detect the presence of the array, but Synapse doesn't tell us about
            // feature support if we didn't upload one, so assume we're on a latest version of Synapse at least.
            if (!(unusedFallbacks === null || unusedFallbacks === void 0 ? void 0 : unusedFallbacks.includes(Crypto_1.OTKAlgorithm.Signed))) {
                yield ((_b = this.crypto) === null || _b === void 0 ? void 0 : _b.updateFallbackKey());
            }
            if (raw['device_lists']) {
                const changed = raw['device_lists']['changed'];
                const removed = raw['device_lists']['left'];
                if (changed) {
                    (_c = this.crypto) === null || _c === void 0 ? void 0 : _c.flagUsersDeviceListsOutdated(changed, true);
                }
                if (removed) {
                    // Don't resync removed device lists: they are uninteresting according to the server so we
                    // don't need them. When we request the user's device list again, we'll pull it all back in.
                    (_d = this.crypto) === null || _d === void 0 ? void 0 : _d.flagUsersDeviceListsOutdated(removed, false);
                }
            }
            // Always process device messages first to ensure there are decryption keys
            if ((_e = raw['to_device']) === null || _e === void 0 ? void 0 : _e['events']) {
                const inbox = raw['to_device']['events'];
                for (const message of inbox) {
                    if (message['type'] === 'm.room.encrypted') {
                        yield ((_f = this.crypto) === null || _f === void 0 ? void 0 : _f.processInboundDeviceMessage(message));
                    }
                    else {
                        // TODO: Emit or do something with unknown messages?
                    }
                }
            }
            if (raw['groups']) {
                const leave = raw['groups']['leave'] || {};
                for (const groupId of Object.keys(leave)) {
                    yield emitFn("unstable.group.leave", groupId, leave[groupId]);
                }
                const join = raw['groups']['join'] || {};
                for (const groupId of Object.keys(join)) {
                    yield emitFn("unstable.group.join", groupId, join[groupId]);
                }
                const invite = raw['groups']['invite'] || {};
                for (const groupId of Object.keys(invite)) {
                    yield emitFn("unstable.group.invite", groupId, invite[groupId]);
                }
            }
            if (raw['account_data'] && raw['account_data']['events']) {
                for (const event of raw['account_data']['events']) {
                    yield emitFn("account_data", event);
                }
            }
            if (!raw['rooms'])
                return; // nothing more to process
            let leftRooms = raw['rooms']['leave'] || {};
            let inviteRooms = raw['rooms']['invite'] || {};
            let joinedRooms = raw['rooms']['join'] || {};
            // Process rooms we've left first
            for (let roomId in leftRooms) {
                const room = leftRooms[roomId];
                if (room['account_data'] && room['account_data']['events']) {
                    for (const event of room['account_data']['events']) {
                        yield emitFn("room.account_data", roomId, event);
                    }
                }
                if (!room['timeline'] || !room['timeline']['events'])
                    continue;
                let leaveEvent = null;
                for (let event of room['timeline']['events']) {
                    if (event['type'] !== 'm.room.member')
                        continue;
                    if (event['state_key'] !== (yield this.getUserId()))
                        continue;
                    const oldAge = leaveEvent && leaveEvent['unsigned'] && leaveEvent['unsigned']['age'] ? leaveEvent['unsigned']['age'] : 0;
                    const newAge = event['unsigned'] && event['unsigned']['age'] ? event['unsigned']['age'] : 0;
                    if (leaveEvent && oldAge < newAge)
                        continue;
                    leaveEvent = event;
                }
                if (!leaveEvent) {
                    LogService_1.LogService.warn("MatrixClientLite", "Left room " + roomId + " without receiving an event");
                    continue;
                }
                leaveEvent = yield this.processEvent(leaveEvent);
                yield emitFn("room.leave", roomId, leaveEvent);
                this.lastJoinedRoomIds = this.lastJoinedRoomIds.filter(r => r !== roomId);
            }
            // Process rooms we've been invited to
            for (let roomId in inviteRooms) {
                const room = inviteRooms[roomId];
                if (!room['invite_state'] || !room['invite_state']['events'])
                    continue;
                let inviteEvent = null;
                for (let event of room['invite_state']['events']) {
                    if (event['type'] !== 'm.room.member')
                        continue;
                    if (event['state_key'] !== (yield this.getUserId()))
                        continue;
                    if (!event['content'])
                        continue;
                    if (event['content']['membership'] !== "invite")
                        continue;
                    const oldAge = inviteEvent && inviteEvent['unsigned'] && inviteEvent['unsigned']['age'] ? inviteEvent['unsigned']['age'] : 0;
                    const newAge = event['unsigned'] && event['unsigned']['age'] ? event['unsigned']['age'] : 0;
                    if (inviteEvent && oldAge < newAge)
                        continue;
                    inviteEvent = event;
                }
                if (!inviteEvent) {
                    LogService_1.LogService.warn("MatrixClientLite", "Invited to room " + roomId + " without receiving an event");
                    continue;
                }
                inviteEvent = yield this.processEvent(inviteEvent);
                yield emitFn("room.invite", roomId, inviteEvent);
            }
            // Process rooms we've joined and their events
            for (let roomId in joinedRooms) {
                if (this.lastJoinedRoomIds.indexOf(roomId) === -1) {
                    yield emitFn("room.join", roomId);
                    this.lastJoinedRoomIds.push(roomId);
                }
                const room = joinedRooms[roomId];
                if (room['account_data'] && room['account_data']['events']) {
                    for (const event of room['account_data']['events']) {
                        yield emitFn("room.account_data", roomId, event);
                    }
                }
                if (!room['timeline'] || !room['timeline']['events'])
                    continue;
                for (let event of room['timeline']['events']) {
                    event = yield this.processEvent(event);
                    if (event['type'] === 'm.room.encrypted' && (yield ((_g = this.crypto) === null || _g === void 0 ? void 0 : _g.isRoomEncrypted(roomId)))) {
                        yield emitFn("room.encrypted_event", roomId, event);
                        try {
                            event = (yield this.crypto.decryptRoomEvent(new EncryptedRoomEvent_1.EncryptedRoomEvent(event), roomId)).raw;
                            event = yield this.processEvent(event);
                            yield emitFn("room.decrypted_event", roomId, event);
                        }
                        catch (e) {
                            LogService_1.LogService.error("MatrixClientLite", `Decryption error on ${roomId} ${event['event_id']}`, e);
                            yield emitFn("room.failed_decryption", roomId, event, e);
                        }
                    }
                    if (event['type'] === 'm.room.message') {
                        yield emitFn("room.message", roomId, event);
                    }
                    if (event['type'] === 'm.room.tombstone' && event['state_key'] === '') {
                        yield emitFn("room.archived", roomId, event);
                    }
                    if (event['type'] === 'm.room.create' && event['state_key'] === '' && event['content']
                        && event['content']['predecessor'] && event['content']['predecessor']['room_id']) {
                        yield emitFn("room.upgraded", roomId, event);
                    }
                    yield emitFn("room.event", roomId, event);
                }
            }
        });
    }
    /**
     * Gets an event for a room. If the event is encrypted, and the client supports encryption,
     * and the room is encrypted, then this will return a decrypted event.
     * @param {string} roomId the room ID to get the event in
     * @param {string} eventId the event ID to look up
     * @returns {Promise<any>} resolves to the found event
     */
    getEvent(roomId, eventId) {
        var _a;
        return __awaiter(this, void 0, void 0, function* () {
            const event = yield this.getRawEvent(roomId, eventId);
            if (event['type'] === 'm.room.encrypted' && (yield ((_a = this.crypto) === null || _a === void 0 ? void 0 : _a.isRoomEncrypted(roomId)))) {
                return this.processEvent((yield this.crypto.decryptRoomEvent(new EncryptedRoomEvent_1.EncryptedRoomEvent(event), roomId)).raw);
            }
            return event;
        });
    }
    /**
     * Gets an event for a room. Returned as a raw event.
     * @param {string} roomId the room ID to get the event in
     * @param {string} eventId the event ID to look up
     * @returns {Promise<any>} resolves to the found event
     */
    getRawEvent(roomId, eventId) {
        return this.doRequest("GET", "/_matrix/client/r0/rooms/" + encodeURIComponent(roomId) + "/event/" + encodeURIComponent(eventId))
            .then(ev => this.processEvent(ev));
    }
    /**
     * Gets the room state for the given room. Returned as raw events.
     * @param {string} roomId the room ID to get state for
     * @returns {Promise<any[]>} resolves to the room's state
     */
    getRoomState(roomId) {
        return this.doRequest("GET", "/_matrix/client/r0/rooms/" + encodeURIComponent(roomId) + "/state")
            .then(state => Promise.all(state.map(ev => this.processEvent(ev))));
    }
    /**
     * Gets the state events for a given room of a given type under the given state key.
     * @param {string} roomId the room ID
     * @param {string} type the event type
     * @param {String} stateKey the state key, falsey if not needed
     * @returns {Promise<any|any[]>} resolves to the state event(s)
     * @deprecated It is not possible to get an array of events - use getRoomStateEvent instead
     */
    getRoomStateEvents(roomId, type, stateKey) {
        return this.getRoomStateEvent(roomId, type, stateKey);
    }
    /**
     * Gets a state event for a given room of a given type under the given state key.
     * @param {string} roomId the room ID
     * @param {string} type the event type
     * @param {String} stateKey the state key
     * @returns {Promise<any>} resolves to the state event
     */
    getRoomStateEvent(roomId, type, stateKey) {
        return this.doRequest("GET", "/_matrix/client/r0/rooms/" + encodeURIComponent(roomId) + "/state/" + encodeURIComponent(type) + "/" + encodeURIComponent(stateKey ? stateKey : ''))
            .then(ev => this.processEvent(ev));
    }
    /**
     * Gets the context surrounding an event.
     * @param {string} roomId The room ID to get the context in.
     * @param {string} eventId The event ID to get the context of.
     * @param {number} limit The maximum number of events to return on either side of the event.
     * @returns {Promise<EventContext>} The context of the event
     */
    getEventContext(roomId, eventId, limit = 10) {
        return __awaiter(this, void 0, void 0, function* () {
            const res = yield this.doRequest("GET", "/_matrix/client/r0/rooms/" + encodeURIComponent(roomId) + "/context/" + encodeURIComponent(eventId), { limit });
            return {
                event: new RoomEvent_1.RoomEvent(res['event']),
                before: res['events_before'].map(e => new RoomEvent_1.RoomEvent(e)),
                after: res['events_after'].map(e => new RoomEvent_1.RoomEvent(e)),
                state: res['state'].map(e => new RoomEvent_1.StateEvent(e)),
            };
        });
    }
    /**
     * Gets the profile for a given user
     * @param {string} userId the user ID to lookup
     * @returns {Promise<any>} the profile of the user
     */
    getUserProfile(userId) {
        return this.doRequest("GET", "/_matrix/client/r0/profile/" + encodeURIComponent(userId));
    }
    /**
     * Sets a new display name for the user.
     * @param {string} displayName the new display name for the user, or null to clear
     * @returns {Promise<any>} resolves when complete
     */
    setDisplayName(displayName) {
        return __awaiter(this, void 0, void 0, function* () {
            const userId = encodeURIComponent(yield this.getUserId());
            return this.doRequest("PUT", "/_matrix/client/r0/profile/" + userId + "/displayname", null, {
                displayname: displayName,
            });
        });
    }
    /**
     * Sets a new avatar url for the user.
     * @param {string} avatarUrl the new avatar URL for the user, in the form of a Matrix Content URI
     * @returns {Promise<any>} resolves when complete
     */
    setAvatarUrl(avatarUrl) {
        return __awaiter(this, void 0, void 0, function* () {
            const userId = encodeURIComponent(yield this.getUserId());
            return this.doRequest("PUT", "/_matrix/client/r0/profile/" + userId + "/avatar_url", null, {
                avatar_url: avatarUrl,
            });
        });
    }
    /**
     * Joins the given room
     * @param {string} roomIdOrAlias the room ID or alias to join
     * @param {string[]} viaServers the server names to try and join through
     * @returns {Promise<string>} resolves to the joined room ID
     */
    joinRoom(roomIdOrAlias, viaServers = []) {
        return __awaiter(this, void 0, void 0, function* () {
            const apiCall = (targetIdOrAlias) => {
                targetIdOrAlias = encodeURIComponent(targetIdOrAlias);
                const qs = {};
                if (viaServers.length > 0)
                    qs['server_name'] = viaServers;
                return this.doRequest("POST", "/_matrix/client/r0/join/" + targetIdOrAlias, qs).then(response => {
                    return response['room_id'];
                });
            };
            const userId = yield this.getUserId();
            if (this.joinStrategy)
                return this.joinStrategy.joinRoom(roomIdOrAlias, userId, apiCall);
            else
                return apiCall(roomIdOrAlias);
        });
    }
    /**
     * Gets a list of joined room IDs
     * @returns {Promise<string[]>} resolves to a list of room IDs the client participates in
     */
    getJoinedRooms() {
        return this.doRequest("GET", "/_matrix/client/r0/joined_rooms").then(response => response['joined_rooms']);
    }
    /**
     * Gets the joined members in a room. The client must be in the room to make this request.
     * @param {string} roomId The room ID to get the joined members of.
     * @returns {Promise<string>} The joined user IDs in the room
     */
    getJoinedRoomMembers(roomId) {
        return this.doRequest("GET", "/_matrix/client/r0/rooms/" + encodeURIComponent(roomId) + "/joined_members").then(response => {
            return Object.keys(response['joined']);
        });
    }
    /**
     * Gets the joined members in a room, as an object mapping userIds to profiles. The client must be in the room to make this request.
     * @param {string} roomId The room ID to get the joined members of.
     * @returns {Object} The joined user IDs in the room as an object mapped to a set of profiles.
     */
    getJoinedRoomMembersWithProfiles(roomId) {
        return __awaiter(this, void 0, void 0, function* () {
            return (yield this.doRequest("GET", "/_matrix/client/r0/rooms/" + encodeURIComponent(roomId) + "/joined_members")).joined;
        });
    }
    /**
     * Gets the membership events of users in the room. Defaults to all membership
     * types, though this can be controlled with the membership and notMembership
     * arguments. To change the point in time, use the batchToken.
     * @param {string} roomId The room ID to get members in.
     * @param {string} batchToken The point in time to get members at (or null for 'now')
     * @param {string[]} membership The membership kinds to search for.
     * @param {string[]} notMembership The membership kinds to not search for.
     * @returns {Promise<any[]>} Resolves to the membership events of the users in the room.
     */
    getRoomMembers(roomId, batchToken = null, membership = null, notMembership = null) {
        const qs = {};
        if (batchToken)
            qs["at"] = batchToken;
        if (membership)
            qs["membership"] = membership;
        if (notMembership)
            qs["not_membership"] = notMembership;
        return this.doRequest("GET", "/_matrix/client/r0/rooms/" + encodeURIComponent(roomId) + "/members", qs).then(r => {
            return r['chunk'].map(e => new MembershipEvent_1.MembershipEvent(e));
        });
    }
    /**
     * Leaves the given room
     * @param {string} roomId the room ID to leave
     * @returns {Promise<any>} resolves when left
     */
    leaveRoom(roomId) {
        return this.doRequest("POST", "/_matrix/client/r0/rooms/" + encodeURIComponent(roomId) + "/leave");
    }
    /**
     * Sends a read receipt for an event in a room
     * @param {string} roomId the room ID to send the receipt to
     * @param {string} eventId the event ID to set the receipt at
     * @returns {Promise<any>} resolves when the receipt has been sent
     */
    sendReadReceipt(roomId, eventId) {
        return this.doRequest("POST", "/_matrix/client/r0/rooms/" + encodeURIComponent(roomId) + "/receipt/m.read/" + encodeURIComponent(eventId), null, {});
    }
    /**
     * Sets the typing status of the current user in a room
     * @param {string} roomId the room ID the user is typing in
     * @param {boolean} typing is the user currently typing
     * @param {number} timeout how long should the server preserve the typing state, in milliseconds
     * @returns {Promise<any>} resolves when the typing state has been set
     */
    setTyping(roomId, typing, timeout = 30000) {
        return __awaiter(this, void 0, void 0, function* () {
            const userId = yield this.getUserId();
            return this.doRequest("PUT", "/_matrix/client/r0/rooms/" + encodeURIComponent(roomId) + "/typing/" + encodeURIComponent(userId), null, {
                typing,
                timeout,
            });
        });
    }
    /**
     * Replies to a given event with the given text. The event is sent with a msgtype of m.text.
     * The message will be encrypted if the client supports encryption and the room is encrypted.
     * @param {string} roomId the room ID to reply in
     * @param {any} event the event to reply to
     * @param {string} text the text to reply with
     * @param {string} html the HTML to reply with, or falsey to use the `text`
     * @returns {Promise<string>} resolves to the event ID which was sent
     */
    replyText(roomId, event, text, html = null) {
        if (!html)
            html = htmlencode_1.htmlEncode(text);
        const reply = RichReply_1.RichReply.createFor(roomId, event, text, html);
        return this.sendMessage(roomId, reply);
    }
    /**
     * Replies to a given event with the given HTML. The event is sent with a msgtype of m.text.
     * The message will be encrypted if the client supports encryption and the room is encrypted.
     * @param {string} roomId the room ID to reply in
     * @param {any} event the event to reply to
     * @param {string} html the HTML to reply with.
     * @returns {Promise<string>} resolves to the event ID which was sent
     */
    replyHtmlText(roomId, event, html) {
        const text = html_to_text_1.htmlToText(html, { wordwrap: false });
        const reply = RichReply_1.RichReply.createFor(roomId, event, text, html);
        return this.sendMessage(roomId, reply);
    }
    /**
     * Replies to a given event with the given text. The event is sent with a msgtype of m.notice.
     * The message will be encrypted if the client supports encryption and the room is encrypted.
     * @param {string} roomId the room ID to reply in
     * @param {any} event the event to reply to
     * @param {string} text the text to reply with
     * @param {string} html the HTML to reply with, or falsey to use the `text`
     * @returns {Promise<string>} resolves to the event ID which was sent
     */
    replyNotice(roomId, event, text, html = null) {
        if (!html)
            html = htmlencode_1.htmlEncode(text);
        const reply = RichReply_1.RichReply.createFor(roomId, event, text, html);
        reply['msgtype'] = 'm.notice';
        return this.sendMessage(roomId, reply);
    }
    /**
     * Replies to a given event with the given HTML. The event is sent with a msgtype of m.notice.
     * The message will be encrypted if the client supports encryption and the room is encrypted.
     * @param {string} roomId the room ID to reply in
     * @param {any} event the event to reply to
     * @param {string} html the HTML to reply with.
     * @returns {Promise<string>} resolves to the event ID which was sent
     */
    replyHtmlNotice(roomId, event, html) {
        const text = html_to_text_1.htmlToText(html, { wordwrap: false });
        const reply = RichReply_1.RichReply.createFor(roomId, event, text, html);
        reply['msgtype'] = 'm.notice';
        return this.sendMessage(roomId, reply);
    }
    /**
     * Sends a notice to the given room. The message will be encrypted if the client supports
     * encryption and the room is encrypted.
     * @param {string} roomId the room ID to send the notice to
     * @param {string} text the text to send
     * @returns {Promise<string>} resolves to the event ID that represents the message
     */
    sendNotice(roomId, text) {
        return this.sendMessage(roomId, {
            body: text,
            msgtype: "m.notice",
        });
    }
    /**
     * Sends a notice to the given room with HTML content. The message will be encrypted if the client supports
     * encryption and the room is encrypted.
     * @param {string} roomId the room ID to send the notice to
     * @param {string} html the HTML to send
     * @returns {Promise<string>} resolves to the event ID that represents the message
     */
    sendHtmlNotice(roomId, html) {
        return this.sendMessage(roomId, {
            body: html_to_text_1.htmlToText(html, { wordwrap: false }),
            msgtype: "m.notice",
            format: "org.matrix.custom.html",
            formatted_body: html,
        });
    }
    /**
     * Sends a text message to the given room. The message will be encrypted if the client supports
     * encryption and the room is encrypted.
     * @param {string} roomId the room ID to send the text to
     * @param {string} text the text to send
     * @returns {Promise<string>} resolves to the event ID that represents the message
     */
    sendText(roomId, text) {
        return this.sendMessage(roomId, {
            body: text,
            msgtype: "m.text",
        });
    }
    /**
     * Sends a text message to the given room with HTML content. The message will be encrypted if the client supports
     * encryption and the room is encrypted.
     * @param {string} roomId the room ID to send the text to
     * @param {string} html the HTML to send
     * @returns {Promise<string>} resolves to the event ID that represents the message
     */
    sendHtmlText(roomId, html) {
        return this.sendMessage(roomId, {
            body: html_to_text_1.htmlToText(html, { wordwrap: false }),
            msgtype: "m.text",
            format: "org.matrix.custom.html",
            formatted_body: html,
        });
    }
    /**
     * Sends a message to the given room. The message will be encrypted if the client supports
     * encryption and the room is encrypted.
     * @param {string} roomId the room ID to send the message to
     * @param {object} content the event content to send
     * @returns {Promise<string>} resolves to the event ID that represents the message
     */
    sendMessage(roomId, content) {
        return this.sendEvent(roomId, "m.room.message", content);
    }
    /**
     * Sends an event to the given room. This will encrypt the event before sending if the room is
     * encrypted and the client supports encryption. Use sendRawEvent() to avoid this behaviour.
     * @param {string} roomId the room ID to send the event to
     * @param {string} eventType the type of event to send
     * @param {string} content the event body to send
     * @returns {Promise<string>} resolves to the event ID that represents the event
     */
    sendEvent(roomId, eventType, content) {
        var _a;
        return __awaiter(this, void 0, void 0, function* () {
            if (yield ((_a = this.crypto) === null || _a === void 0 ? void 0 : _a.isRoomEncrypted(roomId))) {
                content = yield this.crypto.encryptRoomEvent(roomId, eventType, content);
                eventType = "m.room.encrypted";
            }
            return this.sendRawEvent(roomId, eventType, content);
        });
    }
    /**
     * Sends an event to the given room.
     * @param {string} roomId the room ID to send the event to
     * @param {string} eventType the type of event to send
     * @param {string} content the event body to send
     * @returns {Promise<string>} resolves to the event ID that represents the event
     */
    sendRawEvent(roomId, eventType, content) {
        return __awaiter(this, void 0, void 0, function* () {
            const txnId = (new Date().getTime()) + "__inc" + (++this.requestId);
            return this.doRequest("PUT", "/_matrix/client/r0/rooms/" + encodeURIComponent(roomId) + "/send/" + encodeURIComponent(eventType) + "/" + encodeURIComponent(txnId), null, content).then(response => {
                return response['event_id'];
            });
        });
    }
    /**
     * Sends a state event to the given room
     * @param {string} roomId the room ID to send the event to
     * @param {string} type the event type to send
     * @param {string} stateKey the state key to send, should not be null
     * @param {string} content the event body to send
     * @returns {Promise<string>} resolves to the event ID that represents the message
     */
    sendStateEvent(roomId, type, stateKey, content) {
        return this.doRequest("PUT", "/_matrix/client/r0/rooms/" + encodeURIComponent(roomId) + "/state/" + encodeURIComponent(type) + "/" + encodeURIComponent(stateKey), null, content).then(response => {
            return response['event_id'];
        });
    }
    /**
     * Redact an event in a given room
     * @param {string} roomId the room ID to send the redaction to
     * @param {string} eventId the event ID to redact
     * @param {String} reason an optional reason for redacting the event
     * @returns {Promise<string>} resolves to the event ID that represents the redaction
     */
    redactEvent(roomId, eventId, reason = null) {
        const txnId = (new Date().getTime()) + "__inc" + (++this.requestId);
        const content = reason !== null ? { reason } : {};
        return this.doRequest("PUT", `/_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/redact/${encodeURIComponent(eventId)}/${txnId}`, null, content).then(response => {
            return response['event_id'];
        });
    }
    /**
     * Creates a room. This does not break out the various options for creating a room
     * due to the large number of possibilities. See the /createRoom endpoint in the
     * spec for more information on what to provide for `properties`. Note that creating
     * a room may cause the bot/appservice to raise a join event.
     * @param {any} properties the properties of the room. See the spec for more information
     * @returns {Promise<string>} resolves to the room ID that represents the room
     */
    createRoom(properties = {}) {
        return this.doRequest("POST", "/_matrix/client/r0/createRoom", null, properties).then(response => {
            return response['room_id'];
        });
    }
    /**
     * Checks if a given user has a required power level required to send the given event.
     * @param {string} userId the user ID to check the power level of
     * @param {string} roomId the room ID to check the power level in
     * @param {string} eventType the event type to look for in the `events` property of the power levels
     * @param {boolean} isState true to indicate the event is intended to be a state event
     * @returns {Promise<boolean>} resolves to true if the user has the required power level, resolves to false otherwise
     */
    userHasPowerLevelFor(userId, roomId, eventType, isState) {
        var _a, _b;
        return __awaiter(this, void 0, void 0, function* () {
            const powerLevelsEvent = yield this.getRoomStateEvent(roomId, "m.room.power_levels", "");
            if (!powerLevelsEvent) {
                // This is technically supposed to be non-fatal, but it's pretty unreasonable for a room to be missing
                // power levels.
                throw new Error("No power level event found");
            }
            let requiredPower = isState ? 50 : 0;
            if (isState && Number.isFinite(powerLevelsEvent["state_default"]))
                requiredPower = powerLevelsEvent["state_default"];
            if (!isState && Number.isFinite(powerLevelsEvent["events_default"]))
                requiredPower = powerLevelsEvent["events_default"];
            if (Number.isFinite((_a = powerLevelsEvent["events"]) === null || _a === void 0 ? void 0 : _a[eventType]))
                requiredPower = powerLevelsEvent["events"][eventType];
            let userPower = 0;
            if (Number.isFinite(powerLevelsEvent["users_default"]))
                userPower = powerLevelsEvent["users_default"];
            if (Number.isFinite((_b = powerLevelsEvent["users"]) === null || _b === void 0 ? void 0 : _b[userId]))
                userPower = powerLevelsEvent["users"][userId];
            return userPower >= requiredPower;
        });
    }
    /**
     * Checks if a given user has a required power level to perform the given action
     * @param {string} userId the user ID to check the power level of
     * @param {string} roomId the room ID to check the power level in
     * @param {PowerLevelAction} action the action to check power level for
     * @returns {Promise<boolean>} resolves to true if the user has the required power level, resolves to false otherwise
     */
    userHasPowerLevelForAction(userId, roomId, action) {
        var _a;
        return __awaiter(this, void 0, void 0, function* () {
            const powerLevelsEvent = yield this.getRoomStateEvent(roomId, "m.room.power_levels", "");
            if (!powerLevelsEvent) {
                // This is technically supposed to be non-fatal, but it's pretty unreasonable for a room to be missing
                // power levels.
                throw new Error("No power level event found");
            }
            const defaultForActions = {
                [PowerLevelAction_1.PowerLevelAction.Ban]: 50,
                [PowerLevelAction_1.PowerLevelAction.Invite]: 50,
                [PowerLevelAction_1.PowerLevelAction.Kick]: 50,
                [PowerLevelAction_1.PowerLevelAction.RedactEvents]: 50,
                [PowerLevelAction_1.PowerLevelAction.NotifyRoom]: 50,
            };
            let requiredPower = defaultForActions[action];
            let investigate = powerLevelsEvent;
            action.split('.').forEach(k => (investigate = investigate === null || investigate === void 0 ? void 0 : investigate[k]));
            if (Number.isFinite(investigate))
                requiredPower = investigate;
            let userPower = 0;
            if (Number.isFinite(powerLevelsEvent["users_default"]))
                userPower = powerLevelsEvent["users_default"];
            if (Number.isFinite((_a = powerLevelsEvent["users"]) === null || _a === void 0 ? void 0 : _a[userId]))
                userPower = powerLevelsEvent["users"][userId];
            return userPower >= requiredPower;
        });
    }
    /**
     * Determines the boundary conditions for this client's ability to change another user's power level
     * in a given room. This will identify the maximum possible level this client can change the user to,
     * and if that change could even be possible. If the returned object indicates that the client can
     * change the power level of the user, the client is able to set the power level to any value equal
     * to or less than the maximum value.
     * @param {string} targetUserId The user ID to compare against.
     * @param {string} roomId The room ID to compare within.
     * @returns {Promise<PowerLevelBounds>} The bounds of the client's ability to change the user's power level.
     */
    calculatePowerLevelChangeBoundsOn(targetUserId, roomId) {
        return __awaiter(this, void 0, void 0, function* () {
            const myUserId = yield this.getUserId();
            const canChangePower = yield this.userHasPowerLevelFor(myUserId, roomId, "m.room.power_levels", true);
            if (!canChangePower)
                return { canModify: false, maximumPossibleLevel: 0 };
            const powerLevelsEvent = yield this.getRoomStateEvent(roomId, "m.room.power_levels", "");
            if (!powerLevelsEvent) {
                throw new Error("No power level event found");
            }
            let targetUserPower = 0;
            let myUserPower = 0;
            if (powerLevelsEvent["users"] && powerLevelsEvent["users"][targetUserId])
                targetUserPower = powerLevelsEvent["users"][targetUserId];
            if (powerLevelsEvent["users"] && powerLevelsEvent["users"][myUserId])
                myUserPower = powerLevelsEvent["users"][myUserId];
            if (myUserId === targetUserId) {
                return { canModify: true, maximumPossibleLevel: myUserPower };
            }
            if (targetUserPower >= myUserPower) {
                return { canModify: false, maximumPossibleLevel: myUserPower };
            }
            return { canModify: true, maximumPossibleLevel: myUserPower };
        });
    }
    /**
     * Sets the power level for a given user ID in the given room. Note that this is not safe to
     * call multiple times concurrently as changes are not atomic. This will throw an error if
     * the user lacks enough permission to change the power level, or if a power level event is
     * missing from the room.
     * @param {string} userId The user ID to change
     * @param {string} roomId The room ID to change the power level in
     * @param {number} newLevel The integer power level to set the user to.
     * @returns {Promise<any>} Resolves when complete.
     */
    setUserPowerLevel(userId, roomId, newLevel) {
        return __awaiter(this, void 0, void 0, function* () {
            const currentLevels = yield this.getRoomStateEvent(roomId, "m.room.power_levels", "");
            if (!currentLevels['users'])
                currentLevels['users'] = {};
            currentLevels['users'][userId] = newLevel;
            yield this.sendStateEvent(roomId, "m.room.power_levels", "", currentLevels);
        });
    }
    /**
     * Converts a MXC URI to an HTTP URL.
     * @param {string} mxc The MXC URI to convert
     * @returns {string} The HTTP URL for the content.
     */
    mxcToHttp(mxc) {
        if (!mxc.startsWith("mxc://"))
            throw new Error("Not a MXC URI");
        const parts = mxc.substring("mxc://".length).split('/');
        const originHomeserver = parts[0];
        const mediaId = parts.slice(1, parts.length).join('/');
        return `${this.homeserverUrl}/_matrix/media/r0/download/${encodeURIComponent(originHomeserver)}/${encodeURIComponent(mediaId)}`;
    }
    /**
     * Converts a MXC URI to an HTTP URL for downsizing the content.
     * @param {string} mxc The MXC URI to convert and downsize.
     * @param {number} width The width, as an integer, for the thumbnail.
     * @param {number} height The height, as an intenger, for the thumbnail.
     * @param {"crop"|"scale"} method Whether to crop or scale (preserve aspect ratio) the content.
     * @returns {string} The HTTP URL for the downsized content.
     */
    mxcToHttpThumbnail(mxc, width, height, method) {
        const downloadUri = this.mxcToHttp(mxc);
        return downloadUri.replace("/_matrix/media/r0/download", "/_matrix/media/r0/thumbnail")
            + `?width=${width}&height=${height}&method=${encodeURIComponent(method)}`;
    }
    /**
     * Uploads data to the homeserver's media repository. Note that this will <b>not</b> automatically encrypt
     * media as it cannot determine if the media should be encrypted.
     * @param {Buffer} data the content to upload.
     * @param {string} contentType the content type of the file. Defaults to application/octet-stream
     * @param {string} filename the name of the file. Optional.
     * @returns {Promise<string>} resolves to the MXC URI of the content
     */
    uploadContent(data, contentType = "application/octet-stream", filename = null) {
        // TODO: Make doRequest take an object for options
        return this.doRequest("POST", "/_matrix/media/r0/upload", { filename: filename }, data, 60000, false, contentType)
            .then(response => response["content_uri"]);
    }
    /**
     * Download content from the homeserver's media repository. Note that this will <b>not</b> automatically decrypt
     * media as it cannot determine if the media is encrypted.
     * @param {string} mxcUrl The MXC URI for the content.
     * @param {string} allowRemote Indicates to the server that it should not attempt to fetch the
     * media if it is deemed remote. This is to prevent routing loops where the server contacts itself.
     * Defaults to true if not provided.
     * @returns {Promise<{data: Buffer, contentType: string}>} Resolves to the downloaded content.
     */
    downloadContent(mxcUrl, allowRemote = true) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!mxcUrl.toLowerCase().startsWith("mxc://")) {
                throw Error("'mxcUrl' does not begin with mxc://");
            }
            const urlParts = mxcUrl.substr("mxc://".length).split("/");
            const domain = encodeURIComponent(urlParts[0]);
            const mediaId = encodeURIComponent(urlParts[1].split("/")[0]);
            const path = `/_matrix/media/r0/download/${domain}/${mediaId}`;
            const res = yield this.doRequest("GET", path, { allow_remote: allowRemote }, null, null, true, null, true);
            return {
                data: res.body,
                contentType: res.headers["content-type"],
            };
        });
    }
    /**
     * Uploads data to the homeserver's media repository after downloading it from the
     * provided URL.
     * @param {string} url The URL to download content from.
     * @returns {Promise<string>} Resolves to the MXC URI of the content
     */
    uploadContentFromUrl(url) {
        return new Promise((resolve, reject) => {
            const requestId = ++this.requestId;
            const params = {
                uri: url,
                method: "GET",
                encoding: null,
            };
            request_1.getRequestFn()(params, (err, response, resBody) => {
                if (err) {
                    LogService_1.LogService.error("MatrixLiteClient (REQ-" + requestId + ")", LogService_1.extractRequestError(err));
                    reject(err);
                }
                else {
                    const contentType = response.headers['content-type'] || "application/octet-stream";
                    LogService_1.LogService.trace("MatrixLiteClient (REQ-" + requestId + " RESP-H" + response.statusCode + ")", "<data>");
                    if (response.statusCode < 200 || response.statusCode >= 300) {
                        LogService_1.LogService.error("MatrixLiteClient (REQ-" + requestId + ")", "<data>");
                        reject(response);
                    }
                    else
                        resolve({ body: resBody, contentType: contentType });
                }
            });
        }).then(obj => {
            return this.uploadContent(obj.body, obj.contentType);
        });
    }
    /**
     * Determines the upgrade history for a given room as a doubly-linked list styled structure. Given
     * a room ID in the history of upgrades, the resulting `previous` array will hold any rooms which
     * are older than the given room. The resulting `newer` array will hold any rooms which are newer
     * versions of the room. Both arrays will be defined, but may be empty individually. Element zero
     * of each will always be the nearest to the given room ID and the last element will be the furthest
     * from the room. The given room will never be in either array.
     * @param {string} roomId the room ID to get the history of
     * @returns {Promise<{previous: RoomReference[], newer: RoomReference[]}>} Resolves to the room's
     * upgrade history
     */
    getRoomUpgradeHistory(roomId) {
        return __awaiter(this, void 0, void 0, function* () {
            const result = { previous: [], newer: [], current: null };
            const chaseCreates = (findRoomId) => __awaiter(this, void 0, void 0, function* () {
                try {
                    const createEvent = yield this.getRoomStateEvent(findRoomId, "m.room.create", "");
                    if (!createEvent)
                        return;
                    if (findRoomId === roomId && !result.current) {
                        const version = createEvent['room_version'] || '1';
                        result.current = {
                            roomId: roomId,
                            version: version,
                            refEventId: null,
                        };
                    }
                    if (createEvent['predecessor'] && createEvent['predecessor']['room_id']) {
                        const prevRoomId = createEvent['predecessor']['room_id'];
                        if (prevRoomId === findRoomId)
                            return; // Recursion is bad
                        if (result.previous.find(r => r.roomId === prevRoomId))
                            return; // Already found
                        let tombstoneEventId = null;
                        let prevVersion = "1";
                        try {
                            const roomState = yield this.getRoomState(prevRoomId);
                            const tombstone = roomState.find(e => e['type'] === 'm.room.tombstone' && e['state_key'] === '');
                            const create = roomState.find(e => e['type'] === 'm.room.create' && e['state_key'] === '');
                            if (tombstone) {
                                if (!tombstone['content'])
                                    tombstone['content'] = {};
                                const tombstoneRefRoomId = tombstone['content']['replacement_room'];
                                if (tombstoneRefRoomId === findRoomId)
                                    tombstoneEventId = tombstone['event_id'];
                            }
                            if (create) {
                                if (!create['content'])
                                    create['content'] = {};
                                prevVersion = create['content']['room_version'] || "1";
                            }
                        }
                        catch (e) {
                            // state not available
                        }
                        result.previous.push({
                            roomId: prevRoomId,
                            version: prevVersion,
                            refEventId: tombstoneEventId,
                        });
                        return chaseCreates(prevRoomId);
                    }
                }
                catch (e) {
                    // no create event - that's fine
                }
            });
            const chaseTombstones = (findRoomId) => __awaiter(this, void 0, void 0, function* () {
                try {
                    const tombstoneEvent = yield this.getRoomStateEvent(findRoomId, "m.room.tombstone", "");
                    if (!tombstoneEvent)
                        return;
                    if (!tombstoneEvent['replacement_room'])
                        return;
                    const newRoomId = tombstoneEvent['replacement_room'];
                    if (newRoomId === findRoomId)
                        return; // Recursion is bad
                    if (result.newer.find(r => r.roomId === newRoomId))
                        return; // Already found
                    let newRoomVersion = "1";
                    let createEventId = null;
                    try {
                        const roomState = yield this.getRoomState(newRoomId);
                        const create = roomState.find(e => e['type'] === 'm.room.create' && e['state_key'] === '');
                        if (create) {
                            if (!create['content'])
                                create['content'] = {};
                            const predecessor = create['content']['predecessor'] || {};
                            const refPrevRoomId = predecessor['room_id'];
                            if (refPrevRoomId === findRoomId) {
                                createEventId = create['event_id'];
                            }
                            newRoomVersion = create['content']['room_version'] || "1";
                        }
                    }
                    catch (e) {
                        // state not available
                    }
                    result.newer.push({
                        roomId: newRoomId,
                        version: newRoomVersion,
                        refEventId: createEventId,
                    });
                    return yield chaseTombstones(newRoomId);
                }
                catch (e) {
                    // no tombstone - that's fine
                }
            });
            yield chaseCreates(roomId);
            yield chaseTombstones(roomId);
            return result;
        });
    }
    /**
     * Creates a Space room.
     * @param {SpaceCreateOptions} opts The creation options.
     * @returns {Promise<Space>} Resolves to the created space.
     */
    createSpace(opts) {
        return __awaiter(this, void 0, void 0, function* () {
            const roomCreateOpts = {
                name: opts.name,
                topic: opts.topic || "",
                preset: opts.isPublic ? 'public_chat' : 'private_chat',
                room_alias_name: opts.localpart,
                initial_state: [
                    {
                        type: "m.room.history_visibility",
                        state_key: "",
                        content: {
                            history_visibility: opts.isPublic ? 'world_readable' : 'shared',
                        },
                    },
                ],
                creation_content: {
                    type: "m.space",
                },
                invite: opts.invites || [],
                power_level_content_override: {
                    ban: 100,
                    events_default: 50,
                    invite: 50,
                    kick: 100,
                    notifications: {
                        room: 100,
                    },
                    redact: 100,
                    state_default: 100,
                    users: {
                        [yield this.getUserId()]: 100,
                    },
                    users_default: 0,
                },
            };
            if (opts.avatarUrl) {
                roomCreateOpts.initial_state.push({
                    type: 'm.room.avatar',
                    state_key: "",
                    content: {
                        url: opts.avatarUrl,
                    },
                });
            }
            const roomId = yield this.createRoom(roomCreateOpts);
            return new Spaces_1.Space(roomId, this);
        });
    }
    /**
     * Gets a Space.
     * This API does not work with unstable spaces (e.g. org.matrix.msc.1772.space)
     *
     * @throws If the room is not a space or there was an error
     * @returns {Promise<Space>} Resolves to the space.
     */
    getSpace(roomIdOrAlias) {
        return __awaiter(this, void 0, void 0, function* () {
            const roomId = yield this.resolveRoom(roomIdOrAlias);
            const createEvent = yield this.getRoomStateEvent(roomId, "m.room.create", "");
            if (createEvent["type"] !== "m.space") {
                throw new Error("Room is not a space");
            }
            return new Spaces_1.Space(roomId, this);
        });
    }
    /**
     * Uploads new identity keys for the current device.
     * @param {EncryptionAlgorithm[]} algorithms The supported algorithms.
     * @param {Record<DeviceKeyLabel<DeviceKeyAlgorithm, string>, string>} keys The keys for the device.
     * @returns {Promise<OTKCounts>} Resolves to the current One Time Key counts when complete.
     */
    uploadDeviceKeys(algorithms, keys) {
        return __awaiter(this, void 0, void 0, function* () {
            const obj = {
                user_id: yield this.getUserId(),
                device_id: this.crypto.clientDeviceId,
                algorithms: algorithms,
                keys: keys,
            };
            obj['signatures'] = yield this.crypto.sign(obj);
            return this.doRequest("POST", "/_matrix/client/r0/keys/upload", null, {
                device_keys: obj,
            }).then(r => r['one_time_key_counts']);
        });
    }
    /**
     * Uploads One Time Keys for the current device.
     * @param {OTKs} keys The keys to upload.
     * @returns {Promise<OTKCounts>} Resolves to the current One Time Key counts when complete.
     */
    uploadDeviceOneTimeKeys(keys) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.doRequest("POST", "/_matrix/client/r0/keys/upload", null, {
                one_time_keys: keys,
            }).then(r => r['one_time_key_counts']);
        });
    }
    /**
     * Gets the current One Time Key counts.
     * @returns {Promise<OTKCounts>} Resolves to the One Time Key counts.
     */
    checkOneTimeKeyCounts() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.doRequest("POST", "/_matrix/client/r0/keys/upload", null, {})
                .then(r => r['one_time_key_counts']);
        });
    }
    /**
     * Uploads a fallback One Time Key to the server for usage. This will replace the existing fallback
     * key.
     * @param {FallbackKey} fallbackKey The fallback key.
     * @returns {Promise<OTKCounts>} Resolves to the One Time Key counts.
     */
    uploadFallbackKey(fallbackKey) {
        return __awaiter(this, void 0, void 0, function* () {
            const keyObj = {
                [`${Crypto_1.OTKAlgorithm.Signed}:${fallbackKey.keyId}`]: fallbackKey.key,
            };
            return this.doRequest("POST", "/_matrix/client/r0/keys/upload", null, {
                "org.matrix.msc2732.fallback_keys": keyObj,
                "fallback_keys": keyObj,
            }).then(r => r['one_time_key_counts']);
        });
    }
    /**
     * Gets <b>unverified</b> device lists for the given users. The caller is expected to validate
     * and verify the device lists, including that the returned devices belong to the claimed users.
     *
     * Failures with federation are reported in the returned object. Users which did not fail a federation
     * lookup but have no devices will not appear in either the failures or in the returned devices.
     *
     * See https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-keys-query for more
     * information.
     * @param {string[]} userIds The user IDs to query.
     * @param {number} federationTimeoutMs The default timeout for requesting devices over federation. Defaults to
     * 10 seconds.
     * @returns {Promise<MultiUserDeviceListResponse>} Resolves to the device list/errors for the requested user IDs.
     */
    getUserDevices(userIds, federationTimeoutMs = 10000) {
        return __awaiter(this, void 0, void 0, function* () {
            const req = {};
            for (const userId of userIds) {
                req[userId] = [];
            }
            return this.doRequest("POST", "/_matrix/client/r0/keys/query", {}, {
                timeout: federationTimeoutMs,
                device_keys: req,
            });
        });
    }
    /**
     * Claims One Time Keys for a set of user devices, returning those keys. The caller is expected to verify
     * and validate the returned keys.
     *
     * Failures with federation are reported in the returned object.
     * @param {Record<string, Record<string, OTKAlgorithm>>} userDeviceMap The map of user IDs to device IDs to
     * OTKAlgorithm to request a claim for.
     * @param {number} federationTimeoutMs The default timeout for claiming keys over federation. Defaults to
     * 10 seconds.
     */
    claimOneTimeKeys(userDeviceMap, federationTimeoutMs = 10000) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.doRequest("POST", "/_matrix/client/r0/keys/claim", {}, {
                timeout: federationTimeoutMs,
                one_time_keys: userDeviceMap,
            });
        });
    }
    /**
     * Sends to-device messages to the respective users/devices.
     * @param {string} type The message type being sent.
     * @param {Record<string, Record<string, any>>} messages The messages to send, mapped as user ID to
     * device ID (or "*" to denote all of the user's devices) to message payload (content).
     * @returns {Promise<void>} Resolves when complete.
     */
    sendToDevices(type, messages) {
        return __awaiter(this, void 0, void 0, function* () {
            const txnId = (new Date().getTime()) + "_TDEV__inc" + (++this.requestId);
            return this.doRequest("PUT", `/_matrix/client/r0/sendToDevice/${encodeURIComponent(type)}/${encodeURIComponent(txnId)}`, null, {
                messages: messages,
            });
        });
    }
    /**
     * Performs a web request to the homeserver, applying appropriate authorization headers for
     * this client.
     * @param {"GET"|"POST"|"PUT"|"DELETE"} method The HTTP method to use in the request
     * @param {string} endpoint The endpoint to call. For example: "/_matrix/client/r0/account/whoami"
     * @param {any} qs The query string to send. Optional.
     * @param {any} body The request body to send. Optional. Will be converted to JSON unless the type is a Buffer.
     * @param {number} timeout The number of milliseconds to wait before timing out.
     * @param {boolean} raw If true, the raw response will be returned instead of the response body.
     * @param {string} contentType The content type to send. Only used if the `body` is a Buffer.
     * @param {string} noEncoding Set to true to disable encoding, and return a Buffer. Defaults to false
     * @returns {Promise<any>} Resolves to the response (body), rejected if a non-2xx status code was returned.
     */
    doRequest(method, endpoint, qs = null, body = null, timeout = 60000, raw = false, contentType = "application/json", noEncoding = false) {
        if (this.impersonatedUserId) {
            if (!qs)
                qs = { "user_id": this.impersonatedUserId };
            else
                qs["user_id"] = this.impersonatedUserId;
        }
        const headers = {};
        if (this.accessToken) {
            headers["Authorization"] = `Bearer ${this.accessToken}`;
        }
        return http_1.doHttpRequest(this.homeserverUrl, method, endpoint, qs, body, headers, timeout, raw, contentType, noEncoding);
    }
}
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getOpenIDConnectToken", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getAccountData", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getRoomAccountData", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, typeof (_a = typeof T !== "undefined" && T) === "function" ? _a : Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getSafeAccountData", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String, typeof (_b = typeof T !== "undefined" && T) === "function" ? _b : Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getSafeRoomAccountData", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "setAccountData", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String, Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "setRoomAccountData", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getPresenceStatus", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getPresenceStatusFor", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "setPresenceStatus", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getPublishedAlias", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "createRoomAlias", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "deleteRoomAlias", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "setDirectoryVisibility", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getDirectoryVisibility", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "resolveRoom", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "lookupRoomAlias", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object]),
    __metadata("design:returntype", void 0)
], MatrixClient.prototype, "inviteUser", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object, Object]),
    __metadata("design:returntype", void 0)
], MatrixClient.prototype, "kickUser", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object, Object]),
    __metadata("design:returntype", void 0)
], MatrixClient.prototype, "banUser", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object]),
    __metadata("design:returntype", void 0)
], MatrixClient.prototype, "unbanUser", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getUserId", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "doSync", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Function]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "processSync", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getEvent", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getRawEvent", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getRoomState", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object, Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getRoomStateEvents", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object, Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getRoomStateEvent", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String, Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getEventContext", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getUserProfile", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "setDisplayName", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "setAvatarUrl", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Array]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "joinRoom", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getJoinedRooms", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getJoinedRoomMembers", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getJoinedRoomMembersWithProfiles", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "leaveRoom", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "sendReadReceipt", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Boolean, Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "setTyping", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Object, String, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "replyText", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Object, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "replyHtmlText", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Object, String, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "replyNotice", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Object, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "replyHtmlNotice", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "sendNotice", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "sendHtmlNotice", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "sendText", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "sendHtmlText", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "sendMessage", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String, Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "sendEvent", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String, Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "sendRawEvent", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String, String, Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "sendStateEvent", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "redactEvent", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "createRoom", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String, String, Boolean]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "userHasPowerLevelFor", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "userHasPowerLevelForAction", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "calculatePowerLevelChangeBoundsOn", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String, Number]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "setUserPowerLevel", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", String)
], MatrixClient.prototype, "mxcToHttp", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Number, Number, String]),
    __metadata("design:returntype", String)
], MatrixClient.prototype, "mxcToHttpThumbnail", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Buffer, Object, String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "uploadContent", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "uploadContentFromUrl", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getRoomUpgradeHistory", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "createSpace", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getSpace", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    decorators_2.requiresCrypto(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Array, Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "uploadDeviceKeys", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    decorators_2.requiresCrypto(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "uploadDeviceOneTimeKeys", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    decorators_2.requiresCrypto(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "checkOneTimeKeyCounts", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    decorators_2.requiresCrypto(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "uploadFallbackKey", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Array, Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "getUserDevices", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    decorators_2.requiresCrypto(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "claimOneTimeKeys", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "sendToDevices", null);
__decorate([
    decorators_1.timedMatrixClientFunctionCall(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object, Object, Object, Object, Object, Object, Object]),
    __metadata("design:returntype", Promise)
], MatrixClient.prototype, "doRequest", null);
exports.MatrixClient = MatrixClient;
