import { EventEmitter } from "events" ; import { IStorageProvider } from "./storage/IStorageProvider" ; import { MemoryStorageProvider } from "./storage/MemoryStorageProvider" ; import { IJoinRoomStrategy } from "./strategies/JoinRoomStrategy" ; import { UnstableApis } from "./UnstableApis" ; import { IPreprocessor } from "./preprocessors/IPreprocessor" ; import { getRequestFn } from "./request" ; import { extractRequestError , LogService } from "./logging/LogService" ; import { htmlEncode } from "htmlencode" ; import { RichReply } from "./helpers/RichReply" ; import { Metrics } from "./metrics/Metrics" ; import { timedMatrixClientFunctionCall } from "./metrics/decorators" ; import { AdminApis } from "./AdminApis" ; import { Presence } from "./models/Presence" ; import { Membership , MembershipEvent } from "./models/events/MembershipEvent" ; import { RoomEvent , RoomEventContent , StateEvent } from "./models/events/RoomEvent" ; import { EventContext } from "./models/EventContext" ; import { PowerLevelBounds } from "./models/PowerLevelBounds" ; import { EventKind } from "./models/events/EventKind" ; import { IdentityClient } from "./identity/IdentityClient" ; import { OpenIDConnectToken } from "./models/OpenIDConnect" ; import { doHttpRequest } from "./http" ; import { htmlToText } from "html-to-text" ; import { Space , SpaceCreateOptions } from "./models/Spaces" ; import { PowerLevelAction } from "./models/PowerLevelAction" ; import { CryptoClient } from "./e2ee/CryptoClient" ; import { DeviceKeyAlgorithm , DeviceKeyLabel , EncryptionAlgorithm , FallbackKey , IToDeviceMessage , MultiUserDeviceListResponse , OTKAlgorithm , OTKClaimResponse , OTKCounts , OTKs , OwnUserDevice , } from "./models/Crypto" ; import { requiresCrypto } from "./e2ee/decorators" ; import { ICryptoStorageProvider } from "./storage/ICryptoStorageProvider" ; import { EncryptedRoomEvent } from "./models/events/EncryptedRoomEvent" ; import { IWhoAmI } from "./models/Account" ; import { RustSdkCryptoStorageProvider } from "./storage/RustSdkCryptoStorageProvider" ; const SYNC_BACKOFF_MIN_MS = 5000 ; const SYNC_BACKOFF_MAX_MS = 15000 ; /**
 * A client that is capable of interacting with a matrix homeserver.
 */ export class MatrixClient extends EventEmitter { /**
     * 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.
     */ public syncingPresence : "online" | "offline" | "unavailable" | null = 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.
     */ public syncingTimeout = 30000 ; /**
     * The crypto manager instance for this client. Generally speaking, this shouldn't
     * need to be accessed but is made available.
     *
     * Will be null/undefined if crypto is not possible.
     */ public readonly crypto : CryptoClient ; private userId : string ; private requestId = 0 ; private lastJoinedRoomIds : string [ ] = [ ] ; private impersonatedUserId : string ; private impersonatedDeviceId : string ; private joinStrategy : IJoinRoomStrategy = null ; private eventProcessors : { [ eventType ] : IPreprocessor [ ] ; } = { } ; private filterId = 0 ; private stopSyncing = false ; private metricsInstance : Metrics = new Metrics ( ) ; private unstableApisInstance = new 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.
     */ protected persistTokenAfterSync = false ; param { string } homeserverUrl param { string } accessToken param { IStorageProvider } storage param { ICryptoStorageProvider } cryptoStore constructor ( public readonly homeserverUrl , public readonly accessToken , private storage = null , public readonly cryptoStore = null ) { super ( ) ; if ( this . homeserverUrl . endsWith ( "/" ) ) { this . homeserverUrl = this . homeserverUrl . substring ( 0 , this . homeserverUrl . length - 1 ) ; } if ( this . cryptoStore ) { if ( ! this . storage || this . storage instanceof MemoryStorageProvider ) { LogService . warn ( "MatrixClientLite" , "Starting an encryption-capable client with a memory store is not considered a good idea." ) ; } if ( ! ( this . cryptoStore instanceof RustSdkCryptoStorageProvider ) ) { throw new Error ( "Cannot support custom encryption stores: Use a RustSdkCryptoStorageProvider" ) ; } this . crypto = new CryptoClient ( this ) ; LogService . debug ( "MatrixClientLite" , "End-to-end encryption client created" ) ; } else { } if ( ! this . storage ) this . storage = new MemoryStorageProvider ( ) ; } /**
     * The storage provider for this client. Direct access is usually not required.
     */ public get storageProvider ( ) : IStorageProvider { return this . storage ; } /**
     * The metrics instance for this client
     */ public get metrics ( ) : Metrics { return this . metricsInstance ; } param { Metrics } metrics public set metrics ( metrics ) { if ( ! metrics ) throw new Error ( "Metrics cannot be null/undefined" ) ; this . metricsInstance = metrics ; } return { UnstableApis } public get unstableApis ( ) : UnstableApis { return this . unstableApisInstance ; } return { AdminApis } public get adminApis ( ) : AdminApis { return new AdminApis ( this ) ; } param { string } userId param { string } deviceId public impersonateUserId ( userId , deviceId ? ) { this . impersonatedUserId = userId ; this . userId = userId ; if ( userId ) { this . impersonatedDeviceId = deviceId ; } else if ( deviceId ) { throw new Error ( "Cannot impersonate just a device: need a user ID" ) ; } else { this . impersonatedDeviceId = null ; } } param { string } identityServerName returns { Promise < IdentityClient > } public async getIdentityServerClient ( identityServerName ) { const oidcToken = await this . getOpenIDConnectToken ( ) ; return IdentityClient . acquire ( oidcToken , `https://${ identityServerName }` , this ) ; } param { IJoinRoomStrategy } strategy public setJoinStrategy ( strategy ) { this . joinStrategy = strategy ; } param { IPreprocessor } preprocessor public addPreprocessor ( preprocessor ) { if ( ! preprocessor ) throw new Error ( "Preprocessor cannot be null" ) ; const eventTypes = preprocessor . getSupportedEventTypes ( ) ; if ( ! eventTypes ) return ; for ( const eventType of eventTypes ) { if ( ! this . eventProcessors [ eventType ] ) this . eventProcessors [ eventType ] = [ ] ; this . eventProcessors [ eventType ] . push ( preprocessor ) ; } } private async processEvent ( event ) { if ( ! event ) return event ; if ( ! this . eventProcessors [ event [ "type" ] ] ) return event ; for ( const processor of this . eventProcessors [ event [ "type" ] ] ) { await processor . processEvent ( event , this , EventKind . RoomEvent ) ; } return event ; } returns { Promise < OpenIDConnectToken > } @ timedMatrixClientFunctionCall ( ) public async getOpenIDConnectToken ( ) { const userId = encodeURIComponent ( await this . getUserId ( ) ) ; return this . doRequest ( "POST" , "/_matrix/client/r0/user/" + userId + "/openid/request_token" , null , { } ) ; } param { string } eventType returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public async getAccountData < T > ( eventType ) { const userId = encodeURIComponent ( await this . getUserId ( ) ) ; eventType = encodeURIComponent ( eventType ) ; return this . doRequest ( "GET" , "/_matrix/client/r0/user/" + userId + "/account_data/" + eventType ) ; } param { string } eventType param { string } roomId returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public async getRoomAccountData < T > ( eventType , roomId ) { const userId = encodeURIComponent ( await this . getUserId ( ) ) ; eventType = encodeURIComponent ( eventType ) ; roomId = encodeURIComponent ( roomId ) ; return this . doRequest ( "GET" , "/_matrix/client/r0/user/" + userId + "/rooms/" + roomId + "/account_data/" + eventType ) ; } param { string } eventType param { any } defaultContent returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public async getSafeAccountData < T > ( eventType , defaultContent = null ) { try { return await this . getAccountData ( eventType ) ; } catch ( e ) { LogService . warn ( "MatrixClient" , `Error getting ${ eventType } account data:` , extractRequestError ( e ) ) ; return defaultContent ; } } param { string } eventType param { string } roomId param { any } defaultContent returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public async getSafeRoomAccountData < T > ( eventType , roomId , defaultContent = null ) { try { return await this . getRoomAccountData ( eventType , roomId ) ; } catch ( e ) { LogService . warn ( "MatrixClient" , `Error getting ${ eventType } room account data in ${ roomId }:` , extractRequestError ( e ) ) ; return defaultContent ; } } param { string } eventType param { any } content returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public async setAccountData ( eventType , content ) { const userId = encodeURIComponent ( await this . getUserId ( ) ) ; eventType = encodeURIComponent ( eventType ) ; return this . doRequest ( "PUT" , "/_matrix/client/r0/user/" + userId + "/account_data/" + eventType , null , content ) ; } param { string } eventType param { string } roomId param { any } content returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public async setRoomAccountData ( eventType , roomId , content ) { const userId = encodeURIComponent ( await this . getUserId ( ) ) ; eventType = encodeURIComponent ( eventType ) ; roomId = encodeURIComponent ( roomId ) ; return this . doRequest ( "PUT" , "/_matrix/client/r0/user/" + userId + "/rooms/" + roomId + "/account_data/" + eventType , null , content ) ; } returns { Promise < Presence > } @ timedMatrixClientFunctionCall ( ) public async getPresenceStatus ( ) { return this . getPresenceStatusFor ( await this . getUserId ( ) ) ; } param { string } userId returns { Promise < Presence > } @ timedMatrixClientFunctionCall ( ) public async getPresenceStatusFor ( userId ) { return this . doRequest ( "GET" , "/_matrix/client/r0/presence/" + encodeURIComponent ( userId ) + "/status" ) . then ( r => new Presence ( r ) ) ; } param { "online" | "offline" | "unavailable" } presence param { string } statusMessage returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public async setPresenceStatus ( presence , statusMessage = null ) { return this . doRequest ( "PUT" , "/_matrix/client/r0/presence/" + encodeURIComponent ( await this . getUserId ( ) ) + "/status" , null , { presence : presence , status_msg : statusMessage , } ) ; } param { string } roomIdOrAlias returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public async getPublishedAlias ( roomIdOrAlias ) { try { const roomId = await this . resolveRoom ( roomIdOrAlias ) ; const event = await 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 ) { return null ; } } param { string } alias param { string } roomId returns { Promise } @ timedMatrixClientFunctionCall ( ) public createRoomAlias ( alias , roomId ) { alias = encodeURIComponent ( alias ) ; return this . doRequest ( "PUT" , "/_matrix/client/r0/directory/room/" + alias , null , { "room_id" : roomId , } ) ; } param { string } alias returns { Promise } @ timedMatrixClientFunctionCall ( ) public deleteRoomAlias ( alias ) { alias = encodeURIComponent ( alias ) ; return this . doRequest ( "DELETE" , "/_matrix/client/r0/directory/room/" + alias ) ; } param { string } roomId param { "public" | "private" } visibility return { Promise } @ timedMatrixClientFunctionCall ( ) public setDirectoryVisibility ( roomId , visibility ) { roomId = encodeURIComponent ( roomId ) ; return this . doRequest ( "PUT" , "/_matrix/client/r0/directory/list/room/" + roomId , null , { "visibility" : visibility , } ) ; } param { string } roomId return { Promise < "public" | "private" > } @ timedMatrixClientFunctionCall ( ) public getDirectoryVisibility ( roomId ) { roomId = encodeURIComponent ( roomId ) ; return this . doRequest ( "GET" , "/_matrix/client/r0/directory/list/room/" + roomId ) . then ( response => { return response [ "visibility" ] ; } ) ; } param { string } roomIdOrAlias returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public async resolveRoom ( roomIdOrAlias ) { if ( roomIdOrAlias . startsWith ( "!" ) ) return roomIdOrAlias ; if ( roomIdOrAlias . startsWith ( "#" ) ) return this . lookupRoomAlias ( roomIdOrAlias ) . then ( r => r . roomId ) ; throw new Error ( "Invalid room ID or alias" ) ; } param { string } roomAlias returns { Promise < RoomDirectoryLookupResponse > } @ timedMatrixClientFunctionCall ( ) public lookupRoomAlias ( roomAlias ) { return this . doRequest ( "GET" , "/_matrix/client/r0/directory/room/" + encodeURIComponent ( roomAlias ) ) . then ( response => { return { roomId : response [ "room_id" ] , residentServers : response [ "servers" ] , } ; } ) ; } param { string } userId param { string } roomId returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public inviteUser ( userId , roomId ) { return this . doRequest ( "POST" , "/_matrix/client/r0/rooms/" + encodeURIComponent ( roomId ) + "/invite" , null , { user_id : userId , } ) ; } param { string } userId param { string } roomId param { string ? } reason returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public kickUser ( userId , roomId , reason = null ) { return this . doRequest ( "POST" , "/_matrix/client/r0/rooms/" + encodeURIComponent ( roomId ) + "/kick" , null , { user_id : userId , reason : reason , } ) ; } param { string } userId param { string } roomId param { string ? } reason returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public banUser ( userId , roomId , reason = null ) { return this . doRequest ( "POST" , "/_matrix/client/r0/rooms/" + encodeURIComponent ( roomId ) + "/ban" , null , { user_id : userId , reason : reason , } ) ; } param { string } userId param { string } roomId returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public unbanUser ( userId , roomId ) { return this . doRequest ( "POST" , "/_matrix/client/r0/rooms/" + encodeURIComponent ( roomId ) + "/unban" , null , { user_id : userId , } ) ; } returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public async getUserId ( ) { if ( this . userId ) return this . userId ; await this . getWhoAmI ( ) ; return this . userId ; } returns { Promise < IWhoAmI > } public async getWhoAmI ( ) { const whoami = await this . doRequest ( "GET" , "/_matrix/client/r0/account/whoami" ) ; this . userId = whoami [ "user_id" ] ; return whoami ; } /**
     * Stops the client from syncing.
     */ public stop ( ) { this . stopSyncing = true ; } param { any } filter returns { Promise < any > } public async start ( filter = null ) { this . stopSyncing = false ; if ( ! filter || typeof ( filter ) !== "object" ) { LogService . trace ( "MatrixClientLite" , "No filter given or invalid object - using defaults." ) ; filter = null ; } LogService . trace ( "MatrixClientLite" , "Populating joined rooms to avoid excessive join emits" ) ; this . lastJoinedRoomIds = await this . getJoinedRooms ( ) ; const userId = await this . getUserId ( ) ; if ( this . crypto ) { LogService . debug ( "MatrixClientLite" , "Preparing end-to-end encryption" ) ; await this . crypto . prepare ( this . lastJoinedRoomIds ) ; LogService . info ( "MatrixClientLite" , "End-to-end encryption enabled" ) ; } let createFilter = false ; let existingFilter = await Promise . resolve ( this . storage . getFilter ( ) ) ; if ( existingFilter ) { LogService . trace ( "MatrixClientLite" , "Found existing filter. Checking consistency with given filter" ) ; if ( JSON . stringify ( existingFilter . filter ) === JSON . stringify ( filter ) ) { LogService . trace ( "MatrixClientLite" , "Filters match" ) ; this . filterId = existingFilter . id ; } else { createFilter = true ; } } else { createFilter = true ; } if ( createFilter && filter ) { LogService . trace ( "MatrixClientLite" , "Creating new filter" ) ; return this . doRequest ( "POST" , "/_matrix/client/r0/user/" + encodeURIComponent ( userId ) + "/filter" , null , filter ) . then ( async ( response ) => { this . filterId = response [ "filter_id" ] ; await Promise . resolve ( this . storage . setSyncToken ( null ) ) ; await Promise . resolve ( this . storage . setFilter ( { id : this . filterId , filter : filter , } ) ) ; } ) ; } LogService . trace ( "MatrixClientLite" , "Starting sync with filter ID " + this . filterId ) ; return this . startSyncInternal ( ) ; } protected startSyncInternal ( ) { return this . startSync ( ) ; } protected async startSync ( emitFn = null ) { let token = await Promise . resolve ( this . storage . getSyncToken ( ) ) ; const promiseWhile = async ( ) => { if ( this . stopSyncing ) { LogService . info ( "MatrixClientLite" , "Client stop requested - stopping sync" ) ; return ; } try { const response = await this . doSync ( token ) ; token = response [ "next_batch" ] ; if ( ! this . persistTokenAfterSync ) { await Promise . resolve ( this . storage . setSyncToken ( token ) ) ; } LogService . debug ( "MatrixClientLite" , "Received sync. Next token: " + token ) ; await this . processSync ( response , emitFn ) ; if ( this . persistTokenAfterSync ) { await Promise . resolve ( this . storage . setSyncToken ( token ) ) ; } } catch ( e ) { LogService . error ( "MatrixClientLite" , "Error handling sync " + extractRequestError ( e ) ) ; const backoffTime = SYNC_BACKOFF_MIN_MS + Math . random ( ) * ( SYNC_BACKOFF_MAX_MS - SYNC_BACKOFF_MIN_MS ) ; LogService . info ( "MatrixClientLite" , `Backing off for ${ backoffTime }ms` ) ; await new Promise ( ( r ) => setTimeout ( r , backoffTime ) ) ; } return promiseWhile ( ) ; } ; promiseWhile ( ) ; } @ timedMatrixClientFunctionCall ( ) protected doSync ( token ) { LogService . debug ( "MatrixClientLite" , "Performing sync with token " + token ) ; const conf = { full_state : false , timeout : Math . max ( 0 , this . syncingTimeout ) , } ; if ( token ) conf [ "since" ] = token ; if ( this . filterId ) conf [ 'filter' ] = this . filterId ; if ( this . syncingPresence ) conf [ 'presence' ] = this . syncingPresence ; return this . doRequest ( "GET" , "/_matrix/client/r0/sync" , conf , null , ( token ? 40000 : 600000 ) ) ; } @ timedMatrixClientFunctionCall ( ) protected async processSync ( raw , emitFn = null ) { if ( ! emitFn ) emitFn = ( e , ... p ) => Promise . resolve < any > ( this . emit ( e , ... p ) ) ; if ( ! raw ) return ; if ( this . crypto ) { const inbox = [ ] ; if ( raw [ 'to_device' ] ?. [ 'events' ] ) { inbox . push ( ... raw [ 'to_device' ] [ 'events' ] ) ; } let unusedFallbacks = [ ] ; 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' ] ; } const counts = raw [ 'device_one_time_keys_count' ] ?? { } ; const changed = raw [ 'device_lists' ] ?. [ 'changed' ] ?? [ ] ; const left = raw [ 'device_lists' ] ?. [ 'left' ] ?? [ ] ; await this . crypto . updateSyncData ( inbox , counts , unusedFallbacks , changed , left ) ; } if ( raw [ 'groups' ] ) { const leave = raw [ 'groups' ] [ 'leave' ] || { } ; for ( const groupId of Object . keys ( leave ) ) { await emitFn ( "unstable.group.leave" , groupId , leave [ groupId ] ) ; } const join = raw [ 'groups' ] [ 'join' ] || { } ; for ( const groupId of Object . keys ( join ) ) { await emitFn ( "unstable.group.join" , groupId , join [ groupId ] ) ; } const invite = raw [ 'groups' ] [ 'invite' ] || { } ; for ( const groupId of Object . keys ( invite ) ) { await emitFn ( "unstable.group.invite" , groupId , invite [ groupId ] ) ; } } if ( raw [ 'account_data' ] && raw [ 'account_data' ] [ 'events' ] ) { for ( const event of raw [ 'account_data' ] [ 'events' ] ) { await emitFn ( "account_data" , event ) ; } } if ( ! raw [ 'rooms' ] ) return ; let leftRooms = raw [ 'rooms' ] [ 'leave' ] || { } ; let inviteRooms = raw [ 'rooms' ] [ 'invite' ] || { } ; let joinedRooms = raw [ 'rooms' ] [ 'join' ] || { } ; 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' ] ) { await 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' ] !== await 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 . warn ( "MatrixClientLite" , "Left room " + roomId + " without receiving an event" ) ; continue ; } leaveEvent = await this . processEvent ( leaveEvent ) ; await emitFn ( "room.leave" , roomId , leaveEvent ) ; this . lastJoinedRoomIds = this . lastJoinedRoomIds . filter ( r => r !== roomId ) ; } 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' ] !== await 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 . warn ( "MatrixClientLite" , "Invited to room " + roomId + " without receiving an event" ) ; continue ; } inviteEvent = await this . processEvent ( inviteEvent ) ; await emitFn ( "room.invite" , roomId , inviteEvent ) ; } for ( let roomId in joinedRooms ) { if ( this . lastJoinedRoomIds . indexOf ( roomId ) === - 1 ) { await 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' ] ) { await emitFn ( "room.account_data" , roomId , event ) ; } } if ( ! room [ 'timeline' ] || ! room [ 'timeline' ] [ 'events' ] ) continue ; for ( let event of room [ 'timeline' ] [ 'events' ] ) { event = await this . processEvent ( event ) ; if ( event [ 'type' ] === 'm.room.encrypted' && await this . crypto ?. isRoomEncrypted ( roomId ) ) { await emitFn ( "room.encrypted_event" , roomId , event ) ; try { event = ( await this . crypto . decryptRoomEvent ( new EncryptedRoomEvent ( event ) , roomId ) ) . raw ; event = await this . processEvent ( event ) ; await emitFn ( "room.decrypted_event" , roomId , event ) ; } catch ( e ) { LogService . error ( "MatrixClientLite" , `Decryption error on ${ roomId } ${ event [ 'event_id' ] }` , e ) ; await emitFn ( "room.failed_decryption" , roomId , event , e ) ; } } if ( event [ 'type' ] === 'm.room.message' ) { await emitFn ( "room.message" , roomId , event ) ; } if ( event [ 'type' ] === 'm.room.tombstone' && event [ 'state_key' ] === '' ) { await emitFn ( "room.archived" , roomId , event ) ; } if ( event [ 'type' ] === 'm.room.create' && event [ 'state_key' ] === '' && event [ 'content' ] && event [ 'content' ] [ 'predecessor' ] && event [ 'content' ] [ 'predecessor' ] [ 'room_id' ] ) { await emitFn ( "room.upgraded" , roomId , event ) ; } await emitFn ( "room.event" , roomId , event ) ; } } } param { string } roomId param { string } eventId returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public async getEvent ( roomId , eventId ) { const event = await this . getRawEvent ( roomId , eventId ) ; if ( event [ 'type' ] === 'm.room.encrypted' && await this . crypto ?. isRoomEncrypted ( roomId ) ) { return this . processEvent ( ( await this . crypto . decryptRoomEvent ( new EncryptedRoomEvent ( event ) , roomId ) ) . raw ) ; } return event ; } param { string } roomId param { string } eventId returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public getRawEvent ( roomId , eventId ) { return this . doRequest ( "GET" , "/_matrix/client/r0/rooms/" + encodeURIComponent ( roomId ) + "/event/" + encodeURIComponent ( eventId ) ) . then ( ev => this . processEvent ( ev ) ) ; } param { string } roomId returns { Promise < any [ ] > } @ timedMatrixClientFunctionCall ( ) public getRoomState ( roomId ) { return this . doRequest ( "GET" , "/_matrix/client/r0/rooms/" + encodeURIComponent ( roomId ) + "/state" ) . then ( state => Promise . all ( state . map ( ev => this . processEvent ( ev ) ) ) ) ; } param { string } roomId param { string } type param { String } stateKey returns { Promise < any | any [ ] > } deprecated @ timedMatrixClientFunctionCall ( ) public getRoomStateEvents ( roomId , type , stateKey ) { return this . getRoomStateEvent ( roomId , type , stateKey ) ; } param { string } roomId param { string } type param { String } stateKey returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public 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 ) ) ; } param { string } roomId param { string } eventId param { number } limit returns { Promise < EventContext > } @ timedMatrixClientFunctionCall ( ) public async getEventContext ( roomId , eventId , limit = 10 ) { const res = await this . doRequest ( "GET" , "/_matrix/client/r0/rooms/" + encodeURIComponent ( roomId ) + "/context/" + encodeURIComponent ( eventId ) , { limit } ) ; return { event : new RoomEvent < RoomEventContent > ( res [ 'event' ] ) , before : res [ 'events_before' ] . map ( e => new RoomEvent < RoomEventContent > ( e ) ) , after : res [ 'events_after' ] . map ( e => new RoomEvent < RoomEventContent > ( e ) ) , state : res [ 'state' ] . map ( e => new StateEvent < RoomEventContent > ( e ) ) , } ; } param { string } userId returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public getUserProfile ( userId ) { return this . doRequest ( "GET" , "/_matrix/client/r0/profile/" + encodeURIComponent ( userId ) ) ; } param { string } displayName returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public async setDisplayName ( displayName ) { const userId = encodeURIComponent ( await this . getUserId ( ) ) ; return this . doRequest ( "PUT" , "/_matrix/client/r0/profile/" + userId + "/displayname" , null , { displayname : displayName , } ) ; } param { string } avatarUrl returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public async setAvatarUrl ( avatarUrl ) { const userId = encodeURIComponent ( await this . getUserId ( ) ) ; return this . doRequest ( "PUT" , "/_matrix/client/r0/profile/" + userId + "/avatar_url" , null , { avatar_url : avatarUrl , } ) ; } param { string } roomIdOrAlias param { string [ ] } viaServers returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public async joinRoom ( roomIdOrAlias , viaServers = [ ] ) { 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 = await this . getUserId ( ) ; if ( this . joinStrategy ) return this . joinStrategy . joinRoom ( roomIdOrAlias , userId , apiCall ) ; else return apiCall ( roomIdOrAlias ) ; } returns { Promise < string [ ] > } @ timedMatrixClientFunctionCall ( ) public getJoinedRooms ( ) { return this . doRequest ( "GET" , "/_matrix/client/r0/joined_rooms" ) . then ( response => response [ 'joined_rooms' ] ) ; } param { string } roomId returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public getJoinedRoomMembers ( roomId ) { return this . doRequest ( "GET" , "/_matrix/client/r0/rooms/" + encodeURIComponent ( roomId ) + "/joined_members" ) . then ( response => { return Object . keys ( response [ 'joined' ] ) ; } ) ; } param { string } roomId returns { Object } @ timedMatrixClientFunctionCall ( ) public async getJoinedRoomMembersWithProfiles ( roomId ) { return ( await this . doRequest ( "GET" , "/_matrix/client/r0/rooms/" + encodeURIComponent ( roomId ) + "/joined_members" ) ) . joined ; } param { string } roomId param { string } batchToken param { string [ ] } membership param { string [ ] } notMembership returns { Promise < any [ ] > } public 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 ( e ) ) ; } ) ; } param { string } roomId returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public leaveRoom ( roomId ) { return this . doRequest ( "POST" , "/_matrix/client/r0/rooms/" + encodeURIComponent ( roomId ) + "/leave" ) ; } param { string } roomId param { string } eventId returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public sendReadReceipt ( roomId , eventId ) { return this . doRequest ( "POST" , "/_matrix/client/r0/rooms/" + encodeURIComponent ( roomId ) + "/receipt/m.read/" + encodeURIComponent ( eventId ) , null , { } ) ; } param { string } roomId param { boolean } typing param { number } timeout returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public async setTyping ( roomId , typing , timeout = 30000 ) { const userId = await this . getUserId ( ) ; return this . doRequest ( "PUT" , "/_matrix/client/r0/rooms/" + encodeURIComponent ( roomId ) + "/typing/" + encodeURIComponent ( userId ) , null , { typing , timeout , } ) ; } param { string } roomId param { any } event param { string } text param { string } html returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public replyText ( roomId , event , text , html = null ) { if ( ! html ) html = htmlEncode ( text ) ; const reply = RichReply . createFor ( roomId , event , text , html ) ; return this . sendMessage ( roomId , reply ) ; } param { string } roomId param { any } event param { string } html returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public replyHtmlText ( roomId , event , html ) { const text = htmlToText ( html , { wordwrap : false } ) ; const reply = RichReply . createFor ( roomId , event , text , html ) ; return this . sendMessage ( roomId , reply ) ; } param { string } roomId param { any } event param { string } text param { string } html returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public replyNotice ( roomId , event , text , html = null ) { if ( ! html ) html = htmlEncode ( text ) ; const reply = RichReply . createFor ( roomId , event , text , html ) ; reply [ 'msgtype' ] = 'm.notice' ; return this . sendMessage ( roomId , reply ) ; } param { string } roomId param { any } event param { string } html returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public replyHtmlNotice ( roomId , event , html ) { const text = htmlToText ( html , { wordwrap : false } ) ; const reply = RichReply . createFor ( roomId , event , text , html ) ; reply [ 'msgtype' ] = 'm.notice' ; return this . sendMessage ( roomId , reply ) ; } param { string } roomId param { string } text returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public sendNotice ( roomId , text ) { return this . sendMessage ( roomId , { body : text , msgtype : "m.notice" , } ) ; } param { string } roomId param { string } html returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public sendHtmlNotice ( roomId , html ) { return this . sendMessage ( roomId , { body : htmlToText ( html , { wordwrap : false } ) , msgtype : "m.notice" , format : "org.matrix.custom.html" , formatted_body : html , } ) ; } param { string } roomId param { string } text returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public sendText ( roomId , text ) { return this . sendMessage ( roomId , { body : text , msgtype : "m.text" , } ) ; } param { string } roomId param { string } html returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public sendHtmlText ( roomId , html ) { return this . sendMessage ( roomId , { body : htmlToText ( html , { wordwrap : false } ) , msgtype : "m.text" , format : "org.matrix.custom.html" , formatted_body : html , } ) ; } param { string } roomId param { object } content returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public sendMessage ( roomId , content ) { return this . sendEvent ( roomId , "m.room.message" , content ) ; } param { string } roomId param { string } eventType param { string } content returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public async sendEvent ( roomId , eventType , content ) { if ( await this . crypto ?. isRoomEncrypted ( roomId ) ) { content = await this . crypto . encryptRoomEvent ( roomId , eventType , content ) ; eventType = "m.room.encrypted" ; } return this . sendRawEvent ( roomId , eventType , content ) ; } param { string } roomId param { string } eventType param { string } content returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public async sendRawEvent ( roomId , eventType , content ) { 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' ] ; } ) ; } param { string } roomId param { string } type param { string } stateKey param { string } content returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public 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' ] ; } ) ; } param { string } roomId param { string } eventId param { String } reason returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public 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' ] ; } ) ; } param { any } properties returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public createRoom ( properties = { } ) { return this . doRequest ( "POST" , "/_matrix/client/r0/createRoom" , null , properties ) . then ( response => { return response [ 'room_id' ] ; } ) ; } param { string } userId param { string } roomId param { string } eventType param { boolean } isState returns { Promise < boolean > } @ timedMatrixClientFunctionCall ( ) public async userHasPowerLevelFor ( userId , roomId , eventType , isState ) { const powerLevelsEvent = await this . getRoomStateEvent ( roomId , "m.room.power_levels" , "" ) ; if ( ! powerLevelsEvent ) { 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 ( powerLevelsEvent [ "events" ] ?. [ eventType ] ) ) requiredPower = powerLevelsEvent [ "events" ] [ eventType ] ; let userPower = 0 ; if ( Number . isFinite ( powerLevelsEvent [ "users_default" ] ) ) userPower = powerLevelsEvent [ "users_default" ] ; if ( Number . isFinite ( powerLevelsEvent [ "users" ] ?. [ userId ] ) ) userPower = powerLevelsEvent [ "users" ] [ userId ] ; return userPower >= requiredPower ; } param { string } userId param { string } roomId param { PowerLevelAction } action returns { Promise < boolean > } @ timedMatrixClientFunctionCall ( ) public async userHasPowerLevelForAction ( userId , roomId , action ) { const powerLevelsEvent = await this . getRoomStateEvent ( roomId , "m.room.power_levels" , "" ) ; if ( ! powerLevelsEvent ) { throw new Error ( "No power level event found" ) ; } const defaultForActions = { [ PowerLevelAction . Ban ] : 50 , [ PowerLevelAction . Invite ] : 50 , [ PowerLevelAction . Kick ] : 50 , [ PowerLevelAction . RedactEvents ] : 50 , [ PowerLevelAction . NotifyRoom ] : 50 , } ; let requiredPower = defaultForActions [ action ] ; let investigate = powerLevelsEvent ; action . split ( '.' ) . forEach ( k => ( investigate = investigate ?. [ k ] ) ) ; if ( Number . isFinite ( investigate ) ) requiredPower = investigate ; let userPower = 0 ; if ( Number . isFinite ( powerLevelsEvent [ "users_default" ] ) ) userPower = powerLevelsEvent [ "users_default" ] ; if ( Number . isFinite ( powerLevelsEvent [ "users" ] ?. [ userId ] ) ) userPower = powerLevelsEvent [ "users" ] [ userId ] ; return userPower >= requiredPower ; } param { string } targetUserId param { string } roomId returns { Promise < PowerLevelBounds > } @ timedMatrixClientFunctionCall ( ) public async calculatePowerLevelChangeBoundsOn ( targetUserId , roomId ) { const myUserId = await this . getUserId ( ) ; const canChangePower = await this . userHasPowerLevelFor ( myUserId , roomId , "m.room.power_levels" , true ) ; if ( ! canChangePower ) return { canModify : false , maximumPossibleLevel : 0 } ; const powerLevelsEvent = await 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 } ; } param { string } userId param { string } roomId param { number } newLevel returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public async setUserPowerLevel ( userId , roomId , newLevel ) { const currentLevels = await this . getRoomStateEvent ( roomId , "m.room.power_levels" , "" ) ; if ( ! currentLevels [ 'users' ] ) currentLevels [ 'users' ] = { } ; currentLevels [ 'users' ] [ userId ] = newLevel ; await this . sendStateEvent ( roomId , "m.room.power_levels" , "" , currentLevels ) ; } param { string } mxc returns { string } @ timedMatrixClientFunctionCall ( ) public 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 ) }` ; } param { string } mxc param { number } width param { number } height param { "crop" | "scale" } method returns { string } @ timedMatrixClientFunctionCall ( ) public 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 ) }` ; } param { Buffer } data param { string } contentType param { string } filename returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public uploadContent ( data , contentType = "application/octet-stream" , filename = null ) { return this . doRequest ( "POST" , "/_matrix/media/r0/upload" , { filename : filename } , data , 60000 , false , contentType ) . then ( response => response [ "content_uri" ] ) ; } param { string } mxcUrl param { string } allowRemote returns { Promise < { data : Buffer , contentType : string } > } public async downloadContent ( mxcUrl , allowRemote = true ) { 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 = await this . doRequest ( "GET" , path , { allow_remote : allowRemote } , null , null , true , null , true ) ; return { data : res . body , contentType : res . headers [ "content-type" ] , } ; } param { string } url returns { Promise < string > } @ timedMatrixClientFunctionCall ( ) public uploadContentFromUrl ( url ) { return new Promise < { body : Buffer ; contentType : string ; } > ( ( resolve , reject ) => { const requestId = ++ this . requestId ; const params = { uri : url , method : "GET" , encoding : null , } ; getRequestFn ( ) ( params , ( err , response , resBody ) => { if ( err ) { LogService . error ( "MatrixLiteClient (REQ-" + requestId + ")" , extractRequestError ( err ) ) ; reject ( err ) ; } else { const contentType = response . headers [ 'content-type' ] || "application/octet-stream" ; LogService . trace ( "MatrixLiteClient (REQ-" + requestId + " RESP-H" + response . statusCode + ")" , "<data>" ) ; if ( response . statusCode < 200 || response . statusCode >= 300 ) { LogService . error ( "MatrixLiteClient (REQ-" + requestId + ")" , "<data>" ) ; reject ( response ) ; } else resolve ( { body : resBody , contentType : contentType } ) ; } } ) ; } ) . then ( obj => { return this . uploadContent ( obj . body , obj . contentType ) ; } ) ; } param { string } roomId returns { Promise < { previous : RoomReference [ ] , newer : RoomReference [ ] } > } @ timedMatrixClientFunctionCall ( ) public async getRoomUpgradeHistory ( roomId ) { const result = { previous : [ ] , newer : [ ] , current : null } ; const chaseCreates = async ( findRoomId ) => { try { const createEvent = await 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 ; if ( result . previous . find ( r => r . roomId === prevRoomId ) ) return ; let tombstoneEventId = null ; let prevVersion = "1" ; try { const roomState = await 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 ) { } result . previous . push ( { roomId : prevRoomId , version : prevVersion , refEventId : tombstoneEventId , } ) ; return chaseCreates ( prevRoomId ) ; } } catch ( e ) { } } ; const chaseTombstones = async ( findRoomId ) => { try { const tombstoneEvent = await this . getRoomStateEvent ( findRoomId , "m.room.tombstone" , "" ) ; if ( ! tombstoneEvent ) return ; if ( ! tombstoneEvent [ 'replacement_room' ] ) return ; const newRoomId = tombstoneEvent [ 'replacement_room' ] ; if ( newRoomId === findRoomId ) return ; if ( result . newer . find ( r => r . roomId === newRoomId ) ) return ; let newRoomVersion = "1" ; let createEventId = null ; try { const roomState = await 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 ) { } result . newer . push ( { roomId : newRoomId , version : newRoomVersion , refEventId : createEventId , } ) ; return await chaseTombstones ( newRoomId ) ; } catch ( e ) { } } ; await chaseCreates ( roomId ) ; await chaseTombstones ( roomId ) ; return result ; } param { SpaceCreateOptions } opts returns { Promise < Space > } @ timedMatrixClientFunctionCall ( ) public async createSpace ( opts ) { 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' , } , } , ] as unknown [ ] , 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 : { [ await 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 = await this . createRoom ( roomCreateOpts ) ; return new Space ( roomId , this ) ; } throws returns { Promise < Space > } @ timedMatrixClientFunctionCall ( ) public async getSpace ( roomIdOrAlias ) { const roomId = await this . resolveRoom ( roomIdOrAlias ) ; const createEvent = await this . getRoomStateEvent ( roomId , "m.room.create" , "" ) ; if ( createEvent [ "type" ] !== "m.space" ) { throw new Error ( "Room is not a space" ) ; } return new Space ( roomId , this ) ; } param { EncryptionAlgorithm [ ] } algorithms param { Record < DeviceKeyLabel < DeviceKeyAlgorithm , string > , string > } keys returns { Promise < OTKCounts > } @ timedMatrixClientFunctionCall ( ) @ requiresCrypto ( ) public async uploadDeviceKeys ( algorithms , keys ) { const obj = { user_id : await this . getUserId ( ) , device_id : this . crypto . clientDeviceId , algorithms : algorithms , keys : keys , } ; obj [ 'signatures' ] = await this . crypto . sign ( obj ) ; return this . doRequest ( "POST" , "/_matrix/client/r0/keys/upload" , null , { device_keys : obj , } ) . then ( r => r [ 'one_time_key_counts' ] ) ; } param { OTKs } keys returns { Promise < OTKCounts > } @ timedMatrixClientFunctionCall ( ) @ requiresCrypto ( ) public async uploadDeviceOneTimeKeys ( keys ) { return this . doRequest ( "POST" , "/_matrix/client/r0/keys/upload" , null , { one_time_keys : keys , } ) . then ( r => r [ 'one_time_key_counts' ] ) ; } returns { Promise < OTKCounts > } @ timedMatrixClientFunctionCall ( ) @ requiresCrypto ( ) public async checkOneTimeKeyCounts ( ) { return this . doRequest ( "POST" , "/_matrix/client/r0/keys/upload" , null , { } ) . then ( r => r [ 'one_time_key_counts' ] ) ; } param { FallbackKey } fallbackKey returns { Promise < OTKCounts > } @ timedMatrixClientFunctionCall ( ) @ requiresCrypto ( ) public async uploadFallbackKey ( fallbackKey ) { const keyObj = { [ `${ 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' ] ) ; } param { string [ ] } userIds param { number } federationTimeoutMs returns { Promise < MultiUserDeviceListResponse > } @ timedMatrixClientFunctionCall ( ) public async getUserDevices ( userIds , federationTimeoutMs = 10000 ) { const req = { } ; for ( const userId of userIds ) { req [ userId ] = [ ] ; } return this . doRequest ( "POST" , "/_matrix/client/r0/keys/query" , { } , { timeout : federationTimeoutMs , device_keys : req , } ) ; } returns { Promise < OwnUserDevice [ ] > } @ timedMatrixClientFunctionCall ( ) public async getOwnDevices ( ) { return this . doRequest ( "GET" , "/_matrix/client/r0/devices" ) . then ( r => { return r [ 'devices' ] ; } ) ; } param { Record < string , Record < string , OTKAlgorithm > > } userDeviceMap param { number } federationTimeoutMs @ timedMatrixClientFunctionCall ( ) @ requiresCrypto ( ) public async claimOneTimeKeys ( userDeviceMap , federationTimeoutMs = 10000 ) { return this . doRequest ( "POST" , "/_matrix/client/r0/keys/claim" , { } , { timeout : federationTimeoutMs , one_time_keys : userDeviceMap , } ) ; } param { string } type param { Record < string , Record < string , any > > } messages returns { Promise < void > } @ timedMatrixClientFunctionCall ( ) public async sendToDevices ( type , messages ) { const txnId = ( new Date ( ) . getTime ( ) ) + "_TDEV__inc" + ( ++ this . requestId ) ; return this . doRequest ( "PUT" , `/_matrix/client/r0/sendToDevice/${ encodeURIComponent ( type ) }/${ encodeURIComponent ( txnId ) }` , null , { messages : messages , } ) ; } param { "GET" | "POST" | "PUT" | "DELETE" } method param { string } endpoint param { any } qs param { any } body param { number } timeout param { boolean } raw param { string } contentType param { string } noEncoding returns { Promise < any > } @ timedMatrixClientFunctionCall ( ) public 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 ; } if ( this . impersonatedDeviceId ) { if ( ! qs ) qs = { "org.matrix.msc3202.device_id" : this . impersonatedDeviceId } ; else qs [ "org.matrix.msc3202.device_id" ] = this . impersonatedDeviceId ; } const headers = { } ; if ( this . accessToken ) { headers [ "Authorization" ] = `Bearer ${ this . accessToken }` ; } return doHttpRequest ( this . homeserverUrl , method , endpoint , qs , body , headers , timeout , raw , contentType , noEncoding ) ; } } export interface RoomDirectoryLookupResponse { roomId : string ; residentServers : string [ ] ; } export interface RoomReference { /**
     * The room ID being referenced
     */ roomId : string ; /**
     * The version of the room at the time
     */ version : string ; /**
     * If going backwards, the tombstone event ID, otherwise the creation
     * event. If the room can't be verified, this will be null. Will be
     * null if this reference is to the current room.
     */ refEventId : string ; }