element-web-synapse01pim.gr.jp

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

4925.js.map (2086383B)


      1 {"version":3,"file":"bundles/12c8185ea2689492c07b/4925.js","mappings":"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","sources":["webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/@types/PushRules.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/@types/beacon.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/@types/event.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/@types/extensible_events.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/@types/location.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/@types/partials.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/@types/polls.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/@types/retention.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/@types/sync.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/@types/topic.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/@types/type-guards.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/autodiscovery.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/client.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/content-helpers.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/crypto-api/recovery-key.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/crypto-api/index.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/errors.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/extensible_events_v1/utilities.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/filter.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/http-api/index.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/store/indexeddb-store-worker.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/logger.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/matrixrtc/CallMembership.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/matrixrtc/MatrixRTCSession.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/matrixrtc/MatrixRTCSessionManager.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/models/beacon.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/models/event-timeline-set.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/models/event-timeline.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/models/event.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/models/invites-ignorer.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/models/poll.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/models/relations.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/models/room-member.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/models/room-state.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/models/room-sticky-events.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/models/room.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/models/search-result.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/models/thread.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/models/typed-event-emitter.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/models/user.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/oauth/register.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/randomstring.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/scheduler.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/secret-storage.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/serverCapabilities.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/sliding-sync.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/receipt-accumulator.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/sync-accumulator.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/sync.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/utils.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/utils/decryptAESSecretStorageItem.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/version-support.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/webrtc/call.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/webrtc/callFeed.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/webrtc/groupCall.ts","webpack://element-web/../node_modules/.pnpm/matrix-js-sdk@42.0.0/node_modules/matrix-js-sdk/src/webrtc/mediaHandler.ts","webpack://element-web/../node_modules/.pnpm/source-map-loader@5.0.0_webpack@5.108.4/node_modules/source-map-loader/dist/node_modules/.pnpm/loglevel@1.9.2/node_modules/loglevel/lib/loglevel.js","webpack://element-web/javascript/node_modules/.pnpm/source-map-loader@5.0.0_webpack@5.108.4/node_modules/source-map-loader/dist/node_modules/.pnpm/@babel+runtime@8.0.0/node_modules/@babel/runtime/helpers/esm/typeof.js","webpack://element-web/javascript/node_modules/.pnpm/source-map-loader@5.0.0_webpack@5.108.4/node_modules/source-map-loader/dist/node_modules/.pnpm/@babel+runtime@8.0.0/node_modules/@babel/runtime/helpers/esm/toPropertyKey.js","webpack://element-web/javascript/node_modules/.pnpm/source-map-loader@5.0.0_webpack@5.108.4/node_modules/source-map-loader/dist/node_modules/.pnpm/@babel+runtime@8.0.0/node_modules/@babel/runtime/helpers/esm/toPrimitive.js","webpack://element-web/javascript/node_modules/.pnpm/source-map-loader@5.0.0_webpack@5.108.4/node_modules/source-map-loader/dist/node_modules/.pnpm/@babel+runtime@8.0.0/node_modules/@babel/runtime/helpers/esm/defineProperty.js"],"sourcesContent":["/*\nCopyright 2021 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// allow camelcase as these are things that go onto the wire\n/* eslint-disable camelcase */\n\nexport enum PushRuleActionName {\n    DontNotify = \"dont_notify\",\n    Notify = \"notify\",\n    Coalesce = \"coalesce\",\n}\n\nexport enum TweakName {\n    Highlight = \"highlight\",\n    Sound = \"sound\",\n}\n\nexport type Tweak<N extends TweakName, V> = {\n    set_tweak: N;\n    value?: V;\n};\n\nexport type TweakHighlight = Tweak<TweakName.Highlight, boolean>;\nexport type TweakSound = Tweak<TweakName.Sound, string>;\n\nexport type Tweaks = TweakHighlight | TweakSound;\n\nexport enum ConditionOperator {\n    ExactEquals = \"==\",\n    LessThan = \"<\",\n    GreaterThan = \">\",\n    GreaterThanOrEqual = \">=\",\n    LessThanOrEqual = \"<=\",\n}\n\nexport type PushRuleAction = Tweaks | PushRuleActionName;\n\nexport type MemberCountCondition<N extends number, Op extends ConditionOperator = ConditionOperator.ExactEquals> =\n    | `${Op}${N}`\n    | (Op extends ConditionOperator.ExactEquals ? `${N}` : never);\n\nexport type AnyMemberCountCondition = MemberCountCondition<number, ConditionOperator>;\n\nexport const DMMemberCountCondition: MemberCountCondition<2> = \"2\";\n\nexport function isDmMemberCountCondition(condition: AnyMemberCountCondition): boolean {\n    return condition === \"==2\" || condition === \"2\";\n}\n\nexport enum ConditionKind {\n    EventMatch = \"event_match\",\n    EventPropertyIs = \"event_property_is\",\n    EventPropertyContains = \"event_property_contains\",\n    ContainsDisplayName = \"contains_display_name\",\n    RoomMemberCount = \"room_member_count\",\n    SenderNotificationPermission = \"sender_notification_permission\",\n    CallStarted = \"call_started\",\n    CallStartedPrefix = \"org.matrix.msc3914.call_started\",\n}\n\nexport interface IPushRuleCondition<N extends ConditionKind | string> {\n    [k: string]: any; // for custom conditions, there can be other fields here\n    kind: N;\n}\n\nexport interface IEventMatchCondition extends IPushRuleCondition<ConditionKind.EventMatch> {\n    key: string;\n    pattern?: string;\n    // Note that value property is an optimization for patterns which do not do\n    // any globbing and when the key is not \"content.body\".\n    value?: string;\n}\n\nexport interface IEventPropertyIsCondition extends IPushRuleCondition<ConditionKind.EventPropertyIs> {\n    key: string;\n    value: string | boolean | null | number;\n}\n\nexport interface IEventPropertyContainsCondition extends IPushRuleCondition<ConditionKind.EventPropertyContains> {\n    key: string;\n    value: string | boolean | null | number;\n}\n\nexport interface IContainsDisplayNameCondition extends IPushRuleCondition<ConditionKind.ContainsDisplayName> {\n    // no additional fields\n}\n\nexport interface IRoomMemberCountCondition extends IPushRuleCondition<ConditionKind.RoomMemberCount> {\n    is: AnyMemberCountCondition;\n}\n\nexport interface ISenderNotificationPermissionCondition extends IPushRuleCondition<ConditionKind.SenderNotificationPermission> {\n    key: string;\n}\n\nexport interface ICallStartedCondition extends IPushRuleCondition<ConditionKind.CallStarted> {\n    // no additional fields\n}\n\nexport interface ICallStartedPrefixCondition extends IPushRuleCondition<ConditionKind.CallStartedPrefix> {\n    // no additional fields\n}\n\n// XXX: custom conditions are possible but always fail, and break the typescript discriminated union so ignore them here\n// IPushRuleCondition<Exclude<string, ConditionKind>> unfortunately does not resolve this at the time of writing.\nexport type PushRuleCondition =\n    | IEventMatchCondition\n    | IEventPropertyIsCondition\n    | IEventPropertyContainsCondition\n    | IContainsDisplayNameCondition\n    | IRoomMemberCountCondition\n    | ISenderNotificationPermissionCondition\n    | ICallStartedCondition\n    | ICallStartedPrefixCondition;\n\nexport enum PushRuleKind {\n    Override = \"override\",\n    ContentSpecific = \"content\",\n    RoomSpecific = \"room\",\n    SenderSpecific = \"sender\",\n    Underride = \"underride\",\n}\n\nexport enum RuleId {\n    Master = \".m.rule.master\",\n    IsUserMention = \".m.rule.is_user_mention\",\n    IsRoomMention = \".m.rule.is_room_mention\",\n    ContainsDisplayName = \".m.rule.contains_display_name\",\n    ContainsUserName = \".m.rule.contains_user_name\",\n    AtRoomNotification = \".m.rule.roomnotif\",\n    DM = \".m.rule.room_one_to_one\",\n    EncryptedDM = \".m.rule.encrypted_room_one_to_one\",\n    Message = \".m.rule.message\",\n    EncryptedMessage = \".m.rule.encrypted\",\n    InviteToSelf = \".m.rule.invite_for_me\",\n    MemberEvent = \".m.rule.member_event\",\n    IncomingCall = \".m.rule.call\",\n    SuppressNotices = \".m.rule.suppress_notices\",\n    Tombstone = \".m.rule.tombstone\",\n    PollStart = \".m.rule.poll_start\",\n    PollStartUnstable = \".org.matrix.msc3930.rule.poll_start\",\n    PollEnd = \".m.rule.poll_end\",\n    PollEndUnstable = \".org.matrix.msc3930.rule.poll_end\",\n    PollStartOneToOne = \".m.rule.poll_start_one_to_one\",\n    PollStartOneToOneUnstable = \".org.matrix.msc3930.rule.poll_start_one_to_one\",\n    PollEndOneToOne = \".m.rule.poll_end_one_to_one\",\n    PollEndOneToOneUnstable = \".org.matrix.msc3930.rule.poll_end_one_to_one\",\n}\n\nexport type PushRuleSet = {\n    [k in PushRuleKind]?: IPushRule[];\n};\n\nexport interface IPushRule {\n    actions: PushRuleAction[];\n    conditions?: PushRuleCondition[];\n    default: boolean;\n    enabled: boolean;\n    pattern?: string;\n    rule_id: RuleId | string;\n}\n\nexport interface IAnnotatedPushRule extends IPushRule {\n    kind: PushRuleKind;\n}\n\nexport interface IPushRules {\n    global: PushRuleSet;\n    device?: PushRuleSet;\n}\n\nexport interface IPusher {\n    \"app_display_name\": string;\n    \"app_id\": string;\n    \"data\": {\n        format?: string;\n        url?: string; // TODO: Required if kind==http\n        brand?: string; // TODO: For email notifications only? Unspecced field\n    };\n    \"device_display_name\": string;\n    \"kind\": \"http\" | string;\n    \"lang\": string;\n    \"profile_tag\"?: string;\n    \"pushkey\": string;\n    \"enabled\"?: boolean | null;\n    \"org.matrix.msc3881.enabled\"?: boolean | null;\n    \"device_id\"?: string | null;\n    \"org.matrix.msc3881.device_id\"?: string | null;\n}\n\nexport interface IPusherRequest extends Omit<IPusher, \"device_id\" | \"org.matrix.msc3881.device_id\"> {\n    append?: boolean;\n}\n\n/* eslint-enable camelcase */\n","/*\nCopyright 2022 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type RelatesToRelationship, type REFERENCE_RELATION } from \"./extensible_events.ts\";\nimport { UnstableValue } from \"../NamespacedValue.ts\";\nimport { type MAssetEvent, type MLocationEvent, type MTimestampEvent } from \"./location.ts\";\n\n/**\n * Beacon info and beacon event types as described in MSC3672\n * https://github.com/matrix-org/matrix-spec-proposals/pull/3672\n */\n\n/**\n * Beacon info events are state events.\n * We have two requirements for these events:\n * 1. they can only be written by their owner\n * 2. a user can have an arbitrary number of beacon_info events\n *\n * 1. is achieved by setting the state_key to the owners mxid.\n * Event keys in room state are a combination of `type` + `state_key`.\n * To achieve an arbitrary number of only owner-writable state events\n * we introduce a variable suffix to the event type\n *\n * @example\n * ```\n * {\n *      \"type\": \"m.beacon_info.@matthew:matrix.org.1\",\n *      \"state_key\": \"@matthew:matrix.org\",\n *      \"content\": {\n *          \"m.beacon_info\": {\n *              \"description\": \"The Matthew Tracker\",\n *              \"timeout\": 86400000,\n *          },\n *          // more content as described below\n *      }\n * },\n * {\n *      \"type\": \"m.beacon_info.@matthew:matrix.org.2\",\n *      \"state_key\": \"@matthew:matrix.org\",\n *      \"content\": {\n *          \"m.beacon_info\": {\n *              \"description\": \"Another different Matthew tracker\",\n *              \"timeout\": 400000,\n *          },\n *          // more content as described below\n *      }\n * }\n * ```\n */\n\n/**\n * Non-variable type for m.beacon_info event content\n */\nexport const M_BEACON_INFO = new UnstableValue(\"m.beacon_info\", \"org.matrix.msc3672.beacon_info\");\nexport const M_BEACON = new UnstableValue(\"m.beacon\", \"org.matrix.msc3672.beacon\");\n\nexport type MBeaconInfoContent = {\n    description?: string;\n    // how long from the last event until we consider the beacon inactive in milliseconds\n    timeout: number;\n    // true when this is a live location beacon\n    // https://github.com/matrix-org/matrix-spec-proposals/pull/3672\n    live?: boolean;\n};\n\n/**\n * m.beacon_info Event example from the spec\n * https://github.com/matrix-org/matrix-spec-proposals/pull/3672\n * @example\n * ```\n * {\n *   \"type\": \"m.beacon_info\",\n *   \"state_key\": \"@matthew:matrix.org\",\n *   \"content\": {\n *     \"m.beacon_info\": {\n *       \"description\": \"The Matthew Tracker\", // same as an `m.location` description\n *       \"timeout\": 86400000, // how long from the last event until we consider the beacon inactive in milliseconds\n *     },\n *     \"m.ts\": 1436829458432, // creation timestamp of the beacon on the client\n *     \"m.asset\": {\n *       \"type\": \"m.self\" // the type of asset being tracked as per MSC3488\n *     }\n *   }\n * }\n * ```\n */\n\n/**\n * m.beacon_info.* event content\n */\nexport type MBeaconInfoEventContent = MBeaconInfoContent &\n    // creation timestamp of the beacon on the client\n    MTimestampEvent &\n    // the type of asset being tracked as per MSC3488\n    MAssetEvent;\n\n/**\n * m.beacon event example\n * https://github.com/matrix-org/matrix-spec-proposals/pull/3672\n * @example\n * ```\n * {\n *   \"type\": \"m.beacon\",\n *   \"sender\": \"@matthew:matrix.org\",\n *   \"content\": {\n *       \"m.relates_to\": { // from MSC2674: https://github.com/matrix-org/matrix-doc/pull/2674\n *           \"rel_type\": \"m.reference\", // from MSC3267: https://github.com/matrix-org/matrix-doc/pull/3267\n *           \"event_id\": \"$beacon_info\"\n *       },\n *       \"m.location\": {\n *           \"uri\": \"geo:51.5008,0.1247;u=35\",\n *           \"description\": \"Arbitrary beacon information\"\n *       },\n *       \"m.ts\": 1636829458432,\n *   }\n * }\n * ```\n */\n\n/**\n * Content of an m.beacon event\n */\nexport type MBeaconEventContent = MLocationEvent &\n    // timestamp when location was taken\n    MTimestampEvent &\n    // relates to a beacon_info event\n    RelatesToRelationship<typeof REFERENCE_RELATION>;\n","/*\nCopyright 2020-2026 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type EitherAnd } from \"matrix-events-sdk\";\n\nimport { NamespacedValue, UnstableValue } from \"../NamespacedValue.ts\";\nimport {\n    type PolicyRuleEventContent,\n    type RoomAvatarEventContent,\n    type RoomCanonicalAliasEventContent,\n    type RoomCreateEventContent,\n    type RoomEncryptionEventContent,\n    type RoomGuestAccessEventContent,\n    type RoomHistoryVisibilityEventContent,\n    type RoomJoinRulesEventContent,\n    type RoomMemberEventContent,\n    type RoomNameEventContent,\n    type RoomPinnedEventsEventContent,\n    type RoomPolicyContent,\n    type RoomPowerLevelsEventContent,\n    type RoomServerAclEventContent,\n    type RoomThirdPartyInviteEventContent,\n    type RoomTombstoneEventContent,\n    type RoomTopicEventContent,\n    type SpaceChildEventContent,\n    type SpaceParentEventContent,\n} from \"./state_events.ts\";\nimport { type IGroupCallRoomMemberState, type IGroupCallRoomState } from \"../webrtc/groupCall.ts\";\nimport { type MSC3089EventContent } from \"../models/MSC3089Branch.ts\";\nimport { type M_BEACON, type M_BEACON_INFO, type MBeaconEventContent, type MBeaconInfoEventContent } from \"./beacon.ts\";\nimport { type EmptyObject } from \"./common.ts\";\nimport { type ReactionEventContent, type RoomMessageEventContent, type StickerEventContent } from \"./events.ts\";\nimport {\n    type MCallAnswer,\n    type MCallBase,\n    type MCallCandidates,\n    type MCallHangupReject,\n    type MCallInviteNegotiate,\n    type MCallReplacesEvent,\n    type MCallSelectAnswer,\n    type SDPStreamMetadata,\n} from \"../webrtc/callEventTypes.ts\";\nimport {\n    type IRTCNotificationContent,\n    type IRTCDeclineContent,\n    type EncryptionKeysEventContent,\n    type ICallNotifyContent,\n} from \"../matrixrtc/types.ts\";\nimport { type M_POLL_END, type M_POLL_START, type PollEndEventContent, type PollStartEventContent } from \"./polls.ts\";\nimport { type RtcMembershipData, type SessionMembershipData } from \"../matrixrtc/membershipData/index.ts\";\nimport { type LocalNotificationSettings } from \"./local_notifications.ts\";\nimport { type IPushRules } from \"./PushRules.ts\";\nimport { type SecretInfo, type SecretStorageKeyDescription } from \"../secret-storage.ts\";\nimport { type POLICIES_ACCOUNT_EVENT_TYPE } from \"../models/invites-ignorer-types.ts\";\nimport type { ROOM_RETENTION_TYPE, RoomRetentionContent } from \"./retention.ts\";\n\nexport enum EventType {\n    // Room state events\n    RoomCanonicalAlias = \"m.room.canonical_alias\",\n    RoomCreate = \"m.room.create\",\n    RoomJoinRules = \"m.room.join_rules\",\n    RoomMember = \"m.room.member\",\n    RoomThirdPartyInvite = \"m.room.third_party_invite\",\n    RoomPowerLevels = \"m.room.power_levels\",\n    RoomName = \"m.room.name\",\n    RoomTopic = \"m.room.topic\",\n    RoomAvatar = \"m.room.avatar\",\n    RoomPinnedEvents = \"m.room.pinned_events\",\n    RoomEncryption = \"m.room.encryption\",\n    RoomHistoryVisibility = \"m.room.history_visibility\",\n    RoomGuestAccess = \"m.room.guest_access\",\n    RoomServerAcl = \"m.room.server_acl\",\n    RoomTombstone = \"m.room.tombstone\",\n    RoomPredecessor = \"org.matrix.msc3946.room_predecessor\",\n\n    // Moderation policy lists\n    PolicyRuleUser = \"m.policy.rule.user\",\n    PolicyRuleRoom = \"m.policy.rule.room\",\n    PolicyRuleServer = \"m.policy.rule.server\",\n\n    SpaceChild = \"m.space.child\",\n    SpaceParent = \"m.space.parent\",\n\n    // Room timeline events\n    RoomRedaction = \"m.room.redaction\",\n    RoomMessage = \"m.room.message\",\n    RoomMessageEncrypted = \"m.room.encrypted\",\n    Sticker = \"m.sticker\",\n    CallInvite = \"m.call.invite\",\n    CallCandidates = \"m.call.candidates\",\n    CallAnswer = \"m.call.answer\",\n    CallHangup = \"m.call.hangup\",\n    CallReject = \"m.call.reject\",\n    CallSelectAnswer = \"m.call.select_answer\",\n    CallNegotiate = \"m.call.negotiate\",\n    CallSDPStreamMetadataChanged = \"m.call.sdp_stream_metadata_changed\",\n    CallSDPStreamMetadataChangedPrefix = \"org.matrix.call.sdp_stream_metadata_changed\",\n    CallReplaces = \"m.call.replaces\",\n    CallAssertedIdentity = \"m.call.asserted_identity\",\n    CallAssertedIdentityPrefix = \"org.matrix.call.asserted_identity\",\n    CallEncryptionKeysPrefix = \"io.element.call.encryption_keys\",\n    KeyVerificationRequest = \"m.key.verification.request\",\n    KeyVerificationStart = \"m.key.verification.start\",\n    KeyVerificationCancel = \"m.key.verification.cancel\",\n    KeyVerificationMac = \"m.key.verification.mac\",\n    KeyVerificationDone = \"m.key.verification.done\",\n    KeyVerificationKey = \"m.key.verification.key\",\n    KeyVerificationAccept = \"m.key.verification.accept\",\n    // Not used directly - see READY_TYPE in VerificationRequest.\n    KeyVerificationReady = \"m.key.verification.ready\",\n    // use of this is discouraged https://matrix.org/docs/spec/client_server/r0.6.1#m-room-message-feedback\n    RoomMessageFeedback = \"m.room.message.feedback\",\n    Reaction = \"m.reaction\",\n    PollStart = \"org.matrix.msc3381.poll.start\",\n\n    // Room ephemeral events\n    Typing = \"m.typing\",\n    Receipt = \"m.receipt\",\n    Presence = \"m.presence\",\n\n    // Room account_data events\n    FullyRead = \"m.fully_read\",\n    Tag = \"m.tag\",\n    SpaceOrder = \"org.matrix.msc3230.space_order\", // MSC3230\n    MarkedUnread = \"m.marked_unread\",\n\n    // User account_data events\n    PushRules = \"m.push_rules\",\n    Direct = \"m.direct\",\n    IgnoredUserList = \"m.ignored_user_list\",\n    InvitePermissionConfig = \"m.invite_permission_config\", // MSC4380\n\n    // to_device events\n    RoomKey = \"m.room_key\",\n    RoomKeyRequest = \"m.room_key_request\",\n    ForwardedRoomKey = \"m.forwarded_room_key\",\n    Dummy = \"m.dummy\",\n    SecretRequest = \"m.secret.request\",\n    SecretSend = \"m.secret.send\",\n\n    // Group call events\n    GroupCallPrefix = \"org.matrix.msc3401.call\",\n    GroupCallMemberPrefix = \"org.matrix.msc3401.call.member\",\n\n    // MatrixRTC events\n    RTCMembership = \"org.matrix.msc4143.rtc.member\",\n    CallNotify = \"org.matrix.msc4075.call.notify\",\n    RTCNotification = \"org.matrix.msc4075.rtc.notification\",\n    RTCDecline = \"org.matrix.msc4310.rtc.decline\",\n\n    // Policy servers\n    RoomPolicy = \"org.matrix.msc4284.policy\",\n\n    // Retention\n    RetentionPolicy = \"m.room.retention\",\n    RetentionPolicyUnstable = \"org.matrix.msc1763.retention\",\n}\n\nexport enum RelationType {\n    Annotation = \"m.annotation\",\n    Replace = \"m.replace\",\n    Reference = \"m.reference\",\n\n    // Don't use this yet: it's only the stable version. The code still assumes we support the unstable prefix and,\n    // moreover, our tests currently use the unstable prefix. Use THREAD_RELATION_TYPE.name.\n    // Once we support *only* the stable prefix, THREAD_RELATION_TYPE can die and we can switch to this.\n    Thread = \"m.thread\",\n}\n\nexport enum MsgType {\n    Text = \"m.text\",\n    Emote = \"m.emote\",\n    Notice = \"m.notice\",\n    Image = \"m.image\",\n    File = \"m.file\",\n    Audio = \"m.audio\",\n    Location = \"m.location\",\n    Video = \"m.video\",\n    KeyVerificationRequest = \"m.key.verification.request\",\n}\n\nexport const RoomCreateTypeField = \"type\";\n\nexport enum RoomType {\n    Space = \"m.space\",\n    UnstableCall = \"org.matrix.msc3417.call\",\n    ElementVideo = \"io.element.video\",\n}\n\nexport const ToDeviceMessageId = \"org.matrix.msgid\";\n\n/**\n * Identifier for an [MSC3088](https://github.com/matrix-org/matrix-doc/pull/3088)\n * room purpose. Note that this reference is UNSTABLE and subject to breaking changes,\n * including its eventual removal.\n */\nexport const UNSTABLE_MSC3088_PURPOSE = new UnstableValue(\"m.room.purpose\", \"org.matrix.msc3088.purpose\");\n\n/**\n * Enabled flag for an [MSC3088](https://github.com/matrix-org/matrix-doc/pull/3088)\n * room purpose. Note that this reference is UNSTABLE and subject to breaking changes,\n * including its eventual removal.\n */\nexport const UNSTABLE_MSC3088_ENABLED = new UnstableValue(\"m.enabled\", \"org.matrix.msc3088.enabled\");\n\n/**\n * Subtype for an [MSC3089](https://github.com/matrix-org/matrix-doc/pull/3089) space-room.\n * Note that this reference is UNSTABLE and subject to breaking changes, including its\n * eventual removal.\n */\nexport const UNSTABLE_MSC3089_TREE_SUBTYPE = new UnstableValue(\"m.data_tree\", \"org.matrix.msc3089.data_tree\");\n\n/**\n * Leaf type for an event in a [MSC3089](https://github.com/matrix-org/matrix-doc/pull/3089) space-room.\n * Note that this reference is UNSTABLE and subject to breaking changes, including its\n * eventual removal.\n */\nexport const UNSTABLE_MSC3089_LEAF = new UnstableValue(\"m.leaf\", \"org.matrix.msc3089.leaf\");\n\n/**\n * Branch (Leaf Reference) type for the index approach in a\n * [MSC3089](https://github.com/matrix-org/matrix-doc/pull/3089) space-room. Note that this reference is\n * UNSTABLE and subject to breaking changes, including its eventual removal.\n */\nexport const UNSTABLE_MSC3089_BRANCH = new UnstableValue(\"m.branch\", \"org.matrix.msc3089.branch\");\n\n/**\n * Marker event type to point back at imported historical content in a room. See\n * [MSC2716](https://github.com/matrix-org/matrix-spec-proposals/pull/2716).\n * Note that this reference is UNSTABLE and subject to breaking changes,\n * including its eventual removal.\n */\nexport const UNSTABLE_MSC2716_MARKER = new UnstableValue(\"m.room.marker\", \"org.matrix.msc2716.marker\");\n\n/**\n * Name of the request property for relation based redactions.\n * {@link https://github.com/matrix-org/matrix-spec-proposals/pull/3912}\n */\nexport const MSC3912_RELATION_BASED_REDACTIONS_PROP = new UnstableValue(\n    \"with_rel_types\",\n    \"org.matrix.msc3912.with_relations\",\n);\n\n/**\n * Functional members type for declaring a purpose of room members (e.g. helpful bots).\n * Note that this reference is UNSTABLE and subject to breaking changes, including its\n * eventual removal.\n *\n * Schema (TypeScript):\n * ```\n * {\n *   service_members?: string[]\n * }\n * ```\n *\n * @example\n * ```\n * {\n *   \"service_members\": [\n *     \"@helperbot:localhost\",\n *     \"@reminderbot:alice.tdl\"\n *   ]\n * }\n * ```\n */\nexport const UNSTABLE_ELEMENT_FUNCTIONAL_USERS = new UnstableValue(\n    \"io.element.functional_members\",\n    \"io.element.functional_members\",\n);\n\n/**\n * A type of message that affects visibility of a message,\n * as per https://github.com/matrix-org/matrix-doc/pull/3531\n *\n * @experimental\n */\nexport const EVENT_VISIBILITY_CHANGE_TYPE = new UnstableValue(\"m.visibility\", \"org.matrix.msc3531.visibility\");\n\n/**\n * https://github.com/matrix-org/matrix-doc/pull/3881\n *\n * @experimental\n */\nexport const PUSHER_ENABLED = new UnstableValue(\"enabled\", \"org.matrix.msc3881.enabled\");\n\n/**\n * https://github.com/matrix-org/matrix-doc/pull/3881\n *\n * @experimental\n */\nexport const PUSHER_DEVICE_ID = new UnstableValue(\"device_id\", \"org.matrix.msc3881.device_id\");\n\n/**\n * https://github.com/matrix-org/matrix-doc/pull/3890\n *\n * @experimental\n */\nexport const LOCAL_NOTIFICATION_SETTINGS_PREFIX = new UnstableValue(\n    \"m.local_notification_settings\",\n    \"org.matrix.msc3890.local_notification_settings\",\n);\n\n/**\n * https://github.com/matrix-org/matrix-doc/pull/4023\n *\n * @experimental\n */\nexport const UNSIGNED_THREAD_ID_FIELD = new UnstableValue(\"thread_id\", \"org.matrix.msc4023.thread_id\");\n\n/**\n * https://github.com/matrix-org/matrix-spec-proposals/pull/4115\n *\n * @experimental\n */\nexport const UNSIGNED_MEMBERSHIP_FIELD = new NamespacedValue(\"membership\", \"io.element.msc4115.membership\");\n\n/**\n * Mapped type from event type to content type for all specified non-state room events.\n */\nexport interface TimelineEvents {\n    [EventType.RoomMessage]: RoomMessageEventContent;\n    [EventType.Sticker]: StickerEventContent;\n    [EventType.Reaction]: ReactionEventContent;\n    [EventType.CallReplaces]: MCallReplacesEvent;\n    [EventType.CallAnswer]: MCallAnswer;\n    [EventType.CallSelectAnswer]: MCallSelectAnswer;\n    [EventType.CallNegotiate]: Omit<MCallInviteNegotiate, \"offer\">;\n    [EventType.CallInvite]: MCallInviteNegotiate;\n    [EventType.CallCandidates]: MCallCandidates;\n    [EventType.CallHangup]: MCallHangupReject;\n    [EventType.CallReject]: MCallHangupReject;\n    [EventType.CallSDPStreamMetadataChangedPrefix]: MCallBase &\n        EitherAnd<\n            { sdp_stream_metadata: SDPStreamMetadata },\n            { \"org.matrix.msc3077.sdp_stream_metadata\": SDPStreamMetadata }\n        >;\n    [EventType.CallSDPStreamMetadataChanged]: MCallBase &\n        EitherAnd<\n            { sdp_stream_metadata: SDPStreamMetadata },\n            { \"org.matrix.msc3077.sdp_stream_metadata\": SDPStreamMetadata }\n        >;\n    [EventType.CallEncryptionKeysPrefix]: EncryptionKeysEventContent;\n    [EventType.CallNotify]: ICallNotifyContent;\n    [EventType.RTCNotification]: IRTCNotificationContent;\n    [EventType.RTCDecline]: IRTCDeclineContent;\n    [M_BEACON.name]: MBeaconEventContent;\n    [M_POLL_START.name]: PollStartEventContent;\n    [M_POLL_END.name]: PollEndEventContent;\n    [EventType.RTCMembership]: RtcMembershipData | { msc4354_sticky_key: string }; // An object containing just the sticky key is empty.\n}\n\n/**\n * Mapped type from event type to content type for all specified room state events.\n */\nexport interface StateEvents {\n    [EventType.RoomCanonicalAlias]: RoomCanonicalAliasEventContent;\n    [EventType.RoomCreate]: RoomCreateEventContent;\n    [EventType.RoomJoinRules]: RoomJoinRulesEventContent;\n    [EventType.RoomMember]: RoomMemberEventContent;\n    // XXX: Spec says this event has 3 required fields but kicking such an invitation requires sending `{}`\n    [EventType.RoomThirdPartyInvite]: RoomThirdPartyInviteEventContent | EmptyObject;\n    [EventType.RoomPowerLevels]: RoomPowerLevelsEventContent;\n    [EventType.RoomName]: RoomNameEventContent;\n    [EventType.RoomTopic]: RoomTopicEventContent;\n    [EventType.RoomAvatar]: RoomAvatarEventContent;\n    [EventType.RoomPinnedEvents]: RoomPinnedEventsEventContent;\n    [EventType.RoomEncryption]: RoomEncryptionEventContent;\n    [EventType.RoomHistoryVisibility]: RoomHistoryVisibilityEventContent;\n    [EventType.RoomGuestAccess]: RoomGuestAccessEventContent;\n    [EventType.RoomServerAcl]: RoomServerAclEventContent;\n    [EventType.RoomTombstone]: RoomTombstoneEventContent;\n    [EventType.SpaceChild]: SpaceChildEventContent;\n    [EventType.SpaceParent]: SpaceParentEventContent;\n\n    [EventType.PolicyRuleUser]: PolicyRuleEventContent | EmptyObject;\n    [EventType.PolicyRuleRoom]: PolicyRuleEventContent | EmptyObject;\n    [EventType.PolicyRuleServer]: PolicyRuleEventContent | EmptyObject;\n\n    // MSC4284: Policy servers\n    [EventType.RoomPolicy]: RoomPolicyContent | EmptyObject;\n\n    // MSC3401\n    [EventType.GroupCallPrefix]: IGroupCallRoomState;\n    [EventType.GroupCallMemberPrefix]: IGroupCallRoomMemberState | SessionMembershipData | EmptyObject;\n    [EventType.RTCMembership]: RtcMembershipData | EmptyObject;\n    // MSC3089\n    [UNSTABLE_MSC3089_BRANCH.name]: MSC3089EventContent;\n\n    // MSC3672\n    [M_BEACON_INFO.name]: MBeaconInfoEventContent;\n\n    // MSC1763\n    [ROOM_RETENTION_TYPE.name]: RoomRetentionContent | EmptyObject;\n    [ROOM_RETENTION_TYPE.altName]: RoomRetentionContent | EmptyObject;\n}\n\n/**\n * Mapped type from event type to content type for all specified room-specific account_data events.\n */\nexport interface RoomAccountDataEvents extends SecretStorageAccountDataEvents {\n    [EventType.FullyRead]: { event_id: string };\n    [EventType.Tag]: { tags: { [name: string]: { order?: number } } };\n    [EventType.SpaceOrder]: { order: string };\n    [EventType.MarkedUnread]: { unread: boolean };\n}\n\n/**\n * Mapped type from event type to content type for all specified global account_data events.\n */\nexport interface AccountDataEvents extends SecretStorageAccountDataEvents {\n    [EventType.PushRules]: IPushRules;\n    [EventType.Direct]: { [userId: string]: string[] };\n    [EventType.IgnoredUserList]: { ignored_users: { [userId: string]: EmptyObject } };\n    \"m.secret_storage.default_key\": { key: string };\n\n    // MSC4287: Sharing key backup preference between clients - used to mark that the user opted out of key storage\n    \"m.key_backup\": { enabled: boolean };\n    // MSC4287 unstable prefix (note the boolean property has the opposite sense)\n    \"m.org.matrix.custom.backup_disabled\": { disabled: boolean };\n\n    \"m.identity_server\": { base_url: string | null };\n    [key: `${typeof LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${string}`]: LocalNotificationSettings;\n    [key: `m.secret_storage.key.${string}`]: SecretStorageKeyDescription;\n\n    // Invites-ignorer events\n    [POLICIES_ACCOUNT_EVENT_TYPE.name]: { [key: string]: any };\n    [POLICIES_ACCOUNT_EVENT_TYPE.altName]: { [key: string]: any };\n\n    [EventType.InvitePermissionConfig]: { default_action?: string };\n\n    // List of recently used reaction emojis\n    // https://spec.matrix.org/v1.18/client-server-api/#mrecent_emoji\n    \"m.recent_emoji\": {\n        recent_emoji: Array<{\n            emoji: string;\n            total: number;\n        }>;\n    };\n}\n\n/**\n * Subset of AccountDataEvents, excluding events specified in https://spec.matrix.org/v1.17/client-server-api/#server-behaviour-12\n */\nexport type WritableAccountDataEvents = Exclude<AccountDataEvents, \"m.fully_read\" | \"m.push_rules\">;\n\n/**\n * Mapped type from event type to content type for all specified global events encrypted by secret storage.\n *\n * See https://spec.matrix.org/v1.13/client-server-api/#msecret_storagev1aes-hmac-sha2-1\n */\nexport interface SecretStorageAccountDataEvents {\n    \"m.megolm_backup.v1\": SecretInfo;\n    \"m.cross_signing.master\": SecretInfo;\n    \"m.cross_signing.self_signing\": SecretInfo;\n    \"m.cross_signing.user_signing\": SecretInfo;\n    \"org.matrix.msc3814\": SecretInfo;\n}\n","/*\nCopyright 2021 - 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type EitherAnd, NamespacedValue, UnstableValue } from \"matrix-events-sdk\";\n\nimport { isProvided } from \"../extensible_events_v1/utilities.ts\";\n\n// Types and utilities for MSC1767: Extensible events (version 1) in Matrix\n\n/**\n * Represents the stable and unstable values of a given namespace.\n */\nexport type TSNamespace<N> =\n    N extends NamespacedValue<infer S, infer U> ? TSNamespaceValue<S> | TSNamespaceValue<U> : never;\n\n/**\n * Represents a namespaced value, if the value is a string. Used to extract provided types\n * from a TSNamespace<N> (in cases where only stable *or* unstable is provided).\n */\nexport type TSNamespaceValue<V> = V extends string ? V : never;\n\n/**\n * Creates a type which is V when T is `never`, otherwise T.\n */\n// See https://github.com/microsoft/TypeScript/issues/23182#issuecomment-379091887 for details on the array syntax.\nexport type DefaultNever<T, V> = [T] extends [never] ? V : T;\n\n/**\n * The namespaced value for m.message\n */\nexport const M_MESSAGE = new UnstableValue(\"m.message\", \"org.matrix.msc1767.message\");\n\n/**\n * An m.message event rendering\n */\nexport interface IMessageRendering {\n    body: string;\n    mimetype?: string;\n}\n\n/**\n * The content for an m.message event\n */\nexport type ExtensibleMessageEventContent = EitherAnd<\n    { [M_MESSAGE.name]: IMessageRendering[] },\n    { [M_MESSAGE.altName]: IMessageRendering[] }\n>;\n\n/**\n * The namespaced value for m.text\n */\nexport const M_TEXT = new UnstableValue(\"m.text\", \"org.matrix.msc1767.text\");\n\n/**\n * The content for an m.text event\n */\nexport type TextEventContent = EitherAnd<{ [M_TEXT.name]: string }, { [M_TEXT.altName]: string }>;\n\n/**\n * The namespaced value for m.html\n */\nexport const M_HTML = new UnstableValue(\"m.html\", \"org.matrix.msc1767.html\");\n\n/**\n * The content for an m.html event\n */\nexport type HtmlEventContent = EitherAnd<{ [M_HTML.name]: string }, { [M_HTML.altName]: string }>;\n\n/**\n * The content for an m.message, m.text, or m.html event\n */\nexport type ExtensibleAnyMessageEventContent = ExtensibleMessageEventContent | TextEventContent | HtmlEventContent;\n\n/**\n * The namespaced value for an m.reference relation\n */\nexport const REFERENCE_RELATION = new NamespacedValue(\"m.reference\");\n\n/**\n * Represents any relation type\n */\nexport type AnyRelation = TSNamespace<typeof REFERENCE_RELATION> | string;\n\n/**\n * An m.relates_to relationship\n */\nexport type RelatesToRelationship<R = never> = {\n    \"m.relates_to\": {\n        // See https://github.com/microsoft/TypeScript/issues/23182#issuecomment-379091887 for array syntax\n        rel_type: [R] extends [never] ? AnyRelation : TSNamespace<R>;\n        event_id: string;\n    };\n};\n\n/**\n * Partial types for a Matrix Event.\n */\nexport interface IPartialEvent<TContent> {\n    type: string;\n    content: TContent;\n}\n\n/**\n * Represents a potentially namespaced event type.\n */\nexport type ExtensibleEventType = NamespacedValue<string, string> | string;\n\n/**\n * Determines if two event types are the same, including namespaces.\n * @param given - The given event type. This will be compared\n * against the expected type.\n * @param expected - The expected event type.\n * @returns True if the given type matches the expected type.\n */\nexport function isEventTypeSame(given: ExtensibleEventType | null, expected: ExtensibleEventType | null): boolean {\n    if (typeof given === \"string\") {\n        if (typeof expected === \"string\") {\n            return expected === given;\n        } else {\n            return (expected as NamespacedValue<string, string>).matches(given as string);\n        }\n    } else {\n        if (typeof expected === \"string\") {\n            return (given as NamespacedValue<string, string>).matches(expected as string);\n        } else {\n            const expectedNs = expected as NamespacedValue<string, string>;\n            const givenNs = given as NamespacedValue<string, string>;\n            return (\n                expectedNs.matches(givenNs.name) ||\n                (isProvided(givenNs.altName) && expectedNs.matches(givenNs.altName!))\n            );\n        }\n    }\n}\n","/*\nCopyright 2021 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Types for MSC3488 - m.location: Extending events with location data\nimport { type EitherAnd } from \"matrix-events-sdk\";\n\nimport { UnstableValue } from \"../NamespacedValue.ts\";\nimport { type M_TEXT } from \"./extensible_events.ts\";\n\nexport enum LocationAssetType {\n    Self = \"m.self\",\n    Pin = \"m.pin\",\n}\n\nexport const M_ASSET = new UnstableValue(\"m.asset\", \"org.matrix.msc3488.asset\");\nexport type MAssetContent = { type: LocationAssetType };\n/**\n * The event definition for an m.asset event (in content)\n */\nexport type MAssetEvent = EitherAnd<{ [M_ASSET.name]: MAssetContent }, { [M_ASSET.altName]: MAssetContent }>;\n\nexport const M_TIMESTAMP = new UnstableValue(\"m.ts\", \"org.matrix.msc3488.ts\");\n/**\n * The event definition for an m.ts event (in content)\n */\nexport type MTimestampEvent = EitherAnd<{ [M_TIMESTAMP.name]: number }, { [M_TIMESTAMP.altName]: number }>;\n\nexport const M_LOCATION = new UnstableValue(\"m.location\", \"org.matrix.msc3488.location\");\n\nexport type MLocationContent = {\n    uri: string;\n    description?: string | null;\n};\n\nexport type MLocationEvent = EitherAnd<\n    { [M_LOCATION.name]: MLocationContent },\n    { [M_LOCATION.altName]: MLocationContent }\n>;\n\nexport type MTextEvent = EitherAnd<{ [M_TEXT.name]: string }, { [M_TEXT.altName]: string }>;\n\n/* From the spec at:\n * https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md\n{\n    \"type\": \"m.room.message\",\n    \"content\": {\n        \"body\": \"Matthew was at geo:51.5008,0.1247;u=35 as of Sat Nov 13 18:50:58 2021\",\n        \"msgtype\": \"m.location\",\n        \"geo_uri\": \"geo:51.5008,0.1247;u=35\",\n        \"m.location\": {\n            \"uri\": \"geo:51.5008,0.1247;u=35\",\n            \"description\": \"Matthew's whereabouts\",\n        },\n        \"m.asset\": {\n            \"type\": \"m.self\"\n        },\n        \"m.text\": \"Matthew was at geo:51.5008,0.1247;u=35 as of Sat Nov 13 18:50:58 2021\",\n        \"m.ts\": 1636829458432,\n    }\n}\n*/\ntype OptionalTimestampEvent = MTimestampEvent | undefined;\n/**\n * The content for an m.location event\n */\nexport type MLocationEventContent = MLocationEvent & MAssetEvent & MTextEvent & OptionalTimestampEvent;\n\nexport type LegacyLocationEventContent = {\n    body: string;\n    msgtype: string;\n    geo_uri: string;\n};\n\n/**\n * Possible content for location events as sent over the wire\n */\nexport type LocationEventWireContent = Partial<LegacyLocationEventContent & MLocationEventContent>;\n\nexport type ILocationContent = MLocationEventContent & LegacyLocationEventContent;\n","/*\nCopyright 2021 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nexport enum Visibility {\n    Public = \"public\",\n    Private = \"private\",\n}\n\nexport enum Preset {\n    PrivateChat = \"private_chat\",\n    TrustedPrivateChat = \"trusted_private_chat\",\n    PublicChat = \"public_chat\",\n}\n\nexport type ResizeMethod = \"crop\" | \"scale\";\n\nexport type IdServerUnbindResult = \"no-support\" | \"success\";\n\n// Knock and private are reserved keywords which are not yet implemented.\nexport enum JoinRule {\n    Public = \"public\",\n    Invite = \"invite\",\n    /**\n     * @deprecated Reserved keyword. Should not be used. Not yet implemented.\n     */\n    Private = \"private\",\n    Knock = \"knock\",\n    Restricted = \"restricted\",\n}\n\nexport enum RestrictedAllowType {\n    RoomMembership = \"m.room_membership\",\n}\n\nexport enum GuestAccess {\n    CanJoin = \"can_join\",\n    Forbidden = \"forbidden\",\n}\n\nexport enum HistoryVisibility {\n    Invited = \"invited\",\n    Joined = \"joined\",\n    Shared = \"shared\",\n    WorldReadable = \"world_readable\",\n}\n\nexport interface IUsageLimit {\n    // \"hs_disabled\" is NOT a specced string, but is used in Synapse\n    // This is tracked over at https://github.com/matrix-org/synapse/issues/9237\n    // eslint-disable-next-line camelcase\n    limit_type: \"monthly_active_user\" | \"hs_disabled\" | string;\n    // eslint-disable-next-line camelcase\n    admin_contact?: string;\n}\n\n/**\n * A policy name & url in a specific internationalisation\n * @see https://spec.matrix.org/v1.13/identity-service-api/#get_matrixidentityv2terms_response-200_internationalised-policy\n */\nexport interface InternationalisedPolicy {\n    name: string;\n    url: string;\n}\n\n/**\n * A versioned policy with internationalised variants\n * @see https://spec.matrix.org/v1.13/identity-service-api/#get_matrixidentityv2terms_response-200_policy-object\n */\nexport interface Policy {\n    /**\n     * The version for the policy.\n     * There are no requirements on what this might be and could be “alpha”, semantically versioned, or arbitrary.\n     */\n    version: string;\n    /**\n     * The policy information for the specified language.\n     * @remarks the type has to include a union with string due to limitations in the type system.\n     */\n    [lang: string]: InternationalisedPolicy | string;\n}\n\n/**\n * Response from the Terms API for Identity servers\n * @see https://spec.matrix.org/v1.13/identity-service-api/#get_matrixidentityv2terms\n */\nexport interface Terms {\n    policies: {\n        [policyName: string]: Policy;\n    };\n}\n","/*\nCopyright 2022 - 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type EitherAnd, UnstableValue } from \"matrix-events-sdk\";\n\nimport {\n    type ExtensibleAnyMessageEventContent,\n    type REFERENCE_RELATION,\n    type RelatesToRelationship,\n    type TSNamespace,\n} from \"./extensible_events.ts\";\nimport { type EmptyObject } from \"./common.ts\";\n\n/**\n * Identifier for a disclosed poll.\n */\nexport const M_POLL_KIND_DISCLOSED = new UnstableValue(\"m.poll.disclosed\", \"org.matrix.msc3381.poll.disclosed\");\n\n/**\n * Identifier for an undisclosed poll.\n */\nexport const M_POLL_KIND_UNDISCLOSED = new UnstableValue(\"m.poll.undisclosed\", \"org.matrix.msc3381.poll.undisclosed\");\n\n/**\n * Any poll kind.\n */\nexport type PollKind = TSNamespace<typeof M_POLL_KIND_DISCLOSED> | TSNamespace<typeof M_POLL_KIND_UNDISCLOSED> | string;\n\n/**\n * Known poll kind namespaces.\n */\nexport type KnownPollKind = typeof M_POLL_KIND_DISCLOSED | typeof M_POLL_KIND_UNDISCLOSED;\n\n/**\n * The namespaced value for m.poll.start\n */\nexport const M_POLL_START = new UnstableValue(\"m.poll.start\", \"org.matrix.msc3381.poll.start\");\n\n/**\n * The m.poll.start type within event content\n */\nexport type PollStartSubtype = {\n    question: ExtensibleAnyMessageEventContent;\n    kind: PollKind;\n    max_selections?: number; // default 1, always positive\n    answers: PollAnswer[];\n};\n\n/**\n * A poll answer.\n */\nexport type PollAnswer = ExtensibleAnyMessageEventContent & { id: string };\n\n/**\n * The event definition for an m.poll.start event (in content)\n */\nexport type PollStartEvent = EitherAnd<\n    { [M_POLL_START.name]: PollStartSubtype },\n    { [M_POLL_START.altName]: PollStartSubtype }\n>;\n\n/**\n * The content for an m.poll.start event\n */\nexport type PollStartEventContent = PollStartEvent & ExtensibleAnyMessageEventContent;\n\n/**\n * The namespaced value for m.poll.response\n */\nexport const M_POLL_RESPONSE = new UnstableValue(\"m.poll.response\", \"org.matrix.msc3381.poll.response\");\n\n/**\n * The m.poll.response type within event content\n */\nexport type PollResponseSubtype = {\n    answers: string[];\n};\n\n/**\n * The event definition for an m.poll.response event (in content)\n */\nexport type PollResponseEvent = EitherAnd<\n    { [M_POLL_RESPONSE.name]: PollResponseSubtype },\n    { [M_POLL_RESPONSE.altName]: PollResponseSubtype }\n>;\n\n/**\n * The content for an m.poll.response event\n */\nexport type PollResponseEventContent = PollResponseEvent & RelatesToRelationship<typeof REFERENCE_RELATION>;\n\n/**\n * The namespaced value for m.poll.end\n */\nexport const M_POLL_END = new UnstableValue(\"m.poll.end\", \"org.matrix.msc3381.poll.end\");\n\n/**\n * The event definition for an m.poll.end event (in content)\n */\nexport type PollEndEvent = EitherAnd<{ [M_POLL_END.name]: EmptyObject }, { [M_POLL_END.altName]: EmptyObject }>;\n\n/**\n * The content for an m.poll.end event\n */\nexport type PollEndEventContent = PollEndEvent &\n    RelatesToRelationship<typeof REFERENCE_RELATION> &\n    ExtensibleAnyMessageEventContent;\n","import { UnstableValue } from \"../NamespacedValue.ts\";\n\n/**\n * Event type for a room policy event.\n * NOTE: While MSC1763 has not been merged, `m.room.retention` is unfortunately\n * already in use in production.\n */\nexport const ROOM_RETENTION_TYPE = new UnstableValue(\"m.room.retention\", \"org.matrix.msc1763.retention\");\n\n/**\n * The content of a `m.room.retention` state event.\n */\nexport interface RoomRetentionContent {\n    max_lifetime?: number;\n    min_lifetime?: number;\n}\n","/*\nCopyright 2022 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { ServerControlledNamespacedValue } from \"../NamespacedValue.ts\";\n\n/**\n * https://github.com/matrix-org/matrix-doc/pull/3773\n *\n * @experimental\n */\nexport const UNREAD_THREAD_NOTIFICATIONS = new ServerControlledNamespacedValue(\n    \"unread_thread_notifications\",\n    \"org.matrix.msc3773.unread_thread_notifications\",\n);\n","/*\nCopyright 2022 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\nimport { NamespacedValue } from \"../NamespacedValue.ts\";\nimport { type IMessageRendering } from \"./extensible_events.ts\";\n\n/**\n * Extensible topic event type based on MSC3765\n * https://github.com/matrix-org/matrix-spec-proposals/pull/3765\n *\n * @example\n * ```\n * {\n *      \"type\": \"m.room.topic,\n *      \"state_key\": \"\",\n *      \"content\": {\n *          \"topic\": \"All about **pizza**\",\n *          \"m.topic\": [{\n *              \"body\": \"All about **pizza**\",\n *              \"mimetype\": \"text/plain\",\n *          }, {\n *              \"body\": \"All about <b>pizza</b>\",\n *              \"mimetype\": \"text/html\",\n *          }],\n *      }\n * }\n * ```\n */\n\n/**\n * The event type for an m.topic event (in content)\n */\nexport const M_TOPIC = new NamespacedValue(\"m.topic\", null);\n\n/**\n * The event content for an m.topic event (in content)\n */\nexport type MTopicContent = { \"m.text\": IMessageRendering[] };\n\n/**\n * A previous incorrect form of m.topic used by matrix-js-sdk\n * TODO remove this after a few releases\n *      https://github.com/matrix-org/matrix-js-sdk/pull/4984#pullrequestreview-3174251065\n */\nexport type MalformedMTopicEvent = { \"m.topic\": IMessageRendering[] };\n\n/**\n * The event definition for an m.topic event (in content)\n */\nexport type MTopicEvent = { \"m.topic\": MTopicContent } | MalformedMTopicEvent;\n\n/**\n * The event content for an m.room.topic event\n */\nexport type MRoomTopicEventContent = {\n    topic: string | null | undefined;\n} & Partial<MTopicEvent>;\n","/*\nCopyright 2026 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { logger } from \"../logger.ts\";\n\n/**\n * Type guard to check the given value is a Record type with string keys.\n * @param value - the value to check\n */\nexport const isRecord = (value: unknown): value is Record<string, unknown> =>\n    !!value && typeof value === \"object\" && !Array.isArray(value);\n\n/**\n * Type guard to check the given record has a key and its value is a string\n * @param metadata - the record to check\n * @param key - the key to check\n */\nexport const hasRequiredStringProperty = <K extends string>(\n    metadata: Record<string, unknown>,\n    key: K,\n): metadata is Record<string, unknown> & Record<K, string> => {\n    if (!metadata[key] || !hasOptionalStringProperty(metadata, key)) {\n        logger.error(`Missing or invalid property: ${key}`);\n        return false;\n    }\n    return true;\n};\n\n/**\n * Type guard to check that if the given record has a key then its value is a string\n * @param metadata - the record to check\n * @param key - the key to check\n */\nexport const hasOptionalStringProperty = <K extends string>(\n    metadata: Record<string, unknown>,\n    key: K,\n): metadata is Record<string, unknown> & Record<K, string | undefined> => {\n    if (!!metadata[key] && typeof metadata[key] !== \"string\") {\n        logger.error(`Invalid property: ${key}`);\n        return false;\n    }\n    return true;\n};\n\n/**\n * Type guard to check the given record has a key and its value is a number\n * @param metadata - the record to check\n * @param key - the key to check\n */\nexport const hasRequiredNumberProperty = <K extends string>(\n    metadata: Record<string, unknown>,\n    key: K,\n): metadata is Record<string, unknown> & Record<K, number> => {\n    if (!metadata[key] || !hasOptionalNumberProperty(metadata, key)) {\n        logger.error(`Missing or invalid property: ${key}`);\n        return false;\n    }\n    return true;\n};\n\n/**\n * Type guard to check that if the given record has a key then its value is a number\n * @param metadata - the record to check\n * @param key - the key to check\n */\nexport const hasOptionalNumberProperty = <K extends string>(\n    metadata: Record<string, unknown>,\n    key: K,\n): metadata is Record<string, unknown> & Record<K, number | undefined> => {\n    if (!!metadata[key] && typeof metadata[key] !== \"number\") {\n        logger.error(`Invalid property: ${key}`);\n        return false;\n    }\n    return true;\n};\n\n/**\n * Type guard to check that if the given record has a key then its value is an array containing only strings\n * @param metadata - the record to check\n * @param key - the key to check\n */\nexport const optionalStringArrayProperty = <K extends string>(\n    metadata: Record<string, unknown>,\n    key: K,\n): metadata is Record<string, unknown> & Record<K, string[] | undefined> => {\n    if (\n        !!metadata[key] &&\n        (!Array.isArray(metadata[key]) || !(<unknown[]>metadata[key]).every((v) => typeof v === \"string\"))\n    ) {\n        logger.error(`Invalid property: ${key}`);\n        return false;\n    }\n    return true;\n};\n\n/**\n * Type guard to check the given record has a key and its value is an array of strings containing at least the given value\n * @param metadata - the record to check\n * @param key - the key to check\n * @param value - the value to require to be present in the array\n */\nexport const requiredArrayValue = <K extends string, V>(\n    metadata: Record<string, unknown>,\n    key: K,\n    value: V,\n): metadata is Record<string, unknown> & Record<K, V[]> => {\n    const array = metadata[key];\n    if (!array || !Array.isArray(array) || !array.includes(value)) {\n        logger.error(`Invalid property: ${key}. ${value} is required.`);\n        return false;\n    }\n    return true;\n};\n","/*\nCopyright 2018 New Vector Ltd\nCopyright 2019 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type IClientWellKnown, type IWellKnownConfig, type IServerVersions } from \"./client.ts\";\nimport { logger } from \"./logger.ts\";\nimport { type MatrixError, Method, timeoutSignal } from \"./http-api/index.ts\";\nimport { SUPPORTED_MATRIX_VERSIONS } from \"./version-support.ts\";\n\n// Dev note: Auto discovery is part of the spec.\n// See: https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery\n\nexport enum AutoDiscoveryAction {\n    SUCCESS = \"SUCCESS\",\n    IGNORE = \"IGNORE\",\n    PROMPT = \"PROMPT\",\n    FAIL_PROMPT = \"FAIL_PROMPT\",\n    FAIL_ERROR = \"FAIL_ERROR\",\n}\n\nexport enum AutoDiscoveryError {\n    Invalid = \"Invalid homeserver discovery response\",\n    GenericFailure = \"Failed to get autodiscovery configuration from server\",\n    InvalidHsBaseUrl = \"Invalid base_url for m.homeserver\",\n    InvalidHomeserver = \"Homeserver URL does not appear to be a valid Matrix homeserver\",\n    InvalidIsBaseUrl = \"Invalid base_url for m.identity_server\",\n    InvalidIdentityServer = \"Identity server URL does not appear to be a valid identity server\",\n    InvalidIs = \"Invalid identity server discovery response\",\n    MissingWellknown = \"No .well-known JSON file found\",\n    InvalidJson = \"Invalid JSON\",\n    UnsupportedHomeserverSpecVersion = \"The homeserver does not meet the version requirements\",\n\n    // TODO: Implement when Sydent supports the `/versions` endpoint - https://github.com/matrix-org/sydent/issues/424\n    //IdentityServerTooOld = \"The identity server does not meet the minimum version requirements\",\n}\n\ninterface AutoDiscoveryState {\n    state: AutoDiscoveryAction;\n    error?: IWellKnownConfig[\"error\"] | null;\n}\ninterface WellKnownConfig extends Omit<IWellKnownConfig, \"error\">, AutoDiscoveryState {}\n\nexport interface ClientConfig extends Omit<IClientWellKnown, \"m.homeserver\" | \"m.identity_server\"> {\n    \"m.homeserver\": WellKnownConfig;\n    \"m.identity_server\": WellKnownConfig;\n}\n\n/**\n * Utilities for automatically discovery resources, such as homeservers\n * for users to log in to.\n */\nexport class AutoDiscovery {\n    // Dev note: the constants defined here are related to but not\n    // exactly the same as those in the spec. This is to hopefully\n    // translate the meaning of the states in the spec, but also\n    // support our own if needed.\n\n    public static readonly ERROR_INVALID = AutoDiscoveryError.Invalid;\n\n    public static readonly ERROR_GENERIC_FAILURE = AutoDiscoveryError.GenericFailure;\n\n    public static readonly ERROR_INVALID_HS_BASE_URL = AutoDiscoveryError.InvalidHsBaseUrl;\n\n    public static readonly ERROR_INVALID_HOMESERVER = AutoDiscoveryError.InvalidHomeserver;\n\n    public static readonly ERROR_INVALID_IS_BASE_URL = AutoDiscoveryError.InvalidIsBaseUrl;\n\n    public static readonly ERROR_INVALID_IDENTITY_SERVER = AutoDiscoveryError.InvalidIdentityServer;\n\n    public static readonly ERROR_INVALID_IS = AutoDiscoveryError.InvalidIs;\n\n    public static readonly ERROR_MISSING_WELLKNOWN = AutoDiscoveryError.MissingWellknown;\n\n    public static readonly ERROR_INVALID_JSON = AutoDiscoveryError.InvalidJson;\n\n    public static readonly ERROR_UNSUPPORTED_HOMESERVER_SPEC_VERSION =\n        AutoDiscoveryError.UnsupportedHomeserverSpecVersion;\n\n    public static readonly ALL_ERRORS = Object.keys(AutoDiscoveryError) as AutoDiscoveryError[];\n\n    /**\n     * The auto discovery failed. The client is expected to communicate\n     * the error to the user and refuse logging in.\n     */\n    public static readonly FAIL_ERROR = AutoDiscoveryAction.FAIL_ERROR;\n\n    /**\n     * The auto discovery failed, however the client may still recover\n     * from the problem. The client is recommended to that the same\n     * action it would for PROMPT while also warning the user about\n     * what went wrong. The client may also treat this the same as\n     * a FAIL_ERROR state.\n     */\n    public static readonly FAIL_PROMPT = AutoDiscoveryAction.FAIL_PROMPT;\n\n    /**\n     * The auto discovery didn't fail but did not find anything of\n     * interest. The client is expected to prompt the user for more\n     * information, or fail if it prefers.\n     */\n    public static readonly PROMPT = AutoDiscoveryAction.PROMPT;\n\n    /**\n     * The auto discovery was successful.\n     */\n    public static readonly SUCCESS = AutoDiscoveryAction.SUCCESS;\n\n    /**\n     * Validates and verifies client configuration information for purposes\n     * of logging in. Such information includes the homeserver URL\n     * and identity server URL the client would want. Additional details\n     * may also be included, and will be transparently brought into the\n     * response object unaltered.\n     * @param wellknown - The configuration object itself, as returned\n     * by the .well-known auto-discovery endpoint.\n     * @returns Promise which resolves to the verified\n     * configuration, which may include error states. Rejects on unexpected\n     * failure, not when verification fails.\n     */\n    public static async fromDiscoveryConfig(wellknown?: IClientWellKnown): Promise<ClientConfig> {\n        // Step 1 is to get the config, which is provided to us here.\n\n        // We default to an error state to make the first few checks easier to\n        // write. We'll update the properties of this object over the duration\n        // of this function.\n        const clientConfig: ClientConfig = {\n            \"m.homeserver\": {\n                state: AutoDiscovery.FAIL_ERROR,\n                error: AutoDiscovery.ERROR_INVALID,\n                base_url: null,\n            },\n            \"m.identity_server\": {\n                // Technically, we don't have a problem with the identity server\n                // config at this point.\n                state: AutoDiscovery.PROMPT,\n                error: null,\n                base_url: null,\n            },\n        };\n\n        if (!wellknown?.[\"m.homeserver\"]) {\n            logger.error(\"No m.homeserver key in config\");\n\n            clientConfig[\"m.homeserver\"].state = AutoDiscovery.FAIL_PROMPT;\n            clientConfig[\"m.homeserver\"].error = AutoDiscovery.ERROR_INVALID;\n\n            return Promise.resolve(clientConfig);\n        }\n\n        if (!wellknown[\"m.homeserver\"][\"base_url\"]) {\n            logger.error(\"No m.homeserver base_url in config\");\n\n            clientConfig[\"m.homeserver\"].state = AutoDiscovery.FAIL_PROMPT;\n            clientConfig[\"m.homeserver\"].error = AutoDiscovery.ERROR_INVALID_HS_BASE_URL;\n\n            return Promise.resolve(clientConfig);\n        }\n\n        // Step 2: Make sure the homeserver URL is valid *looking*. We'll make\n        // sure it points to a homeserver in Step 3.\n        const hsUrl = this.sanitizeWellKnownUrl(wellknown[\"m.homeserver\"][\"base_url\"]);\n        if (!hsUrl) {\n            logger.error(\"Invalid base_url for m.homeserver\");\n            clientConfig[\"m.homeserver\"].error = AutoDiscovery.ERROR_INVALID_HS_BASE_URL;\n            return Promise.resolve(clientConfig);\n        }\n\n        // Step 3: Make sure the homeserver URL points to a homeserver.\n        const hsVersions = await this.fetchWellKnownObject<IServerVersions>(`${hsUrl}/_matrix/client/versions`);\n        if (!hsVersions || !Array.isArray(hsVersions.raw?.[\"versions\"])) {\n            logger.error(\"Invalid /versions response\");\n            clientConfig[\"m.homeserver\"].error = AutoDiscovery.ERROR_INVALID_HOMESERVER;\n\n            // Supply the base_url to the caller because they may be ignoring liveliness\n            // errors, like this one.\n            clientConfig[\"m.homeserver\"].base_url = hsUrl;\n\n            return Promise.resolve(clientConfig);\n        }\n\n        // Step 3.1: Non-spec check to ensure the server will actually work for us. We need to check if\n        // any of the versions in `SUPPORTED_MATRIX_VERSIONS` are listed in the /versions response.\n        const hsVersionSet = new Set(hsVersions.raw![\"versions\"]);\n        let supportedVersionFound = false;\n        for (const version of SUPPORTED_MATRIX_VERSIONS) {\n            if (hsVersionSet.has(version)) {\n                supportedVersionFound = true;\n                break;\n            }\n        }\n        if (!supportedVersionFound) {\n            logger.error(\"Homeserver does not meet version requirements\");\n            clientConfig[\"m.homeserver\"].error = AutoDiscovery.ERROR_UNSUPPORTED_HOMESERVER_SPEC_VERSION;\n\n            // Supply the base_url to the caller because they may be ignoring liveliness\n            // errors, like this one.\n            clientConfig[\"m.homeserver\"].base_url = hsUrl;\n\n            return Promise.resolve(clientConfig);\n        }\n\n        // Step 4: Now that the homeserver looks valid, update our client config.\n        clientConfig[\"m.homeserver\"] = {\n            state: AutoDiscovery.SUCCESS,\n            error: null,\n            base_url: hsUrl,\n        };\n\n        // Step 5: Try to pull out the identity server configuration\n        let isUrl: string | boolean = \"\";\n        if (wellknown[\"m.identity_server\"]) {\n            // We prepare a failing identity server response to save lines later\n            // in this branch.\n            const failingClientConfig: ClientConfig = {\n                \"m.homeserver\": clientConfig[\"m.homeserver\"],\n                \"m.identity_server\": {\n                    state: AutoDiscovery.FAIL_PROMPT,\n                    error: AutoDiscovery.ERROR_INVALID_IS,\n                    base_url: null,\n                },\n            };\n\n            // Step 5a: Make sure the URL is valid *looking*. We'll make sure it\n            // points to an identity server in Step 5b.\n            isUrl = this.sanitizeWellKnownUrl(wellknown[\"m.identity_server\"][\"base_url\"]);\n            if (!isUrl) {\n                logger.error(\"Invalid base_url for m.identity_server\");\n                failingClientConfig[\"m.identity_server\"].error = AutoDiscovery.ERROR_INVALID_IS_BASE_URL;\n                return Promise.resolve(failingClientConfig);\n            }\n\n            // Step 5b: Verify there is an identity server listening on the provided\n            // URL.\n            const isResponse = await this.fetchWellKnownObject(`${isUrl}/_matrix/identity/v2`);\n            if (!isResponse?.raw || isResponse.action !== AutoDiscoveryAction.SUCCESS) {\n                logger.error(\"Invalid /v2 response\");\n                failingClientConfig[\"m.identity_server\"].error = AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER;\n\n                // Supply the base_url to the caller because they may be ignoring\n                // liveliness errors, like this one.\n                failingClientConfig[\"m.identity_server\"].base_url = isUrl;\n\n                return Promise.resolve(failingClientConfig);\n            }\n        }\n\n        // Step 6: Now that the identity server is valid, or never existed,\n        // populate the IS section.\n        if (isUrl && isUrl.toString().length > 0) {\n            clientConfig[\"m.identity_server\"] = {\n                state: AutoDiscovery.SUCCESS,\n                error: null,\n                base_url: isUrl,\n            };\n        }\n\n        // Step 7: Copy any other keys directly into the clientConfig. This is for\n        // things like custom configuration of services.\n        Object.keys(wellknown).forEach((k: keyof IClientWellKnown) => {\n            if (k === \"m.homeserver\" || k === \"m.identity_server\") {\n                // Only copy selected parts of the config to avoid overwriting\n                // properties computed by the validation logic above.\n                const notProps = [\"error\", \"state\", \"base_url\"];\n                for (const prop of Object.keys(wellknown[k]!)) {\n                    if (notProps.includes(prop)) continue;\n                    type Prop = Exclude<keyof IWellKnownConfig, \"error\" | \"state\" | \"base_url\">;\n                    // @ts-ignore - ts gets unhappy as we're mixing types here\n                    clientConfig[k][prop as Prop] = wellknown[k]![prop as Prop];\n                }\n            } else {\n                // Just copy the whole thing over otherwise\n                clientConfig[k] = wellknown[k];\n            }\n        });\n\n        // Step 8: Give the config to the caller (finally)\n        return Promise.resolve(clientConfig);\n    }\n\n    /**\n     * Attempts to automatically discover client configuration information\n     * prior to logging in. Such information includes the homeserver URL\n     * and identity server URL the client would want. Additional details\n     * may also be discovered, and will be transparently included in the\n     * response object unaltered.\n     * @param domain - The homeserver domain to perform discovery\n     * on. For example, \"matrix.org\".\n     * @returns Promise which resolves to the discovered\n     * configuration, which may include error states. Rejects on unexpected\n     * failure, not when discovery fails.\n     */\n    public static async findClientConfig(domain: string): Promise<ClientConfig> {\n        if (!domain || typeof domain !== \"string\" || domain.length === 0) {\n            throw new Error(\"'domain' must be a string of non-zero length\");\n        }\n\n        // We use a .well-known lookup for all cases. According to the spec, we\n        // can do other discovery mechanisms if we want such as custom lookups\n        // however we won't bother with that here (mostly because the spec only\n        // supports .well-known right now).\n        //\n        // By using .well-known, we need to ensure we at least pull out a URL\n        // for the homeserver. We don't really need an identity server configuration\n        // but will return one anyways (with state PROMPT) to make development\n        // easier for clients. If we can't get a homeserver URL, all bets are\n        // off on the rest of the config and we'll assume it is invalid too.\n\n        // We default to an error state to make the first few checks easier to\n        // write. We'll update the properties of this object over the duration\n        // of this function.\n        const clientConfig: ClientConfig = {\n            \"m.homeserver\": {\n                state: AutoDiscovery.FAIL_ERROR,\n                error: AutoDiscovery.ERROR_INVALID,\n                base_url: null,\n            },\n            \"m.identity_server\": {\n                // Technically, we don't have a problem with the identity server\n                // config at this point.\n                state: AutoDiscovery.PROMPT,\n                error: null,\n                base_url: null,\n            },\n        };\n\n        // Step 1: Actually request the .well-known JSON file and make sure it\n        // at least has a homeserver definition.\n        const domainWithProtocol = domain.includes(\"://\") ? domain : `https://${domain}`;\n        const wellknown = await this.fetchWellKnownObject(`${domainWithProtocol}/.well-known/matrix/client`);\n        if (!wellknown || wellknown.action !== AutoDiscoveryAction.SUCCESS) {\n            logger.error(\"No response or error when parsing .well-known\");\n            if (wellknown.reason) logger.error(wellknown.reason);\n            if (wellknown.action === AutoDiscoveryAction.IGNORE) {\n                clientConfig[\"m.homeserver\"] = {\n                    state: AutoDiscovery.PROMPT,\n                    error: null,\n                    base_url: null,\n                };\n            } else {\n                // this can only ever be FAIL_PROMPT at this point.\n                clientConfig[\"m.homeserver\"].state = AutoDiscovery.FAIL_PROMPT;\n                clientConfig[\"m.homeserver\"].error = AutoDiscovery.ERROR_INVALID;\n            }\n            return Promise.resolve(clientConfig);\n        }\n\n        // Step 2: Validate and parse the config\n        return AutoDiscovery.fromDiscoveryConfig(wellknown.raw!);\n    }\n\n    /**\n     * Gets the raw discovery client configuration for the given domain name.\n     * Should only be used if there's no validation to be done on the resulting\n     * object, otherwise use findClientConfig().\n     * @param domain - The domain to get the client config for.\n     * @returns Promise which resolves to the domain's client config. Can\n     * be an empty object.\n     */\n    public static async getRawClientConfig(domain?: string): Promise<IClientWellKnown> {\n        if (!domain || typeof domain !== \"string\" || domain.length === 0) {\n            throw new Error(\"'domain' must be a string of non-zero length\");\n        }\n\n        const response = await this.fetchWellKnownObject(`https://${domain}/.well-known/matrix/client`);\n        if (!response) return {};\n        return response.raw ?? {};\n    }\n\n    /**\n     * Sanitizes a given URL to ensure it is either an HTTP or HTTP URL and\n     * is suitable for the requirements laid out by .well-known auto discovery.\n     * If valid, the URL will also be stripped of any trailing slashes.\n     * @param url - The potentially invalid URL to sanitize.\n     * @returns The sanitized URL or a falsey value if the URL is invalid.\n     * @internal\n     */\n    private static sanitizeWellKnownUrl(url?: string | null): string | false {\n        if (!url) return false;\n\n        try {\n            let parsed: URL | undefined;\n            try {\n                parsed = new URL(url);\n            } catch (e) {\n                logger.error(\"Could not parse url\", e);\n            }\n\n            if (!parsed?.hostname) return false;\n            if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") return false;\n\n            const port = parsed.port ? `:${parsed.port}` : \"\";\n            const path = parsed.pathname ? parsed.pathname : \"\";\n            let saferUrl = `${parsed.protocol}//${parsed.hostname}${port}${path}`;\n            if (saferUrl.endsWith(\"/\")) {\n                saferUrl = saferUrl.substring(0, saferUrl.length - 1);\n            }\n            return saferUrl;\n        } catch (e) {\n            logger.error(e);\n            return false;\n        }\n    }\n\n    private static fetch(resource: URL | string, options?: RequestInit): ReturnType<typeof globalThis.fetch> {\n        if (this.fetchFn) {\n            return this.fetchFn(resource, options);\n        }\n        return globalThis.fetch(resource, options);\n    }\n\n    private static fetchFn?: typeof globalThis.fetch;\n\n    public static setFetchFn(fetchFn: typeof globalThis.fetch): void {\n        AutoDiscovery.fetchFn = fetchFn;\n    }\n\n    /**\n     * Fetches a JSON object from a given URL, as expected by all .well-known\n     * related lookups. If the server gives a 404 then the `action` will be\n     * IGNORE. If the server returns something that isn't JSON, the `action`\n     * will be FAIL_PROMPT. For any other failure the `action` will be FAIL_PROMPT.\n     *\n     * The returned object will be a result of the call in object form with\n     * the following properties:\n     *   raw: The JSON object returned by the server.\n     *   action: One of SUCCESS, IGNORE, or FAIL_PROMPT.\n     *   reason: Relatively human-readable description of what went wrong.\n     *   error: The actual Error, if one exists.\n     * @param url - The URL to fetch a JSON object from.\n     * @returns Promise which resolves to the returned state.\n     * @internal\n     */\n    private static async fetchWellKnownObject<T = IWellKnownConfig>(\n        url: string,\n    ): Promise<IWellKnownConfig<Partial<T>>> {\n        let response: Response;\n\n        try {\n            response = await AutoDiscovery.fetch(url, {\n                method: Method.Get,\n                signal: timeoutSignal(5000),\n            });\n\n            if (response.status === 404) {\n                return {\n                    raw: {},\n                    action: AutoDiscoveryAction.IGNORE,\n                    reason: AutoDiscovery.ERROR_MISSING_WELLKNOWN,\n                };\n            }\n\n            if (response.status !== 200) {\n                return {\n                    raw: {},\n                    action: AutoDiscoveryAction.FAIL_PROMPT,\n                    reason: \"General failure\",\n                };\n            }\n        } catch (err) {\n            const error = err as AutoDiscoveryError | string | undefined;\n            let reason = \"\";\n            if (typeof error === \"object\") {\n                reason = (<Error>error)?.message;\n            }\n\n            return {\n                error,\n                raw: {},\n                action: AutoDiscoveryAction.FAIL_PROMPT,\n                reason: reason || \"General failure\",\n            };\n        }\n\n        try {\n            return {\n                raw: await response.json(),\n                action: AutoDiscoveryAction.SUCCESS,\n            };\n        } catch (err) {\n            const error = err as Error;\n            return {\n                error,\n                raw: {},\n                action: AutoDiscoveryAction.FAIL_PROMPT,\n                reason:\n                    (error as MatrixError)?.name === \"SyntaxError\"\n                        ? AutoDiscovery.ERROR_INVALID_JSON\n                        : AutoDiscovery.ERROR_INVALID,\n            };\n        }\n    }\n}\n","/*\nCopyright 2015-2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n/**\n * This is an internal module. See {@link MatrixClient} for the public class.\n */\n\nimport type { IDeviceKeys, IOneTimeKey } from \"./@types/crypto.ts\";\nimport { type ISyncStateData, type SetPresence, SyncApi, type SyncApiOptions, SyncState } from \"./sync.ts\";\nimport {\n    EventStatus,\n    type IContent,\n    type IDecryptOptions,\n    type IEvent,\n    MatrixEvent,\n    MatrixEventEvent,\n    type MatrixEventHandlerMap,\n    type PushDetails,\n} from \"./models/event.ts\";\nimport { StubStore } from \"./store/stub.ts\";\nimport {\n    type CallEvent,\n    type CallEventHandlerMap,\n    createNewMatrixCall,\n    type MatrixCall,\n    supportsMatrixCall,\n} from \"./webrtc/call.ts\";\nimport { Filter, type IFilterDefinition, type IRoomEventFilter } from \"./filter.ts\";\nimport {\n    CallEventHandler,\n    type CallEventHandlerEvent,\n    type CallEventHandlerEventHandlerMap,\n} from \"./webrtc/callEventHandler.ts\";\nimport {\n    GroupCallEventHandler,\n    type GroupCallEventHandlerEvent,\n    type GroupCallEventHandlerEventHandlerMap,\n} from \"./webrtc/groupCallEventHandler.ts\";\nimport * as utils from \"./utils.ts\";\nimport { deepCompare, noUnsafeEventProps, type QueryDict, replaceParam, safeSet, sleep } from \"./utils.ts\";\nimport { Direction, EventTimeline } from \"./models/event-timeline.ts\";\nimport { type IActionsObject, PushProcessor } from \"./pushprocessor.ts\";\nimport { AutoDiscovery, type AutoDiscoveryAction } from \"./autodiscovery.ts\";\nimport { encodeUnpaddedBase64Url } from \"./base64.ts\";\nimport { TypedReEmitter } from \"./ReEmitter.ts\";\nimport { logger, type Logger } from \"./logger.ts\";\nimport { SERVICE_TYPES } from \"./service-types.ts\";\nimport {\n    type Body,\n    ClientPrefix,\n    type FileType,\n    type HttpApiEvent,\n    type HttpApiEventHandlerMap,\n    type HTTPError,\n    IdentityPrefix,\n    type IHttpOpts,\n    type IRequestOpts,\n    MatrixError,\n    MatrixHttpApi,\n    MediaPrefix,\n    Method,\n    retryNetworkOperation,\n    type TokenRefreshFunction,\n    type Upload,\n    type UploadOpts,\n    type UploadResponse,\n} from \"./http-api/index.ts\";\nimport { type SyncUserProfile, User, UserEvent, type UserEventHandlerMap } from \"./models/user.ts\";\nimport { getHttpUriForMxc } from \"./content-repo.ts\";\nimport { SearchResult } from \"./models/search-result.ts\";\nimport { type IIdentityServerProvider } from \"./@types/IIdentityServerProvider.ts\";\nimport { type MatrixScheduler } from \"./scheduler.ts\";\nimport { type BeaconEvent, type BeaconEventHandlerMap } from \"./models/beacon.ts\";\nimport { type AuthDict } from \"./interactive-auth.ts\";\nimport {\n    type IMinimalEvent,\n    type IRoomEvent,\n    type IStateEvent,\n    type ReceivedToDeviceMessage,\n} from \"./sync-accumulator.ts\";\nimport type { EventTimelineSet } from \"./models/event-timeline-set.ts\";\nimport * as ContentHelpers from \"./content-helpers.ts\";\nimport {\n    NotificationCountType,\n    type Room,\n    type RoomEvent,\n    type RoomEventHandlerMap,\n    type RoomNameState,\n} from \"./models/room.ts\";\nimport { RoomMemberEvent, type RoomMemberEventHandlerMap } from \"./models/room-member.ts\";\nimport { RoomStateEvent, type IPowerLevelsContent, type RoomStateEventHandlerMap } from \"./models/room-state.ts\";\nimport {\n    isSendDelayedEventRequestOpts,\n    UpdateDelayedEventAction,\n    type DelayedEventInfo,\n    type IAddThreePidOnlyBody,\n    type IBindThreePidBody,\n    type IContextResponse,\n    type ICreateRoomOpts,\n    type IEventSearchOpts,\n    type IFilterResponse,\n    type IGuestAccessOpts,\n    type IJoinRoomOpts,\n    type INotificationsResponse,\n    type InviteOpts,\n    type IPaginateOpts,\n    type IPresenceOpts,\n    type IRedactOpts,\n    type IRelationsRequestOpts,\n    type IRelationsResponse,\n    type IRoomDirectoryOptions,\n    type ISearchOpts,\n    type ISendEventResponse,\n    type IStatusResponse,\n    type ITagsResponse,\n    type KnockRoomOpts,\n    type SendDelayedEventRequestOpts,\n    type SendDelayedEventResponse,\n} from \"./@types/requests.ts\";\nimport {\n    type AccountDataEvents,\n    EventType,\n    LOCAL_NOTIFICATION_SETTINGS_PREFIX,\n    MSC3912_RELATION_BASED_REDACTIONS_PROP,\n    MsgType,\n    PUSHER_ENABLED,\n    RelationType,\n    type RoomAccountDataEvents,\n    RoomCreateTypeField,\n    RoomType,\n    type StateEvents,\n    type TimelineEvents,\n    UNSTABLE_MSC3088_ENABLED,\n    UNSTABLE_MSC3088_PURPOSE,\n    UNSTABLE_MSC3089_TREE_SUBTYPE,\n    type WritableAccountDataEvents,\n} from \"./@types/event.ts\";\nimport {\n    GuestAccess,\n    HistoryVisibility,\n    type IdServerUnbindResult,\n    type JoinRule,\n    Preset,\n    type Terms,\n    type Visibility,\n} from \"./@types/partials.ts\";\nimport { type EventMapper, eventMapperFor, type MapperOpts } from \"./event-mapper.ts\";\nimport { secureRandomString } from \"./randomstring.ts\";\nimport { DEFAULT_TREE_POWER_LEVELS_TEMPLATE, MSC3089TreeSpace } from \"./models/MSC3089TreeSpace.ts\";\nimport { type ISignatures } from \"./@types/signed.ts\";\nimport { type IStore } from \"./store/index.ts\";\nimport {\n    type IEventWithRoomId,\n    type ISearchRequestBody,\n    type ISearchResponse,\n    type ISearchResults,\n    type IStateEventWithRoomId,\n    SearchOrderBy,\n} from \"./@types/search.ts\";\nimport { type ISynapseAdminDeactivateResponse, type ISynapseAdminWhoisResponse } from \"./@types/synapse.ts\";\nimport { type IHierarchyRoom } from \"./@types/spaces.ts\";\nimport {\n    type IPusher,\n    type IPusherRequest,\n    type IPushRule,\n    type IPushRules,\n    type PushRuleAction,\n    PushRuleActionName,\n    PushRuleKind,\n    type RuleId,\n} from \"./@types/PushRules.ts\";\nimport { type IThreepid } from \"./@types/threepids.ts\";\nimport { type CryptoStore } from \"./crypto/store/base.ts\";\nimport {\n    GroupCall,\n    type GroupCallIntent,\n    type GroupCallType,\n    type IGroupCallDataChannelOptions,\n} from \"./webrtc/groupCall.ts\";\nimport { MediaHandler } from \"./webrtc/mediaHandler.ts\";\nimport {\n    type ILoginFlowsResponse,\n    type IRefreshTokenResponse,\n    type LoginRequest,\n    type LoginResponse,\n    type LoginTokenPostResponse,\n    type SSOAction,\n} from \"./@types/auth.ts\";\nimport { TypedEventEmitter } from \"./models/typed-event-emitter.ts\";\nimport { MAIN_ROOM_TIMELINE, ReceiptType } from \"./@types/read_receipts.ts\";\nimport { type MSC3575SlidingSyncRequest, type MSC3575SlidingSyncResponse, type SlidingSync } from \"./sliding-sync.ts\";\nimport { SlidingSyncSdk } from \"./sliding-sync-sdk.ts\";\nimport {\n    determineFeatureSupport,\n    FeatureSupport,\n    Thread,\n    THREAD_RELATION_TYPE,\n    ThreadFilterType,\n    threadFilterTypeToFilter,\n} from \"./models/thread.ts\";\nimport { M_BEACON_INFO, type MBeaconInfoEventContent } from \"./@types/beacon.ts\";\nimport { NamespacedValue, UnstableValue } from \"./NamespacedValue.ts\";\nimport { ToDeviceMessageQueue } from \"./ToDeviceMessageQueue.ts\";\nimport { type ToDeviceBatch, type ToDevicePayload } from \"./models/ToDeviceMessage.ts\";\nimport { IgnoredInvites } from \"./models/invites-ignorer.ts\";\nimport { type UIARequest } from \"./@types/uia.ts\";\nimport { type LocalNotificationSettings } from \"./@types/local_notifications.ts\";\nimport { buildFeatureSupportMap, Feature, ServerSupport } from \"./feature.ts\";\nimport { type CryptoBackend } from \"./common-crypto/CryptoBackend.ts\";\nimport { RUST_SDK_STORE_PREFIX } from \"./rust-crypto/constants.ts\";\nimport {\n    type CrossSigningKeyInfo,\n    type CrossSigningKeys,\n    type CryptoApi,\n    type CryptoCallbacks,\n    CryptoEvent,\n    type CryptoEventHandlerMap,\n} from \"./crypto-api/index.ts\";\nimport {\n    type SecretStorageKeyDescription,\n    type ServerSideSecretStorage,\n    ServerSideSecretStorageImpl,\n} from \"./secret-storage.ts\";\nimport { type RegisterRequest, type RegisterResponse } from \"./@types/registration.ts\";\nimport { MatrixRTCSessionManager } from \"./matrixrtc/MatrixRTCSessionManager.ts\";\nimport { getRelationsThreadFilter } from \"./thread-utils.ts\";\nimport { KnownMembership, type Membership } from \"./@types/membership.ts\";\nimport { type RoomMessageEventContent, type StickerEventContent } from \"./@types/events.ts\";\nimport { type ImageInfo } from \"./@types/media.ts\";\nimport { type Capabilities, ServerCapabilities } from \"./serverCapabilities.ts\";\nimport { sha256 } from \"./digest.ts\";\nimport { type ValidatedAuthMetadata, OAuth2Error, isValidAuthMetadata } from \"./oauth/index.ts\";\nimport { type EmptyObject } from \"./@types/common.ts\";\nimport { UnsupportedDelayedEventsEndpointError, UnsupportedStickyEventsEndpointError } from \"./errors.ts\";\nimport { type Transport } from \"./matrixrtc/index.ts\";\nimport { RetentionPolicyService } from \"./retentionPolicy.ts\";\n\nexport type Store = IStore;\n\nexport type ResetTimelineCallback = (roomId: string) => boolean;\n\nconst SCROLLBACK_DELAY_MS = 3000;\n\nconst TURN_CHECK_INTERVAL = 10 * 60 * 1000; // poll for turn credentials every 10 minutes\n\nexport const UNSTABLE_MSC3852_LAST_SEEN_UA = new UnstableValue(\n    \"last_seen_user_agent\",\n    \"org.matrix.msc3852.last_seen_user_agent\",\n);\n\nexport interface IKeysUploadResponse {\n    one_time_key_counts: {\n        // eslint-disable-line camelcase\n        [algorithm: string]: number;\n    };\n}\n\nexport interface ICreateClientOpts {\n    baseUrl: string;\n\n    idBaseUrl?: string;\n\n    /**\n     * The data store used for sync data from the homeserver. If not specified,\n     * this client will not store any HTTP responses. The `createClient` helper\n     * will create a default store if needed.\n     */\n    store?: Store;\n\n    /**\n     * A store to be used for end-to-end crypto session data.\n     * The `createClient` helper will create a default store if needed. Calls the factory supplied to\n     * {@link setCryptoStoreFactory} if unspecified; or if no factory has been\n     * specified, uses a default implementation (indexeddb in the browser,\n     * in-memory otherwise).\n     *\n     * This is only used for the legacy crypto implementation,\n     * but if you use the rust crypto implementation ({@link MatrixClient#initRustCrypto}) and the device\n     * previously used legacy crypto (so must be migrated), then this must still be provided, so that the\n     * data can be migrated from the legacy store.\n     */\n    cryptoStore?: CryptoStore;\n\n    /**\n     * The scheduler to use. If not\n     * specified, this client will not retry requests on failure. This client\n     * will supply its own processing function to\n     * {@link MatrixScheduler#setProcessFunction}.\n     */\n    scheduler?: MatrixScheduler;\n\n    /**\n     * The function to invoke for HTTP requests.\n     * Most supported environments have a global `fetch` registered to which this will fall back.\n     */\n    fetchFn?: typeof globalThis.fetch;\n\n    userId?: string;\n\n    /**\n     * A unique identifier for this device; used for tracking things like crypto\n     * keys and access tokens. If not specified, end-to-end encryption will be\n     * disabled.\n     */\n    deviceId?: string;\n\n    accessToken?: string;\n    refreshToken?: string;\n\n    /**\n     * Function used to attempt refreshing access and refresh tokens\n     * Called by http-api when a possibly expired token is encountered\n     * and a refreshToken is found\n     */\n    tokenRefreshFunction?: TokenRefreshFunction;\n\n    /**\n     * Identity server provider to retrieve the user's access token when accessing\n     * the identity server. See also https://github.com/vector-im/element-web/issues/10615\n     * which seeks to replace the previous approach of manual access tokens params\n     * with this callback throughout the SDK.\n     */\n    identityServer?: IIdentityServerProvider;\n\n    /**\n     * The default maximum amount of\n     * time to wait before timing out HTTP requests. If not specified, there is no timeout.\n     */\n    localTimeoutMs?: number;\n\n    /**\n     * Set to false to send the access token to the server via a query parameter rather\n     * than the Authorization HTTP header.\n     *\n     * @deprecated as of v1.11 in https://spec.matrix.org/v1.17/client-server-api/#using-access-tokens\n     *\n     * Default true.\n     */\n    useAuthorizationHeader?: boolean;\n\n    /**\n     * Set to true to enable\n     * improved timeline support, see {@link MatrixClient#getEventTimeline}.\n     * It is disabled by default for compatibility with older clients - in particular to\n     * maintain support for back-paginating the live timeline after a '/sync'\n     * result with a gap.\n     */\n    timelineSupport?: boolean;\n\n    /**\n     * Extra query parameters to append\n     * to all requests with this client. Useful for application services which require\n     * `?user_id=`.\n     */\n    queryParams?: QueryDict;\n\n    /**\n     * Encryption key used for encrypting sensitive data (such as e2ee keys) in {@link ICreateClientOpts#cryptoStore}.\n     *\n     * This must be set to the same value every time the client is initialised for the same device.\n     *\n     * This is only used for the legacy crypto implementation,\n     * but if you use the rust crypto implementation ({@link MatrixClient#initRustCrypto}) and the device\n     * previously used legacy crypto (so must be migrated), then this must still be provided, so that the\n     * data can be migrated from the legacy store.\n     */\n    pickleKey?: string;\n\n    /**\n     * Verification methods we should offer to the other side when performing an interactive verification.\n     * If unset, we will offer all known methods. Currently these are: showing a QR code, scanning a QR code, and SAS\n     * (aka \"emojis\").\n     *\n     * See {@link types.VerificationMethod} for a set of useful constants for this parameter.\n     */\n    verificationMethods?: Array<string>;\n\n    /**\n     * Whether relaying calls through a TURN server should be forced. Default false.\n     */\n    forceTURN?: boolean;\n\n    /**\n     * Up to this many ICE candidates will be gathered when an incoming call arrives.\n     * Gathering does not send data to the caller, but will communicate with the configured TURN\n     * server. Default 0.\n     */\n    iceCandidatePoolSize?: number;\n\n    /**\n     * True to advertise support for call transfers to other parties on Matrix calls. Default false.\n     */\n    supportsCallTransfer?: boolean;\n\n    /**\n     * Whether to allow a fallback ICE server should be used for negotiating a\n     * WebRTC connection if the homeserver doesn't provide any servers. Defaults to false.\n     */\n    fallbackICEServerAllowed?: boolean;\n\n    /**\n     * If true, to-device signalling for group calls will be encrypted\n     * with Olm. Default: true.\n     */\n    useE2eForGroupCall?: boolean;\n\n    livekitServiceURL?: string;\n\n    /**\n     * Crypto callbacks provided by the application\n     */\n    cryptoCallbacks?: CryptoCallbacks;\n\n    /**\n     * Enable encrypted state events.\n     */\n    enableEncryptedStateEvents?: boolean;\n\n    /**\n     * Method to generate room names for empty rooms and rooms names based on membership.\n     * Defaults to a built-in English handler with basic pluralisation.\n     */\n    roomNameGenerator?: (roomId: string, state: RoomNameState) => string | null;\n\n    /**\n     * If true, participant can join group call without video and audio this has to be allowed. By default, a local\n     * media stream is needed to establish a group call.\n     * Default: false.\n     */\n    isVoipWithNoMediaAllowed?: boolean;\n\n    /**\n     * Disable VoIP support (prevents fetching TURN servers, etc.)\n     * Default: false (VoIP enabled)\n     */\n    disableVoip?: boolean;\n\n    /**\n     * If true, group calls will not establish media connectivity and only create the signaling events,\n     * so that livekit media can be used in the application layer (js-sdk contains no livekit code).\n     */\n    useLivekitForGroupCalls?: boolean;\n\n    /**\n     * A logger to associate with this MatrixClient.\n     * Defaults to the built-in global logger; see {@link DebugLogger} for an alternative.\n     */\n    logger?: Logger;\n\n    /**\n     * Locally remove events past the server global or per room retention setting.\n     */\n    unstableMSC1763Retention?: boolean;\n}\n\nexport interface IMatrixClientCreateOpts extends ICreateClientOpts {\n    /**\n     * Whether to allow sending messages to encrypted rooms when encryption\n     * is not available internally within this SDK. This is useful if you are using an external\n     * E2E proxy, for example. Defaults to false.\n     */\n    usingExternalCrypto?: boolean;\n}\n\nexport enum PendingEventOrdering {\n    Chronological = \"chronological\",\n    Detached = \"detached\",\n}\n\nexport interface IStartClientOpts {\n    /**\n     * The event `limit=` to apply to initial sync. Default: 8.\n     */\n    initialSyncLimit?: number;\n\n    /**\n     * True to put `archived=true</code> on the <code>/initialSync` request. Default: false.\n     */\n    includeArchivedRooms?: boolean;\n\n    /**\n     * True to do /profile requests on every invite event if the displayname/avatar_url is not known for this user ID. Default: false.\n     */\n    resolveInvitesToProfiles?: boolean;\n\n    /**\n     * Controls where pending messages appear in a room's timeline. If \"<b>chronological</b>\", messages will\n     * appear in the timeline when the call to `sendEvent` was made. If \"<b>detached</b>\",\n     * pending messages will appear in a separate list, accessible via {@link Room#getPendingEvents}.\n     * Default: \"chronological\".\n     */\n    pendingEventOrdering?: PendingEventOrdering;\n\n    /**\n     * The number of milliseconds to wait on /sync. Default: 30000 (30 seconds).\n     */\n    pollTimeout?: number;\n\n    /**\n     * The filter to apply to /sync calls.\n     */\n    filter?: Filter;\n\n    /**\n     * True to perform syncing without automatically updating presence.\n     */\n    disablePresence?: boolean;\n\n    /**\n     * True to not load all membership events during initial sync but fetch them when needed by calling\n     * `loadOutOfBandMembers` This will override the filter option at this moment.\n     */\n    lazyLoadMembers?: boolean;\n\n    /**\n     * The number of seconds between polls to /.well-known/matrix/client, undefined to disable.\n     * This should be in the order of hours. Default: undefined.\n     */\n    clientWellKnownPollPeriod?: number;\n\n    /**\n     * Will organises events in threaded conversations when\n     * a thread relation is encountered\n     */\n    threadSupport?: boolean;\n\n    /**\n     * @experimental\n     */\n    slidingSync?: SlidingSync;\n\n    /**\n     * Include user profiles in sync responses.\n     * Will only work if the server supports MSC4429.\n     */\n    unstableMSC4429SyncUserProfileFields?: string[];\n}\n\nexport interface IStoredClientOpts extends IStartClientOpts {}\n\nexport const GET_LOGIN_TOKEN_CAPABILITY = new NamespacedValue(\n    \"m.get_login_token\",\n    \"org.matrix.msc3882.get_login_token\",\n);\n\nexport const UNSTABLE_MSC2666_SHARED_ROOMS = \"uk.half-shot.msc2666\";\nexport const UNSTABLE_MSC2666_MUTUAL_ROOMS = \"uk.half-shot.msc2666.mutual_rooms\";\nexport const UNSTABLE_MSC2666_QUERY_MUTUAL_ROOMS = \"uk.half-shot.msc2666.query_mutual_rooms\";\n\nexport const UNSTABLE_MSC4140_DELAYED_EVENTS = \"org.matrix.msc4140\";\nexport const UNSTABLE_MSC4354_STICKY_EVENTS = \"org.matrix.msc4354\";\n\nexport const UNSTABLE_MSC4133_EXTENDED_PROFILES = \"uk.tcpip.msc4133\";\nexport const STABLE_MSC4133_EXTENDED_PROFILES = \"uk.tcpip.msc4133.stable\";\n\nexport type SendToDeviceContentMap = Map<string, Map<string, Record<string, any>>>;\n\nexport interface ISignedKey {\n    keys: Record<string, string>;\n    signatures: ISignatures;\n    user_id: string;\n    algorithms: string[];\n    device_id: string;\n}\n\n// re-export for backwards compatibility\nexport { type CrossSigningKeys };\n\nexport type KeySignatures = Record<string, Record<string, CrossSigningKeyInfo | ISignedKey>>;\nexport interface IUploadKeySignaturesResponse {\n    failures: Record<\n        string,\n        Record<\n            string,\n            {\n                errcode: string;\n                error: string;\n            }\n        >\n    >;\n}\n\nexport interface IPreviewUrlResponse {\n    [key: string]: undefined | string | number;\n    \"og:title\": string;\n    \"og:type\": string;\n    \"og:url\": string;\n    \"og:image\"?: string;\n    \"og:image:type\"?: string;\n    \"og:image:height\"?: number;\n    \"og:image:width\"?: number;\n    \"og:description\"?: string;\n    \"matrix:image:size\"?: number;\n}\n\nexport interface ITurnServerResponse {\n    uris: string[];\n    username: string;\n    password: string;\n    ttl: number;\n}\n\nexport interface ITurnServer {\n    urls: string[];\n    username: string;\n    credential: string;\n}\n\nexport interface IServerVersions {\n    versions: string[];\n    unstable_features: Record<string, boolean>;\n}\n\nexport interface IClientWellKnown {\n    [key: string]: any;\n    \"m.homeserver\"?: IWellKnownConfig;\n    \"m.identity_server\"?: IWellKnownConfig;\n}\n\nexport interface IWellKnownConfig<T = IClientWellKnown> {\n    raw?: T;\n    action?: AutoDiscoveryAction;\n    reason?: string;\n    error?: Error | string;\n    // eslint-disable-next-line\n    base_url?: string | null;\n    // XXX: this is undocumented\n    server_name?: string;\n}\n\ninterface IKeyBackupPath {\n    path: string;\n    queryData?: {\n        version: string;\n    };\n}\n\ninterface IMediaConfig {\n    [key: string]: any; // extensible\n    \"m.upload.size\"?: number;\n}\n\ninterface IThirdPartySigned {\n    sender: string;\n    mxid: string;\n    token: string;\n    signatures: ISignatures;\n}\n\ninterface IJoinRequestBody {\n    third_party_signed?: IThirdPartySigned;\n}\n\ninterface ITagMetadata {\n    [key: string]: any;\n    order?: number;\n}\n\ninterface IMessagesResponse {\n    start?: string;\n    end?: string;\n    chunk: IRoomEvent[];\n    state?: IStateEvent[];\n}\n\ninterface IThreadedMessagesResponse {\n    prev_batch: string;\n    next_batch: string;\n    chunk: IRoomEvent[];\n    state: IStateEvent[];\n}\n\nexport interface IRequestTokenResponse {\n    sid: string;\n    submit_url?: string;\n}\n\nexport interface IRequestMsisdnTokenResponse extends IRequestTokenResponse {\n    msisdn: string;\n    success: boolean;\n    intl_fmt: string;\n}\n\nexport interface IUploadKeysRequest {\n    \"device_keys\"?: Required<IDeviceKeys>;\n    \"one_time_keys\"?: Record<string, IOneTimeKey>;\n    \"org.matrix.msc2732.fallback_keys\"?: Record<string, IOneTimeKey>;\n}\n\nexport interface IQueryKeysRequest {\n    device_keys: { [userId: string]: string[] };\n    timeout?: number;\n    token?: string;\n}\n\nexport interface IClaimKeysRequest {\n    one_time_keys: { [userId: string]: { [deviceId: string]: string } };\n    timeout?: number;\n}\n\nexport interface IOpenIDToken {\n    access_token: string;\n    token_type: \"Bearer\" | string;\n    matrix_server_name: string;\n    expires_in: number;\n}\n\ninterface IRoomInitialSyncResponse {\n    room_id: string;\n    membership: Membership;\n    messages?: {\n        start?: string;\n        end?: string;\n        chunk: IEventWithRoomId[];\n    };\n    state?: IStateEventWithRoomId[];\n    visibility: Visibility;\n    account_data?: IMinimalEvent[];\n    presence: Partial<IEvent>; // legacy and undocumented, api is deprecated so this won't get attention\n}\n\ninterface IJoinedRoomsResponse {\n    joined_rooms: string[];\n}\n\ninterface IJoinedMembersResponse {\n    joined: {\n        [userId: string]: {\n            display_name: string;\n            avatar_url: string;\n        };\n    };\n}\n\n// Re-export for backwards compatibility\nexport type IRegisterRequestParams = RegisterRequest;\n\nexport interface IPublicRoomsChunkRoom {\n    room_id: string;\n    name?: string;\n    avatar_url?: string;\n    topic?: string;\n    canonical_alias?: string;\n    aliases?: string[];\n    world_readable: boolean;\n    guest_can_join: boolean;\n    num_joined_members: number;\n    room_type?: RoomType | string; // Added by MSC3827\n    join_rule?: JoinRule.Knock | JoinRule.Public; // Added by MSC2403\n}\n\ninterface IPublicRoomsResponse {\n    chunk: IPublicRoomsChunkRoom[];\n    next_batch?: string;\n    prev_batch?: string;\n    total_room_count_estimate?: number;\n}\n\ninterface IUserDirectoryResponse {\n    results: {\n        user_id: string;\n        display_name?: string;\n        avatar_url?: string;\n    }[];\n    limited: boolean;\n}\n\nexport interface IMyDevice {\n    \"device_id\": string;\n    \"display_name\"?: string;\n    \"last_seen_ip\"?: string;\n    \"last_seen_ts\"?: number;\n    // UNSTABLE_MSC3852_LAST_SEEN_UA\n    \"last_seen_user_agent\"?: string;\n    \"org.matrix.msc3852.last_seen_user_agent\"?: string;\n}\n\nexport interface Keys {\n    keys: { [keyId: string]: string };\n    usage: string[];\n    user_id: string;\n}\n\nexport interface SigningKeys extends Keys {\n    signatures: ISignatures;\n}\n\nexport interface DeviceKeys {\n    [deviceId: string]: IDeviceKeys & {\n        unsigned?: {\n            device_display_name: string;\n        };\n    };\n}\n\nexport interface IDownloadKeyResult {\n    failures: { [serverName: string]: object };\n    device_keys: { [userId: string]: DeviceKeys };\n    // the following three fields were added in 1.1\n    master_keys?: { [userId: string]: Keys };\n    self_signing_keys?: { [userId: string]: SigningKeys };\n    user_signing_keys?: { [userId: string]: SigningKeys };\n}\n\nexport interface IClaimOTKsResult {\n    failures: { [serverName: string]: object };\n    one_time_keys: {\n        [userId: string]: {\n            [deviceId: string]: {\n                [keyId: string]: {\n                    key: string;\n                    signatures: ISignatures;\n                };\n            };\n        };\n    };\n}\n\nexport interface IFieldType {\n    regexp: string;\n    placeholder: string;\n}\n\nexport interface IInstance {\n    desc: string;\n    icon?: string;\n    fields: object;\n    network_id: string;\n    // XXX: this is undocumented but we rely on it: https://github.com/matrix-org/matrix-doc/issues/3203\n    instance_id: string;\n}\n\nexport interface IProtocol {\n    user_fields: string[];\n    location_fields: string[];\n    icon: string;\n    field_types: Record<string, IFieldType>;\n    instances: IInstance[];\n}\n\ninterface IThirdPartyLocation {\n    alias: string;\n    protocol: string;\n    fields: object;\n}\n\ninterface IThirdPartyUser {\n    userid: string;\n    protocol: string;\n    fields: object;\n}\n\n/**\n * The summary of a room as defined by an initial version of MSC3266 and implemented in Synapse\n * Proposed at https://github.com/matrix-org/matrix-doc/pull/3266\n */\nexport interface RoomSummary extends Omit<IPublicRoomsChunkRoom, \"canonical_alias\" | \"aliases\"> {\n    /**\n     * The current membership of this user in the room.\n     * Usually \"leave\" if the room is fetched over federation.\n     */\n    \"membership\"?: Membership;\n    /**\n     * Version of the room.\n     */\n    \"im.nheko.summary.room_version\"?: string;\n    /**\n     * The encryption algorithm used for this room, if the room is encrypted.\n     */\n    \"im.nheko.summary.encryption\"?: string;\n}\n\ninterface IRoomHierarchy {\n    rooms: IHierarchyRoom[];\n    next_batch?: string;\n}\n\nexport interface TimestampToEventResponse {\n    event_id: string;\n    origin_server_ts: number;\n}\n\ninterface IWhoamiResponse {\n    user_id: string;\n    device_id?: string;\n    is_guest?: boolean;\n}\n/* eslint-enable camelcase */\n\n// We're using this constant for methods overloading and inspect whether a variable\n// contains an eventId or not. This was required to ensure backwards compatibility\n// of methods for threads\n// Probably not the most graceful solution but does a good enough job for now\nconst EVENT_ID_PREFIX = \"$\";\n\nexport enum ClientEvent {\n    /**\n     * Fires whenever the SDK's syncing state is updated. The state can be one of:\n     * <ul>\n     *\n     * <li>PREPARED: The client has synced with the server at least once and is\n     * ready for methods to be called on it. This will be immediately followed by\n     * a state of SYNCING. <i>This is the equivalent of \"syncComplete\" in the\n     * previous API.</i></li>\n     *\n     * <li>CATCHUP: The client has detected the connection to the server might be\n     * available again and will now try to do a sync again. As this sync might take\n     * a long time (depending how long ago was last synced, and general server\n     * performance) the client is put in this mode so the UI can reflect trying\n     * to catch up with the server after losing connection.</li>\n     *\n     * <li>SYNCING : The client is currently polling for new events from the server.\n     * This will be called <i>after</i> processing latest events from a sync.</li>\n     *\n     * <li>ERROR : The client has had a problem syncing with the server. If this is\n     * called <i>before</i> PREPARED then there was a problem performing the initial\n     * sync. If this is called <i>after</i> PREPARED then there was a problem polling\n     * the server for updates. This may be called multiple times even if the state is\n     * already ERROR. <i>This is the equivalent of \"syncError\" in the previous\n     * API.</i></li>\n     *\n     * <li>RECONNECTING: The sync connection has dropped, but not (yet) in a way that\n     * should be considered erroneous.\n     * </li>\n     *\n     * <li>STOPPED: The client has stopped syncing with server due to stopClient\n     * being called.\n     * </li>\n     * </ul>\n     * State transition diagram:\n     * ```\n     *                                          +---->STOPPED\n     *                                          |\n     *              +----->PREPARED -------> SYNCING <--+\n     *              |                        ^  |  ^    |\n     *              |      CATCHUP ----------+  |  |    |\n     *              |        ^                  V  |    |\n     *   null ------+        |  +------- RECONNECTING   |\n     *              |        V  V                       |\n     *              +------->ERROR ---------------------+\n     *\n     * NB: 'null' will never be emitted by this event.\n     *\n     * ```\n     * Transitions:\n     * <ul>\n     *\n     * <li>`null -> PREPARED` : Occurs when the initial sync is completed\n     * first time. This involves setting up filters and obtaining push rules.\n     *\n     * <li>`null -> ERROR` : Occurs when the initial sync failed first time.\n     *\n     * <li>`ERROR -> PREPARED` : Occurs when the initial sync succeeds\n     * after previously failing.\n     *\n     * <li>`PREPARED -> SYNCING` : Occurs immediately after transitioning\n     * to PREPARED. Starts listening for live updates rather than catching up.\n     *\n     * <li>`SYNCING -> RECONNECTING` : Occurs when the live update fails.\n     *\n     * <li>`RECONNECTING -> RECONNECTING` : Can occur if the update calls\n     * continue to fail, but the keepalive calls (to /versions) succeed.\n     *\n     * <li>`RECONNECTING -> ERROR` : Occurs when the keepalive call also fails\n     *\n     * <li>`ERROR -> SYNCING` : Occurs when the client has performed a\n     * live update after having previously failed.\n     *\n     * <li>`ERROR -> ERROR` : Occurs when the client has failed to keepalive\n     * for a second time or more.</li>\n     *\n     * <li>`SYNCING -> SYNCING` : Occurs when the client has performed a live\n     * update. This is called <i>after</i> processing.</li>\n     *\n     * <li>`* -> STOPPED` : Occurs once the client has stopped syncing or\n     * trying to sync after stopClient has been called.</li>\n     * </ul>\n     *\n     * The payloads consits of the following 3 parameters:\n     *\n     * - state - An enum representing the syncing state. One of \"PREPARED\",\n     * \"SYNCING\", \"ERROR\", \"STOPPED\".\n     *\n     * - prevState - An enum representing the previous syncing state.\n     * One of \"PREPARED\", \"SYNCING\", \"ERROR\", \"STOPPED\" <b>or null</b>.\n     *\n     * - data - Data about this transition.\n     *\n     * @example\n     * ```\n     * matrixClient.on(\"sync\", function(state, prevState, data) {\n     *   switch (state) {\n     *     case \"ERROR\":\n     *       // update UI to say \"Connection Lost\"\n     *       break;\n     *     case \"SYNCING\":\n     *       // update UI to remove any \"Connection Lost\" message\n     *       break;\n     *     case \"PREPARED\":\n     *       // the client instance is ready to be queried.\n     *       var rooms = matrixClient.getRooms();\n     *       break;\n     *   }\n     * });\n     * ```\n     */\n    Sync = \"sync\",\n    /**\n     * Fires whenever the SDK receives a new event.\n     * <p>\n     * This is only fired for live events received via /sync - it is not fired for\n     * events received over context, search, or pagination APIs.\n     *\n     * The payload is the matrix event which caused this event to fire.\n     * @example\n     * ```\n     * matrixClient.on(\"event\", function(event){\n     *   var sender = event.getSender();\n     * });\n     * ```\n     */\n    Event = \"event\",\n    /** @deprecated Use {@link ReceivedToDeviceMessage}.\n     * Fires whenever the SDK receives a new to-device event.\n     * The payload is the matrix event ({@link MatrixEvent}) which caused this event to fire.\n     * @example\n     * ```\n     * matrixClient.on(\"toDeviceEvent\", function(event){\n     *   var sender = event.getSender();\n     * });\n     * ```\n     */\n    ToDeviceEvent = \"toDeviceEvent\",\n    /**\n     * Fires whenever the SDK receives a new (potentially decrypted) to-device message.\n     * The payload is the to-device message and the encryption info for that message ({@link ReceivedToDeviceMessage}).\n     * @example\n     * ```\n     * matrixClient.on(\"receivedToDeviceMessage\", function(payload){\n     *   const { message, encryptionInfo } = payload;\n     *   var claimed_sender = encryptionInfo ? encryptionInfo.sender : message.sender;\n     *   var isVerified = encryptionInfo ? encryptionInfo.verified : false;\n     *   var type = message.type;\n     * });\n     */\n    ReceivedToDeviceMessage = \"receivedToDeviceMessage\",\n    /**\n     * Fires whenever new user-scoped account_data is added.\n     * The payload is a pair of event ({@link MatrixEvent}) describing the account_data just added, and the previous event, if known:\n     *  - event: The event describing the account_data just added\n     *  - oldEvent: The previous account data, if known.\n     * @example\n     * ```\n     * matrixClient.on(\"accountData\", function(event, oldEvent){\n     *   myAccountData[event.type] = event.content;\n     * });\n     * ```\n     */\n    AccountData = \"accountData\",\n    /**\n     * Fires whenever a new Room is added. This will fire when you are invited to a\n     * room, as well as when you join a room. <strong>This event is experimental and\n     * may change.</strong>\n     *\n     * The payload is the newly created room, fully populated.\n     * @example\n     * ```\n     * matrixClient.on(\"Room\", function(room){\n     *   var roomId = room.roomId;\n     * });\n     * ```\n     */\n    Room = \"Room\",\n    /**\n     * Fires whenever a Room is removed. This will fire when you forget a room.\n     * <strong>This event is experimental and may change.</strong>\n     * The payload is the roomId of the deleted room.\n     * @example\n     * ```\n     * matrixClient.on(\"deleteRoom\", function(roomId){\n     *   // update UI from getRooms()\n     * });\n     * ```\n     */\n    DeleteRoom = \"deleteRoom\",\n    SyncUnexpectedError = \"sync.unexpectedError\",\n    /**\n     * Fires when the client .well-known info is fetched.\n     * The payload is the JSON object (see {@link IClientWellKnown}) returned by the server\n     */\n    ClientWellKnown = \"WellKnown.client\",\n    ReceivedVoipEvent = \"received_voip_event\",\n    TurnServers = \"turnServers\",\n    TurnServersError = \"turnServers.error\",\n    UserProfileUpdate = \"userProfileUpdate\",\n}\n\ntype RoomEvents =\n    | RoomEvent.Name\n    | RoomEvent.Redaction\n    | RoomEvent.RedactionCancelled\n    | RoomEvent.Receipt\n    | RoomEvent.Tags\n    | RoomEvent.LocalEchoUpdated\n    | RoomEvent.HistoryImportedWithinTimeline\n    | RoomEvent.AccountData\n    | RoomEvent.MyMembership\n    | RoomEvent.Timeline\n    | RoomEvent.TimelineReset;\n\ntype RoomStateEvents =\n    | RoomStateEvent.Events\n    | RoomStateEvent.Members\n    | RoomStateEvent.NewMember\n    | RoomStateEvent.Update\n    | RoomStateEvent.Marker;\n\ntype CryptoEvents = (typeof CryptoEvent)[keyof typeof CryptoEvent];\n\ntype MatrixEventEvents = MatrixEventEvent.Decrypted | MatrixEventEvent.Replaced | MatrixEventEvent.VisibilityChange;\n\ntype RoomMemberEvents =\n    | RoomMemberEvent.Name\n    | RoomMemberEvent.Typing\n    | RoomMemberEvent.PowerLevel\n    | RoomMemberEvent.Membership;\n\ntype UserEvents =\n    | UserEvent.AvatarUrl\n    | UserEvent.DisplayName\n    | UserEvent.Presence\n    | UserEvent.CurrentlyActive\n    | UserEvent.LastPresenceTs;\n\nexport type EmittedEvents =\n    | ClientEvent\n    | RoomEvents\n    | RoomStateEvents\n    | CryptoEvents\n    | MatrixEventEvents\n    | RoomMemberEvents\n    | UserEvents\n    | CallEvent // re-emitted by call.ts using Object.values\n    | CallEventHandlerEvent.Incoming\n    | GroupCallEventHandlerEvent.Incoming\n    | GroupCallEventHandlerEvent.Outgoing\n    | GroupCallEventHandlerEvent.Ended\n    | GroupCallEventHandlerEvent.Participants\n    | HttpApiEvent.SessionLoggedOut\n    | HttpApiEvent.NoConsent\n    | BeaconEvent;\n\nexport type ClientEventHandlerMap = {\n    [ClientEvent.Sync]: (state: SyncState, prevState: SyncState | null, data?: ISyncStateData) => void;\n    [ClientEvent.Event]: (event: MatrixEvent) => void;\n    [ClientEvent.ToDeviceEvent]: (event: MatrixEvent) => void;\n    [ClientEvent.ReceivedToDeviceMessage]: (payload: ReceivedToDeviceMessage) => void;\n    [ClientEvent.AccountData]: (event: MatrixEvent, lastEvent?: MatrixEvent) => void;\n    [ClientEvent.Room]: (room: Room) => void;\n    [ClientEvent.DeleteRoom]: (roomId: string) => void;\n    [ClientEvent.SyncUnexpectedError]: (error: Error) => void;\n    [ClientEvent.ClientWellKnown]: (data: IClientWellKnown) => void;\n    [ClientEvent.ReceivedVoipEvent]: (event: MatrixEvent) => void;\n    [ClientEvent.TurnServers]: (servers: ITurnServer[]) => void;\n    [ClientEvent.TurnServersError]: (error: Error, fatal: boolean) => void;\n    /**\n     *\n     * @param userId - the user ID of the profile which was updated\n     * @param profile - the updated profile information\n     */\n    [ClientEvent.UserProfileUpdate]: (userId: string, profile: Record<string, unknown> | null) => void;\n} & RoomEventHandlerMap &\n    RoomStateEventHandlerMap &\n    CryptoEventHandlerMap &\n    MatrixEventHandlerMap &\n    RoomMemberEventHandlerMap &\n    UserEventHandlerMap &\n    CallEventHandlerEventHandlerMap &\n    GroupCallEventHandlerEventHandlerMap &\n    CallEventHandlerMap &\n    HttpApiEventHandlerMap &\n    BeaconEventHandlerMap;\n\nconst SSO_ACTION_PARAM = new UnstableValue(\"action\", \"org.matrix.msc3824.action\");\n\n/**\n * Represents a Matrix Client. Only directly construct this if you want to use\n * custom modules. Normally, {@link createClient} should be used\n * as it specifies 'sensible' defaults for these modules.\n */\nexport class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHandlerMap> {\n    public static readonly RESTORE_BACKUP_ERROR_BAD_KEY = \"RESTORE_BACKUP_ERROR_BAD_KEY\";\n\n    private readonly logger: Logger;\n\n    public reEmitter = new TypedReEmitter<EmittedEvents, ClientEventHandlerMap>(this);\n    public olmVersion: [number, number, number] | null = null; // populated after initLegacyCrypto\n    public usingExternalCrypto = false;\n    private _store!: Store;\n    public deviceId: string | null;\n    public credentials: { userId: string | null };\n\n    /**\n     * Encryption key used for encrypting sensitive data (such as e2ee keys) in storage.\n     *\n     * As supplied in the constructor via {@link IMatrixClientCreateOpts#pickleKey}.\n     * Used for migration from the legacy crypto to the rust crypto\n     */\n    private readonly legacyPickleKey?: string;\n\n    public scheduler?: MatrixScheduler;\n    public clientRunning = false;\n    public timelineSupport = false;\n    public urlPreviewCache: { [key: string]: Promise<IPreviewUrlResponse> } = {};\n    public identityServer?: IIdentityServerProvider;\n    public http: MatrixHttpApi<IHttpOpts & { onlyData: true }>; // XXX: Intended private, used in code.\n\n    private cryptoBackend?: CryptoBackend; // one of crypto or rustCrypto\n\n    /**\n     * Support MSC4362: Simplified Encrypted State Events.\n     *\n     * The client must be recreated for changes to this setting to take effect\n     * reliably.\n     *\n     * When this setting is true, if we find a state event that is encrypted\n     * (within a room that supports encrypted state), we will attempt to decrypt\n     * it as specified in MSC4362. If the user was in the room at the time an\n     * encrypted state event was received (meaning we have the key), even if\n     * this setting was set to false at the time it was received, recreating the\n     * client with this setting set to true will allow decrypting that event.\n     *\n     * When this setting is false, any state event that is encrypted will not be\n     * decrypted, meaning it will have no effect. This matched the behaviour of\n     * a client that does not support MSC4362.\n     */\n    public enableEncryptedStateEvents: boolean;\n\n    public cryptoCallbacks: CryptoCallbacks; // XXX: Intended private, used in code.\n    public callEventHandler?: CallEventHandler; // XXX: Intended private, used in code.\n    public groupCallEventHandler?: GroupCallEventHandler;\n    public supportsCallTransfer = false; // XXX: Intended private, used in code.\n    public forceTURN = false; // XXX: Intended private, used in code.\n    public iceCandidatePoolSize = 0; // XXX: Intended private, used in code.\n    public idBaseUrl?: string;\n    public baseUrl: string;\n    public readonly isVoipWithNoMediaAllowed;\n    public disableVoip: boolean;\n\n    public useLivekitForGroupCalls: boolean;\n\n    // Note: these are all `protected` to let downstream consumers make mistakes if they want to.\n    // We don't technically support this usage, but have reasons to do this.\n\n    protected canSupportVoip = false;\n    protected peekSync: SyncApi | null = null;\n    protected isGuestAccount = false;\n    protected ongoingScrollbacks: { [roomId: string]: { promise?: Promise<Room>; errorTs?: number } } = {};\n    protected notifTimelineSet: EventTimelineSet | null = null;\n\n    /**\n     * Legacy crypto store used for migration from the legacy crypto to the rust crypto\n     * @private\n     */\n    private readonly legacyCryptoStore?: CryptoStore;\n    protected verificationMethods?: string[];\n    protected fallbackICEServerAllowed = false;\n    protected syncApi?: SlidingSyncSdk | SyncApi;\n    public roomNameGenerator?: ICreateClientOpts[\"roomNameGenerator\"];\n    public pushRules?: IPushRules;\n    protected syncLeftRoomsPromise?: Promise<Room[]>;\n    protected syncedLeftRooms = false;\n    protected clientOpts?: IStoredClientOpts;\n    protected clientWellKnownIntervalID?: ReturnType<typeof setInterval>;\n    protected canResetTimelineCallback?: ResetTimelineCallback;\n\n    public canSupport = new Map<Feature, ServerSupport>();\n\n    // The pushprocessor caches useful things, so keep one and re-use it\n    public readonly pushProcessor = new PushProcessor(this);\n\n    // Promise to a response of the server's /versions response\n    // TODO: This should expire: https://github.com/matrix-org/matrix-js-sdk/issues/1020\n    protected serverVersionsPromise?: Promise<IServerVersions>;\n\n    protected clientWellKnown?: IClientWellKnown;\n    protected clientWellKnownPromise?: Promise<IClientWellKnown>;\n    protected turnServers: ITurnServer[] = [];\n    protected turnServersExpiry = 0;\n    protected checkTurnServersIntervalID?: ReturnType<typeof setInterval>;\n    protected txnCtr = 0;\n    protected mediaHandler = new MediaHandler(this);\n    protected sessionId: string;\n\n    /** IDs of events which are currently being encrypted.\n     *\n     * This is part of the cancellation mechanism: if the event is no longer listed here when encryption completes,\n     * that tells us that it has been cancelled, and we should not send it.\n     */\n    private eventsBeingEncrypted = new Set<string>();\n\n    private useE2eForGroupCall = true;\n    private toDeviceMessageQueue: ToDeviceMessageQueue;\n    public livekitServiceURL?: string;\n\n    private _secretStorage: ServerSideSecretStorageImpl;\n\n    // A manager for determining which invites should be ignored.\n    public readonly ignoredInvites: IgnoredInvites;\n\n    public readonly matrixRTC: MatrixRTCSessionManager;\n\n    private serverCapabilitiesService: ServerCapabilities;\n    public readonly retentionPolicyService: RetentionPolicyService;\n    // eslint-disable-next-line\n    public readonly _unstable_shouldApplyMessageRetention: boolean;\n\n    public constructor(opts: IMatrixClientCreateOpts) {\n        super();\n\n        // If a custom logger is provided, use it. Otherwise, default to the global\n        // one in logger.ts.\n        this.logger = opts.logger ?? logger;\n\n        opts.baseUrl = utils.ensureNoTrailingSlash(opts.baseUrl);\n        opts.idBaseUrl = utils.ensureNoTrailingSlash(opts.idBaseUrl);\n\n        this.baseUrl = opts.baseUrl;\n        this.idBaseUrl = opts.idBaseUrl;\n        this.identityServer = opts.identityServer;\n\n        this.usingExternalCrypto = opts.usingExternalCrypto ?? false;\n        this.store = opts.store || new StubStore();\n        this.deviceId = opts.deviceId || null;\n        this.sessionId = secureRandomString(10);\n\n        const userId = opts.userId || null;\n        this.credentials = { userId };\n\n        this.http = new MatrixHttpApi(this as ConstructorParameters<typeof MatrixHttpApi>[0], {\n            fetchFn: opts.fetchFn,\n            baseUrl: opts.baseUrl,\n            idBaseUrl: opts.idBaseUrl,\n            accessToken: opts.accessToken,\n            refreshToken: opts.refreshToken,\n            tokenRefreshFunction: opts.tokenRefreshFunction,\n            prefix: ClientPrefix.V3,\n            onlyData: true,\n            extraParams: opts.queryParams,\n            localTimeoutMs: opts.localTimeoutMs,\n            useAuthorizationHeader: opts.useAuthorizationHeader,\n            logger: this.logger,\n        });\n\n        if (opts.pickleKey) {\n            this.legacyPickleKey = opts.pickleKey;\n        }\n\n        this.useLivekitForGroupCalls = Boolean(opts.useLivekitForGroupCalls);\n\n        this.scheduler = opts.scheduler;\n        if (this.scheduler) {\n            this.scheduler.setProcessFunction(async (eventToSend: MatrixEvent) => {\n                const room = this.getRoom(eventToSend.getRoomId());\n                if (eventToSend.status !== EventStatus.SENDING) {\n                    this.updatePendingEventStatus(room, eventToSend, EventStatus.SENDING);\n                }\n                const res = await this.sendEventHttpRequest(eventToSend);\n                if (room) {\n                    // ensure we update pending event before the next scheduler run so that any listeners to event id\n                    // updates on the synchronous event emitter get a chance to run first.\n                    room.updatePendingEvent(eventToSend, EventStatus.SENT, res.event_id);\n                }\n                return res;\n            });\n        }\n\n        this.disableVoip = opts.disableVoip ?? false;\n\n        if (!this.disableVoip && supportsMatrixCall()) {\n            this.callEventHandler = new CallEventHandler(this);\n            this.groupCallEventHandler = new GroupCallEventHandler(this);\n            this.canSupportVoip = true;\n            // Start listening for calls after the initial sync is done\n            // We do not need to backfill the call event buffer\n            // with encrypted events that might never get decrypted\n            this.on(ClientEvent.Sync, this.startCallEventHandler);\n        }\n\n        // NB. We initialise MatrixRTC whether we have call support or not: this is just\n        // the underlying session management and doesn't use any actual media capabilities\n        this.matrixRTC = new MatrixRTCSessionManager(this.logger, this);\n\n        this.serverCapabilitiesService = new ServerCapabilities(this.logger, this.http);\n        this.retentionPolicyService = new RetentionPolicyService(this.logger, this.http);\n        this._unstable_shouldApplyMessageRetention = opts.unstableMSC1763Retention ?? false;\n\n        this.on(ClientEvent.Sync, this.fixupRoomNotifications);\n\n        this.timelineSupport = Boolean(opts.timelineSupport);\n\n        this.legacyCryptoStore = opts.cryptoStore;\n        this.verificationMethods = opts.verificationMethods;\n        this.cryptoCallbacks = opts.cryptoCallbacks || {};\n        this.enableEncryptedStateEvents = opts.enableEncryptedStateEvents ?? false;\n\n        this.forceTURN = opts.forceTURN || false;\n        this.iceCandidatePoolSize = opts.iceCandidatePoolSize === undefined ? 0 : opts.iceCandidatePoolSize;\n        this.supportsCallTransfer = opts.supportsCallTransfer || false;\n        this.fallbackICEServerAllowed = opts.fallbackICEServerAllowed || false;\n        this.isVoipWithNoMediaAllowed = opts.isVoipWithNoMediaAllowed || false;\n\n        if (opts.useE2eForGroupCall !== undefined) this.useE2eForGroupCall = opts.useE2eForGroupCall;\n\n        this.livekitServiceURL = opts.livekitServiceURL;\n\n        this.roomNameGenerator = opts.roomNameGenerator;\n\n        this.toDeviceMessageQueue = new ToDeviceMessageQueue(this, this.logger);\n\n        // The SDK doesn't really provide a clean way for events to recalculate the push\n        // actions for themselves, so we have to kinda help them out when they are encrypted.\n        // We do this so that push rules are correctly executed on events in their decrypted\n        // state, such as highlights when the user's name is mentioned.\n        this.on(MatrixEventEvent.Decrypted, (event) => {\n            fixNotificationCountOnDecryption(this, event);\n        });\n\n        this.ignoredInvites = new IgnoredInvites(this);\n        this._secretStorage = new ServerSideSecretStorageImpl(this, opts.cryptoCallbacks ?? {});\n\n        // having lots of event listeners is not unusual. 0 means \"unlimited\".\n        this.setMaxListeners(0);\n    }\n\n    public set store(newStore: Store) {\n        this._store = newStore;\n        this._store.setUserCreator((userId) => User.createUser(userId, this));\n    }\n\n    public get store(): Store {\n        return this._store;\n    }\n\n    /**\n     * High level helper method to begin syncing and poll for new events. To listen for these\n     * events, add a listener for {@link ClientEvent.Event}\n     * via {@link MatrixClient#on}. Alternatively, listen for specific\n     * state change events.\n     * @param opts - Options to apply when syncing.\n     */\n    public async startClient(opts?: IStartClientOpts): Promise<void> {\n        if (this.clientRunning) {\n            // client is already running.\n            return;\n        }\n        this.clientRunning = true;\n\n        this.on(ClientEvent.Sync, this.startMatrixRTC);\n\n        // Create our own user object artificially (instead of waiting for sync)\n        // so it's always available, even if the user is not in any rooms etc.\n        const userId = this.getUserId();\n        if (userId) {\n            this.store.storeUser(new User(userId));\n        }\n\n        // periodically poll for turn servers if we support voip\n        if (this.supportsVoip()) {\n            this.checkTurnServersIntervalID = setInterval(() => {\n                void this.checkTurnServers();\n            }, TURN_CHECK_INTERVAL);\n            void this.checkTurnServers();\n        }\n\n        if (this.syncApi) {\n            // This shouldn't happen since we thought the client was not running\n            this.logger.error(\"Still have sync object whilst not running: stopping old one\");\n            this.syncApi.stop();\n        }\n\n        try {\n            await this.getVersions();\n\n            // This should be done with `canSupport`\n            // TODO: https://github.com/vector-im/element-web/issues/23643\n            const { threads, list, fwdPagination } = await this.doesServerSupportThread();\n            Thread.setServerSideSupport(threads);\n            Thread.setServerSideListSupport(list);\n            Thread.setServerSideFwdPaginationSupport(fwdPagination);\n        } catch (e) {\n            this.logger.error(\n                \"Can't fetch server versions, continuing to initialise sync, this will be retried later\",\n                e,\n            );\n        }\n\n        this.clientOpts = opts ?? {};\n        if (this.clientOpts.slidingSync) {\n            this.syncApi = new SlidingSyncSdk(\n                this.clientOpts.slidingSync,\n                this,\n                this.clientOpts,\n                this.buildSyncApiOptions(),\n            );\n        } else {\n            this.syncApi = new SyncApi(this, this.clientOpts, this.buildSyncApiOptions());\n        }\n\n        this.syncApi.sync().catch((e) => this.logger.info(\"Sync startup aborted with an error:\", e));\n\n        if (this.clientOpts.clientWellKnownPollPeriod !== undefined) {\n            this.clientWellKnownIntervalID = setInterval(() => {\n                void this.fetchClientWellKnown();\n            }, 1000 * this.clientOpts.clientWellKnownPollPeriod);\n            void this.fetchClientWellKnown();\n        }\n\n        this.toDeviceMessageQueue.start();\n        this.serverCapabilitiesService.start();\n        if (this._unstable_shouldApplyMessageRetention) {\n            this.retentionPolicyService?.start();\n        }\n    }\n\n    /**\n     * Construct a SyncApiOptions for this client, suitable for passing into the SyncApi constructor\n     */\n    protected buildSyncApiOptions(): SyncApiOptions {\n        return {\n            cryptoCallbacks: this.cryptoBackend,\n            canResetEntireTimeline: (roomId: string): boolean => {\n                if (!this.canResetTimelineCallback) {\n                    return false;\n                }\n                return this.canResetTimelineCallback(roomId);\n            },\n            logger: this.logger.getChild(\"sync\"),\n        };\n    }\n\n    /**\n     * High level helper method to stop the client from polling and allow a\n     * clean shutdown.\n     */\n    public stopClient(): void {\n        this.cryptoBackend?.stop(); // crypto might have been initialised even if the client wasn't fully started\n\n        this.off(ClientEvent.Sync, this.startMatrixRTC);\n\n        if (!this.clientRunning) return; // already stopped\n\n        this.logger.debug(\"stopping MatrixClient\");\n\n        this.clientRunning = false;\n\n        this.syncApi?.stop();\n        this.syncApi = undefined;\n\n        this.peekSync?.stopPeeking();\n\n        this.callEventHandler?.stop();\n        this.groupCallEventHandler?.stop();\n        this.callEventHandler = undefined;\n        this.groupCallEventHandler = undefined;\n\n        globalThis.clearInterval(this.checkTurnServersIntervalID);\n        this.checkTurnServersIntervalID = undefined;\n\n        if (this.clientWellKnownIntervalID !== undefined) {\n            globalThis.clearInterval(this.clientWellKnownIntervalID);\n        }\n\n        this.toDeviceMessageQueue.stop();\n\n        this.matrixRTC.stop();\n\n        this.serverCapabilitiesService.stop();\n    }\n\n    /**\n     * Clear any data out of the persistent stores used by the client.\n     *\n     * @param args.cryptoDatabasePrefix - The database name to use for indexeddb, defaults to 'matrix-js-sdk'.\n     * @returns Promise which resolves when the stores have been cleared.\n     */\n    public clearStores(\n        args: {\n            cryptoDatabasePrefix?: string;\n        } = {},\n    ): Promise<void> {\n        if (this.clientRunning) {\n            throw new Error(\"Cannot clear stores while client is running\");\n        }\n\n        const promises: Promise<void>[] = [];\n\n        promises.push(this.store.deleteAllData());\n        if (this.legacyCryptoStore) {\n            promises.push(this.legacyCryptoStore.deleteAllData());\n        }\n\n        // delete the stores used by the rust matrix-sdk-crypto, in case they were used\n        const deleteRustSdkStore = async (): Promise<void> => {\n            let indexedDB: IDBFactory;\n            try {\n                indexedDB = globalThis.indexedDB;\n                if (!indexedDB) return; // No indexedDB support\n            } catch {\n                // No indexedDB support\n                return;\n            }\n            for (const dbname of [\n                `${args.cryptoDatabasePrefix ?? RUST_SDK_STORE_PREFIX}::matrix-sdk-crypto`,\n                `${args.cryptoDatabasePrefix ?? RUST_SDK_STORE_PREFIX}::matrix-sdk-crypto-meta`,\n            ]) {\n                const prom = new Promise((resolve, reject) => {\n                    this.logger.info(`Removing IndexedDB instance ${dbname}`);\n                    const req = indexedDB.deleteDatabase(dbname);\n                    req.onsuccess = (_): void => {\n                        this.logger.info(`Removed IndexedDB instance ${dbname}`);\n                        resolve(0);\n                    };\n                    req.onerror = (e): void => {\n                        // In private browsing, Firefox has a globalThis.indexedDB, but attempts to delete an indexeddb\n                        // (even a non-existent one) fail with \"DOMException: A mutation operation was attempted on a\n                        // database that did not allow mutations.\"\n                        //\n                        // it seems like the only thing we can really do is ignore the error.\n                        this.logger.warn(`Failed to remove IndexedDB instance ${dbname}:`, e);\n                        resolve(0);\n                    };\n                    req.onblocked = (e): void => {\n                        this.logger.info(`cannot yet remove IndexedDB instance ${dbname}`);\n                    };\n                });\n                await prom;\n            }\n        };\n        promises.push(deleteRustSdkStore());\n\n        return Promise.all(promises).then(); // .then to fix types\n    }\n\n    /**\n     * Get the user-id of the logged-in user\n     *\n     * @returns MXID for the logged-in user, or null if not logged in\n     */\n    public getUserId(): string | null {\n        return this.credentials?.userId ?? null;\n    }\n\n    /**\n     * Get the user-id of the logged-in user\n     *\n     * @returns MXID for the logged-in user\n     * @throws Error if not logged in\n     */\n    public getSafeUserId(): string {\n        const userId = this.getUserId();\n        if (!userId) {\n            throw new Error(\"Expected logged in user but found none.\");\n        }\n        return userId;\n    }\n\n    /**\n     * Get the domain for this client's MXID\n     * @returns Domain of this MXID\n     */\n    public getDomain(): string | null {\n        if (this.credentials?.userId) {\n            return this.credentials.userId.replace(/^.*?:/, \"\");\n        }\n        return null;\n    }\n\n    /**\n     * Get the local part of the current user ID e.g. \"foo\" in \"\\@foo:bar\".\n     * @returns The user ID localpart or null.\n     */\n    public getUserIdLocalpart(): string | null {\n        return this.credentials?.userId?.split(\":\")[0].substring(1) ?? null;\n    }\n\n    /**\n     * Get the device ID of this client\n     * @returns device ID\n     */\n    public getDeviceId(): string | null {\n        return this.deviceId;\n    }\n\n    /**\n     * Get the session ID of this client\n     * @returns session ID\n     */\n    public getSessionId(): string {\n        return this.sessionId;\n    }\n\n    /**\n     * Check if the runtime environment supports VoIP calling.\n     * @returns True if VoIP is supported.\n     */\n    public supportsVoip(): boolean {\n        return !this.disableVoip && this.canSupportVoip;\n    }\n\n    /**\n     * @returns\n     */\n    public getMediaHandler(): MediaHandler {\n        return this.mediaHandler;\n    }\n\n    /**\n     * Set whether VoIP calls are forced to use only TURN\n     * candidates. This is the same as the forceTURN option\n     * when creating the client.\n     * @param force - True to force use of TURN servers\n     */\n    public setForceTURN(force: boolean): void {\n        this.forceTURN = force;\n    }\n\n    /**\n     * Set whether to advertise transfer support to other parties on Matrix calls.\n     * @param support - True to advertise the 'm.call.transferee' capability\n     */\n    public setSupportsCallTransfer(support: boolean): void {\n        this.supportsCallTransfer = support;\n    }\n\n    /**\n     * Returns true if to-device signalling for group calls will be encrypted with Olm.\n     * If false, it will be sent unencrypted.\n     * @returns boolean Whether group call signalling will be encrypted\n     */\n    public getUseE2eForGroupCall(): boolean {\n        return this.useE2eForGroupCall;\n    }\n\n    /**\n     * Creates a new call.\n     * The place*Call methods on the returned call can be used to actually place a call\n     *\n     * @param roomId - The room the call is to be placed in.\n     * @returns the call or null if the browser doesn't support calling.\n     */\n    public createCall(roomId: string): MatrixCall | null {\n        return createNewMatrixCall(this, roomId);\n    }\n\n    /**\n     * Creates a new group call and sends the associated state event\n     * to alert other members that the room now has a group call.\n     *\n     * @param roomId - The room the call is to be placed in.\n     */\n    public async createGroupCall(\n        roomId: string,\n        type: GroupCallType,\n        isPtt: boolean,\n        intent: GroupCallIntent,\n        dataChannelsEnabled?: boolean,\n        dataChannelOptions?: IGroupCallDataChannelOptions,\n    ): Promise<GroupCall> {\n        if (this.getGroupCallForRoom(roomId)) {\n            throw new Error(`${roomId} already has an existing group call`);\n        }\n\n        const room = this.getRoom(roomId);\n\n        if (!room) {\n            throw new Error(`Cannot find room ${roomId}`);\n        }\n\n        // Because without Media section a WebRTC connection is not possible, so need a RTCDataChannel to set up a\n        // no media WebRTC connection anyway.\n        return new GroupCall(\n            this,\n            room,\n            type,\n            isPtt,\n            intent,\n            undefined,\n            dataChannelsEnabled || this.isVoipWithNoMediaAllowed,\n            dataChannelOptions,\n            this.isVoipWithNoMediaAllowed,\n            this.useLivekitForGroupCalls,\n            this.livekitServiceURL,\n        ).create();\n    }\n\n    public getLivekitServiceURL(): string | undefined {\n        return this.livekitServiceURL;\n    }\n\n    // This shouldn't need to exist, but the widget API has startup ordering problems that\n    // mean it doesn't know the livekit URL fast enough: remove this once this is fixed.\n    public setLivekitServiceURL(newURL: string): void {\n        this.livekitServiceURL = newURL;\n    }\n\n    /**\n     * Wait until an initial state for the given room has been processed by the\n     * client and the client is aware of any ongoing group calls. Awaiting on\n     * the promise returned by this method before calling getGroupCallForRoom()\n     * avoids races where getGroupCallForRoom is called before the state for that\n     * room has been processed. It does not, however, fix other races, eg. two\n     * clients both creating a group call at the same time.\n     * @param roomId - The room ID to wait for\n     * @returns A promise that resolves once existing group calls in the room\n     *          have been processed.\n     */\n    public waitUntilRoomReadyForGroupCalls(roomId: string): Promise<void> {\n        return this.groupCallEventHandler!.waitUntilRoomReadyForGroupCalls(roomId);\n    }\n\n    /**\n     * Get an existing group call for the provided room.\n     * @returns The group call or null if it doesn't already exist.\n     */\n    public getGroupCallForRoom(roomId: string): GroupCall | null {\n        return this.groupCallEventHandler!.groupCalls.get(roomId) || null;\n    }\n\n    /**\n     * Get the current sync state.\n     * @returns the sync state, which may be null.\n     * @see MatrixClient#event:\"sync\"\n     */\n    public getSyncState(): SyncState | null {\n        return this.syncApi?.getSyncState() ?? null;\n    }\n\n    /**\n     * Returns the additional data object associated with\n     * the current sync state, or null if there is no\n     * such data.\n     * Sync errors, if available, are put in the 'error' key of\n     * this object.\n     */\n    public getSyncStateData(): ISyncStateData | null {\n        if (!this.syncApi) {\n            return null;\n        }\n        return this.syncApi.getSyncStateData();\n    }\n\n    /**\n     * Whether the initial sync has completed.\n     * @returns True if at least one sync has happened.\n     */\n    public isInitialSyncComplete(): boolean {\n        const state = this.getSyncState();\n        if (!state) {\n            return false;\n        }\n        return state === SyncState.Prepared || state === SyncState.Syncing;\n    }\n\n    /**\n     * Return whether the client is configured for a guest account.\n     * @returns True if this is a guest access_token (or no token is supplied).\n     */\n    public isGuest(): boolean {\n        return this.isGuestAccount;\n    }\n\n    /**\n     * Set whether this client is a guest account. <b>This method is experimental\n     * and may change without warning.</b>\n     * @param guest - True if this is a guest account.\n     * @experimental if the token is a macaroon, it should be encoded in it that it is a 'guest'\n     * access token, which means that the SDK can determine this entirely without\n     * the dev manually flipping this flag.\n     */\n    public setGuest(guest: boolean): void {\n        this.isGuestAccount = guest;\n    }\n\n    /**\n     * Return the provided scheduler, if any.\n     * @returns The scheduler or undefined\n     */\n    public getScheduler(): MatrixScheduler | undefined {\n        return this.scheduler;\n    }\n\n    /**\n     * Retry a backed off syncing request immediately. This should only be used when\n     * the user <b>explicitly</b> attempts to retry their lost connection.\n     * Will also retry any outbound to-device messages currently in the queue to be sent\n     * (retries of regular outgoing events are handled separately, per-event).\n     * @returns True if this resulted in a request being retried.\n     */\n    public retryImmediately(): boolean {\n        // don't await for this promise: we just want to kick it off\n        void this.toDeviceMessageQueue.sendQueue();\n        return this.syncApi?.retryImmediately() ?? false;\n    }\n\n    /**\n     * Return the global notification EventTimelineSet, if any\n     *\n     * @returns the globl notification EventTimelineSet\n     */\n    public getNotifTimelineSet(): EventTimelineSet | null {\n        return this.notifTimelineSet;\n    }\n\n    /**\n     * Set the global notification EventTimelineSet\n     *\n     */\n    public setNotifTimelineSet(set: EventTimelineSet): void {\n        this.notifTimelineSet = set;\n    }\n\n    /**\n     * Gets the cached capabilities of the homeserver, returning cached ones if available.\n     * If there are no cached capabilities and none can be fetched, throw an exception.\n     *\n     * @returns Promise resolving with The capabilities of the homeserver\n     */\n    public async getCapabilities(): Promise<Capabilities> {\n        const caps = this.serverCapabilitiesService.getCached();\n        if (caps) return caps;\n        return this.serverCapabilitiesService.fetch();\n    }\n\n    /**\n     * Gets the cached capabilities of the homeserver. If none have been fetched yet,\n     * return undefined.\n     *\n     * @returns The capabilities of the homeserver\n     */\n    public getCachedCapabilities(): Capabilities | undefined {\n        return this.serverCapabilitiesService.getCached();\n    }\n\n    /**\n     * Fetches the latest capabilities from the homeserver, ignoring any cached\n     * versions. The newly returned version is cached.\n     *\n     * @returns A promise which resolves to the capabilities of the homeserver\n     */\n    public fetchCapabilities(): Promise<Capabilities> {\n        return this.serverCapabilitiesService.fetch();\n    }\n\n    /**\n     * Initialise support for end-to-end encryption in this client, using the rust matrix-sdk-crypto.\n     *\n     * **WARNING**: the cryptography stack is not thread-safe. Having multiple `MatrixClient` instances connected to\n     * the same Indexed DB will cause data corruption and decryption failures. The application layer is responsible for\n     * ensuring that only one `MatrixClient` issue is instantiated at a time.\n     *\n     * @param args.useIndexedDB - True to use an indexeddb store, false to use an in-memory store. Defaults to 'true'.\n     * @param args.cryptoDatabasePrefix - The database name to use for indexeddb, defaults to 'matrix-js-sdk'.\n     *    Unused if useIndexedDB is 'false'.\n     * @param args.storageKey - A key with which to encrypt the indexeddb store. If provided, it must be exactly\n     *    32 bytes of data, and must be the same each time the client is initialised for a given device.\n     *    If both this and `storagePassword` are unspecified, the store will be unencrypted.\n     * @param args.storagePassword - An alternative to `storageKey`. A password which will be used to derive a key to\n     *    encrypt the store with. Deriving a key from a password is (deliberately) a slow operation, so prefer\n     *    to pass a `storageKey` directly where possible.\n     *\n     * @returns a Promise which will resolve when the crypto layer has been\n     *    successfully initialised.\n     */\n    public async initRustCrypto(\n        args: {\n            useIndexedDB?: boolean;\n            cryptoDatabasePrefix?: string;\n            storageKey?: Uint8Array;\n            storagePassword?: string;\n        } = {},\n    ): Promise<void> {\n        if (this.cryptoBackend) {\n            this.logger.warn(\"Attempt to re-initialise e2e encryption on MatrixClient\");\n            return;\n        }\n\n        const userId = this.getUserId();\n        if (userId === null) {\n            throw new Error(\n                `Cannot enable encryption on MatrixClient with unknown userId: ` +\n                    `ensure userId is passed in createClient().`,\n            );\n        }\n        const deviceId = this.getDeviceId();\n        if (deviceId === null) {\n            throw new Error(\n                `Cannot enable encryption on MatrixClient with unknown deviceId: ` +\n                    `ensure deviceId is passed in createClient().`,\n            );\n        }\n\n        // importing rust-crypto will download the webassembly, so we delay it until we know it will be\n        // needed.\n        this.logger.debug(\"Downloading Rust crypto library\");\n        const RustCrypto = await import(\"./rust-crypto/index.ts\");\n\n        const rustCrypto = await RustCrypto.initRustCrypto({\n            logger: this.logger,\n            http: this.http,\n            userId: userId,\n            deviceId: deviceId,\n            secretStorage: this.secretStorage,\n            cryptoCallbacks: this.cryptoCallbacks,\n            storePrefix: args.useIndexedDB === false ? null : (args.cryptoDatabasePrefix ?? RUST_SDK_STORE_PREFIX),\n            storeKey: args.storageKey,\n            storePassphrase: args.storagePassword,\n\n            legacyCryptoStore: this.legacyCryptoStore,\n            legacyPickleKey: this.legacyPickleKey ?? \"DEFAULT_KEY\",\n            legacyMigrationProgressListener: (progress: number, total: number): void => {\n                this.emit(CryptoEvent.LegacyCryptoStoreMigrationProgress, progress, total);\n            },\n\n            enableEncryptedStateEvents: this.enableEncryptedStateEvents,\n        });\n\n        rustCrypto.setSupportedVerificationMethods(this.verificationMethods);\n\n        this.cryptoBackend = rustCrypto;\n\n        // attach the event listeners needed by RustCrypto\n        this.on(RoomMemberEvent.Membership, rustCrypto.onRoomMembership.bind(rustCrypto));\n        this.on(RoomStateEvent.Events, rustCrypto.onRoomStateEvent.bind(rustCrypto));\n        this.on(ClientEvent.Event, (event) => {\n            void rustCrypto.onLiveEventFromSync(event);\n        });\n\n        // re-emit the events emitted by the crypto impl\n        this.reEmitter.reEmit(rustCrypto, [\n            CryptoEvent.VerificationRequestReceived,\n            CryptoEvent.UserTrustStatusChanged,\n            CryptoEvent.KeyBackupStatus,\n            CryptoEvent.KeyBackupSessionsRemaining,\n            CryptoEvent.KeyBackupFailed,\n            CryptoEvent.KeyBackupDecryptionKeyCached,\n            CryptoEvent.KeysChanged,\n            CryptoEvent.DevicesUpdated,\n            CryptoEvent.WillUpdateDevices,\n            CryptoEvent.DehydratedDeviceCreated,\n            CryptoEvent.DehydratedDeviceUploaded,\n            CryptoEvent.RehydrationStarted,\n            CryptoEvent.RehydrationProgress,\n            CryptoEvent.RehydrationCompleted,\n            CryptoEvent.RehydrationError,\n            CryptoEvent.DehydrationKeyCached,\n            CryptoEvent.DehydratedDeviceRotationError,\n        ]);\n    }\n\n    /**\n     * Access the server-side secret storage API for this client.\n     */\n    public get secretStorage(): ServerSideSecretStorage {\n        return this._secretStorage;\n    }\n\n    /**\n     * Access the crypto API for this client.\n     *\n     * If end-to-end encryption has been enabled for this client (via {@link initRustCrypto}),\n     * returns an object giving access to the crypto API. Otherwise, returns `undefined`.\n     */\n    public getCrypto(): CryptoApi | undefined {\n        return this.cryptoBackend;\n    }\n\n    /**\n     * Whether encryption is enabled for a room.\n     * @param roomId - the room id to query.\n     * @returns whether encryption is enabled.\n     *\n     * @deprecated Not correctly supported for Rust Cryptography. Use {@link CryptoApi.isEncryptionEnabledInRoom} and/or\n     *    {@link Room.hasEncryptionStateEvent}.\n     */\n    public isRoomEncrypted(roomId: string): boolean {\n        const room = this.getRoom(roomId);\n        if (!room) {\n            // we don't know about this room, so can't determine if it should be\n            // encrypted. Let's assume not.\n            return false;\n        }\n\n        // if there is an 'm.room.encryption' event in this room, it should be\n        // encrypted (independently of whether we actually support encryption)\n        return room.hasEncryptionStateEvent();\n    }\n\n    /**\n     * Check whether the key backup private key is stored in secret storage.\n     * @returns map of key name to key info the secret is\n     *     encrypted with, or null if it is not present or not encrypted with a\n     *     trusted key\n     */\n    public isKeyBackupKeyStored(): Promise<Record<string, SecretStorageKeyDescription> | null> {\n        return Promise.resolve(this.secretStorage.isStored(\"m.megolm_backup.v1\"));\n    }\n\n    private makeKeyBackupPath(roomId?: string, sessionId?: string, version?: string): IKeyBackupPath {\n        let path: string;\n        if (sessionId !== undefined) {\n            path = utils.encodeUri(\"/room_keys/keys/$roomId/$sessionId\", {\n                $roomId: roomId!,\n                $sessionId: sessionId,\n            });\n        } else if (roomId !== undefined) {\n            path = utils.encodeUri(\"/room_keys/keys/$roomId\", {\n                $roomId: roomId,\n            });\n        } else {\n            path = \"/room_keys/keys\";\n        }\n        const queryData = version === undefined ? undefined : { version };\n        return { path, queryData };\n    }\n\n    public deleteKeysFromBackup(roomId: undefined, sessionId: undefined, version?: string): Promise<void>;\n    public deleteKeysFromBackup(roomId: string, sessionId: undefined, version?: string): Promise<void>;\n    public deleteKeysFromBackup(roomId: string, sessionId: string, version?: string): Promise<void>;\n    public async deleteKeysFromBackup(roomId?: string, sessionId?: string, version?: string): Promise<void> {\n        const path = this.makeKeyBackupPath(roomId!, sessionId!, version);\n        await this.http.authedRequest(Method.Delete, path.path, path.queryData, undefined, { prefix: ClientPrefix.V3 });\n    }\n\n    /**\n     * Get the config for the media repository.\n     *\n     * @param useAuthenticatedMedia - If true, the caller supports authenticated\n     * media and wants an authentication-required URL. Note that server support\n     * for authenticated media will *not* be checked - it is the caller's responsibility\n     * to do so before calling this function.\n     *\n     * @returns Promise which resolves with an object containing the config.\n     */\n    public getMediaConfig(useAuthenticatedMedia: boolean = false): Promise<IMediaConfig> {\n        const path = useAuthenticatedMedia ? \"/media/config\" : \"/config\";\n        return this.http.authedRequest(Method.Get, path, undefined, undefined, {\n            prefix: useAuthenticatedMedia ? ClientPrefix.V1 : MediaPrefix.V3,\n        });\n    }\n\n    /**\n     * Get the room for the given room ID.\n     * This function will return a valid room for any room for which a Room event\n     * has been emitted. Note in particular that other events, eg. RoomState.members\n     * will be emitted for a room before this function will return the given room.\n     * @param roomId - The room ID\n     * @returns The Room or null if it doesn't exist or there is no data store.\n     */\n    public getRoom(roomId: string | undefined): Room | null {\n        if (!roomId) {\n            return null;\n        }\n        return this.store.getRoom(roomId);\n    }\n\n    /**\n     * Retrieve all known rooms.\n     * @returns A list of rooms, or an empty list if there is no data store.\n     */\n    public getRooms(): Room[] {\n        return this.store.getRooms();\n    }\n\n    /**\n     * Retrieve all rooms that should be displayed to the user\n     * This is essentially getRooms() with some rooms filtered out, eg. old versions\n     * of rooms that have been replaced or (in future) other rooms that have been\n     * marked at the protocol level as not to be displayed to the user.\n     *\n     * @param msc3946ProcessDynamicPredecessor - if true, look for an\n     *                                           m.room.predecessor state event and\n     *                                           use it if found (MSC3946).\n     * @returns A list of rooms, or an empty list if there is no data store.\n     */\n    public getVisibleRooms(msc3946ProcessDynamicPredecessor = false): Room[] {\n        const allRooms = this.store.getRooms();\n\n        const visibleRooms = new Set(allRooms);\n        for (const room of visibleRooms) {\n            const predecessors = this.findPredecessorRooms(room, true, msc3946ProcessDynamicPredecessor);\n            for (const predecessor of predecessors) {\n                visibleRooms.delete(predecessor);\n            }\n        }\n        return Array.from(visibleRooms);\n    }\n\n    /**\n     * Retrieve a user.\n     * @param userId - The user ID to retrieve.\n     * @returns A user or null if there is no data store or the user does\n     * not exist.\n     */\n    public getUser(userId: string): User | null {\n        return this.store.getUser(userId);\n    }\n\n    /**\n     * Retrieve all known users.\n     * @returns A list of users, or an empty list if there is no data store.\n     */\n    public getUsers(): User[] {\n        return this.store.getUsers();\n    }\n\n    /**\n     * Set account data event for the current user, and wait for the result to be echoed over `/sync`.\n     *\n     * Waiting for the remote echo ensures that a subsequent call to {@link getAccountData} will return the updated\n     * value.\n     *\n     * If called before the client is started with {@link startClient}, logs a warning and falls back to\n     * {@link setAccountDataRaw}.\n     *\n     * Retries the request up to 5 times in the case of an {@link ConnectionError}.\n     *\n     * @param eventType - The event type\n     * @param content - the contents object for the event\n     */\n    public async setAccountData<K extends keyof WritableAccountDataEvents>(\n        eventType: K,\n        content: AccountDataEvents[K] | Record<string, never>,\n    ): Promise<EmptyObject> {\n        // If the sync loop is not running, fall back to setAccountDataRaw.\n        if (!this.clientRunning) {\n            this.logger.warn(\n                \"Calling `setAccountData` before the client is started: `getAccountData` may return inconsistent results.\",\n            );\n            return await retryNetworkOperation(5, () => this.setAccountDataRaw(eventType, content));\n        }\n\n        // If the account data is already correct, then we cannot expect an update over sync, and the operation\n        // is, in any case, a no-op.\n        //\n        // NB that we rely on this operation being synchronous to avoid a race condition: there must be no `await`\n        // between here and `this.addListener` below, in case we miss an update.\n        const existingData = this.store.getAccountData(eventType);\n        if (existingData && deepCompare(existingData.event.content, content)) return {};\n\n        // Create a promise which will resolve when the update is received\n        const updatedResolvers = Promise.withResolvers<void>();\n        function accountDataListener(event: MatrixEvent): void {\n            // Note that we cannot safely check that the content matches what we expected, because there is a race:\n            //   * We set the new content\n            //   * Another client sets alternative content\n            //   * Then /sync returns, but only reflects the latest content.\n            //\n            // Of course there is room for debate over what we should actually do in that case -- a subsequent\n            // `getAccountData` isn't going to return the expected value, but whose fault is that? Databases are hard.\n            //\n            // Anyway, what we *shouldn't* do is get stuck in a loop. I think the best we can do is check that the event\n            // type matches.\n            if (event.getType() === eventType) updatedResolvers.resolve();\n        }\n        this.addListener(ClientEvent.AccountData, accountDataListener);\n\n        try {\n            const result = await retryNetworkOperation(5, () => this.setAccountDataRaw(eventType, content));\n            await updatedResolvers.promise;\n            return result;\n        } finally {\n            this.removeListener(ClientEvent.AccountData, accountDataListener);\n        }\n    }\n\n    /**\n     * Set account data event for the current user, without waiting for the remote echo.\n     *\n     * @param eventType - The event type\n     * @param content - the contents object for the event\n     */\n    public setAccountDataRaw<K extends keyof WritableAccountDataEvents>(\n        eventType: K,\n        content: AccountDataEvents[K] | Record<string, never>,\n    ): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/user/$userId/account_data/$type\", {\n            $userId: this.credentials.userId!,\n            $type: eventType,\n        });\n\n        return this.http.authedRequest(Method.Put, path, undefined, content);\n    }\n\n    /**\n     * Get account data event of given type for the current user.\n     * @param eventType - The event type\n     * @returns The contents of the given account data event\n     */\n    public getAccountData<K extends keyof AccountDataEvents>(eventType: K): MatrixEvent | undefined {\n        return this.store.getAccountData(eventType);\n    }\n\n    /**\n     * Get account data event of given type for the current user. This variant\n     * gets account data directly from the homeserver if the local store is not\n     * ready, which can be useful very early in startup before the initial sync.\n     * @param eventType - The event type\n     * @returns Promise which resolves: The contents of the given account data event.\n     * @returns Rejects: with an error response.\n     */\n    public async getAccountDataFromServer<K extends keyof AccountDataEvents>(\n        eventType: K,\n    ): Promise<AccountDataEvents[K] | null> {\n        if (this.isInitialSyncComplete()) {\n            const event = this.store.getAccountData(eventType);\n            if (!event) {\n                return null;\n            }\n            // The network version below returns just the content, so this branch\n            // does the same to match.\n            return event.getContent<AccountDataEvents[K]>();\n        }\n        const path = utils.encodeUri(\"/user/$userId/account_data/$type\", {\n            $userId: this.credentials.userId!,\n            $type: eventType,\n        });\n        try {\n            return await this.http.authedRequest(Method.Get, path);\n        } catch (e) {\n            if ((<MatrixError>e).data?.errcode === \"M_NOT_FOUND\") {\n                return null;\n            }\n            throw e;\n        }\n    }\n\n    public async deleteAccountData(eventType: keyof WritableAccountDataEvents): Promise<void> {\n        const msc3391DeleteAccountDataServerSupport = this.canSupport.get(Feature.AccountDataDeletion);\n        // if deletion is not supported overwrite with empty content\n        if (msc3391DeleteAccountDataServerSupport === ServerSupport.Unsupported) {\n            await this.setAccountData(eventType, {});\n            return;\n        }\n        const path = utils.encodeUri(\"/user/$userId/account_data/$type\", {\n            $userId: this.getSafeUserId(),\n            $type: eventType,\n        });\n        const options =\n            msc3391DeleteAccountDataServerSupport === ServerSupport.Unstable\n                ? { prefix: \"/_matrix/client/unstable/org.matrix.msc3391\" }\n                : undefined;\n        return await this.http.authedRequest(Method.Delete, path, undefined, undefined, options);\n    }\n\n    /**\n     * Gets the users that are ignored by this client\n     * @returns The array of users that are ignored (empty if none)\n     */\n    public getIgnoredUsers(): string[] {\n        const event = this.getAccountData(EventType.IgnoredUserList);\n        if (!event?.getContent()[\"ignored_users\"]) return [];\n        return Object.keys(event.getContent()[\"ignored_users\"]);\n    }\n\n    /**\n     * Sets the users that the current user should ignore.\n     * @param userIds - the user IDs to ignore\n     * @returns Promise which resolves: an empty object\n     * @returns Rejects: with an error response.\n     */\n    public setIgnoredUsers(userIds: string[]): Promise<EmptyObject> {\n        const content = { ignored_users: {} as Record<string, EmptyObject> };\n        userIds.forEach((u) => {\n            content.ignored_users[u] = {};\n        });\n        return this.setAccountData(EventType.IgnoredUserList, content);\n    }\n\n    /**\n     * Gets whether or not a specific user is being ignored by this client.\n     * @param userId - the user ID to check\n     * @returns true if the user is ignored, false otherwise\n     */\n    public isUserIgnored(userId: string): boolean {\n        return this.getIgnoredUsers().includes(userId);\n    }\n\n    /**\n     * Join a room. If you have already joined the room, this will no-op.\n     * @param roomIdOrAlias - The room ID or room alias to join.\n     * @param opts - Options when joining the room.\n     * @returns Promise which resolves: Room object.\n     * @returns Rejects: with an error response.\n     */\n    public async joinRoom(roomIdOrAlias: string, opts: IJoinRoomOpts = {}): Promise<Room> {\n        const room = this.getRoom(roomIdOrAlias);\n        const roomMember = room?.getMember(this.getSafeUserId());\n        const preJoinMembership = roomMember?.membership;\n\n        // If we were invited to the room, the ID of the user that sent the invite. Otherwise, `null`.\n        const inviter =\n            preJoinMembership == KnownMembership.Invite ? (roomMember?.events.member?.getSender() ?? null) : null;\n\n        this.logger.debug(\n            `joinRoom[${roomIdOrAlias}]: preJoinMembership=${preJoinMembership}, inviter=${inviter}, opts=${JSON.stringify(opts)}`,\n        );\n        if (preJoinMembership == KnownMembership.Join) return room!;\n\n        let signPromise: Promise<IThirdPartySigned | void> = Promise.resolve();\n\n        if (opts.inviteSignUrl) {\n            const url = new URL(opts.inviteSignUrl);\n            url.searchParams.set(\"mxid\", this.credentials.userId!);\n            signPromise = this.http.requestOtherUrl<IThirdPartySigned>(Method.Post, url);\n        }\n\n        const queryParams: QueryDict = {};\n        if (opts.viaServers) {\n            // server_name has been deprecated in favour of via with Matrix >1.11 (MSC4156)\n            // We only use the first 3 servers, to avoid URI length issues.\n            queryParams.via = queryParams.server_name = opts.viaServers.slice(0, 3);\n        }\n\n        const data: IJoinRequestBody = {};\n        const signedInviteObj = await signPromise;\n        if (signedInviteObj) {\n            data.third_party_signed = signedInviteObj;\n        }\n\n        const path = utils.encodeUri(\"/join/$roomid\", { $roomid: roomIdOrAlias });\n        const res = await this.http.authedRequest<{ room_id: string }>(Method.Post, path, queryParams, data);\n\n        const roomId = res.room_id;\n        if (inviter && this.cryptoBackend) {\n            // Flag upfront that we are waiting for a key bundle, so that if we crash mid-import, we can try again.\n            await this.cryptoBackend.markRoomAsPendingKeyBundle(roomId, inviter);\n            // Try to accept the room key bundle specified in a `m.room_key_bundle` to-device message we (might have) already received.\n            await this.cryptoBackend.maybeAcceptKeyBundle(roomId, inviter);\n        }\n\n        // In case we were originally given an alias, check the room cache again\n        // with the resolved ID - this method is supposed to no-op if we already\n        // were in the room, after all.\n        const resolvedRoom = this.getRoom(roomId);\n        if (resolvedRoom?.hasMembershipState(this.credentials.userId!, KnownMembership.Join)) return resolvedRoom;\n\n        const syncApi = new SyncApi(this, this.clientOpts, this.buildSyncApiOptions());\n        return syncApi.createRoom(roomId);\n    }\n\n    /**\n     * Knock a room. If you have already knocked the room, this will no-op.\n     * @param roomIdOrAlias - The room ID or room alias to knock.\n     * @param opts - Options when knocking the room.\n     * @returns Promise which resolves: `{room_id: {string}}`\n     * @returns Rejects: with an error response.\n     */\n    public knockRoom(roomIdOrAlias: string, opts: KnockRoomOpts = {}): Promise<{ room_id: string }> {\n        const room = this.getRoom(roomIdOrAlias);\n        if (room?.hasMembershipState(this.credentials.userId!, KnownMembership.Knock)) {\n            return Promise.resolve({ room_id: room.roomId });\n        }\n\n        const path = utils.encodeUri(\"/knock/$roomIdOrAlias\", { $roomIdOrAlias: roomIdOrAlias });\n\n        const queryParams: QueryDict = {};\n        if (opts.viaServers) {\n            // We only use the first 3 servers, to avoid URI length issues.\n            const viaServers = Array.isArray(opts.viaServers) ? opts.viaServers.slice(0, 3) : [opts.viaServers];\n            // server_name has been deprecated in favour of via with Matrix >1.11 (MSC4156)\n            queryParams.server_name = viaServers;\n            queryParams.via = viaServers;\n        }\n\n        const body: Record<string, string> = {};\n        if (opts.reason) {\n            body.reason = opts.reason;\n        }\n\n        return this.http.authedRequest(Method.Post, path, queryParams, body);\n    }\n\n    /**\n     * Resend an event. Will also retry any to-device messages waiting to be sent.\n     * @param event - The event to resend.\n     * @param room - Optional. The room the event is in. Will update the\n     * timeline entry if provided.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public resendEvent(event: MatrixEvent, room: Room): Promise<ISendEventResponse> {\n        // also kick the to-device queue to retry\n        void this.toDeviceMessageQueue.sendQueue();\n\n        this.updatePendingEventStatus(room, event, EventStatus.SENDING);\n        return this.encryptAndSendEvent(room, event);\n    }\n\n    /**\n     * Cancel a queued or unsent event.\n     *\n     * @param event -   Event to cancel\n     * @throws Error if the event is not in QUEUED, NOT_SENT or ENCRYPTING state\n     */\n    public cancelPendingEvent(event: MatrixEvent): void {\n        if (![EventStatus.QUEUED, EventStatus.NOT_SENT, EventStatus.ENCRYPTING].includes(event.status!)) {\n            throw new Error(\"cannot cancel an event with status \" + event.status);\n        }\n\n        // If the event is currently being encrypted then remove it from the pending list, to indicate that it should\n        // not be sent.\n        if (event.status === EventStatus.ENCRYPTING) {\n            this.eventsBeingEncrypted.delete(event.getId()!);\n        } else if (this.scheduler && event.status === EventStatus.QUEUED) {\n            // tell the scheduler to forget about it, if it's queued\n            this.scheduler.removeEventFromQueue(event);\n        }\n\n        // then tell the room about the change of state, which will remove it\n        // from the room's list of pending events.\n        const room = this.getRoom(event.getRoomId());\n        this.updatePendingEventStatus(room, event, EventStatus.CANCELLED);\n    }\n\n    /**\n     * @returns Promise which resolves: TODO\n     * @returns Rejects: with an error response.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public setRoomName(roomId: string, name: string): Promise<ISendEventResponse> {\n        return this.sendStateEvent(roomId, EventType.RoomName, { name: name });\n    }\n\n    /**\n     * @param roomId - The room to update the topic in.\n     * @param topic - The plaintext topic. May be empty to remove the topic.\n     * @param htmlTopic - Optional.\n     * @returns Promise which resolves: TODO\n     * @returns Rejects: with an error response.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public setRoomTopic(roomId: string, topic?: string, htmlTopic?: string): Promise<ISendEventResponse> {\n        const content = ContentHelpers.makeTopicContent(topic, htmlTopic);\n        return this.sendStateEvent(roomId, EventType.RoomTopic, content);\n    }\n\n    /**\n     * @returns Promise which resolves: to an object keyed by tagId with objects containing a numeric order field.\n     * @returns Rejects: with an error response.\n     */\n    public getRoomTags(roomId: string): Promise<ITagsResponse> {\n        const path = utils.encodeUri(\"/user/$userId/rooms/$roomId/tags\", {\n            $userId: this.credentials.userId!,\n            $roomId: roomId,\n        });\n        return this.http.authedRequest(Method.Get, path);\n    }\n\n    /**\n     * @param tagName - name of room tag to be set\n     * @param metadata - associated with that tag to be stored\n     * @returns Promise which resolves: to an empty object\n     * @returns Rejects: with an error response.\n     */\n    public setRoomTag(roomId: string, tagName: string, metadata: ITagMetadata = {}): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/user/$userId/rooms/$roomId/tags/$tag\", {\n            $userId: this.credentials.userId!,\n            $roomId: roomId,\n            $tag: tagName,\n        });\n        return this.http.authedRequest(Method.Put, path, undefined, metadata);\n    }\n\n    /**\n     * @param tagName - name of room tag to be removed\n     * @returns Promise which resolves: to an empty object\n     * @returns Rejects: with an error response.\n     */\n    public deleteRoomTag(roomId: string, tagName: string): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/user/$userId/rooms/$roomId/tags/$tag\", {\n            $userId: this.credentials.userId!,\n            $roomId: roomId,\n            $tag: tagName,\n        });\n        return this.http.authedRequest(Method.Delete, path);\n    }\n\n    /**\n     * @param roomId - the ID of the room this event should be stored within\n     * @param eventType - event type to be set\n     * @param content - event content\n     * @returns Promise which resolves: to an empty object `{}`\n     * @returns Rejects: with an error response.\n     */\n    public setRoomAccountData<K extends keyof RoomAccountDataEvents>(\n        roomId: string,\n        eventType: K,\n        content: RoomAccountDataEvents[K] | Record<string, never>,\n    ): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/user/$userId/rooms/$roomId/account_data/$type\", {\n            $userId: this.credentials.userId!,\n            $roomId: roomId,\n            $type: eventType,\n        });\n        return this.http.authedRequest(Method.Put, path, undefined, content);\n    }\n\n    /**\n     * Set a power level to one or multiple users.\n     * Will apply changes atop of current power level event from local state if running & synced, falling back\n     * to fetching latest from the `/state/` API.\n     * @param roomId - the room to update power levels in\n     * @param userId - the ID of the user or users to update power levels of\n     * @param powerLevel - the numeric power level to update given users to\n     * @returns Promise which resolves: to an ISendEventResponse object\n     * @returns Rejects: with an error response.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public async setPowerLevel(\n        roomId: string,\n        userId: string | string[],\n        powerLevel: number | undefined,\n    ): Promise<ISendEventResponse> {\n        let content: IPowerLevelsContent | undefined;\n        if (this.clientRunning && this.isInitialSyncComplete()) {\n            content = this.getRoom(roomId)?.currentState?.getStateEvents(EventType.RoomPowerLevels, \"\")?.getContent();\n        }\n        if (!content) {\n            try {\n                content = await this.getStateEvent(roomId, EventType.RoomPowerLevels, \"\");\n            } catch (e) {\n                // It is possible for a Matrix room to not have a power levels event\n                if (e instanceof MatrixError && e.errcode === \"M_NOT_FOUND\") {\n                    content = {};\n                } else {\n                    throw e;\n                }\n            }\n        }\n\n        // take a copy of the content to ensure we don't corrupt\n        // existing client state with a failed power level change\n        content = utils.deepCopy(content);\n\n        if (!content?.users) {\n            content.users = {};\n        }\n        const users = Array.isArray(userId) ? userId : [userId];\n        for (const user of users) {\n            if (powerLevel == null) {\n                delete content.users[user];\n            } else {\n                content.users[user] = powerLevel;\n            }\n        }\n\n        return this.sendStateEvent(roomId, EventType.RoomPowerLevels, content, \"\");\n    }\n\n    /**\n     * Create an m.beacon_info event\n     * @returns\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    // eslint-disable-next-line @typescript-eslint/naming-convention\n    public async unstable_createLiveBeacon(\n        roomId: Room[\"roomId\"],\n        beaconInfoContent: MBeaconInfoEventContent,\n    ): Promise<ISendEventResponse> {\n        return this.unstable_setLiveBeacon(roomId, beaconInfoContent);\n    }\n\n    /**\n     * Upsert a live beacon event\n     * using a specific m.beacon_info.* event variable type\n     * @param roomId - string\n     * @returns\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    // eslint-disable-next-line @typescript-eslint/naming-convention\n    public async unstable_setLiveBeacon(\n        roomId: string,\n        beaconInfoContent: MBeaconInfoEventContent,\n    ): Promise<ISendEventResponse> {\n        return this.sendStateEvent(roomId, M_BEACON_INFO.name, beaconInfoContent, this.getUserId()!);\n    }\n\n    /**\n     * Send a Matrix timeline event.\n     * @param roomId The room to send to.\n     * @param eventType The event type.\n     * @param content The event content.\n     * @param txnId An optional ID to deduplicate requests in case of repeated attempts.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public sendEvent<K extends keyof TimelineEvents>(\n        roomId: string,\n        eventType: K,\n        content: TimelineEvents[K],\n        txnId?: string,\n    ): Promise<ISendEventResponse>;\n    public sendEvent<K extends keyof TimelineEvents>(\n        roomId: string,\n        threadId: string | null,\n        eventType: K,\n        content: TimelineEvents[K],\n        txnId?: string,\n    ): Promise<ISendEventResponse>;\n    public sendEvent(\n        roomId: string,\n        threadIdOrEventType: string | null,\n        eventTypeOrContent: string | IContent,\n        contentOrTxnId?: IContent | string,\n        txnIdOrVoid?: string,\n    ): Promise<ISendEventResponse> {\n        let threadId: string | null;\n        let eventType: string;\n        let content: IContent;\n        let txnId: string | undefined;\n        if (!threadIdOrEventType?.startsWith(EVENT_ID_PREFIX) && threadIdOrEventType !== null) {\n            txnId = contentOrTxnId as string;\n            content = eventTypeOrContent as IContent;\n            eventType = threadIdOrEventType;\n            threadId = null;\n        } else {\n            txnId = txnIdOrVoid;\n            content = contentOrTxnId as IContent;\n            eventType = eventTypeOrContent as string;\n            threadId = threadIdOrEventType;\n        }\n\n        this.addThreadRelationIfNeeded(content, threadId, roomId);\n        return this.sendCompleteEvent({ roomId, threadId, eventObject: { type: eventType, content }, txnId });\n    }\n\n    /**\n     * If we expect that an event is part of a thread but is missing the relation\n     * we need to add it manually, as well as the reply fallback\n     */\n    private addThreadRelationIfNeeded(content: IContent, threadId: string | null, roomId: string): void {\n        if (threadId && !content[\"m.relates_to\"]?.rel_type) {\n            const isReply = !!content[\"m.relates_to\"]?.[\"m.in_reply_to\"];\n            content[\"m.relates_to\"] = {\n                ...content[\"m.relates_to\"],\n                rel_type: THREAD_RELATION_TYPE.name,\n                event_id: threadId,\n                // Set is_falling_back to true unless this is actually intended to be a reply\n                is_falling_back: !isReply,\n            };\n            const thread = this.getRoom(roomId)?.getThread(threadId);\n            if (thread && !isReply) {\n                content[\"m.relates_to\"][\"m.in_reply_to\"] = {\n                    event_id:\n                        thread\n                            .lastReply((ev: MatrixEvent) => {\n                                return ev.isRelation(THREAD_RELATION_TYPE.name) && !ev.status;\n                            })\n                            ?.getId() ?? threadId,\n                };\n            }\n        }\n    }\n\n    /**\n     * @param eventObject - An object with the partial structure of an event, to which event_id, user_id, room_id and origin_server_ts will be added.\n     * @param txnId - Optional.\n     * @returns Promise which resolves: to an empty object `{}`\n     * @returns Rejects: with an error response.\n     */\n    private sendCompleteEvent(params: {\n        roomId: string;\n        threadId: string | null;\n        eventObject: Partial<IEvent>;\n        queryDict?: QueryDict;\n        txnId?: string;\n    }): Promise<ISendEventResponse>;\n    /**\n     * Sends a delayed event (MSC4140).\n     * @param eventObject - An object with the partial structure of an event, to which event_id, user_id, room_id and origin_server_ts will be added.\n     * @param delayOpts - Properties of the delay for this event.\n     * @param txnId - Optional.\n     * @returns Promise which resolves: to an empty object `{}`\n     * @returns Rejects: with an error response.\n     */\n    private sendCompleteEvent(params: {\n        roomId: string;\n        threadId: string | null;\n        eventObject: Partial<IEvent>;\n        delayOpts: SendDelayedEventRequestOpts;\n        queryDict?: QueryDict;\n        txnId?: string;\n    }): Promise<SendDelayedEventResponse>;\n    private sendCompleteEvent({\n        roomId,\n        threadId,\n        eventObject,\n        delayOpts,\n        queryDict,\n        txnId,\n    }: {\n        roomId: string;\n        threadId: string | null;\n        eventObject: Partial<IEvent>;\n        delayOpts?: SendDelayedEventRequestOpts;\n        queryDict?: QueryDict;\n        txnId?: string;\n    }): Promise<SendDelayedEventResponse | ISendEventResponse> {\n        if (!txnId) {\n            txnId = this.makeTxnId();\n        }\n\n        // We always construct a MatrixEvent when sending because the store and scheduler use them.\n        // We'll extract the params back out if it turns out the client has no scheduler or store.\n        const localEvent = new MatrixEvent(\n            Object.assign(eventObject, {\n                event_id: \"~\" + roomId + \":\" + txnId,\n                user_id: this.credentials.userId,\n                sender: this.credentials.userId,\n                room_id: roomId,\n                origin_server_ts: new Date().getTime(),\n            }),\n        );\n\n        const room = this.getRoom(roomId);\n        const thread = threadId ? room?.getThread(threadId) : undefined;\n        if (thread) {\n            localEvent.setThread(thread);\n        }\n\n        if (!delayOpts) {\n            // set up re-emitter for this new event - this is normally the job of EventMapper but we don't use it here\n            this.reEmitter.reEmit(localEvent, [MatrixEventEvent.Replaced, MatrixEventEvent.VisibilityChange]);\n            room?.reEmitter.reEmit(localEvent, [MatrixEventEvent.BeforeRedaction]);\n        }\n\n        // if this is a relation or redaction of an event\n        // that hasn't been sent yet (e.g. with a local id starting with a ~)\n        // then listen for the remote echo of that event so that by the time\n        // this event does get sent, we have the correct event_id\n        const targetId = localEvent.getAssociatedId();\n        if (targetId?.startsWith(\"~\")) {\n            const target = room?.getPendingEvents().find((e) => e.getId() === targetId);\n            target?.once(MatrixEventEvent.LocalEventIdReplaced, () => {\n                localEvent.updateAssociatedId(target.getId()!);\n            });\n        }\n\n        const type = localEvent.getType();\n        this.logger.debug(\n            `sendEvent of type ${type} in ${roomId} with txnId ${txnId}${delayOpts ? \" (delayed event)\" : \"\"}${queryDict ? \" query params: \" + JSON.stringify(queryDict) : \"\"}`,\n        );\n\n        localEvent.setTxnId(txnId);\n        localEvent.setStatus(EventStatus.SENDING);\n\n        // TODO: separate store for delayed events?\n        if (!delayOpts) {\n            // add this event immediately to the local store as 'sending'.\n            room?.addPendingEvent(localEvent, txnId);\n\n            // addPendingEvent can change the state to NOT_SENT if it believes\n            // that there's other events that have failed. We won't bother to\n            // try sending the event if the state has changed as such.\n            if (localEvent.status === EventStatus.NOT_SENT) {\n                return Promise.reject(new Error(\"Event blocked by other events not yet sent\"));\n            }\n\n            return this.encryptAndSendEvent(room, localEvent, queryDict);\n        } else {\n            return this.encryptAndSendEvent(room, localEvent, delayOpts, queryDict);\n        }\n    }\n\n    /**\n     * encrypts the event if necessary; adds the event to the queue, or sends it; marks the event as sent/unsent\n     * @returns returns a promise which resolves with the result of the send request\n     */\n    protected async encryptAndSendEvent(\n        room: Room | null,\n        event: MatrixEvent,\n        queryDict?: QueryDict,\n    ): Promise<ISendEventResponse>;\n    /**\n     * Simply sends a delayed event without encrypting it.\n     * TODO: Allow encrypted delayed events, and encrypt them properly\n     * @param delayOpts - Properties of the delay for this event.\n     * @returns returns a promise which resolves with the result of the delayed send request\n     */\n    protected async encryptAndSendEvent(\n        room: Room | null,\n        event: MatrixEvent,\n        delayOpts: SendDelayedEventRequestOpts,\n        queryDict?: QueryDict,\n    ): Promise<ISendEventResponse>;\n    protected async encryptAndSendEvent(\n        room: Room | null,\n        event: MatrixEvent,\n        delayOptsOrQuery?: SendDelayedEventRequestOpts | QueryDict,\n        queryDict?: QueryDict,\n    ): Promise<ISendEventResponse | SendDelayedEventResponse> {\n        let queryOpts = queryDict;\n        if (delayOptsOrQuery && isSendDelayedEventRequestOpts(delayOptsOrQuery)) {\n            return this.sendEventHttpRequest(event, delayOptsOrQuery, queryOpts);\n        } else if (!queryOpts) {\n            queryOpts = delayOptsOrQuery;\n        }\n        try {\n            let cancelled: boolean;\n            this.eventsBeingEncrypted.add(event.getId()!);\n            try {\n                await this.encryptEventIfNeeded(event, room ?? undefined);\n            } finally {\n                cancelled = !this.eventsBeingEncrypted.delete(event.getId()!);\n            }\n\n            if (cancelled) {\n                // cancelled via MatrixClient::cancelPendingEvent\n                return {} as ISendEventResponse;\n            }\n\n            // encryptEventIfNeeded may have updated the status from SENDING to ENCRYPTING. If so, we need\n            // to put it back.\n            if (event.status === EventStatus.ENCRYPTING) {\n                this.updatePendingEventStatus(room, event, EventStatus.SENDING);\n            }\n\n            let promise: Promise<ISendEventResponse> | null = null;\n            if (this.scheduler) {\n                // if this returns a promise then the scheduler has control now and will\n                // resolve/reject when it is done. Internally, the scheduler will invoke\n                // processFn which is set to this._sendEventHttpRequest so the same code\n                // path is executed regardless.\n                promise = this.scheduler.queueEvent(event);\n                if (promise && this.scheduler.getQueueForEvent(event)!.length > 1) {\n                    // event is processed FIFO so if the length is 2 or more we know\n                    // this event is stuck behind an earlier event.\n                    this.updatePendingEventStatus(room, event, EventStatus.QUEUED);\n                }\n            }\n\n            if (!promise) {\n                promise = this.sendEventHttpRequest(event, queryOpts);\n                if (room) {\n                    promise = promise.then((res) => {\n                        room.updatePendingEvent(event, EventStatus.SENT, res[\"event_id\"]);\n                        return res;\n                    });\n                }\n            }\n\n            return await promise;\n        } catch (err) {\n            this.logger.error(\"Error sending event\", err);\n            try {\n                // set the error on the event before we update the status:\n                // updating the status emits the event, so the state should be\n                // consistent at that point.\n                event.error = <MatrixError>err;\n                this.updatePendingEventStatus(room, event, EventStatus.NOT_SENT);\n            } catch (e) {\n                this.logger.error(\"Exception in error handler!\", e);\n            }\n            if (err instanceof MatrixError) {\n                err.event = event;\n            }\n            throw err;\n        }\n    }\n\n    private async encryptEventIfNeeded(event: MatrixEvent, room?: Room): Promise<void> {\n        // If the room is unknown, we cannot encrypt for it\n        if (!room) return;\n\n        if (!(await this.shouldEncryptEventForRoom(event, room))) return;\n\n        if (!this.cryptoBackend && this.usingExternalCrypto) {\n            // The client has opted to allow sending messages to encrypted\n            // rooms even if the room is encrypted, and we haven't set up\n            // crypto. This is useful for users of matrix-org/pantalaimon\n            return;\n        }\n\n        if (!this.cryptoBackend) {\n            throw new Error(\"This room is configured to use encryption, but your client does not support encryption.\");\n        }\n\n        this.updatePendingEventStatus(room, event, EventStatus.ENCRYPTING);\n        await this.cryptoBackend.encryptEvent(event, room);\n    }\n\n    /**\n     * Determine whether a given event should be encrypted when we send it to the given room.\n     *\n     * This takes into account event type and room configuration.\n     */\n    private async shouldEncryptEventForRoom(event: MatrixEvent, room: Room): Promise<boolean> {\n        if (event.isEncrypted()) {\n            // this event has already been encrypted; this happens if the\n            // encryption step succeeded, but the send step failed on the first\n            // attempt.\n            return false;\n        }\n\n        if (event.getType() === EventType.Reaction) {\n            // For reactions, there is a very little gained by encrypting the entire\n            // event, as relation data is already kept in the clear. Event\n            // encryption for a reaction effectively only obscures the event type,\n            // but the purpose is still obvious from the relation data, so nothing\n            // is really gained. It also causes quite a few problems, such as:\n            //   * triggers notifications via default push rules\n            //   * prevents server-side bundling for reactions\n            // The reaction key / content / emoji value does warrant encrypting, but\n            // this will be handled separately by encrypting just this value.\n            // See https://github.com/matrix-org/matrix-doc/pull/1849#pullrequestreview-248763642\n            return false;\n        }\n\n        if (event.isRedaction()) {\n            // Redactions do not support encryption in the spec at this time.\n            // Whilst it mostly worked in some clients, it wasn't compliant.\n            return false;\n        }\n\n        // If the room has an m.room.encryption event, we should encrypt.\n        if (room.hasEncryptionStateEvent()) return true;\n\n        // If we have a crypto impl, and *it* thinks we should encrypt, then we should.\n        if (await this.cryptoBackend?.isEncryptionEnabledInRoom(room.roomId)) return true;\n\n        // Otherwise, no need to encrypt.\n        return false;\n    }\n\n    /**\n     * Returns the eventType that should be used taking encryption into account\n     * for a given eventType.\n     * @param roomId - the room for the events `eventType` relates to\n     * @param eventType - the event type\n     * @returns the event type taking encryption into account\n     */\n    private getEncryptedIfNeededEventType(\n        roomId: string,\n        eventType?: EventType | string | null,\n    ): EventType | string | null | undefined {\n        if (eventType === EventType.Reaction) return eventType;\n        return this.getRoom(roomId)?.hasEncryptionStateEvent() ? EventType.RoomMessageEncrypted : eventType;\n    }\n\n    protected updatePendingEventStatus(room: Room | null, event: MatrixEvent, newStatus: EventStatus): void {\n        if (room) {\n            room.updatePendingEvent(event, newStatus);\n        } else {\n            event.setStatus(newStatus);\n        }\n    }\n\n    private sendEventHttpRequest(event: MatrixEvent, queryDict?: QueryDict): Promise<ISendEventResponse>;\n    private sendEventHttpRequest(\n        event: MatrixEvent,\n        delayOpts: SendDelayedEventRequestOpts,\n        queryDict?: QueryDict,\n    ): Promise<SendDelayedEventResponse>;\n    private sendEventHttpRequest(\n        event: MatrixEvent,\n        queryOrDelayOpts?: SendDelayedEventRequestOpts | QueryDict,\n        queryDict?: QueryDict,\n    ): Promise<ISendEventResponse | SendDelayedEventResponse> {\n        let txnId = event.getTxnId();\n        if (!txnId) {\n            txnId = this.makeTxnId();\n            event.setTxnId(txnId);\n        }\n\n        const pathParams = {\n            $roomId: event.getRoomId()!,\n            $eventType: event.getWireType(),\n            $stateKey: event.getStateKey()!,\n            $txnId: txnId,\n        };\n\n        let path: string;\n\n        if (event.isState()) {\n            let pathTemplate = \"/rooms/$roomId/state/$eventType\";\n            if (event.getStateKey() && event.getStateKey()!.length > 0) {\n                pathTemplate = \"/rooms/$roomId/state/$eventType/$stateKey\";\n            }\n            path = utils.encodeUri(pathTemplate, pathParams);\n        } else if (event.isRedaction() && event.event.redacts) {\n            const pathTemplate = `/rooms/$roomId/redact/$redactsEventId/$txnId`;\n            path = utils.encodeUri(pathTemplate, {\n                $redactsEventId: event.event.redacts,\n                ...pathParams,\n            });\n        } else {\n            path = utils.encodeUri(\"/rooms/$roomId/send/$eventType/$txnId\", pathParams);\n        }\n\n        const delayOpts =\n            queryOrDelayOpts && isSendDelayedEventRequestOpts(queryOrDelayOpts) ? queryOrDelayOpts : undefined;\n        const queryOpts = !delayOpts ? queryOrDelayOpts : queryDict;\n        const content = event.getWireContent();\n        if (delayOpts) {\n            return this.http.authedRequest<SendDelayedEventResponse>(\n                Method.Put,\n                path,\n                { ...getUnstableDelayQueryOpts(delayOpts), ...queryOpts },\n                content,\n            );\n        } else {\n            return this.http.authedRequest<ISendEventResponse>(Method.Put, path, queryOpts, content).then((res) => {\n                this.logger.debug(`Event sent to ${event.getRoomId()} with event id ${res.event_id}`);\n                return res;\n            });\n        }\n    }\n\n    /**\n     * @param txnId -  transaction id. One will be made up if not supplied.\n     * @param opts - Redact options\n     * @returns Promise which resolves: TODO\n     * @returns Rejects: with an error response.\n     * @throws Error if called with `with_rel_types` (MSC3912) but the server does not support it.\n     *         Callers should check whether the server supports MSC3912 via `MatrixClient.canSupport`.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public redactEvent(\n        roomId: string,\n        eventId: string,\n        txnId?: string | undefined,\n        opts?: IRedactOpts,\n    ): Promise<ISendEventResponse>;\n    public redactEvent(\n        roomId: string,\n        threadId: string | null,\n        eventId: string,\n        txnId?: string | undefined,\n        opts?: IRedactOpts,\n    ): Promise<ISendEventResponse>;\n    public redactEvent(\n        roomId: string,\n        threadId: string | null,\n        eventId?: string,\n        txnId?: string | IRedactOpts,\n        opts?: IRedactOpts,\n    ): Promise<ISendEventResponse> {\n        if (!eventId?.startsWith(EVENT_ID_PREFIX)) {\n            opts = txnId as IRedactOpts;\n            txnId = eventId;\n            eventId = threadId!;\n            threadId = null;\n        }\n        const reason = opts?.reason;\n        const content: IContent = { reason };\n\n        if (opts?.with_rel_types !== undefined) {\n            if (this.canSupport.get(Feature.RelationBasedRedactions) === ServerSupport.Unsupported) {\n                throw new Error(\n                    \"Server does not support relation based redactions \" +\n                        `roomId ${roomId} eventId ${eventId} txnId: ${txnId as string} threadId ${threadId}`,\n                );\n            }\n\n            const withRelTypesPropName =\n                this.canSupport.get(Feature.RelationBasedRedactions) === ServerSupport.Stable\n                    ? MSC3912_RELATION_BASED_REDACTIONS_PROP.stable!\n                    : MSC3912_RELATION_BASED_REDACTIONS_PROP.unstable!;\n\n            content[withRelTypesPropName] = opts.with_rel_types;\n        }\n\n        return this.sendCompleteEvent({\n            roomId,\n            threadId,\n            eventObject: {\n                type: EventType.RoomRedaction,\n                content,\n                redacts: eventId,\n            },\n            txnId: txnId as string,\n        });\n    }\n\n    /**\n     * @param txnId - Optional.\n     * @returns Promise which resolves: to an ISendEventResponse object\n     * @returns Rejects: with an error response.\n     */\n    public sendMessage(roomId: string, content: RoomMessageEventContent, txnId?: string): Promise<ISendEventResponse>;\n    public sendMessage(\n        roomId: string,\n        threadId: string | null,\n        content: RoomMessageEventContent,\n        txnId?: string,\n    ): Promise<ISendEventResponse>;\n    public sendMessage(\n        roomId: string,\n        threadId: string | null | RoomMessageEventContent,\n        content?: RoomMessageEventContent | string,\n        txnId?: string,\n    ): Promise<ISendEventResponse> {\n        if (typeof threadId !== \"string\" && threadId !== null) {\n            txnId = content as string;\n            content = threadId as RoomMessageEventContent;\n            threadId = null;\n        }\n\n        const eventType = EventType.RoomMessage;\n        const sendContent = content as RoomMessageEventContent;\n\n        return this.sendEvent(roomId, threadId as string | null, eventType, sendContent, txnId);\n    }\n\n    /**\n     * @param txnId - Optional.\n     * @returns\n     * @returns Rejects: with an error response.\n     */\n    public sendTextMessage(roomId: string, body: string, txnId?: string): Promise<ISendEventResponse>;\n    public sendTextMessage(\n        roomId: string,\n        threadId: string | null,\n        body: string,\n        txnId?: string,\n    ): Promise<ISendEventResponse>;\n    public sendTextMessage(\n        roomId: string,\n        threadId: string | null,\n        body: string,\n        txnId?: string,\n    ): Promise<ISendEventResponse> {\n        if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {\n            txnId = body;\n            body = threadId;\n            threadId = null;\n        }\n        const content = ContentHelpers.makeTextMessage(body);\n        return this.sendMessage(roomId, threadId, content, txnId);\n    }\n\n    /**\n     * @param txnId - Optional.\n     * @returns Promise which resolves: to a ISendEventResponse object\n     * @returns Rejects: with an error response.\n     */\n    public sendNotice(roomId: string, body: string, txnId?: string): Promise<ISendEventResponse>;\n    public sendNotice(\n        roomId: string,\n        threadId: string | null,\n        body: string,\n        txnId?: string,\n    ): Promise<ISendEventResponse>;\n    public sendNotice(\n        roomId: string,\n        threadId: string | null,\n        body: string,\n        txnId?: string,\n    ): Promise<ISendEventResponse> {\n        if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {\n            txnId = body;\n            body = threadId;\n            threadId = null;\n        }\n        const content = ContentHelpers.makeNotice(body);\n        return this.sendMessage(roomId, threadId, content, txnId);\n    }\n\n    /**\n     * @param txnId - Optional.\n     * @returns Promise which resolves: to a ISendEventResponse object\n     * @returns Rejects: with an error response.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public sendEmoteMessage(roomId: string, body: string, txnId?: string): Promise<ISendEventResponse>;\n    public sendEmoteMessage(\n        roomId: string,\n        threadId: string | null,\n        body: string,\n        txnId?: string,\n    ): Promise<ISendEventResponse>;\n    public sendEmoteMessage(\n        roomId: string,\n        threadId: string | null,\n        body: string,\n        txnId?: string,\n    ): Promise<ISendEventResponse> {\n        if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {\n            txnId = body;\n            body = threadId;\n            threadId = null;\n        }\n        const content = ContentHelpers.makeEmoteMessage(body);\n        return this.sendMessage(roomId, threadId, content, txnId);\n    }\n\n    /**\n     * @returns Promise which resolves: to a ISendEventResponse object\n     * @returns Rejects: with an error response.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public sendImageMessage(roomId: string, url: string, info?: ImageInfo, text?: string): Promise<ISendEventResponse>;\n    public sendImageMessage(\n        roomId: string,\n        threadId: string | null,\n        url: string,\n        info?: ImageInfo,\n        text?: string,\n    ): Promise<ISendEventResponse>;\n    public sendImageMessage(\n        roomId: string,\n        threadId: string | null,\n        url?: string | ImageInfo,\n        info?: ImageInfo | string,\n        text = \"Image\",\n    ): Promise<ISendEventResponse> {\n        if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {\n            text = (info as string) || \"Image\";\n            info = url as ImageInfo;\n            url = threadId as string;\n            threadId = null;\n        }\n        const content = {\n            msgtype: MsgType.Image,\n            url: url as string,\n            info: info as ImageInfo,\n            body: text,\n        } satisfies RoomMessageEventContent;\n        return this.sendMessage(roomId, threadId, content);\n    }\n\n    /**\n     * @returns Promise which resolves: to a ISendEventResponse object\n     * @returns Rejects: with an error response.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public sendStickerMessage(\n        roomId: string,\n        url: string,\n        info?: ImageInfo,\n        text?: string,\n    ): Promise<ISendEventResponse>;\n    public sendStickerMessage(\n        roomId: string,\n        threadId: string | null,\n        url: string,\n        info?: ImageInfo,\n        text?: string,\n    ): Promise<ISendEventResponse>;\n    public sendStickerMessage(\n        roomId: string,\n        threadId: string | null,\n        url?: string | ImageInfo,\n        info?: ImageInfo | string,\n        text = \"Sticker\",\n    ): Promise<ISendEventResponse> {\n        if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {\n            text = (info as string) || \"Sticker\";\n            info = url as ImageInfo;\n            url = threadId as string;\n            threadId = null;\n        }\n        const content = {\n            url: url as string,\n            info: info as ImageInfo,\n            body: text,\n        } satisfies StickerEventContent;\n\n        return this.sendEvent(roomId, threadId, EventType.Sticker, content);\n    }\n\n    /**\n     * @returns Promise which resolves: to a ISendEventResponse object\n     * @returns Rejects: with an error response.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public sendHtmlMessage(roomId: string, body: string, htmlBody: string): Promise<ISendEventResponse>;\n    public sendHtmlMessage(\n        roomId: string,\n        threadId: string | null,\n        body: string,\n        htmlBody: string,\n    ): Promise<ISendEventResponse>;\n    public sendHtmlMessage(\n        roomId: string,\n        threadId: string | null,\n        body: string,\n        htmlBody?: string,\n    ): Promise<ISendEventResponse> {\n        if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {\n            htmlBody = body as string;\n            body = threadId;\n            threadId = null;\n        }\n        const content = ContentHelpers.makeHtmlMessage(body, htmlBody!);\n        return this.sendMessage(roomId, threadId, content);\n    }\n\n    /**\n     * @returns Promise which resolves: to a ISendEventResponse object\n     * @returns Rejects: with an error response.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public sendHtmlNotice(roomId: string, body: string, htmlBody: string): Promise<ISendEventResponse>;\n    public sendHtmlNotice(\n        roomId: string,\n        threadId: string | null,\n        body: string,\n        htmlBody: string,\n    ): Promise<ISendEventResponse>;\n    public sendHtmlNotice(\n        roomId: string,\n        threadId: string | null,\n        body: string,\n        htmlBody?: string,\n    ): Promise<ISendEventResponse> {\n        if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {\n            htmlBody = body as string;\n            body = threadId;\n            threadId = null;\n        }\n        const content = ContentHelpers.makeHtmlNotice(body, htmlBody!);\n        return this.sendMessage(roomId, threadId, content);\n    }\n\n    /**\n     * @returns Promise which resolves: to a ISendEventResponse object\n     * @returns Rejects: with an error response.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public sendHtmlEmote(roomId: string, body: string, htmlBody: string): Promise<ISendEventResponse>;\n    public sendHtmlEmote(\n        roomId: string,\n        threadId: string | null,\n        body: string,\n        htmlBody: string,\n    ): Promise<ISendEventResponse>;\n    public sendHtmlEmote(\n        roomId: string,\n        threadId: string | null,\n        body: string,\n        htmlBody?: string,\n    ): Promise<ISendEventResponse> {\n        if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {\n            htmlBody = body as string;\n            body = threadId;\n            threadId = null;\n        }\n        const content = ContentHelpers.makeHtmlEmote(body, htmlBody!);\n        return this.sendMessage(roomId, threadId, content);\n    }\n\n    /**\n     * Send a delayed timeline event.\n     *\n     * Note: This endpoint is unstable, and can throw an `Error`.\n     *   Check progress on [MSC4140](https://github.com/matrix-org/matrix-spec-proposals/pull/4140) for more details.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    // eslint-disable-next-line\n    public async _unstable_sendDelayedEvent<K extends keyof TimelineEvents>(\n        roomId: string,\n        delayOpts: SendDelayedEventRequestOpts,\n        threadId: string | null,\n        eventType: K,\n        content: TimelineEvents[K],\n        txnId?: string,\n    ): Promise<SendDelayedEventResponse> {\n        if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {\n            throw new UnsupportedDelayedEventsEndpointError(\n                \"Server does not support the delayed events API\",\n                \"sendDelayedEvent\",\n            );\n        }\n\n        this.addThreadRelationIfNeeded(content, threadId, roomId);\n        return this.sendCompleteEvent({\n            roomId,\n            threadId,\n            eventObject: { type: eventType, content },\n            delayOpts,\n            txnId,\n        });\n    }\n\n    /**\n     * Send a delayed sticky timeline event.\n     *\n     * Note: This endpoint is unstable, and can throw an `Error`.\n     *   Check progress on [MSC4140](https://github.com/matrix-org/matrix-spec-proposals/pull/4140) and\n     *   [MSC4354](https://github.com/matrix-org/matrix-spec-proposals/pull/4354) for more details.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    // eslint-disable-next-line\n    public async _unstable_sendStickyDelayedEvent<K extends keyof TimelineEvents>(\n        roomId: string,\n        stickDuration: number,\n        delayOpts: SendDelayedEventRequestOpts,\n        threadId: string | null,\n        eventType: K,\n        content: TimelineEvents[K] & { msc4354_sticky_key?: string },\n        txnId?: string,\n    ): Promise<SendDelayedEventResponse> {\n        if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {\n            throw new UnsupportedDelayedEventsEndpointError(\n                \"Server does not support the delayed events API\",\n                \"getDelayedEvents\",\n            );\n        }\n        if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4354_STICKY_EVENTS))) {\n            throw new UnsupportedStickyEventsEndpointError(\n                \"Server does not support the sticky events\",\n                \"sendStickyEvent\",\n            );\n        }\n\n        this.addThreadRelationIfNeeded(content, threadId, roomId);\n        return this.sendCompleteEvent({\n            roomId,\n            threadId,\n            eventObject: { type: eventType, content },\n            queryDict: { \"org.matrix.msc4354.sticky_duration_ms\": stickDuration },\n            delayOpts,\n            txnId,\n        });\n    }\n\n    /**\n     * Send a delayed state event.\n     *\n     * Note: This endpoint is unstable, and can throw an `Error`.\n     *   Check progress on [MSC4140](https://github.com/matrix-org/matrix-spec-proposals/pull/4140) for more details.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    // eslint-disable-next-line\n    public async _unstable_sendDelayedStateEvent<K extends keyof StateEvents>(\n        roomId: string,\n        delayOpts: SendDelayedEventRequestOpts,\n        eventType: K,\n        content: StateEvents[K],\n        stateKey = \"\",\n        opts: IRequestOpts = {},\n    ): Promise<SendDelayedEventResponse> {\n        if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {\n            throw new UnsupportedDelayedEventsEndpointError(\n                \"Server does not support the delayed events API\",\n                \"sendDelayedStateEvent\",\n            );\n        }\n\n        const pathParams = {\n            $roomId: roomId,\n            $eventType: eventType,\n            $stateKey: stateKey,\n        };\n        let path = utils.encodeUri(\"/rooms/$roomId/state/$eventType\", pathParams);\n        if (stateKey !== undefined) {\n            path = utils.encodeUri(path + \"/$stateKey\", pathParams);\n        }\n        return this.http.authedRequest(Method.Put, path, getUnstableDelayQueryOpts(delayOpts), content as Body, opts);\n    }\n\n    /**\n     * Send a sticky timeline event.\n     *\n     * Note: This endpoint is unstable, and can throw an `Error`.\n     *   Check progress on [MSC4354](https://github.com/matrix-org/matrix-spec-proposals/pull/4354) for more details.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    // eslint-disable-next-line\n    public async _unstable_sendStickyEvent<K extends keyof TimelineEvents>(\n        roomId: string,\n        stickDuration: number,\n        threadId: string | null,\n        eventType: K,\n        content: TimelineEvents[K] & { msc4354_sticky_key?: string },\n        txnId?: string,\n    ): Promise<ISendEventResponse> {\n        if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4354_STICKY_EVENTS))) {\n            throw new UnsupportedStickyEventsEndpointError(\n                \"Server does not support the sticky events\",\n                \"sendStickyEvent\",\n            );\n        }\n\n        this.addThreadRelationIfNeeded(content, threadId, roomId);\n        return this.sendCompleteEvent({\n            roomId,\n            threadId,\n            eventObject: { type: eventType, content },\n            queryDict: { \"org.matrix.msc4354.sticky_duration_ms\": stickDuration },\n            txnId,\n        });\n    }\n\n    /**\n     * Get information about delayed events owned by the requesting user.\n     *\n     * Note: This endpoint is unstable, and can throw an `Error`.\n     *   Check progress on [MSC4140](https://github.com/matrix-org/matrix-spec-proposals/pull/4140) for more details.\n     */\n    // eslint-disable-next-line\n    public async _unstable_getDelayedEvents(\n        status?: \"scheduled\" | \"finalised\",\n        delayId?: string | string[],\n        fromToken?: string,\n    ): Promise<DelayedEventInfo> {\n        if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {\n            throw new UnsupportedDelayedEventsEndpointError(\n                \"Server does not support the delayed events API\",\n                \"getDelayedEvents\",\n            );\n        }\n\n        const queryDict = {\n            from: fromToken,\n            status,\n            delay_id: delayId,\n        };\n        return await this.http.authedRequest(Method.Get, \"/delayed_events\", queryDict, undefined, {\n            prefix: `${ClientPrefix.Unstable}/${UNSTABLE_MSC4140_DELAYED_EVENTS}`,\n        });\n    }\n\n    /**\n     * Manage a delayed event associated with the given delay_id.\n     *\n     * Note: This endpoint is unstable, and can throw an `Error`.\n     *   Check progress on [MSC4140](https://github.com/matrix-org/matrix-spec-proposals/pull/4140) for more details.\n     *\n     * @deprecated Instead use one of:\n     * - {@link _unstable_cancelScheduledDelayedEvent}\n     * - {@link _unstable_restartScheduledDelayedEvent}\n     * - {@link _unstable_sendScheduledDelayedEvent}\n     */\n    // eslint-disable-next-line @typescript-eslint/naming-convention\n    public async _unstable_updateDelayedEvent(\n        delayId: string,\n        action: UpdateDelayedEventAction,\n        requestOptions: IRequestOpts = {},\n    ): Promise<EmptyObject> {\n        if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {\n            throw new UnsupportedDelayedEventsEndpointError(\n                \"Server does not support the delayed events API\",\n                \"updateDelayedEvent\",\n            );\n        }\n        return await this.updateScheduledDelayedEventWithActionInBody(delayId, action, requestOptions);\n    }\n\n    /**\n     * Cancel the scheduled delivery of the delayed event matching the provided delayId.\n     *\n     * Note: This endpoint is unstable, and can throw an `Error`.\n     *   Check progress on [MSC4140](https://github.com/matrix-org/matrix-spec-proposals/pull/4140) for more details.\n     *\n     * @throws A M_NOT_FOUND error if no matching delayed event could be found.\n     */\n    // eslint-disable-next-line @typescript-eslint/naming-convention\n    public async _unstable_cancelScheduledDelayedEvent(\n        delayId: string,\n        requestOptions: IRequestOpts = {},\n    ): Promise<EmptyObject> {\n        return await this.updateScheduledDelayedEvent(delayId, UpdateDelayedEventAction.Cancel, requestOptions);\n    }\n\n    /**\n     * Restart the scheduled delivery of the delayed event matching the given delayId.\n     *\n     * Note: This endpoint is unstable, and can throw an `Error`.\n     *   Check progress on [MSC4140](https://github.com/matrix-org/matrix-spec-proposals/pull/4140) for more details.\n     *\n     * @throws A M_NOT_FOUND error if no matching delayed event could be found.\n     */\n    // eslint-disable-next-line @typescript-eslint/naming-convention\n    public async _unstable_restartScheduledDelayedEvent(\n        delayId: string,\n        requestOptions: IRequestOpts = {},\n    ): Promise<EmptyObject> {\n        return await this.updateScheduledDelayedEvent(delayId, UpdateDelayedEventAction.Restart, requestOptions);\n    }\n\n    /**\n     * Immediately send the delayed event matching the given delayId,\n     * instead of waiting for its scheduled delivery.\n     *\n     * Note: This endpoint is unstable, and can throw an `Error`.\n     *   Check progress on [MSC4140](https://github.com/matrix-org/matrix-spec-proposals/pull/4140) for more details.\n     *\n     * @throws A M_NOT_FOUND error if no matching delayed event could be found.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    // eslint-disable-next-line @typescript-eslint/naming-convention\n    public async _unstable_sendScheduledDelayedEvent(\n        delayId: string,\n        requestOptions: IRequestOpts = {},\n    ): Promise<EmptyObject> {\n        return await this.updateScheduledDelayedEvent(delayId, UpdateDelayedEventAction.Send, requestOptions);\n    }\n\n    private async updateScheduledDelayedEvent(\n        delayId: string,\n        action: UpdateDelayedEventAction,\n        requestOptions: IRequestOpts = {},\n    ): Promise<EmptyObject> {\n        if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {\n            throw new UnsupportedDelayedEventsEndpointError(\n                \"Server does not support the delayed events API\",\n                `${action}ScheduledDelayedEvent`,\n            );\n        }\n\n        try {\n            const path = utils.encodeUri(\"/delayed_events/$delayId/$action\", {\n                $delayId: delayId,\n                $action: action,\n            });\n            // Although this endpoint supports being used without authentication,\n            // do authenticate here to make ratelimiting apply per user instead of per source IP address,\n            // if the server supports that\n            return await this.http.authedRequest(Method.Post, path, undefined, undefined, {\n                ...requestOptions,\n                prefix: `${ClientPrefix.Unstable}/${UNSTABLE_MSC4140_DELAYED_EVENTS}`,\n            });\n        } catch (e) {\n            if (e instanceof MatrixError && e.errcode === \"M_UNRECOGNIZED\") {\n                // For backwards compatibility with an older version of this endpoint\n                // which put the update action in the request body instead of the path\n                return await this.updateScheduledDelayedEventWithActionInBody(delayId, action, requestOptions);\n            } else {\n                throw e;\n            }\n        }\n    }\n\n    /**\n     * @deprecated Present for backwards compatibility with an older version of MSC4140\n     * which had a single, authenticated endpoint for updating a delayed event, instead\n     * of one unauthenticated endpoint per update action.\n     */\n    private async updateScheduledDelayedEventWithActionInBody(\n        delayId: string,\n        action: UpdateDelayedEventAction,\n        requestOptions: IRequestOpts = {},\n    ): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/delayed_events/$delayId\", {\n            $delayId: delayId,\n        });\n        const data = {\n            action,\n        };\n        try {\n            return await this.http.request(Method.Post, path, undefined, data, {\n                ...requestOptions,\n                prefix: `${ClientPrefix.Unstable}/${UNSTABLE_MSC4140_DELAYED_EVENTS}`,\n            });\n        } catch (e) {\n            if (e instanceof MatrixError && e.errcode === \"M_MISSING_TOKEN\") {\n                // For backwards compatibility with an older version of this endpoint\n                // which required authentication\n                return await this.http.authedRequest(Method.Post, path, undefined, data, {\n                    ...requestOptions,\n                    prefix: `${ClientPrefix.Unstable}/${UNSTABLE_MSC4140_DELAYED_EVENTS}`,\n                });\n            } else {\n                throw e;\n            }\n        }\n    }\n\n    /**\n     * Send a receipt.\n     * @param event - The event being acknowledged\n     * @param receiptType - The kind of receipt e.g. \"m.read\". Other than\n     * ReceiptType.Read are experimental!\n     * @param body - Additional content to send alongside the receipt.\n     * @param unthreaded - An unthreaded receipt will clear room+thread notifications\n     * @returns Promise which resolves: to an empty object `{}`\n     * @returns Rejects: with an error response.\n     */\n    public async sendReceipt(\n        event: MatrixEvent,\n        receiptType: ReceiptType,\n        body?: Record<string, any>,\n        unthreaded = false,\n    ): Promise<EmptyObject> {\n        if (this.isGuest()) {\n            return Promise.resolve({}); // guests cannot send receipts so don't bother.\n        }\n\n        const path = utils.encodeUri(\"/rooms/$roomId/receipt/$receiptType/$eventId\", {\n            $roomId: event.getRoomId()!,\n            $receiptType: receiptType,\n            $eventId: event.getId()!,\n        });\n\n        // Unless we're explicitly making an unthreaded receipt or we don't\n        // support threads, include the `thread_id` property in the body.\n        const shouldAddThreadId = !unthreaded && this.supportsThreads();\n        const fullBody = shouldAddThreadId ? { ...body, thread_id: threadIdForReceipt(event) } : body;\n\n        const promise = this.http.authedRequest<EmptyObject>(Method.Post, path, undefined, fullBody || {});\n\n        const room = this.getRoom(event.getRoomId());\n        if (room && this.credentials.userId) {\n            room.addLocalEchoReceipt(this.credentials.userId, event, receiptType, unthreaded);\n        }\n        return promise;\n    }\n\n    /**\n     * Send a read receipt.\n     * @param event - The event that has been read.\n     * @param receiptType - other than ReceiptType.Read are experimental! Optional.\n     * @returns Promise which resolves: to an empty object `{}`\n     * @returns Rejects: with an error response.\n     */\n    public async sendReadReceipt(\n        event: MatrixEvent | null,\n        receiptType = ReceiptType.Read,\n        unthreaded = false,\n    ): Promise<EmptyObject | undefined> {\n        if (!event) return;\n        const eventId = event.getId()!;\n        const room = this.getRoom(event.getRoomId());\n        if (room?.hasPendingEvent(eventId)) {\n            throw new Error(`Cannot set read receipt to a pending event (${eventId})`);\n        }\n\n        return this.sendReceipt(event, receiptType, {}, unthreaded);\n    }\n\n    /**\n     * Set a marker to indicate the point in a room before which the user has read every\n     * event. This can be retrieved from room account data (the event type is `m.fully_read`)\n     * and displayed as a horizontal line in the timeline that is visually distinct to the\n     * position of the user's own read receipt.\n     * @param roomId - ID of the room that has been read\n     * @param rmEventId - ID of the event that has been read\n     * @param rrEvent - the event tracked by the read receipt. This is here for\n     * convenience because the RR and the RM are commonly updated at the same time as each\n     * other. The local echo of this receipt will be done if set. Optional.\n     * @param rpEvent - the m.read.private read receipt event for when we don't\n     * want other users to see the read receipts. This is experimental. Optional.\n     * @returns Promise which resolves: the empty object, `{}`.\n     */\n    public async setRoomReadMarkers(\n        roomId: string,\n        rmEventId: string,\n        rrEvent?: MatrixEvent,\n        rpEvent?: MatrixEvent,\n    ): Promise<EmptyObject> {\n        const room = this.getRoom(roomId);\n        if (room?.hasPendingEvent(rmEventId)) {\n            throw new Error(`Cannot set read marker to a pending event (${rmEventId})`);\n        }\n\n        // Add the optional RR update, do local echo like `sendReceipt`\n        let rrEventId: string | undefined;\n        if (rrEvent) {\n            rrEventId = rrEvent.getId()!;\n            if (room?.hasPendingEvent(rrEventId)) {\n                throw new Error(`Cannot set read receipt to a pending event (${rrEventId})`);\n            }\n            room?.addLocalEchoReceipt(this.credentials.userId!, rrEvent, ReceiptType.Read);\n        }\n\n        // Add the optional private RR update, do local echo like `sendReceipt`\n        let rpEventId: string | undefined;\n        if (rpEvent) {\n            rpEventId = rpEvent.getId()!;\n            if (room?.hasPendingEvent(rpEventId)) {\n                throw new Error(`Cannot set read receipt to a pending event (${rpEventId})`);\n            }\n            room?.addLocalEchoReceipt(this.credentials.userId!, rpEvent, ReceiptType.ReadPrivate);\n        }\n\n        return await this.setRoomReadMarkersHttpRequest(roomId, rmEventId, rrEventId, rpEventId);\n    }\n\n    /**\n     *\n     * @param roomId\n     * @param notificationEventId\n     * @returns\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public sendRtcDecline(roomId: string, notificationEventId: string): Promise<ISendEventResponse> {\n        return this.sendEvent(roomId, EventType.RTCDecline, {\n            \"m.relates_to\": { event_id: notificationEventId, rel_type: RelationType.Reference },\n        });\n    }\n\n    /**\n     * Get a preview of the given URL as of (roughly) the given point in time,\n     * described as an object with OpenGraph keys and associated values.\n     * Attributes may be synthesized where actual OG metadata is lacking.\n     * Caches results to prevent hammering the server.\n     * @param url - The URL to get preview data for\n     * @param ts - The preferred point in time that the preview should\n     * describe (ms since epoch).  The preview returned will either be the most\n     * recent one preceding this timestamp if available, or failing that the next\n     * most recent available preview.\n     * @returns Promise which resolves: Object of OG metadata.\n     * @returns Rejects: with an error response.\n     * May return synthesized attributes if the URL lacked OG meta.\n     */\n    public async getUrlPreview(url: string, ts: number): Promise<IPreviewUrlResponse> {\n        // bucket the timestamp to the nearest minute to prevent excessive spam to the server\n        // Surely 60-second accuracy is enough for anyone.\n        ts = Math.floor(ts / 60000) * 60000;\n\n        const parsed = new URL(url);\n        parsed.hash = \"\"; // strip the hash as it won't affect the preview\n        url = parsed.toString();\n\n        const key = ts + \"_\" + url;\n\n        // If there's already a request in flight (or we've handled it), return that instead.\n        if (key in this.urlPreviewCache) {\n            return this.urlPreviewCache[key];\n        }\n\n        const supportsNewEndpoint = await this.isVersionSupported(\"v1.11\");\n\n        const resp = this.http.authedRequest<IPreviewUrlResponse>(\n            Method.Get,\n            supportsNewEndpoint ? \"/media/preview_url\" : \"/preview_url\",\n            {\n                url,\n                ts: ts.toString(),\n            },\n            undefined,\n            {\n                prefix: supportsNewEndpoint ? ClientPrefix.V1 : MediaPrefix.V3,\n                priority: \"low\",\n            },\n        );\n        // TODO: Expire the URL preview cache sometimes\n        this.urlPreviewCache[key] = resp;\n        return resp;\n    }\n\n    /**\n     * @returns Promise which resolves: to an empty object `{}`\n     * @returns Rejects: with an error response.\n     */\n    public sendTyping(roomId: string, isTyping: boolean, timeoutMs: number): Promise<EmptyObject> {\n        if (this.isGuest()) {\n            return Promise.resolve({}); // guests cannot send typing notifications so don't bother.\n        }\n\n        const path = utils.encodeUri(\"/rooms/$roomId/typing/$userId\", {\n            $roomId: roomId,\n            $userId: this.getUserId()!,\n        });\n        const data: QueryDict = {\n            typing: isTyping,\n        };\n        if (isTyping) {\n            data.timeout = timeoutMs ? timeoutMs : 20000;\n        }\n        return this.http.authedRequest(Method.Put, path, undefined, data);\n    }\n\n    /**\n     * Determines the history of room upgrades for a given room, as far as the\n     * client can see. Returns an array of Rooms where the first entry is the\n     * oldest and the last entry is the newest (likely current) room. If the\n     * provided room is not found, this returns an empty list. This works in\n     * both directions, looking for older and newer rooms of the given room.\n     * @param roomId - The room ID to search from\n     * @param verifyLinks - If true, the function will only return rooms\n     * which can be proven to be linked. For example, rooms which have a create\n     * event pointing to an old room which the client is not aware of or doesn't\n     * have a matching tombstone would not be returned.\n     * @param msc3946ProcessDynamicPredecessor - if true, look for\n     * m.room.predecessor state events as well as create events, and prefer\n     * predecessor events where they exist (MSC3946).\n     * @returns An array of rooms representing the upgrade\n     * history.\n     */\n    public getRoomUpgradeHistory(\n        roomId: string,\n        verifyLinks = false,\n        msc3946ProcessDynamicPredecessor = false,\n    ): Room[] {\n        const currentRoom = this.getRoom(roomId);\n        if (!currentRoom) return [];\n\n        const before = this.findPredecessorRooms(currentRoom, verifyLinks, msc3946ProcessDynamicPredecessor);\n        const after = this.findSuccessorRooms(currentRoom, verifyLinks, msc3946ProcessDynamicPredecessor);\n\n        return [...before, currentRoom, ...after];\n    }\n\n    private findPredecessorRooms(room: Room, verifyLinks: boolean, msc3946ProcessDynamicPredecessor: boolean): Room[] {\n        const ret: Room[] = [];\n        const seenRoomIDs = new Set<string>([room.roomId]);\n\n        // Work backwards from newer to older rooms\n        let predecessorRoomId = room.findPredecessor(msc3946ProcessDynamicPredecessor)?.roomId;\n        while (predecessorRoomId !== null) {\n            if (predecessorRoomId) {\n                if (seenRoomIDs.has(predecessorRoomId)) break;\n                seenRoomIDs.add(predecessorRoomId);\n            }\n            const predecessorRoom = this.getRoom(predecessorRoomId);\n            if (predecessorRoom === null) {\n                break;\n            }\n            if (verifyLinks) {\n                const tombstone = predecessorRoom.currentState.getStateEvents(EventType.RoomTombstone, \"\");\n                if (!tombstone || tombstone.getContent()[\"replacement_room\"] !== room.roomId) {\n                    break;\n                }\n            }\n\n            // Insert at the front because we're working backwards from the currentRoom\n            ret.splice(0, 0, predecessorRoom);\n\n            room = predecessorRoom;\n            predecessorRoomId = room.findPredecessor(msc3946ProcessDynamicPredecessor)?.roomId;\n        }\n        return ret;\n    }\n\n    private findSuccessorRooms(room: Room, verifyLinks: boolean, msc3946ProcessDynamicPredecessor: boolean): Room[] {\n        const ret: Room[] = [];\n\n        // Work forwards, looking at tombstone events\n        let tombstoneEvent = room.currentState.getStateEvents(EventType.RoomTombstone, \"\");\n        while (tombstoneEvent) {\n            const successorRoom = this.getRoom(tombstoneEvent.getContent()[\"replacement_room\"]);\n            if (!successorRoom) break; // end of the chain\n            if (successorRoom.roomId === room.roomId) break; // Tombstone is referencing its own room\n\n            if (verifyLinks) {\n                const predecessorRoomId = successorRoom.findPredecessor(msc3946ProcessDynamicPredecessor)?.roomId;\n                if (!predecessorRoomId || predecessorRoomId !== room.roomId) {\n                    break;\n                }\n            }\n\n            // Push to the end because we're looking forwards\n            ret.push(successorRoom);\n            const roomIds = new Set(ret.map((ref) => ref.roomId));\n            if (roomIds.size < ret.length) {\n                // The last room added to the list introduced a previous roomId\n                // To avoid recursion, return the last rooms - 1\n                return ret.slice(0, ret.length - 1);\n            }\n\n            // Set the current room to the reference room so we know where we're at\n            room = successorRoom;\n            tombstoneEvent = room.currentState.getStateEvents(EventType.RoomTombstone, \"\");\n        }\n        return ret;\n    }\n\n    /**\n     * Send an invite to the given user to join the given room.\n     *\n     * @param roomId - The ID of the room to which the user should be invited.\n     * @param userId - The ID of the user that should be invited.\n     * @param opts - Optional reason object. For backwards compatibility, a string is also accepted, and will be interpreted as a reason.\n     *\n     * @returns An empty object.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public async invite(roomId: string, userId: string, opts: InviteOpts | string = {}): Promise<EmptyObject> {\n        if (typeof opts != \"object\") {\n            opts = { reason: opts };\n        }\n\n        const historyVisibility = this.getRoom(roomId)?.getHistoryVisibility() ?? HistoryVisibility.Shared;\n        // We should only share room history if the *current* visibility allows it.\n        if ([HistoryVisibility.Invited, HistoryVisibility.Joined].includes(historyVisibility)) {\n            this.logger.debug(\"Not sharing message history as the room history visibility is currently unshared\");\n        } else {\n            await this.cryptoBackend?.shareRoomHistoryWithUser(roomId, userId);\n        }\n\n        return await this.membershipChange(roomId, userId, KnownMembership.Invite, opts.reason);\n    }\n\n    /**\n     * Invite a user to a room based on their email address.\n     * @param roomId - The room to invite the user to.\n     * @param email - The email address to invite.\n     * @returns Promise which resolves: `{}` an empty object.\n     * @returns Rejects: with an error response.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public inviteByEmail(roomId: string, email: string): Promise<EmptyObject> {\n        return this.inviteByThreePid(roomId, \"email\", email);\n    }\n\n    /**\n     * Invite a user to a room based on a third-party identifier.\n     * @param roomId - The room to invite the user to.\n     * @param medium - The medium to invite the user e.g. \"email\".\n     * @param address - The address for the specified medium.\n     * @returns Promise which resolves: `{}` an empty object.\n     * @returns Rejects: with an error response.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public async inviteByThreePid(roomId: string, medium: string, address: string): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/rooms/$roomId/invite\", { $roomId: roomId });\n\n        const identityServerUrl = this.getIdentityServerUrl(true);\n        if (!identityServerUrl) {\n            return Promise.reject(\n                new MatrixError({\n                    error: \"No supplied identity server URL\",\n                    errcode: \"ORG.MATRIX.JSSDK_MISSING_PARAM\",\n                }),\n            );\n        }\n        const params: Record<string, string> = {\n            id_server: identityServerUrl,\n            medium: medium,\n            address: address,\n        };\n\n        if (this.identityServer?.getAccessToken) {\n            const identityAccessToken = await this.identityServer.getAccessToken();\n            if (identityAccessToken) {\n                params[\"id_access_token\"] = identityAccessToken;\n            }\n        }\n\n        return this.http.authedRequest(Method.Post, path, undefined, params);\n    }\n\n    /**\n     * @returns Promise which resolves: `{}` an empty object.\n     * @returns Rejects: with an error response.\n     */\n    public leave(roomId: string): Promise<EmptyObject> {\n        return this.membershipChange(roomId, undefined, KnownMembership.Leave);\n    }\n\n    /**\n     * Leaves all rooms in the chain of room upgrades based on the given room. By\n     * default, this will leave all the previous and upgraded rooms, including the\n     * given room. To only leave the given room and any previous rooms, keeping the\n     * upgraded (modern) rooms untouched supply `false` to `includeFuture`.\n     * @param roomId - The room ID to start leaving at\n     * @param includeFuture - If true, the whole chain (past and future) of\n     * upgraded rooms will be left.\n     * @returns Promise which resolves when completed with an object keyed\n     * by room ID and value of the error encountered when leaving or null.\n     */\n    public leaveRoomChain(\n        roomId: string,\n        includeFuture = true,\n    ): Promise<{ [roomId: string]: Error | MatrixError | null }> {\n        const upgradeHistory = this.getRoomUpgradeHistory(roomId, true);\n\n        let eligibleToLeave = upgradeHistory;\n        if (!includeFuture) {\n            eligibleToLeave = [];\n            for (const room of upgradeHistory) {\n                eligibleToLeave.push(room);\n                if (room.roomId === roomId) {\n                    break;\n                }\n            }\n        }\n\n        const populationResults: { [roomId: string]: Error } = {};\n        const promises: Promise<unknown>[] = [];\n\n        const doLeave = (roomId: string): Promise<void> => {\n            return this.leave(roomId)\n                .then(() => {\n                    delete populationResults[roomId];\n                })\n                .catch((err) => {\n                    // suppress error\n                    populationResults[roomId] = err;\n                });\n        };\n\n        for (const room of eligibleToLeave) {\n            promises.push(doLeave(room.roomId));\n        }\n\n        return Promise.all(promises).then(() => populationResults);\n    }\n\n    /**\n     * @param reason - Optional.\n     * @returns Promise which resolves: TODO\n     * @returns Rejects: with an error response.\n     */\n    public ban(roomId: string, userId: string, reason?: string): Promise<EmptyObject> {\n        return this.membershipChange(roomId, userId, KnownMembership.Ban, reason);\n    }\n\n    /**\n     * @param deleteRoom - True to delete the room from the store on success.\n     * Default: true.\n     * @returns Promise which resolves: `{}` an empty object.\n     * @returns Rejects: with an error response.\n     */\n    public async forget(roomId: string, deleteRoom = true): Promise<EmptyObject> {\n        // API returns an empty object\n        const path = utils.encodeUri(\"/rooms/$room_id/forget\", {\n            $room_id: roomId,\n        });\n        const response = await this.http.authedRequest<EmptyObject>(Method.Post, path);\n        if (deleteRoom) {\n            this.store.removeRoom(roomId);\n            this.emit(ClientEvent.DeleteRoom, roomId);\n        }\n        return response;\n    }\n\n    /**\n     * @returns Promise which resolves: Object (currently empty)\n     * @returns Rejects: with an error response.\n     */\n    public unban(roomId: string, userId: string): Promise<EmptyObject> {\n        // unbanning != set their state to leave: this used to be\n        // the case, but was then changed so that leaving was always\n        // a revoking of privilege, otherwise two people racing to\n        // kick / ban someone could end up banning and then un-banning\n        // them.\n        const path = utils.encodeUri(\"/rooms/$roomId/unban\", {\n            $roomId: roomId,\n        });\n        const data = {\n            user_id: userId,\n        };\n        return this.http.authedRequest(Method.Post, path, undefined, data);\n    }\n\n    /**\n     * @param reason - Optional.\n     * @returns Promise which resolves: `{}` an empty object.\n     * @returns Rejects: with an error response.\n     */\n    public kick(roomId: string, userId: string, reason?: string): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/rooms/$roomId/kick\", {\n            $roomId: roomId,\n        });\n        const data = {\n            user_id: userId,\n            reason: reason,\n        };\n        return this.http.authedRequest(Method.Post, path, undefined, data);\n    }\n\n    private membershipChange(\n        roomId: string,\n        userId: string | undefined,\n        membership: Membership,\n        reason?: string,\n    ): Promise<EmptyObject> {\n        // API returns an empty object\n        const path = utils.encodeUri(\"/rooms/$room_id/$membership\", {\n            $room_id: roomId,\n            $membership: membership,\n        });\n        return this.http.authedRequest(Method.Post, path, undefined, {\n            user_id: userId, // may be undefined e.g. on leave\n            reason: reason,\n        });\n    }\n\n    /**\n     * Obtain a dict of actions which should be performed for this event according\n     * to the push rules for this user.  Caches the dict on the event.\n     * @param event - The event to get push actions for.\n     * @param forceRecalculate - forces to recalculate actions for an event\n     * Useful when an event just got decrypted\n     * @returns A dict of actions to perform.\n     */\n    public getPushActionsForEvent(event: MatrixEvent, forceRecalculate = false): IActionsObject | null {\n        if (!event.getPushActions() || forceRecalculate) {\n            const { actions, rule } = this.pushProcessor.actionsAndRuleForEvent(event);\n            event.setPushDetails(actions, rule);\n        }\n        return event.getPushActions();\n    }\n\n    /**\n     * Obtain a dict of actions which should be performed for this event according\n     * to the push rules for this user.  Caches the dict on the event.\n     * @param event - The event to get push actions for.\n     * @param forceRecalculate - forces to recalculate actions for an event\n     * Useful when an event just got decrypted\n     * @returns A dict of actions to perform.\n     */\n    public getPushDetailsForEvent(event: MatrixEvent, forceRecalculate = false): PushDetails | null {\n        if (!event.getPushDetails() || forceRecalculate) {\n            const { actions, rule } = this.pushProcessor.actionsAndRuleForEvent(event);\n            event.setPushDetails(actions, rule);\n        }\n        return event.getPushDetails();\n    }\n\n    /**\n     * @param info - The kind of info to set (e.g. 'avatar_url')\n     * @param data - The JSON object to set.\n     * @returns\n     * @returns Rejects: with an error response.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    // eslint-disable-next-line camelcase\n    public setProfileInfo(info: \"avatar_url\", data: { avatar_url: string }): Promise<EmptyObject>;\n    public setProfileInfo(info: \"displayname\", data: { displayname: string }): Promise<EmptyObject>;\n    public setProfileInfo(info: \"avatar_url\" | \"displayname\", data: object): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/profile/$userId/$info\", {\n            $userId: this.credentials.userId!,\n            $info: info,\n        });\n        return this.http.authedRequest(Method.Put, path, undefined, data);\n    }\n\n    /**\n     * @returns Promise which resolves: `{}` an empty object.\n     * @returns Rejects: with an error response.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public async setDisplayName(name: string): Promise<EmptyObject> {\n        const prom = await this.setProfileInfo(\"displayname\", { displayname: name });\n        // XXX: synthesise a profile update for ourselves because Synapse is broken and won't\n        const user = this.getUser(this.getUserId()!);\n        if (user) {\n            user.displayName = name;\n            user.emit(UserEvent.DisplayName, user.events.presence, user);\n        }\n        return prom;\n    }\n\n    /**\n     * @returns Promise which resolves: `{}` an empty object.\n     * @returns Rejects: with an error response.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public async setAvatarUrl(url: string): Promise<EmptyObject> {\n        const prom = await this.setProfileInfo(\"avatar_url\", { avatar_url: url });\n        // XXX: synthesise a profile update for ourselves because Synapse is broken and won't\n        const user = this.getUser(this.getUserId()!);\n        if (user) {\n            user.avatarUrl = url;\n            user.emit(UserEvent.AvatarUrl, user.events.presence, user);\n        }\n        return prom;\n    }\n\n    /**\n     * Turn an MXC URL into an HTTP one. <strong>This method is experimental and\n     * may change.</strong>\n     * @param mxcUrl - The MXC URL\n     * @param width - The desired width of the thumbnail.\n     * @param height - The desired height of the thumbnail.\n     * @param resizeMethod - The thumbnail resize method to use, either\n     * \"crop\" or \"scale\".\n     * @param allowDirectLinks - If true, return any non-mxc URLs\n     * directly. Fetching such URLs will leak information about the user to\n     * anyone they share a room with. If false, will return null for such URLs.\n     * @param allowRedirects - If true, the caller supports the URL being 307 or\n     * 308 redirected to another resource upon request. If false, redirects\n     * are not expected. Implied `true` when `useAuthentication` is `true`.\n     * @param useAuthentication - If true, the caller supports authenticated\n     * media and wants an authentication-required URL. Note that server support\n     * for authenticated media will *not* be checked - it is the caller's responsibility\n     * to do so before calling this function. Note also that `useAuthentication`\n     * implies `allowRedirects`. Defaults to false (unauthenticated endpoints).\n     * @returns the avatar URL or null.\n     */\n    public mxcUrlToHttp(\n        mxcUrl: string,\n        width?: number,\n        height?: number,\n        resizeMethod?: string,\n        allowDirectLinks?: boolean,\n        allowRedirects?: boolean,\n        useAuthentication?: boolean,\n    ): string | null {\n        return getHttpUriForMxc(\n            this.baseUrl,\n            mxcUrl,\n            width,\n            height,\n            resizeMethod,\n            allowDirectLinks,\n            allowRedirects,\n            useAuthentication,\n        );\n    }\n\n    /**\n     * Specify the set_presence value to be used for subsequent calls to the Sync API.\n     * This has an advantage over calls to the PUT /presence API in that it\n     * doesn't clobber status_msg set by other devices.\n     * @param presence - the presence to specify to set_presence of sync calls\n     */\n    public async setSyncPresence(presence?: SetPresence): Promise<void> {\n        this.syncApi?.setPresence(presence);\n    }\n\n    /**\n     * @param opts - Options to apply\n     * @returns Promise which resolves\n     * @returns Rejects: with an error response.\n     * @throws If 'presence' isn't a valid presence enum value.\n     */\n    public async setPresence(opts: IPresenceOpts): Promise<void> {\n        const path = utils.encodeUri(\"/presence/$userId/status\", {\n            $userId: this.credentials.userId!,\n        });\n\n        const validStates = [\"offline\", \"online\", \"unavailable\"];\n        if (validStates.indexOf(opts.presence) === -1) {\n            throw new Error(\"Bad presence value: \" + opts.presence);\n        }\n        await this.http.authedRequest(Method.Put, path, undefined, opts);\n    }\n\n    /**\n     * @param userId - The user to get presence for\n     * @returns Promise which resolves: The presence state for this user.\n     * @returns Rejects: with an error response.\n     */\n    public getPresence(userId: string): Promise<IStatusResponse> {\n        const path = utils.encodeUri(\"/presence/$userId/status\", {\n            $userId: userId,\n        });\n\n        return this.http.authedRequest(Method.Get, path);\n    }\n\n    /**\n     * Retrieve older messages from the given room and put them in the timeline.\n     *\n     * If this is called multiple times whilst a request is ongoing, the <i>same</i>\n     * Promise will be returned. If there was a problem requesting scrollback, there\n     * will be a small delay before another request can be made (to prevent tight-looping\n     * when there is no connection).\n     *\n     * @param room - The room to get older messages in.\n     * @param limit - Optional. The maximum number of previous events to\n     * pull in. Default: 30.\n     * @returns Promise which resolves: Room. If you are at the beginning\n     * of the timeline, `Room.oldState.paginationToken` will be\n     * `null`.\n     * @returns Rejects: with an error response.\n     */\n    public scrollback(room: Room, limit = 30): Promise<Room> {\n        let timeToWaitMs = 0;\n\n        let info = this.ongoingScrollbacks[room.roomId] || {};\n        if (info.promise) {\n            return info.promise;\n        } else if (info.errorTs) {\n            const timeWaitedMs = Date.now() - info.errorTs;\n            timeToWaitMs = Math.max(SCROLLBACK_DELAY_MS - timeWaitedMs, 0);\n        }\n\n        if (room.oldState.paginationToken === null) {\n            return Promise.resolve(room); // already at the start.\n        }\n        // attempt to grab more events from the store first\n        const numAdded = this.store.scrollback(room, limit).length;\n        if (numAdded === limit) {\n            // store contained everything we needed.\n            return Promise.resolve(room);\n        }\n        // reduce the required number of events appropriately\n        limit = limit - numAdded;\n\n        const promise = new Promise<Room>((resolve, reject) => {\n            // wait for a time before doing this request\n            // (which may be 0 in order not to special case the code paths)\n            sleep(timeToWaitMs)\n                .then(() => {\n                    return this.createMessagesRequest(\n                        room.roomId,\n                        room.oldState.paginationToken,\n                        limit,\n                        Direction.Backward,\n                    );\n                })\n                .then((res: IMessagesResponse) => {\n                    const matrixEvents = res.chunk.map(this.getEventMapper());\n                    if (res.state) {\n                        const stateEvents = res.state.map(this.getEventMapper());\n                        room.currentState.setUnknownStateEvents(stateEvents);\n                    }\n\n                    const [timelineEvents, threadedEvents, unknownRelations] =\n                        room.partitionThreadedEvents(matrixEvents);\n\n                    this.processAggregatedTimelineEvents(room, timelineEvents);\n                    room.addEventsToTimeline(timelineEvents, true, true, room.getLiveTimeline());\n                    this.processThreadEvents(room, threadedEvents, true);\n                    unknownRelations.forEach((event) => room.relations.aggregateChildEvent(event));\n\n                    room.oldState.paginationToken = res.end ?? null;\n                    if (res.chunk.length === 0) {\n                        room.oldState.paginationToken = null;\n                    }\n                    this.store.storeEvents(room, matrixEvents, res.end ?? null, true);\n                    delete this.ongoingScrollbacks[room.roomId];\n                    resolve(room);\n                })\n                .catch((err) => {\n                    this.ongoingScrollbacks[room.roomId] = {\n                        errorTs: Date.now(),\n                    };\n                    reject(err);\n                });\n        });\n\n        info = { promise };\n\n        this.ongoingScrollbacks[room.roomId] = info;\n        return promise;\n    }\n\n    public getEventMapper(options?: MapperOpts): EventMapper {\n        return eventMapperFor(this, options || {});\n    }\n\n    /**\n     * Calls the `/context` API for the given room ID & event ID.\n     * Returns the response, with `event` asserted and all optional arrays defaulted to an empty array.\n     * @param roomId - the room ID to request a context for\n     * @param eventId - the event ID to request a context for\n     * @throws if `event` in the response is missing\n     * @private\n     */\n    private async getEventContext(\n        roomId: string,\n        eventId: string,\n    ): Promise<IContextResponse & Omit<Required<IContextResponse>, \"start\" | \"end\">> {\n        const path = utils.encodeUri(\"/rooms/$roomId/context/$eventId\", {\n            $roomId: roomId,\n            $eventId: eventId,\n        });\n\n        const params: Record<string, string | string[]> = {\n            limit: \"0\",\n        };\n        if (this.clientOpts?.lazyLoadMembers) {\n            params.filter = JSON.stringify(Filter.LAZY_LOADING_MESSAGES_FILTER);\n        }\n\n        // TODO: we should implement a backoff (as per scrollback()) to deal more nicely with HTTP errors.\n        const res = await this.http.authedRequest<IContextResponse>(Method.Get, path, params);\n        if (res.event) {\n            return {\n                start: res.start,\n                end: res.end,\n                event: res.event,\n                events_after: res.events_after ?? [],\n                events_before: res.events_before ?? [],\n                state: res.state ?? [],\n            };\n        }\n\n        throw new Error(\"'event' not in '/context' result - homeserver too old?\");\n    }\n\n    /**\n     * Get an EventTimeline for the given event\n     *\n     * <p>If the EventTimelineSet object already has the given event in its store, the\n     * corresponding timeline will be returned. Otherwise, a /context request is\n     * made, and used to construct an EventTimeline.\n     * If the event does not belong to this EventTimelineSet then undefined will be returned.\n     *\n     * @param timelineSet -  The timelineSet to look for the event in, must be bound to a room\n     * @param eventId -  The ID of the event to look for\n     *\n     * @returns Promise which resolves:\n     *    {@link EventTimeline} including the given event\n     */\n    public async getEventTimeline(timelineSet: EventTimelineSet, eventId: string): Promise<EventTimeline | null> {\n        // don't allow any timeline support unless it's been enabled.\n        if (!this.timelineSupport) {\n            throw new Error(\n                \"timeline support is disabled. Set the 'timelineSupport'\" +\n                    \" parameter to true when creating MatrixClient to enable it.\",\n            );\n        }\n\n        if (!timelineSet?.room) {\n            throw new Error(\"getEventTimeline only supports room timelines\");\n        }\n\n        if (timelineSet.getTimelineForEvent(eventId)) {\n            return timelineSet.getTimelineForEvent(eventId);\n        }\n\n        if (timelineSet.thread && this.supportsThreads()) {\n            return (await this.getThreadTimeline(timelineSet, eventId)) ?? null;\n        }\n\n        const res = await this.getEventContext(timelineSet.room.roomId, eventId);\n\n        // by the time the request completes, the event might have ended up in the timeline.\n        if (timelineSet.getTimelineForEvent(eventId)) {\n            return timelineSet.getTimelineForEvent(eventId);\n        }\n\n        const mapper = this.getEventMapper();\n        const event = mapper(res.event);\n        if (event.isRelation(THREAD_RELATION_TYPE.name)) {\n            this.logger.warn(\"Tried loading a regular timeline at the position of a thread event\");\n            return null;\n        }\n        const events = [\n            // Order events from most recent to oldest (reverse-chronological).\n            // We start with the last event, since that's the point at which we have known state.\n            // events_after is already backwards; events_before is forwards.\n            ...res.events_after.reverse().map(mapper),\n            event,\n            ...res.events_before.map(mapper),\n        ];\n\n        // Here we handle non-thread timelines only, but still process any thread events to populate thread summaries.\n        let timeline = timelineSet.getTimelineForEvent(events[0].getId());\n        if (timeline) {\n            timeline.getState(EventTimeline.BACKWARDS)!.setUnknownStateEvents(res.state.map(mapper));\n        } else {\n            timeline = timelineSet.addTimeline();\n            timeline.initialiseState(res.state.map(mapper));\n            timeline.getState(EventTimeline.FORWARDS)!.paginationToken = res.end ?? null;\n        }\n\n        const [timelineEvents, threadedEvents, unknownRelations] = timelineSet.room.partitionThreadedEvents(events);\n        timelineSet.addEventsToTimeline(timelineEvents, true, false, timeline, res.start);\n        // The target event is not in a thread but process the contextual events, so we can show any threads around it.\n        this.processThreadEvents(timelineSet.room, threadedEvents, true);\n        this.processAggregatedTimelineEvents(timelineSet.room, timelineEvents);\n        unknownRelations.forEach((event) => timelineSet.relations.aggregateChildEvent(event));\n\n        // There is no guarantee that the event ended up in \"timeline\" (we might have switched to a neighbouring\n        // timeline) - so check the room's index again. On the other hand, there's no guarantee the event ended up\n        // anywhere, if it was later redacted, so we just return the timeline we first thought of.\n        return (\n            timelineSet.getTimelineForEvent(eventId) ??\n            timelineSet.room.findThreadForEvent(event)?.liveTimeline ?? // for Threads degraded support\n            timeline\n        );\n    }\n\n    public async getThreadTimeline(timelineSet: EventTimelineSet, eventId: string): Promise<EventTimeline | undefined> {\n        if (!this.supportsThreads()) {\n            throw new Error(\"could not get thread timeline: no client support\");\n        }\n\n        if (!timelineSet.room) {\n            throw new Error(\"could not get thread timeline: not a room timeline\");\n        }\n\n        if (!timelineSet.thread) {\n            throw new Error(\"could not get thread timeline: not a thread timeline\");\n        }\n\n        const res = await this.getEventContext(timelineSet.room.roomId, eventId);\n\n        const mapper = this.getEventMapper();\n        const event = mapper(res.event);\n\n        if (!timelineSet.canContain(event)) {\n            return undefined;\n        }\n\n        const recurse = this.canSupport.get(Feature.RelationsRecursion) !== ServerSupport.Unsupported;\n        if (Thread.hasServerSideSupport) {\n            if (Thread.hasServerSideFwdPaginationSupport) {\n                if (!timelineSet.thread) {\n                    throw new Error(\"could not get thread timeline: not a thread timeline\");\n                }\n\n                const thread = timelineSet.thread;\n                const resOlder: IRelationsResponse = await this.fetchRelations(\n                    timelineSet.room.roomId,\n                    thread.id,\n                    null,\n                    null,\n                    { dir: Direction.Backward, from: res.start, recurse: recurse || undefined },\n                );\n                const resNewer: IRelationsResponse = await this.fetchRelations(\n                    timelineSet.room.roomId,\n                    thread.id,\n                    null,\n                    null,\n                    { dir: Direction.Forward, from: res.end, recurse: recurse || undefined },\n                );\n                const events = [\n                    // Order events from most recent to oldest (reverse-chronological).\n                    // We start with the last event, since that's the point at which we have known state.\n                    // events_after is already backwards; events_before is forwards.\n                    ...resNewer.chunk.reverse().filter(getRelationsThreadFilter(thread.id)).map(mapper),\n                    event,\n                    ...resOlder.chunk.filter(getRelationsThreadFilter(thread.id)).map(mapper),\n                ];\n\n                for (const event of events) {\n                    await timelineSet.thread?.processEvent(event);\n                }\n\n                // Here we handle non-thread timelines only, but still process any thread events to populate thread summaries.\n                let timeline = timelineSet.getTimelineForEvent(event.getId());\n                if (timeline) {\n                    timeline.getState(EventTimeline.BACKWARDS)!.setUnknownStateEvents(res.state.map(mapper));\n                } else {\n                    timeline = timelineSet.addTimeline();\n                    timeline.initialiseState(res.state.map(mapper));\n                }\n\n                timelineSet.addEventsToTimeline(events, true, false, timeline, resNewer.next_batch);\n                if (!resOlder.next_batch) {\n                    const originalEvent = await this.fetchRoomEvent(timelineSet.room.roomId, thread.id);\n                    timelineSet.addEventsToTimeline([mapper(originalEvent)], true, false, timeline, null);\n                }\n                timeline.setPaginationToken(resOlder.next_batch ?? null, Direction.Backward);\n                timeline.setPaginationToken(resNewer.next_batch ?? null, Direction.Forward);\n                this.processAggregatedTimelineEvents(timelineSet.room, events);\n\n                // There is no guarantee that the event ended up in \"timeline\" (we might have switched to a neighbouring\n                // timeline) - so check the room's index again. On the other hand, there's no guarantee the event ended up\n                // anywhere, if it was later redacted, so we just return the timeline we first thought of.\n                return timelineSet.getTimelineForEvent(eventId) ?? timeline;\n            } else {\n                // Where the event is a thread reply (not a root) and running in MSC-enabled mode the Thread timeline only\n                // functions contiguously, so we have to jump through some hoops to get our target event in it.\n                // XXX: workaround for https://github.com/vector-im/element-meta/issues/150\n\n                const thread = timelineSet.thread;\n\n                const resOlder = await this.fetchRelations(\n                    timelineSet.room.roomId,\n                    thread.id,\n                    THREAD_RELATION_TYPE.name,\n                    null,\n                    { dir: Direction.Backward, from: res.start, recurse: recurse || undefined },\n                );\n                const eventsNewer: IEvent[] = [];\n                let nextBatch = res.end;\n                while (nextBatch) {\n                    const resNewer: IRelationsResponse = await this.fetchRelations(\n                        timelineSet.room.roomId,\n                        thread.id,\n                        THREAD_RELATION_TYPE.name,\n                        null,\n                        { dir: Direction.Forward, from: nextBatch, recurse: recurse || undefined },\n                    );\n                    nextBatch = resNewer.next_batch;\n                    eventsNewer.push(...resNewer.chunk);\n                }\n                const events = [\n                    // Order events from most recent to oldest (reverse-chronological).\n                    // We start with the last event, since that's the point at which we have known state.\n                    // events_after is already backwards; events_before is forwards.\n                    ...eventsNewer.reverse().map(mapper),\n                    event,\n                    ...resOlder.chunk.map(mapper),\n                ];\n                for (const event of events) {\n                    await timelineSet.thread?.processEvent(event);\n                }\n\n                // Here we handle non-thread timelines only, but still process any thread events to populate thread\n                // summaries.\n                const timeline = timelineSet.getLiveTimeline();\n                timeline.getState(EventTimeline.BACKWARDS)!.setUnknownStateEvents(res.state.map(mapper));\n\n                timelineSet.addEventsToTimeline(events, true, false, timeline, null);\n                if (!resOlder.next_batch) {\n                    const originalEvent = await this.fetchRoomEvent(timelineSet.room.roomId, thread.id);\n                    timelineSet.addEventsToTimeline([mapper(originalEvent)], true, false, timeline, null);\n                }\n                timeline.setPaginationToken(resOlder.next_batch ?? null, Direction.Backward);\n                timeline.setPaginationToken(null, Direction.Forward);\n                this.processAggregatedTimelineEvents(timelineSet.room, events);\n\n                return timeline;\n            }\n        }\n    }\n\n    /**\n     * Get an EventTimeline for the latest events in the room. This will just\n     * call `/messages` to get the latest message in the room, then use\n     * `client.getEventTimeline(...)` to construct a new timeline from it.\n     *\n     * @param timelineSet -  The timelineSet to find or add the timeline to\n     *\n     * @returns Promise which resolves:\n     *    {@link EventTimeline} timeline with the latest events in the room\n     */\n    public async getLatestTimeline(timelineSet: EventTimelineSet): Promise<EventTimeline | null> {\n        // don't allow any timeline support unless it's been enabled.\n        if (!this.timelineSupport) {\n            throw new Error(\n                \"timeline support is disabled. Set the 'timelineSupport'\" +\n                    \" parameter to true when creating MatrixClient to enable it.\",\n            );\n        }\n\n        if (!timelineSet.room) {\n            throw new Error(\"getLatestTimeline only supports room timelines\");\n        }\n\n        let event: IRoomEvent | undefined;\n        if (timelineSet.threadListType !== null) {\n            const res = await this.createThreadListMessagesRequest(\n                timelineSet.room.roomId,\n                null,\n                1,\n                Direction.Backward,\n                timelineSet.threadListType,\n                timelineSet.getFilter(),\n            );\n            event = res.chunk?.[0];\n        } else if (timelineSet.thread && Thread.hasServerSideSupport) {\n            const recurse = this.canSupport.get(Feature.RelationsRecursion) !== ServerSupport.Unsupported;\n            const res = await this.fetchRelations(\n                timelineSet.room.roomId,\n                timelineSet.thread.id,\n                THREAD_RELATION_TYPE.name,\n                null,\n                { dir: Direction.Backward, limit: 1, recurse: recurse || undefined },\n            );\n            event = res.chunk?.[0];\n        } else {\n            const messagesPath = utils.encodeUri(\"/rooms/$roomId/messages\", {\n                $roomId: timelineSet.room.roomId,\n            });\n\n            const params: Record<string, string | string[]> = {\n                dir: \"b\",\n            };\n            if (this.clientOpts?.lazyLoadMembers) {\n                params.filter = JSON.stringify(Filter.LAZY_LOADING_MESSAGES_FILTER);\n            }\n\n            const res = await this.http.authedRequest<IMessagesResponse>(Method.Get, messagesPath, params);\n            event = res.chunk?.[0];\n        }\n        if (!event) {\n            throw new Error(\"No message returned when trying to construct getLatestTimeline\");\n        }\n\n        return this.getEventTimeline(timelineSet, event.event_id);\n    }\n\n    /**\n     * Makes a request to /messages with the appropriate lazy loading filter set.\n     * XXX: if we do get rid of scrollback (as it's not used at the moment),\n     * we could inline this method again in paginateEventTimeline as that would\n     * then be the only call-site\n     * @param limit - the maximum amount of events the retrieve\n     * @param dir - 'f' or 'b'\n     * @param timelineFilter - the timeline filter to pass\n     */\n    // XXX: Intended private, used in code.\n    public createMessagesRequest(\n        roomId: string,\n        fromToken: string | null,\n        limit = 30,\n        dir: Direction,\n        timelineFilter?: Filter,\n    ): Promise<IMessagesResponse> {\n        const path = utils.encodeUri(\"/rooms/$roomId/messages\", { $roomId: roomId });\n\n        const params: Record<string, string> = {\n            limit: limit.toString(),\n            dir: dir,\n        };\n\n        if (fromToken) {\n            params.from = fromToken;\n        }\n\n        let filter: IRoomEventFilter | null = null;\n        if (this.clientOpts?.lazyLoadMembers) {\n            // create a shallow copy of LAZY_LOADING_MESSAGES_FILTER,\n            // so the timelineFilter doesn't get written into it below\n            filter = Object.assign({}, Filter.LAZY_LOADING_MESSAGES_FILTER);\n        }\n        if (timelineFilter) {\n            // XXX: it's horrific that /messages' filter parameter doesn't match\n            // /sync's one - see https://matrix.org/jira/browse/SPEC-451\n            filter = filter || {};\n            Object.assign(filter, timelineFilter.getRoomTimelineFilterComponent()?.toJSON());\n        }\n        if (filter) {\n            params.filter = JSON.stringify(filter);\n        }\n        return this.http.authedRequest(Method.Get, path, params);\n    }\n\n    /**\n     * Makes a request to /messages with the appropriate lazy loading filter set.\n     * XXX: if we do get rid of scrollback (as it's not used at the moment),\n     * we could inline this method again in paginateEventTimeline as that would\n     * then be the only call-site\n     * @param limit - the maximum amount of events the retrieve\n     * @param dir - 'f' or 'b'\n     * @param timelineFilter - the timeline filter to pass\n     */\n    // XXX: Intended private, used by room.fetchRoomThreads\n    public createThreadListMessagesRequest(\n        roomId: string,\n        fromToken: string | null,\n        limit = 30,\n        dir = Direction.Backward,\n        threadListType: ThreadFilterType | null = ThreadFilterType.All,\n        timelineFilter?: Filter,\n    ): Promise<IMessagesResponse> {\n        const path = utils.encodeUri(\"/rooms/$roomId/threads\", { $roomId: roomId });\n\n        const params: Record<string, string> = {\n            limit: limit.toString(),\n            dir: dir,\n            include: threadFilterTypeToFilter(threadListType),\n        };\n\n        if (fromToken) {\n            params.from = fromToken;\n        }\n\n        let filter: IRoomEventFilter = {};\n        if (this.clientOpts?.lazyLoadMembers) {\n            // create a shallow copy of LAZY_LOADING_MESSAGES_FILTER,\n            // so the timelineFilter doesn't get written into it below\n            filter = {\n                ...Filter.LAZY_LOADING_MESSAGES_FILTER,\n            };\n        }\n        if (timelineFilter) {\n            // XXX: it's horrific that /messages' filter parameter doesn't match\n            // /sync's one - see https://matrix.org/jira/browse/SPEC-451\n            filter = {\n                ...filter,\n                ...timelineFilter.getRoomTimelineFilterComponent()?.toJSON(),\n            };\n        }\n        if (Object.keys(filter).length) {\n            params.filter = JSON.stringify(filter);\n        }\n\n        const opts = {\n            prefix:\n                Thread.hasServerSideListSupport === FeatureSupport.Stable\n                    ? ClientPrefix.V1\n                    : \"/_matrix/client/unstable/org.matrix.msc3856\",\n        };\n\n        return this.http\n            .authedRequest<IThreadedMessagesResponse>(Method.Get, path, params, undefined, opts)\n            .then((res) => ({\n                ...res,\n                chunk: res.chunk?.reverse(),\n                start: res.prev_batch,\n                end: res.next_batch,\n            }));\n    }\n\n    /**\n     * Take an EventTimeline, and back/forward-fill results.\n     *\n     * @param eventTimeline - timeline object to be updated\n     *\n     * @returns Promise which resolves to a boolean: false if there are no\n     *    events and we reached either end of the timeline; else true.\n     */\n    public paginateEventTimeline(eventTimeline: EventTimeline, opts: IPaginateOpts): Promise<boolean> {\n        const isNotifTimeline = eventTimeline.getTimelineSet() === this.notifTimelineSet;\n        const room = this.getRoom(eventTimeline.getRoomId()!);\n        const threadListType = eventTimeline.getTimelineSet().threadListType;\n        const thread = eventTimeline.getTimelineSet().thread;\n\n        // TODO: we should implement a backoff (as per scrollback()) to deal more\n        // nicely with HTTP errors.\n        opts = opts || {};\n        const backwards = opts.backwards || false;\n\n        if (isNotifTimeline) {\n            if (!backwards) {\n                throw new Error(\"paginateNotifTimeline can only paginate backwards\");\n            }\n        }\n\n        const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;\n\n        const token = eventTimeline.getPaginationToken(dir);\n        const pendingRequest = eventTimeline.paginationRequests[dir];\n\n        if (pendingRequest) {\n            // already a request in progress - return the existing promise\n            return pendingRequest;\n        }\n\n        let path: string;\n        let params: Record<string, string>;\n        let promise: Promise<boolean>;\n\n        if (isNotifTimeline) {\n            path = \"/notifications\";\n            params = {\n                limit: (opts.limit ?? 30).toString(),\n                only: \"highlight\",\n            };\n\n            if (token && token !== \"end\") {\n                params.from = token;\n            }\n\n            promise = this.http\n                .authedRequest<INotificationsResponse>(Method.Get, path, params)\n                .then(async (res) => {\n                    const token = res.next_token;\n                    const matrixEvents: MatrixEvent[] = [];\n\n                    res.notifications = res.notifications.filter(noUnsafeEventProps);\n\n                    for (let i = 0; i < res.notifications.length; i++) {\n                        const notification = res.notifications[i];\n                        const event = this.getEventMapper()(notification.event);\n\n                        // @TODO(kerrya) reprocessing every notification is ugly\n                        // remove if we get server MSC3994 support\n                        this.getPushDetailsForEvent(event, true);\n\n                        event.event.room_id = notification.room_id; // XXX: gutwrenching\n                        matrixEvents[i] = event;\n                    }\n\n                    // No need to partition events for threads here, everything lives\n                    // in the notification timeline set\n                    const timelineSet = eventTimeline.getTimelineSet();\n                    timelineSet.addEventsToTimeline(matrixEvents, backwards, false, eventTimeline, token);\n                    this.processAggregatedTimelineEvents(timelineSet.room, matrixEvents);\n\n                    // if we've hit the end of the timeline, we need to stop trying to\n                    // paginate. We need to keep the 'forwards' token though, to make sure\n                    // we can recover from gappy syncs.\n                    if (backwards && !res.next_token) {\n                        eventTimeline.setPaginationToken(null, dir);\n                    }\n                    return Boolean(res.next_token);\n                })\n                .finally(() => {\n                    eventTimeline.paginationRequests[dir] = null;\n                });\n            eventTimeline.paginationRequests[dir] = promise;\n        } else if (threadListType !== null) {\n            if (!room) {\n                throw new Error(\"Unknown room \" + eventTimeline.getRoomId());\n            }\n\n            if (!Thread.hasServerSideFwdPaginationSupport && dir === Direction.Forward) {\n                throw new Error(\"Cannot paginate threads forwards without server-side support for MSC 3715\");\n            }\n\n            promise = this.createThreadListMessagesRequest(\n                eventTimeline.getRoomId()!,\n                token,\n                opts.limit,\n                dir,\n                threadListType,\n                eventTimeline.getFilter(),\n            )\n                .then((res) => {\n                    if (res.state) {\n                        const roomState = eventTimeline.getState(dir)!;\n                        const stateEvents = res.state.filter(noUnsafeEventProps).map(this.getEventMapper());\n                        roomState.setUnknownStateEvents(stateEvents);\n                    }\n\n                    const token = res.end;\n                    const matrixEvents = res.chunk.filter(noUnsafeEventProps).map(this.getEventMapper());\n\n                    const timelineSet = eventTimeline.getTimelineSet();\n                    timelineSet.addEventsToTimeline(matrixEvents, backwards, false, eventTimeline, token);\n                    this.processAggregatedTimelineEvents(room, matrixEvents);\n                    this.processThreadRoots(room, matrixEvents, backwards);\n\n                    // if we've hit the end of the timeline, we need to stop trying to\n                    // paginate. We need to keep the 'forwards' token though, to make sure\n                    // we can recover from gappy syncs.\n                    if (backwards && res.end == res.start) {\n                        eventTimeline.setPaginationToken(null, dir);\n                    }\n                    return res.end !== res.start;\n                })\n                .finally(() => {\n                    eventTimeline.paginationRequests[dir] = null;\n                });\n            eventTimeline.paginationRequests[dir] = promise;\n        } else if (thread) {\n            const room = this.getRoom(eventTimeline.getRoomId() ?? undefined);\n            if (!room) {\n                throw new Error(\"Unknown room \" + eventTimeline.getRoomId());\n            }\n\n            const recurse = this.canSupport.get(Feature.RelationsRecursion) !== ServerSupport.Unsupported;\n            promise = this.fetchRelations(eventTimeline.getRoomId() ?? \"\", thread.id, null, null, {\n                dir,\n                limit: opts.limit,\n                from: token ?? undefined,\n                recurse: recurse || undefined,\n            })\n                .then(async (res) => {\n                    const mapper = this.getEventMapper();\n                    const matrixEvents = res.chunk\n                        .filter(noUnsafeEventProps)\n                        .filter(getRelationsThreadFilter(thread.id))\n                        .map(mapper);\n\n                    // Process latest events first\n                    for (const event of matrixEvents.slice().reverse()) {\n                        await thread?.processEvent(event);\n                        const sender = event.getSender()!;\n                        if (!backwards || thread?.getEventReadUpTo(sender) === null) {\n                            room.addLocalEchoReceipt(sender, event, ReceiptType.Read);\n                        }\n                    }\n\n                    const newToken = res.next_batch;\n\n                    const timelineSet = eventTimeline.getTimelineSet();\n                    timelineSet.addEventsToTimeline(matrixEvents, backwards, false, eventTimeline, newToken ?? null);\n                    if (!newToken && backwards) {\n                        const originalEvent =\n                            thread.rootEvent ??\n                            mapper(await this.fetchRoomEvent(eventTimeline.getRoomId() ?? \"\", thread.id));\n                        timelineSet.addEventsToTimeline([originalEvent], true, false, eventTimeline, null);\n                    }\n                    this.processAggregatedTimelineEvents(timelineSet.room, matrixEvents);\n\n                    // if we've hit the end of the timeline, we need to stop trying to\n                    // paginate. We need to keep the 'forwards' token though, to make sure\n                    // we can recover from gappy syncs.\n                    if (backwards && !newToken) {\n                        eventTimeline.setPaginationToken(null, dir);\n                    }\n                    return Boolean(newToken);\n                })\n                .finally(() => {\n                    eventTimeline.paginationRequests[dir] = null;\n                });\n            eventTimeline.paginationRequests[dir] = promise;\n        } else {\n            if (!room) {\n                throw new Error(\"Unknown room \" + eventTimeline.getRoomId());\n            }\n\n            promise = this.createMessagesRequest(\n                eventTimeline.getRoomId()!,\n                token,\n                opts.limit,\n                dir,\n                eventTimeline.getFilter(),\n            )\n                .then((res) => {\n                    if (res.state) {\n                        const roomState = eventTimeline.getState(dir)!;\n                        const stateEvents = res.state.filter(noUnsafeEventProps).map(this.getEventMapper());\n                        roomState.setUnknownStateEvents(stateEvents);\n                    }\n                    const token = res.end;\n                    const matrixEvents = res.chunk.filter(noUnsafeEventProps).map(this.getEventMapper());\n\n                    const timelineSet = eventTimeline.getTimelineSet();\n                    const [timelineEvents, , unknownRelations] = room.partitionThreadedEvents(matrixEvents);\n                    timelineSet.addEventsToTimeline(timelineEvents, backwards, false, eventTimeline, token);\n                    this.processAggregatedTimelineEvents(room, timelineEvents);\n                    this.processThreadRoots(\n                        room,\n                        timelineEvents.filter((it) => it.getServerAggregatedRelation(THREAD_RELATION_TYPE.name)),\n                        false,\n                    );\n                    unknownRelations.forEach((event) => room.relations.aggregateChildEvent(event));\n\n                    const atEnd = res.end === undefined || res.end === res.start;\n\n                    // if we've hit the end of the timeline, we need to stop trying to\n                    // paginate. We need to keep the 'forwards' token though, to make sure\n                    // we can recover from gappy syncs.\n                    if (backwards && atEnd) {\n                        eventTimeline.setPaginationToken(null, dir);\n                    }\n                    return !atEnd;\n                })\n                .finally(() => {\n                    eventTimeline.paginationRequests[dir] = null;\n                });\n            eventTimeline.paginationRequests[dir] = promise;\n        }\n\n        return promise;\n    }\n\n    /**\n     * Reset the notifTimelineSet entirely, paginating in some historical notifs as\n     * a starting point for subsequent pagination.\n     */\n    public resetNotifTimelineSet(): void {\n        if (!this.notifTimelineSet) {\n            return;\n        }\n\n        // FIXME: This thing is a total hack, and results in duplicate events being\n        // added to the timeline both from /sync and /notifications, and lots of\n        // slow and wasteful processing and pagination.  The correct solution is to\n        // extend /messages or /search or something to filter on notifications.\n\n        // use the fictitious token 'end'. in practice we would ideally give it\n        // the oldest backwards pagination token from /sync, but /sync doesn't\n        // know about /notifications, so we have no choice but to start paginating\n        // from the current point in time.  This may well overlap with historical\n        // notifs which are then inserted into the timeline by /sync responses.\n        this.notifTimelineSet.resetLiveTimeline(\"end\");\n\n        // we could try to paginate a single event at this point in order to get\n        // a more valid pagination token, but it just ends up with an out of order\n        // timeline. given what a mess this is and given we're going to have duplicate\n        // events anyway, just leave it with the dummy token for now.\n        /*\n        this.paginateNotifTimeline(this._notifTimelineSet.getLiveTimeline(), {\n            backwards: true,\n            limit: 1\n        });\n        */\n    }\n\n    /**\n     * Peek into a room and receive updates about the room. This only works if the\n     * history visibility for the room is world_readable.\n     * @param roomId - The room to attempt to peek into.\n     * @param limit - The number of timeline events to initially retrieve.\n     * @returns Promise which resolves: Room object\n     * @returns Rejects: with an error response.\n     */\n    public peekInRoom(roomId: string, limit: number = 20): Promise<Room> {\n        this.peekSync?.stopPeeking();\n        this.peekSync = new SyncApi(this, this.clientOpts, this.buildSyncApiOptions());\n        return this.peekSync.peek(roomId, limit);\n    }\n\n    /**\n     * Stop any ongoing room peeking.\n     */\n    public stopPeeking(): void {\n        if (this.peekSync) {\n            this.peekSync.stopPeeking();\n            this.peekSync = null;\n        }\n    }\n\n    /**\n     * Set r/w flags for guest access in a room.\n     * @param roomId - The room to configure guest access in.\n     * @param opts - Options\n     * @returns Promise which resolves\n     * @returns Rejects: with an error response.\n     */\n    public setGuestAccess(roomId: string, opts: IGuestAccessOpts): Promise<void> {\n        const writePromise = this.sendStateEvent(\n            roomId,\n            EventType.RoomGuestAccess,\n            {\n                guest_access: opts.allowJoin ? GuestAccess.CanJoin : GuestAccess.Forbidden,\n            },\n            \"\",\n        );\n\n        let readPromise: Promise<unknown> = Promise.resolve();\n        if (opts.allowRead) {\n            readPromise = this.sendStateEvent(\n                roomId,\n                EventType.RoomHistoryVisibility,\n                {\n                    history_visibility: HistoryVisibility.WorldReadable,\n                },\n                \"\",\n            );\n        }\n\n        return Promise.all([readPromise, writePromise]).then(); // .then() to hide results for contract\n    }\n\n    /**\n     * Requests an email verification token for the purposes of registration.\n     * This API requests a token from the homeserver.\n     * The doesServerRequireIdServerParam() method can be used to determine if\n     * the server requires the id_server parameter to be provided.\n     *\n     * Parameters and return value are as for requestEmailToken\n\n     * @param email - As requestEmailToken\n     * @param clientSecret - As requestEmailToken\n     * @param sendAttempt - As requestEmailToken\n     * @param nextLink - As requestEmailToken\n     * @returns Promise which resolves: As requestEmailToken\n     */\n    public requestRegisterEmailToken(\n        email: string,\n        clientSecret: string,\n        sendAttempt: number,\n        nextLink?: string,\n    ): Promise<IRequestTokenResponse> {\n        return this.requestTokenFromEndpoint(\"/register/email/requestToken\", {\n            email: email,\n            client_secret: clientSecret,\n            send_attempt: sendAttempt,\n            next_link: nextLink,\n        });\n    }\n\n    /**\n     * Requests a text message verification token for the purposes of registration.\n     * This API requests a token from the homeserver.\n     * The doesServerRequireIdServerParam() method can be used to determine if\n     * the server requires the id_server parameter to be provided.\n     *\n     * @param phoneCountry - The ISO 3166-1 alpha-2 code for the country in which\n     *    phoneNumber should be parsed relative to.\n     * @param phoneNumber - The phone number, in national or international format\n     * @param clientSecret - As requestEmailToken\n     * @param sendAttempt - As requestEmailToken\n     * @param nextLink - As requestEmailToken\n     * @returns Promise which resolves: As requestEmailToken\n     */\n    public requestRegisterMsisdnToken(\n        phoneCountry: string,\n        phoneNumber: string,\n        clientSecret: string,\n        sendAttempt: number,\n        nextLink?: string,\n    ): Promise<IRequestMsisdnTokenResponse> {\n        return this.requestTokenFromEndpoint(\"/register/msisdn/requestToken\", {\n            country: phoneCountry,\n            phone_number: phoneNumber,\n            client_secret: clientSecret,\n            send_attempt: sendAttempt,\n            next_link: nextLink,\n        });\n    }\n\n    /**\n     * Requests an email verification token for the purposes of adding a\n     * third party identifier to an account.\n     * This API requests a token from the homeserver.\n     * The doesServerRequireIdServerParam() method can be used to determine if\n     * the server requires the id_server parameter to be provided.\n     * If an account with the given email address already exists and is\n     * associated with an account other than the one the user is authed as,\n     * it will either send an email to the address informing them of this\n     * or return M_THREEPID_IN_USE (which one is up to the homeserver).\n     *\n     * @param email - As requestEmailToken\n     * @param clientSecret - As requestEmailToken\n     * @param sendAttempt - As requestEmailToken\n     * @param nextLink - As requestEmailToken\n     * @returns Promise which resolves: As requestEmailToken\n     */\n    public requestAdd3pidEmailToken(\n        email: string,\n        clientSecret: string,\n        sendAttempt: number,\n        nextLink?: string,\n    ): Promise<IRequestTokenResponse> {\n        return this.requestTokenFromEndpoint(\"/account/3pid/email/requestToken\", {\n            email: email,\n            client_secret: clientSecret,\n            send_attempt: sendAttempt,\n            next_link: nextLink,\n        });\n    }\n\n    /**\n     * Requests a text message verification token for the purposes of adding a\n     * third party identifier to an account.\n     * This API proxies the identity server /validate/email/requestToken API,\n     * adding specific behaviour for the addition of phone numbers to an\n     * account, as requestAdd3pidEmailToken.\n     *\n     * @param phoneCountry - As requestRegisterMsisdnToken\n     * @param phoneNumber - As requestRegisterMsisdnToken\n     * @param clientSecret - As requestEmailToken\n     * @param sendAttempt - As requestEmailToken\n     * @param nextLink - As requestEmailToken\n     * @returns Promise which resolves: As requestEmailToken\n     */\n    public requestAdd3pidMsisdnToken(\n        phoneCountry: string,\n        phoneNumber: string,\n        clientSecret: string,\n        sendAttempt: number,\n        nextLink?: string,\n    ): Promise<IRequestMsisdnTokenResponse> {\n        return this.requestTokenFromEndpoint(\"/account/3pid/msisdn/requestToken\", {\n            country: phoneCountry,\n            phone_number: phoneNumber,\n            client_secret: clientSecret,\n            send_attempt: sendAttempt,\n            next_link: nextLink,\n        });\n    }\n\n    /**\n     * Requests an email verification token for the purposes of resetting\n     * the password on an account.\n     * This API proxies the identity server /validate/email/requestToken API,\n     * adding specific behaviour for the password resetting. Specifically,\n     * if no account with the given email address exists, it may either\n     * return M_THREEPID_NOT_FOUND or send an email\n     * to the address informing them of this (which one is up to the homeserver).\n     *\n     * requestEmailToken calls the equivalent API directly on the identity server,\n     * therefore bypassing the password reset specific logic.\n     *\n     * @param email - As requestEmailToken\n     * @param clientSecret - As requestEmailToken\n     * @param sendAttempt - As requestEmailToken\n     * @param nextLink - As requestEmailToken\n     * @returns Promise which resolves: As requestEmailToken\n     */\n    public requestPasswordEmailToken(\n        email: string,\n        clientSecret: string,\n        sendAttempt: number,\n        nextLink?: string,\n    ): Promise<IRequestTokenResponse> {\n        return this.requestTokenFromEndpoint(\"/account/password/email/requestToken\", {\n            email: email,\n            client_secret: clientSecret,\n            send_attempt: sendAttempt,\n            next_link: nextLink,\n        });\n    }\n\n    /**\n     * Requests a text message verification token for the purposes of resetting\n     * the password on an account.\n     * This API proxies the identity server /validate/email/requestToken API,\n     * adding specific behaviour for the password resetting, as requestPasswordEmailToken.\n     *\n     * @param phoneCountry - As requestRegisterMsisdnToken\n     * @param phoneNumber - As requestRegisterMsisdnToken\n     * @param clientSecret - As requestEmailToken\n     * @param sendAttempt - As requestEmailToken\n     * @param nextLink - As requestEmailToken\n     * @returns Promise which resolves: As requestEmailToken\n     */\n    public requestPasswordMsisdnToken(\n        phoneCountry: string,\n        phoneNumber: string,\n        clientSecret: string,\n        sendAttempt: number,\n        nextLink: string,\n    ): Promise<IRequestMsisdnTokenResponse> {\n        return this.requestTokenFromEndpoint(\"/account/password/msisdn/requestToken\", {\n            country: phoneCountry,\n            phone_number: phoneNumber,\n            client_secret: clientSecret,\n            send_attempt: sendAttempt,\n            next_link: nextLink,\n        });\n    }\n\n    /**\n     * Internal utility function for requesting validation tokens from usage-specific\n     * requestToken endpoints.\n     *\n     * @param endpoint - The endpoint to send the request to\n     * @param params - Parameters for the POST request\n     * @returns Promise which resolves: As requestEmailToken\n     */\n    private async requestTokenFromEndpoint<T extends IRequestTokenResponse>(\n        endpoint: string,\n        params: QueryDict,\n    ): Promise<T> {\n        const postParams = Object.assign({}, params);\n\n        return this.http.request(Method.Post, endpoint, undefined, postParams);\n    }\n\n    /**\n     * Get the room-kind push rule associated with a room.\n     * @param scope - \"global\" or device-specific.\n     * @param roomId - the id of the room.\n     * @returns the rule or undefined.\n     */\n    public getRoomPushRule(scope: \"global\" | \"device\", roomId: string): IPushRule | undefined {\n        // There can be only room-kind push rule per room\n        // and its id is the room id.\n        if (this.pushRules) {\n            return this.pushRules[scope]?.room?.find((rule) => rule.rule_id === roomId);\n        } else {\n            throw new Error(\"SyncApi.sync() must be done before accessing to push rules.\");\n        }\n    }\n\n    /**\n     * Set a room-kind muting push rule in a room.\n     * The operation also updates MatrixClient.pushRules at the end.\n     * @param scope - \"global\" or device-specific.\n     * @param roomId - the id of the room.\n     * @param mute - the mute state.\n     * @returns Promise which resolves: result object\n     * @returns Rejects: with an error response.\n     */\n    public setRoomMutePushRule(scope: \"global\" | \"device\", roomId: string, mute: boolean): Promise<void> | undefined {\n        let promise: Promise<unknown> | undefined;\n        let hasDontNotifyRule = false;\n\n        // Get the existing room-kind push rule if any\n        const roomPushRule = this.getRoomPushRule(scope, roomId);\n        if (roomPushRule?.actions.includes(PushRuleActionName.DontNotify)) {\n            hasDontNotifyRule = true;\n        }\n\n        if (!mute) {\n            // Remove the rule only if it is a muting rule\n            if (hasDontNotifyRule) {\n                promise = this.deletePushRule(scope, PushRuleKind.RoomSpecific, roomPushRule!.rule_id);\n            }\n        } else {\n            if (!roomPushRule) {\n                promise = this.addPushRule(scope, PushRuleKind.RoomSpecific, roomId, {\n                    actions: [PushRuleActionName.DontNotify],\n                });\n            } else if (!hasDontNotifyRule) {\n                // Remove the existing one before setting the mute push rule\n                // This is a workaround to SYN-590 (Push rule update fails)\n                const doneResolvers = Promise.withResolvers<void>();\n                this.deletePushRule(scope, PushRuleKind.RoomSpecific, roomPushRule.rule_id)\n                    .then(() => {\n                        this.addPushRule(scope, PushRuleKind.RoomSpecific, roomId, {\n                            actions: [PushRuleActionName.DontNotify],\n                        })\n                            .then(() => {\n                                doneResolvers.resolve();\n                            })\n                            .catch((err) => {\n                                doneResolvers.reject(err);\n                            });\n                    })\n                    .catch((err) => {\n                        doneResolvers.reject(err);\n                    });\n\n                promise = doneResolvers.promise;\n            }\n        }\n\n        if (promise) {\n            return new Promise<void>((resolve, reject) => {\n                // Update this.pushRules when the operation completes\n                promise!\n                    .then(() => {\n                        this.getPushRules()\n                            .then((result) => {\n                                this.pushRules = result;\n                                resolve();\n                            })\n                            .catch((err) => {\n                                reject(err);\n                            });\n                    })\n                    .catch((err: Error) => {\n                        // Update it even if the previous operation fails. This can help the\n                        // app to recover when push settings has been modified from another client\n                        this.getPushRules()\n                            .then((result) => {\n                                this.pushRules = result;\n                                reject(err);\n                            })\n                            .catch((err2) => {\n                                reject(err);\n                            });\n                    });\n            });\n        }\n    }\n\n    public searchMessageText(opts: ISearchOpts): Promise<ISearchResponse> {\n        const roomEvents: ISearchRequestBody[\"search_categories\"][\"room_events\"] = {\n            search_term: opts.query,\n        };\n\n        if (\"keys\" in opts) {\n            roomEvents.keys = opts.keys;\n        }\n\n        return this.search({\n            body: {\n                search_categories: {\n                    room_events: roomEvents,\n                },\n            },\n        });\n    }\n\n    /**\n     * Perform a server-side search for room events.\n     *\n     * The returned promise resolves to an object containing the fields:\n     *\n     *  * count:       estimate of the number of results\n     *  * next_batch:  token for back-pagination; if undefined, there are no more results\n     *  * highlights:  a list of words to highlight from the stemming algorithm\n     *  * results:     a list of results\n     *\n     * Each entry in the results list is a SearchResult.\n     *\n     * @returns Promise which resolves: result object\n     * @returns Rejects: with an error response.\n     */\n    public searchRoomEvents(opts: IEventSearchOpts): Promise<ISearchResults> {\n        // TODO: support search groups\n\n        const body = {\n            search_categories: {\n                room_events: {\n                    search_term: opts.term,\n                    filter: opts.filter,\n                    order_by: SearchOrderBy.Recent,\n                    event_context: {\n                        before_limit: 1,\n                        after_limit: 1,\n                        include_profile: true,\n                    },\n                },\n            },\n        };\n\n        const searchResults: ISearchResults = {\n            _query: body,\n            results: [],\n            highlights: [],\n        };\n\n        return this.search({ body: body }).then((res) => this.processRoomEventsSearch(searchResults, res));\n    }\n\n    /**\n     * Take a result from an earlier searchRoomEvents call, and backfill results.\n     *\n     * @param searchResults -  the results object to be updated\n     * @returns Promise which resolves: updated result object\n     * @returns Rejects: with an error response.\n     */\n    public backPaginateRoomEventsSearch<T extends ISearchResults>(searchResults: T): Promise<T> {\n        // TODO: we should implement a backoff (as per scrollback()) to deal more\n        // nicely with HTTP errors.\n\n        if (!searchResults.next_batch) {\n            return Promise.reject(new Error(\"Cannot backpaginate event search any further\"));\n        }\n\n        if (searchResults.pendingRequest) {\n            // already a request in progress - return the existing promise\n            return searchResults.pendingRequest as Promise<T>;\n        }\n\n        const searchOpts = {\n            body: searchResults._query!,\n            next_batch: searchResults.next_batch,\n        };\n\n        const promise = this.search(searchOpts, searchResults.abortSignal)\n            .then((res) => this.processRoomEventsSearch(searchResults, res))\n            .finally(() => {\n                searchResults.pendingRequest = undefined;\n            });\n        searchResults.pendingRequest = promise;\n\n        return promise;\n    }\n\n    /**\n     * helper for searchRoomEvents and backPaginateRoomEventsSearch. Processes the\n     * response from the API call and updates the searchResults\n     *\n     * @returns searchResults\n     * @internal\n     */\n    // XXX: Intended private, used in code\n    public processRoomEventsSearch<T extends ISearchResults>(searchResults: T, response: ISearchResponse): T {\n        const roomEvents = response.search_categories.room_events;\n\n        searchResults.count = roomEvents.count;\n        searchResults.next_batch = roomEvents.next_batch;\n\n        // combine the highlight list with our existing list;\n        const highlights = new Set<string>(roomEvents.highlights);\n        searchResults.highlights.forEach((hl) => {\n            highlights.add(hl);\n        });\n\n        // turn it back into a list.\n        searchResults.highlights = Array.from(highlights);\n\n        const mapper = this.getEventMapper();\n\n        // append the new results to our existing results\n        const resultsLength = roomEvents.results?.length ?? 0;\n        for (let i = 0; i < resultsLength; i++) {\n            const sr = SearchResult.fromJson(roomEvents.results![i], mapper);\n            const room = this.getRoom(sr.context.getEvent().getRoomId());\n            if (room) {\n                for (const ev of sr.context.getTimeline()) {\n                    ev.setMetadata(room.currentState, false);\n                }\n            }\n            searchResults.results.push(sr);\n        }\n        return searchResults;\n    }\n\n    /**\n     * Populate the store with rooms the user has left.\n     * @returns Promise which resolves: TODO - Resolved when the rooms have\n     * been added to the data store.\n     * @returns Rejects: with an error response.\n     */\n    public syncLeftRooms(): Promise<Room[]> {\n        // Guard against multiple calls whilst ongoing and multiple calls post success\n        if (this.syncedLeftRooms) {\n            return Promise.resolve([]); // don't call syncRooms again if it succeeded.\n        }\n        if (this.syncLeftRoomsPromise) {\n            return this.syncLeftRoomsPromise; // return the ongoing request\n        }\n        const syncApi = new SyncApi(this, this.clientOpts, this.buildSyncApiOptions());\n        this.syncLeftRoomsPromise = syncApi.syncLeftRooms();\n\n        // cleanup locks\n        this.syncLeftRoomsPromise\n            .then(() => {\n                this.logger.debug(\"Marking success of sync left room request\");\n                this.syncedLeftRooms = true; // flip the bit on success\n            })\n            .finally(() => {\n                this.syncLeftRoomsPromise = undefined; // cleanup ongoing request state\n            });\n\n        return this.syncLeftRoomsPromise;\n    }\n\n    /**\n     * Create a new filter.\n     * @param content - The HTTP body for the request\n     * @returns Promise which resolves to a Filter object.\n     * @returns Rejects: with an error response.\n     */\n    public createFilter(content: IFilterDefinition): Promise<Filter> {\n        const path = utils.encodeUri(\"/user/$userId/filter\", {\n            $userId: this.credentials.userId!,\n        });\n        return this.http.authedRequest<IFilterResponse>(Method.Post, path, undefined, content).then((response) => {\n            // persist the filter\n            const filter = Filter.fromJson(this.credentials.userId, response.filter_id, content);\n            this.store.storeFilter(filter);\n            return filter;\n        });\n    }\n\n    /**\n     * Retrieve a filter.\n     * @param userId - The user ID of the filter owner\n     * @param filterId - The filter ID to retrieve\n     * @param allowCached - True to allow cached filters to be returned.\n     * Default: True.\n     * @returns Promise which resolves: a Filter object\n     * @returns Rejects: with an error response.\n     */\n    public getFilter(userId: string, filterId: string, allowCached: boolean): Promise<Filter> {\n        if (allowCached) {\n            const filter = this.store.getFilter(userId, filterId);\n            if (filter) {\n                return Promise.resolve(filter);\n            }\n        }\n\n        const path = utils.encodeUri(\"/user/$userId/filter/$filterId\", {\n            $userId: userId,\n            $filterId: filterId,\n        });\n\n        return this.http.authedRequest<IFilterDefinition>(Method.Get, path).then((response) => {\n            // persist the filter\n            const filter = Filter.fromJson(userId, filterId, response);\n            this.store.storeFilter(filter);\n            return filter;\n        });\n    }\n\n    /**\n     * @returns Filter ID\n     */\n    public async getOrCreateFilter(filterName: string, filter: Filter): Promise<string> {\n        const filterId = this.store.getFilterIdByName(filterName);\n        let existingId: string | undefined;\n\n        if (filterId) {\n            // check that the existing filter matches our expectations\n            try {\n                const existingFilter = await this.getFilter(this.credentials.userId!, filterId, true);\n                if (existingFilter) {\n                    const oldDef = existingFilter.getDefinition();\n                    const newDef = filter.getDefinition();\n\n                    if (utils.deepCompare(oldDef, newDef)) {\n                        // super, just use that.\n                        // debuglog(\"Using existing filter ID %s: %s\", filterId,\n                        //          JSON.stringify(oldDef));\n                        existingId = filterId;\n                    }\n                }\n            } catch (error) {\n                // Synapse currently returns the following when the filter cannot be found:\n                // {\n                //     errcode: \"M_UNKNOWN\",\n                //     name: \"M_UNKNOWN\",\n                //     message: \"No row found\",\n                // }\n                if ((<MatrixError>error).errcode !== \"M_UNKNOWN\" && (<MatrixError>error).errcode !== \"M_NOT_FOUND\") {\n                    throw error;\n                }\n            }\n            // if the filter doesn't exist anymore on the server, remove from store\n            if (!existingId) {\n                this.store.setFilterIdByName(filterName, undefined);\n            }\n        }\n\n        if (existingId) {\n            return existingId;\n        }\n\n        // create a new filter\n        const createdFilter = await this.createFilter(filter.getDefinition());\n\n        this.store.setFilterIdByName(filterName, createdFilter.filterId);\n        return createdFilter.filterId!;\n    }\n\n    /**\n     * Gets a bearer token from the homeserver that the user can\n     * present to a third party in order to prove their ownership\n     * of the Matrix account they are logged into.\n     * @returns Promise which resolves: Token object\n     * @returns Rejects: with an error response.\n     */\n    public getOpenIdToken(): Promise<IOpenIDToken> {\n        const path = utils.encodeUri(\"/user/$userId/openid/request_token\", {\n            $userId: this.credentials.userId!,\n        });\n\n        return this.http.authedRequest(Method.Post, path, undefined, {});\n    }\n\n    private startCallEventHandler = (): void => {\n        if (this.isInitialSyncComplete()) {\n            if (supportsMatrixCall()) {\n                this.callEventHandler!.start();\n                void this.groupCallEventHandler!.start();\n            }\n\n            this.off(ClientEvent.Sync, this.startCallEventHandler);\n        }\n    };\n\n    private startMatrixRTC = (): void => {\n        if (this.isInitialSyncComplete()) {\n            this.matrixRTC.start();\n\n            this.off(ClientEvent.Sync, this.startMatrixRTC);\n        }\n    };\n\n    /**\n     * Once the client has been initialised, we want to clear notifications we\n     * know for a fact should be here.\n     * This issue should also be addressed on synapse's side and is tracked as part\n     * of https://github.com/matrix-org/synapse/issues/14837\n     *\n     * We consider a room or a thread as fully read if the current user has sent\n     * the last event in the live timeline of that context and if the read receipt\n     * we have on record matches.\n     */\n    private fixupRoomNotifications = (): void => {\n        if (this.isInitialSyncComplete()) {\n            const unreadRooms = (this.getRooms() ?? []).filter((room) => {\n                return room.getUnreadNotificationCount(NotificationCountType.Total) > 0;\n            });\n\n            for (const room of unreadRooms) {\n                const currentUserId = this.getSafeUserId();\n                room.fixupNotifications(currentUserId);\n            }\n\n            this.off(ClientEvent.Sync, this.fixupRoomNotifications);\n        }\n    };\n\n    /**\n     * @returns Promise which resolves: ITurnServerResponse object\n     * @returns Rejects: with an error response.\n     */\n    public turnServer(): Promise<ITurnServerResponse> {\n        return this.http.authedRequest(Method.Get, \"/voip/turnServer\");\n    }\n\n    /**\n     * Get the TURN servers for this homeserver.\n     * @returns The servers or an empty list.\n     */\n    public getTurnServers(): ITurnServer[] {\n        return this.turnServers || [];\n    }\n\n    /**\n     * Get the unix timestamp (in milliseconds) at which the current\n     * TURN credentials (from getTurnServers) expire\n     * @returns The expiry timestamp in milliseconds\n     */\n    public getTurnServersExpiry(): number {\n        return this.turnServersExpiry;\n    }\n\n    public get pollingTurnServers(): boolean {\n        return this.checkTurnServersIntervalID !== undefined;\n    }\n\n    // XXX: Intended private, used in code.\n    public async checkTurnServers(): Promise<boolean | undefined> {\n        if (!this.supportsVoip()) {\n            return;\n        }\n\n        let credentialsGood = false;\n        const remainingTime = this.turnServersExpiry - Date.now();\n        if (remainingTime > TURN_CHECK_INTERVAL) {\n            this.logger.debug(\"TURN creds are valid for another \" + remainingTime + \" ms: not fetching new ones.\");\n            credentialsGood = true;\n        } else {\n            this.logger.debug(\"Fetching new TURN credentials\");\n            try {\n                const res = await this.turnServer();\n                if (res.uris) {\n                    this.logger.debug(\"Got TURN URIs: \" + res.uris + \" refresh in \" + res.ttl + \" secs\");\n                    // map the response to a format that can be fed to RTCPeerConnection\n                    const servers: ITurnServer = {\n                        urls: res.uris,\n                        username: res.username,\n                        credential: res.password,\n                    };\n                    this.turnServers = [servers];\n                    // The TTL is in seconds but we work in ms\n                    this.turnServersExpiry = Date.now() + res.ttl * 1000;\n                    credentialsGood = true;\n                    this.emit(ClientEvent.TurnServers, this.turnServers);\n                }\n            } catch (err) {\n                this.logger.error(\"Failed to get TURN URIs\", err);\n                if ((<HTTPError>err).httpStatus === 403) {\n                    // We got a 403, so there's no point in looping forever.\n                    this.logger.info(\"TURN access unavailable for this account: stopping credentials checks\");\n                    if (this.checkTurnServersIntervalID !== null) {\n                        globalThis.clearInterval(this.checkTurnServersIntervalID);\n                    }\n                    this.checkTurnServersIntervalID = undefined;\n                    this.emit(ClientEvent.TurnServersError, <HTTPError>err, true); // fatal\n                } else {\n                    // otherwise, if we failed for whatever reason, try again the next time we're called.\n                    this.emit(ClientEvent.TurnServersError, <Error>err, false); // non-fatal\n                }\n            }\n        }\n\n        return credentialsGood;\n    }\n\n    /**\n     * Set whether to allow a fallback ICE server should be used for negotiating a\n     * WebRTC connection if the homeserver doesn't provide any servers. Defaults to\n     * false.\n     *\n     */\n    public setFallbackICEServerAllowed(allow: boolean): void {\n        this.fallbackICEServerAllowed = allow;\n    }\n\n    /**\n     * Get whether to allow a fallback ICE server should be used for negotiating a\n     * WebRTC connection if the homeserver doesn't provide any servers. Defaults to\n     * false.\n     *\n     * @returns\n     */\n    public isFallbackICEServerAllowed(): boolean {\n        return this.fallbackICEServerAllowed;\n    }\n\n    /**\n     * Determines if the current user is an administrator of the Synapse homeserver.\n     * Returns false if untrue or the homeserver does not appear to be a Synapse\n     * homeserver. <strong>This function is implementation specific and may change\n     * as a result.</strong>\n     * @returns true if the user appears to be a Synapse administrator.\n     */\n    public isSynapseAdministrator(): Promise<boolean> {\n        const path = utils.encodeUri(\"/_synapse/admin/v1/users/$userId/admin\", { $userId: this.getUserId()! });\n        return this.http\n            .authedRequest<{ admin: boolean }>(Method.Get, path, undefined, undefined, { prefix: \"\" })\n            .then((r) => r.admin); // pull out the specific boolean we want\n    }\n\n    /**\n     * Performs a whois lookup on a user using Synapse's administrator API.\n     * <strong>This function is implementation specific and may change as a\n     * result.</strong>\n     * @param userId - the User ID to look up.\n     * @returns the whois response - see Synapse docs for information.\n     */\n    public whoisSynapseUser(userId: string): Promise<ISynapseAdminWhoisResponse> {\n        const path = utils.encodeUri(\"/_synapse/admin/v1/whois/$userId\", { $userId: userId });\n        return this.http.authedRequest(Method.Get, path, undefined, undefined, { prefix: \"\" });\n    }\n\n    /**\n     * Deactivates a user using Synapse's administrator API. <strong>This\n     * function is implementation specific and may change as a result.</strong>\n     * @param userId - the User ID to deactivate.\n     * @returns the deactivate response - see Synapse docs for information.\n     */\n    public deactivateSynapseUser(userId: string): Promise<ISynapseAdminDeactivateResponse> {\n        const path = utils.encodeUri(\"/_synapse/admin/v1/deactivate/$userId\", { $userId: userId });\n        return this.http.authedRequest(Method.Post, path, undefined, undefined, { prefix: \"\" });\n    }\n\n    protected async fetchClientWellKnown(): Promise<void> {\n        // `getRawClientConfig` does not throw or reject on network errors, instead\n        // it absorbs errors and returns `{}`.\n        this.clientWellKnownPromise = AutoDiscovery.getRawClientConfig(this.getDomain() ?? undefined);\n        this.clientWellKnown = await this.clientWellKnownPromise;\n        this.emit(ClientEvent.ClientWellKnown, this.clientWellKnown);\n    }\n\n    public getClientWellKnown(): IClientWellKnown | undefined {\n        return this.clientWellKnown;\n    }\n\n    public waitForClientWellKnown(): Promise<IClientWellKnown> {\n        if (!this.clientRunning) {\n            throw new Error(\"Client is not running\");\n        }\n        return this.clientWellKnownPromise!;\n    }\n\n    /**\n     * store client options with boolean/string/numeric values\n     * to know in the next session what flags the sync data was\n     * created with (e.g. lazy loading)\n     * @returns for store operation\n     */\n    public storeClientOptions(): Promise<void> {\n        // XXX: Intended private, used in code\n        const primTypes = [\"boolean\", \"string\", \"number\"];\n        const serializableOpts = Object.entries(this.clientOpts!)\n            .filter(([key, value]) => {\n                return primTypes.includes(typeof value);\n            })\n            .reduce<Record<string, any>>((obj, [key, value]) => {\n                obj[key] = value;\n                return obj;\n            }, {});\n        return this.store.storeClientOptions(serializableOpts);\n    }\n\n    /**\n     * Gets a set of room IDs in common with another user.\n     *\n     * Note: This endpoint is unstable, and can throw an `Error`.\n     *   Check progress on [MSC2666](https://github.com/matrix-org/matrix-spec-proposals/pull/2666) for more details.\n     *\n     * @param userId - The userId to check.\n     * @returns Promise which resolves to an array of rooms\n     * @returns Rejects: with an error response.\n     */\n    // TODO: on spec release, rename this to getMutualRooms\n    // eslint-disable-next-line\n    public async _unstable_getSharedRooms(userId: string): Promise<string[]> {\n        // Initial variant of the MSC\n        const sharedRoomsSupport = await this.doesServerSupportUnstableFeature(UNSTABLE_MSC2666_SHARED_ROOMS);\n\n        // Newer variant that renamed shared rooms to mutual rooms\n        const mutualRoomsSupport = await this.doesServerSupportUnstableFeature(UNSTABLE_MSC2666_MUTUAL_ROOMS);\n\n        // Latest variant that changed from path elements to query elements\n        const queryMutualRoomsSupport = await this.doesServerSupportUnstableFeature(\n            UNSTABLE_MSC2666_QUERY_MUTUAL_ROOMS,\n        );\n\n        if (!sharedRoomsSupport && !mutualRoomsSupport && !queryMutualRoomsSupport) {\n            throw Error(\"Server does not support the Mutual Rooms API\");\n        }\n\n        let path;\n        let query;\n\n        // Cascading unstable support switching.\n        if (queryMutualRoomsSupport) {\n            path = \"/uk.half-shot.msc2666/user/mutual_rooms\";\n            query = { user_id: userId };\n        } else {\n            path = utils.encodeUri(\n                `/uk.half-shot.msc2666/user/${mutualRoomsSupport ? \"mutual_rooms\" : \"shared_rooms\"}/$userId`,\n                { $userId: userId },\n            );\n            query = {};\n        }\n\n        // Accumulated rooms\n        const rooms: string[] = [];\n        let token = null;\n\n        do {\n            const tokenQuery: Record<string, string> = {};\n            if (token != null && queryMutualRoomsSupport) {\n                tokenQuery[\"batch_token\"] = token;\n            }\n\n            const res = await this.http.authedRequest<{\n                joined: string[];\n                next_batch_token?: string;\n            }>(Method.Get, path, { ...query, ...tokenQuery }, undefined, {\n                prefix: ClientPrefix.Unstable,\n            });\n\n            rooms.push(...res.joined);\n\n            if (res.next_batch_token !== undefined) {\n                token = res.next_batch_token;\n            } else {\n                token = null;\n            }\n        } while (token != null);\n\n        return rooms;\n    }\n\n    /**\n     * Returns a set of configured RTC transports supported by the homeserver.\n     * Requires homeserver support for MSC4143.\n     * @throws A M_NOT_FOUND error if not supported by the homeserver.\n     */\n    // eslint-disable-next-line @typescript-eslint/naming-convention\n    public async _unstable_getRTCTransports(): Promise<Transport[]> {\n        // There is no /versions endpoint to check for support, so we just have to attempt a request.\n        return (\n            await this.http.authedRequest<{\n                rtc_transports: Transport[];\n            }>(Method.Get, \"/rtc/transports\", undefined, undefined, {\n                prefix: `${ClientPrefix.Unstable}/org.matrix.msc4143`,\n            })\n        ).rtc_transports;\n    }\n\n    /**\n     * Get the API versions supported by the server, along with any\n     * unstable APIs it supports\n     * @returns The server /versions response\n     */\n    public async getVersions(): Promise<IServerVersions> {\n        if (this.serverVersionsPromise) {\n            return this.serverVersionsPromise;\n        }\n\n        // We send an authenticated request as of MSC4026\n        this.serverVersionsPromise = this.http\n            .authedRequest<IServerVersions>(Method.Get, \"/_matrix/client/versions\", undefined, undefined, {\n                prefix: \"\",\n            })\n            .catch((e) => {\n                // Need to unset this if it fails, otherwise we'll never retry\n                this.serverVersionsPromise = undefined;\n                // but rethrow the exception to anything that was waiting\n                throw e;\n            });\n\n        const serverVersions = await this.serverVersionsPromise;\n        this.canSupport = await buildFeatureSupportMap(serverVersions);\n\n        return this.serverVersionsPromise;\n    }\n\n    /**\n     * Check if a particular spec version is supported by the server.\n     * @param version - The spec version (such as \"r0.5.0\") to check for.\n     * @returns Whether it is supported\n     */\n    public async isVersionSupported(version: string): Promise<boolean> {\n        const { versions } = await this.getVersions();\n        return versions && versions.includes(version);\n    }\n\n    /**\n     * Query the server to see if it lists support for an unstable feature\n     * in the /versions response\n     * @param feature - the feature name\n     * @returns true if the feature is supported\n     */\n    public async doesServerSupportUnstableFeature(feature: string): Promise<boolean> {\n        const response = await this.getVersions();\n        if (!response) return false;\n        const unstableFeatures = response[\"unstable_features\"];\n        return unstableFeatures && !!unstableFeatures[feature];\n    }\n\n    /**\n     * Query the server to see if it is forcing encryption to be enabled for\n     * a given room preset, based on the /versions response.\n     * @param presetName - The name of the preset to check.\n     * @returns true if the server is forcing encryption\n     * for the preset.\n     */\n    public async doesServerForceEncryptionForPreset(presetName: Preset): Promise<boolean> {\n        const response = await this.getVersions();\n        if (!response) return false;\n        const unstableFeatures = response[\"unstable_features\"];\n\n        // The preset name in the versions response will be without the _chat suffix.\n        const versionsPresetName = presetName.includes(\"_chat\")\n            ? presetName.substring(0, presetName.indexOf(\"_chat\"))\n            : presetName;\n\n        return unstableFeatures && !!unstableFeatures[`io.element.e2ee_forced.${versionsPresetName}`];\n    }\n\n    public async doesServerSupportThread(): Promise<{\n        threads: FeatureSupport;\n        list: FeatureSupport;\n        fwdPagination: FeatureSupport;\n    }> {\n        if (await this.isVersionSupported(\"v1.4\")) {\n            return {\n                threads: FeatureSupport.Stable,\n                list: FeatureSupport.Stable,\n                fwdPagination: FeatureSupport.Stable,\n            };\n        }\n\n        try {\n            const [threadUnstable, threadStable, listUnstable, listStable, fwdPaginationUnstable, fwdPaginationStable] =\n                await Promise.all([\n                    this.doesServerSupportUnstableFeature(\"org.matrix.msc3440\"),\n                    this.doesServerSupportUnstableFeature(\"org.matrix.msc3440.stable\"),\n                    this.doesServerSupportUnstableFeature(\"org.matrix.msc3856\"),\n                    this.doesServerSupportUnstableFeature(\"org.matrix.msc3856.stable\"),\n                    this.doesServerSupportUnstableFeature(\"org.matrix.msc3715\"),\n                    this.doesServerSupportUnstableFeature(\"org.matrix.msc3715.stable\"),\n                ]);\n\n            return {\n                threads: determineFeatureSupport(threadStable, threadUnstable),\n                list: determineFeatureSupport(listStable, listUnstable),\n                fwdPagination: determineFeatureSupport(fwdPaginationStable, fwdPaginationUnstable),\n            };\n        } catch {\n            return {\n                threads: FeatureSupport.None,\n                list: FeatureSupport.None,\n                fwdPagination: FeatureSupport.None,\n            };\n        }\n    }\n\n    /**\n     * Get if lazy loading members is being used.\n     * @returns Whether or not members are lazy loaded by this client\n     */\n    public hasLazyLoadMembersEnabled(): boolean {\n        return !!this.clientOpts?.lazyLoadMembers;\n    }\n\n    /**\n     * Set a function which is called when /sync returns a 'limited' response.\n     * It is called with a room ID and returns a boolean. It should return 'true' if the SDK\n     * can SAFELY remove events from this room. It may not be safe to remove events if there\n     * are other references to the timelines for this room, e.g because the client is\n     * actively viewing events in this room.\n     * Default: returns false.\n     * @param cb - The callback which will be invoked.\n     */\n    public setCanResetTimelineCallback(cb: ResetTimelineCallback): void {\n        this.canResetTimelineCallback = cb;\n    }\n\n    /**\n     * Get the callback set via `setCanResetTimelineCallback`.\n     * @returns The callback or null\n     */\n    public getCanResetTimelineCallback(): ResetTimelineCallback | undefined {\n        return this.canResetTimelineCallback;\n    }\n\n    /**\n     * Returns relations for a given event. Handles encryption transparently,\n     * with the caveat that the amount of events returned might be 0, even though you get a nextBatch.\n     * When the returned promise resolves, all messages should have finished trying to decrypt.\n     * @param roomId - the room of the event\n     * @param eventId - the id of the event\n     * @param relationType - the rel_type of the relations requested\n     * @param eventType - the event type of the relations requested\n     * @param opts - options with optional values for the request.\n     * @returns an object with `events` as `MatrixEvent[]` and optionally `nextBatch` if more relations are available.\n     */\n    public async relations(\n        roomId: string,\n        eventId: string,\n        relationType: RelationType | string | null,\n        eventType?: EventType | string | null,\n        opts: IRelationsRequestOpts = { dir: Direction.Backward },\n    ): Promise<{\n        originalEvent?: MatrixEvent | null;\n        events: MatrixEvent[];\n        nextBatch?: string | null;\n        prevBatch?: string | null;\n    }> {\n        const fetchedEventType = eventType ? this.getEncryptedIfNeededEventType(roomId, eventType) : null;\n        const [eventResult, result] = await Promise.all([\n            this.fetchRoomEvent(roomId, eventId),\n            this.fetchRelations(roomId, eventId, relationType, fetchedEventType, opts),\n        ]);\n        const mapper = this.getEventMapper();\n\n        const originalEvent = eventResult ? mapper(eventResult) : undefined;\n        let events = result.chunk.map(mapper);\n\n        if (fetchedEventType === EventType.RoomMessageEncrypted) {\n            const allEvents = originalEvent ? events.concat(originalEvent) : events;\n            await Promise.all(allEvents.map((e) => this.decryptEventIfNeeded(e)));\n            if (eventType !== null) {\n                events = events.filter((e) => e.getType() === eventType);\n            }\n        }\n\n        if (originalEvent && relationType === RelationType.Replace) {\n            events = events.filter((e) => e.getSender() === originalEvent.getSender());\n        }\n        return {\n            originalEvent: originalEvent ?? null,\n            events,\n            nextBatch: result.next_batch ?? null,\n            prevBatch: result.prev_batch ?? null,\n        };\n    }\n\n    /**\n     * Generates a random string suitable for use as a client secret. <strong>This\n     * method is experimental and may change.</strong>\n     * @returns A new client secret\n     */\n    public generateClientSecret(): string {\n        return secureRandomString(32);\n    }\n\n    /**\n     * Attempts to decrypt an event\n     * @param event - The event to decrypt\n     * @returns A decryption promise\n     */\n    public decryptEventIfNeeded(event: MatrixEvent, options?: IDecryptOptions): Promise<void> {\n        if (event.isState() && !this.enableEncryptedStateEvents) {\n            return Promise.resolve();\n        }\n\n        if (event.shouldAttemptDecryption() && this.getCrypto()) {\n            void event.attemptDecryption(this.cryptoBackend!, options);\n        }\n\n        if (event.isBeingDecrypted()) {\n            return event.getDecryptionPromise()!;\n        } else {\n            return Promise.resolve();\n        }\n    }\n\n    private termsUrlForService(serviceType: SERVICE_TYPES, baseUrl: string): URL {\n        switch (serviceType) {\n            case SERVICE_TYPES.IS:\n                return this.http.getUrl(\"/terms\", undefined, IdentityPrefix.V2, baseUrl);\n            case SERVICE_TYPES.IM:\n                return this.http.getUrl(\"/terms\", undefined, \"/_matrix/integrations/v1\", baseUrl);\n            default:\n                throw new Error(\"Unsupported service type\");\n        }\n    }\n\n    /**\n     * Get the Homeserver URL of this client\n     * @returns Homeserver URL of this client\n     */\n    public getHomeserverUrl(): string {\n        return this.baseUrl;\n    }\n\n    /**\n     * Get the identity server URL of this client\n     * @param stripProto - whether or not to strip the protocol from the URL\n     * @returns Identity server URL of this client\n     */\n    public getIdentityServerUrl(stripProto = false): string | undefined {\n        if (stripProto && (this.idBaseUrl?.startsWith(\"http://\") || this.idBaseUrl?.startsWith(\"https://\"))) {\n            return this.idBaseUrl.split(\"://\")[1];\n        }\n        return this.idBaseUrl;\n    }\n\n    /**\n     * Set the identity server URL of this client\n     * @param url - New identity server URL\n     */\n    public setIdentityServerUrl(url?: string): void {\n        this.idBaseUrl = utils.ensureNoTrailingSlash(url);\n        this.http.setIdBaseUrl(this.idBaseUrl);\n    }\n\n    /**\n     * Get the access token associated with this account.\n     * @returns The access_token or null\n     */\n    public getAccessToken(): string | null {\n        return this.http.opts.accessToken || null;\n    }\n\n    /**\n     * Get the refresh token associated with this account.\n     * @returns The refresh_token or null\n     */\n    public getRefreshToken(): string | null {\n        return this.http.opts.refreshToken ?? null;\n    }\n\n    /**\n     * Set the access token associated with this account.\n     * @param token - The new access token.\n     */\n    public setAccessToken(token: string): void {\n        this.http.opts.accessToken = token;\n        // The /versions response can vary for different users so clear the cache\n        this.serverVersionsPromise = undefined;\n    }\n\n    /**\n     * @returns true if there is a valid access_token for this client.\n     */\n    public isLoggedIn(): boolean {\n        return this.http.opts.accessToken !== undefined;\n    }\n\n    /**\n     * Make up a new transaction id\n     *\n     * @returns a new, unique, transaction id\n     */\n    public makeTxnId(): string {\n        return \"m\" + new Date().getTime() + \".\" + this.txnCtr++;\n    }\n\n    /**\n     * Check whether a username is available prior to registration. An error response\n     * indicates an invalid/unavailable username.\n     * @param username - The username to check the availability of.\n     * @returns Promise which resolves: to boolean of whether the username is available.\n     */\n    public isUsernameAvailable(username: string): Promise<boolean> {\n        return this.http\n            .authedRequest<{ available: true }>(Method.Get, \"/register/available\", { username })\n            .then((response) => {\n                return response.available;\n            })\n            .catch((response) => {\n                if (response.errcode === \"M_USER_IN_USE\") {\n                    return false;\n                }\n                return Promise.reject(response);\n            });\n    }\n\n    /**\n     * @param bindThreepids - Set key 'email' to true to bind any email\n     *     threepid uses during registration in the identity server. Set 'msisdn' to\n     *     true to bind msisdn.\n     * @returns Promise which resolves to a RegisterResponse object\n     * @returns Rejects: with an error response.\n     */\n    public register(\n        username: string,\n        password: string,\n        sessionId: string | null,\n        auth: { session?: string; type: string },\n        bindThreepids?: { email?: boolean; msisdn?: boolean },\n        guestAccessToken?: string,\n        inhibitLogin?: boolean,\n    ): Promise<RegisterResponse> {\n        if (sessionId) {\n            auth.session = sessionId;\n        }\n\n        const params: RegisterRequest = {\n            auth: auth,\n            refresh_token: true, // always ask for a refresh token - does nothing if unsupported\n        };\n        if (username !== undefined && username !== null) {\n            params.username = username;\n        }\n        if (password !== undefined && password !== null) {\n            params.password = password;\n        }\n        if (guestAccessToken !== undefined && guestAccessToken !== null) {\n            params.guest_access_token = guestAccessToken;\n        }\n        if (inhibitLogin !== undefined && inhibitLogin !== null) {\n            params.inhibit_login = inhibitLogin;\n        }\n\n        return this.registerRequest(params);\n    }\n\n    /**\n     * Register a guest account.\n     * This method returns the auth info needed to create a new authenticated client,\n     * Remember to call `setGuest(true)` on the (guest-)authenticated client, e.g:\n     * ```javascript\n     * const tmpClient = await sdk.createClient(MATRIX_INSTANCE);\n     * const { user_id, device_id, access_token } = tmpClient.registerGuest();\n     * const client = createClient({\n     *   baseUrl: MATRIX_INSTANCE,\n     *   accessToken: access_token,\n     *   userId: user_id,\n     *   deviceId: device_id,\n     * })\n     * client.setGuest(true);\n     * ```\n     *\n     * @param body - JSON HTTP body to provide.\n     * @returns Promise which resolves: JSON object that contains:\n     *                   `{ user_id, device_id, access_token, home_server }`\n     * @returns Rejects: with an error response.\n     */\n    public registerGuest({ body }: { body?: RegisterRequest } = {}): Promise<RegisterResponse> {\n        return this.registerRequest(body || {}, \"guest\");\n    }\n\n    /**\n     * @param data - parameters for registration request\n     * @param kind - type of user to register. may be \"guest\"\n     * @returns Promise which resolves: to the /register response\n     * @returns Rejects: with an error response.\n     */\n    public registerRequest(data: RegisterRequest, kind?: string): Promise<RegisterResponse> {\n        const params: { kind?: string } = {};\n        if (kind) {\n            params.kind = kind;\n        }\n\n        return this.http.request(Method.Post, \"/register\", params, data);\n    }\n\n    /**\n     * Refreshes an access token using a provided refresh token. The refresh token\n     * must be valid for the current access token known to the client instance.\n     *\n     * Note that this function will not cause a logout if the token is deemed\n     * unknown by the server - the caller is responsible for managing logout\n     * actions on error.\n     * @param refreshToken - The refresh token.\n     * @returns Promise which resolves to the new token.\n     * @returns Rejects with an error response.\n     */\n    public refreshToken(refreshToken: string): Promise<IRefreshTokenResponse> {\n        const performRefreshRequestWithPrefix = (prefix: ClientPrefix): Promise<IRefreshTokenResponse> =>\n            this.http.authedRequest(\n                Method.Post,\n                \"/refresh\",\n                undefined,\n                { refresh_token: refreshToken },\n                {\n                    prefix,\n                    inhibitLogoutEmit: true, // we don't want to cause logout loops\n                },\n            );\n\n        // First try with the (specced) /v3/ prefix.\n        // However, before Synapse 1.72.0, Synapse incorrectly required a /v1/ prefix, so we fall\n        // back to that if the request fails, for backwards compatibility.\n        return performRefreshRequestWithPrefix(ClientPrefix.V3).catch((e) => {\n            if (e.errcode === \"M_UNRECOGNIZED\") {\n                return performRefreshRequestWithPrefix(ClientPrefix.V1);\n            }\n            throw e;\n        });\n    }\n\n    /**\n     * @returns Promise which resolves to the available login flows\n     * @returns Rejects: with an error response.\n     */\n    public loginFlows(): Promise<ILoginFlowsResponse> {\n        return this.http.request(Method.Get, \"/login\");\n    }\n\n    /**\n     * @returns Promise which resolves to a LoginResponse object\n     * @returns Rejects: with an error response.\n     *\n     * @deprecated This method has unintuitive behaviour: it updates the `MatrixClient` instance with *some* of the\n     *    returned credentials. Instead, call {@link loginRequest} and create a new `MatrixClient` instance using the\n     *    results. See https://github.com/matrix-org/matrix-js-sdk/issues/4502.\n     */\n    public login(loginType: LoginRequest[\"type\"], data: Omit<LoginRequest, \"type\">): Promise<LoginResponse> {\n        return this.loginRequest({\n            ...data,\n            type: loginType,\n        }).then((response) => {\n            if (response.access_token && response.user_id) {\n                this.http.opts.accessToken = response.access_token;\n                this.credentials = {\n                    userId: response.user_id,\n                };\n            }\n            return response;\n        });\n    }\n\n    /**\n     * @returns Promise which resolves to a LoginResponse object\n     * @returns Rejects: with an error response.\n     *\n     * @deprecated This method has unintuitive behaviour: it updates the `MatrixClient` instance with *some* of the\n     *   returned credentials. Instead, call {@link loginRequest} with `data.type: \"m.login.password\"`, and create a new\n     *   `MatrixClient` instance using the results. See https://github.com/matrix-org/matrix-js-sdk/issues/4502.\n     */\n    public loginWithPassword(user: string, password: string): Promise<LoginResponse> {\n        return this.login(\"m.login.password\", {\n            user: user,\n            password: password,\n        });\n    }\n\n    /**\n     * @param redirectUrl - The URL to redirect to after the HS\n     * authenticates with CAS.\n     * @returns The HS URL to hit to begin the CAS login process.\n     */\n    public getCasLoginUrl(redirectUrl: string): string {\n        return this.getSsoLoginUrl(redirectUrl, \"cas\");\n    }\n\n    /**\n     * @param redirectUrl - The URL to redirect to after the HS\n     *     authenticates with the SSO.\n     * @param loginType - The type of SSO login we are doing (sso or cas).\n     *     Defaults to 'sso'.\n     * @param idpId - The ID of the Identity Provider being targeted, optional.\n     * @param action - the SSO flow to indicate to the IdP, optional.\n     * @returns The HS URL to hit to begin the SSO login process.\n     */\n    public getSsoLoginUrl(redirectUrl: string, loginType = \"sso\", idpId?: string, action?: SSOAction): string {\n        let url = \"/login/\" + loginType + \"/redirect\";\n        if (idpId) {\n            url += \"/\" + idpId;\n        }\n\n        const params = {\n            redirectUrl,\n            [SSO_ACTION_PARAM.stable!]: action,\n            [SSO_ACTION_PARAM.unstable!]: action,\n        };\n\n        return this.http.getUrl(url, params).href;\n    }\n\n    /**\n     * @param token - Login token previously received from homeserver\n     * @returns Promise which resolves to a LoginResponse object\n     * @returns Rejects: with an error response.\n     *\n     * @deprecated This method has unintuitive behaviour: it updates the `MatrixClient` instance with *some* of the\n     *   returned credentials. Instead, call {@link loginRequest} with `data.type: \"m.login.token\"`, and create a new\n     *   `MatrixClient` instance using the results. See https://github.com/matrix-org/matrix-js-sdk/issues/4502.\n     */\n    public loginWithToken(token: string): Promise<LoginResponse> {\n        return this.login(\"m.login.token\", {\n            token: token,\n        });\n    }\n\n    /**\n     * Sends a `POST /login` request to the server.\n     *\n     * If successful, this will create a new device and access token for the user.\n     *\n     * @see {@link MatrixClient.loginFlows} which makes a `GET /login` request.\n     * @see https://spec.matrix.org/v1.13/client-server-api/#post_matrixclientv3login\n     *\n     * @param data - Credentials and other details for the login request.\n     */\n    public async loginRequest(data: LoginRequest): Promise<LoginResponse> {\n        return await this.http.authedRequest<LoginResponse>(Method.Post, \"/login\", undefined, data);\n    }\n\n    /**\n     * Logs out the current session.\n     * Obviously, further calls that require authorisation should fail after this\n     * method is called. The state of the MatrixClient object is not affected:\n     * it is up to the caller to either reset or destroy the MatrixClient after\n     * this method succeeds.\n     * @param stopClient - whether to stop the client before calling /logout to prevent invalid token errors.\n     * @returns Promise which resolves: On success, the empty object `{}`\n     */\n    public async logout(stopClient = false): Promise<EmptyObject> {\n        if (stopClient) {\n            this.stopClient();\n            this.http.abort();\n        }\n\n        return this.http.authedRequest(Method.Post, \"/logout\");\n    }\n\n    /**\n     * Deactivates the logged-in account.\n     * Obviously, further calls that require authorisation should fail after this\n     * method is called. The state of the MatrixClient object is not affected:\n     * it is up to the caller to either reset or destroy the MatrixClient after\n     * this method succeeds.\n     * @param auth - Optional. Auth data to supply for User-Interactive auth.\n     * @param erase - Optional. If set, send as `erase` attribute in the\n     * JSON request body, indicating whether the account should be erased. Defaults\n     * to false.\n     * @returns Promise which resolves: On success, the empty object\n     */\n    public deactivateAccount(\n        auth?: AuthDict,\n        erase?: boolean,\n    ): Promise<{ id_server_unbind_result: IdServerUnbindResult }> {\n        const body: Body = {};\n        if (auth) {\n            body.auth = auth;\n        }\n        if (erase !== undefined) {\n            body.erase = erase;\n        }\n\n        return this.http.authedRequest(Method.Post, \"/account/deactivate\", undefined, body);\n    }\n\n    /**\n     * Make a request for an `m.login.token` to be issued as per\n     * https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv1loginget_token\n     *\n     * The server may require User-Interactive auth.\n     *\n     * @param auth - Optional. Auth data to supply for User-Interactive auth.\n     * @returns Promise which resolves: On success, the token response\n     * or UIA auth data.\n     */\n    public async requestLoginToken(auth?: AuthDict): Promise<LoginTokenPostResponse> {\n        const body: UIARequest<unknown> = { auth };\n        return this.http.authedRequest<LoginTokenPostResponse>(\n            Method.Post,\n            \"/login/get_token\",\n            undefined, // no query params\n            body,\n            { prefix: ClientPrefix.V1 },\n        );\n    }\n\n    /**\n     * Get the fallback URL to use for unknown interactive-auth stages.\n     *\n     * @param loginType -     the type of stage being attempted\n     * @param authSessionId - the auth session ID provided by the homeserver\n     *\n     * @returns HS URL to hit to for the fallback interface\n     */\n    public getFallbackAuthUrl(loginType: string, authSessionId: string): string {\n        const path = utils.encodeUri(\"/auth/$loginType/fallback/web\", {\n            $loginType: loginType,\n        });\n\n        return this.http.getUrl(path, {\n            session: authSessionId,\n        }).href;\n    }\n\n    /**\n     * Create a new room.\n     * @param options - a list of options to pass to the /createRoom API.\n     * @returns Promise which resolves: `{room_id: {string}}`\n     * @returns Rejects: with an error response.\n     */\n    public async createRoom(options: ICreateRoomOpts): Promise<{ room_id: string }> {\n        // eslint-disable-line camelcase\n        // some valid options include: room_alias_name, visibility, invite\n\n        // inject the id_access_token if inviting 3rd party addresses\n        const invitesNeedingToken = (options.invite_3pid || []).filter((i) => !i.id_access_token);\n        if (invitesNeedingToken.length > 0 && this.identityServer?.getAccessToken) {\n            const identityAccessToken = await this.identityServer.getAccessToken();\n            if (identityAccessToken) {\n                for (const invite of invitesNeedingToken) {\n                    invite.id_access_token = identityAccessToken;\n                }\n            }\n        }\n\n        return this.http.authedRequest(Method.Post, \"/createRoom\", undefined, options);\n    }\n\n    /**\n     * Fetches relations for a given event\n     * @param roomId - the room of the event\n     * @param eventId - the id of the event\n     * @param relationType - the rel_type of the relations requested\n     * @param eventType - the event type of the relations requested\n     * @param opts - options with optional values for the request.\n     * @returns the response, with chunk, prev_batch and, next_batch.\n     */\n    public fetchRelations(\n        roomId: string,\n        eventId: string,\n        relationType: RelationType | string | null,\n        eventType?: EventType | string | null,\n        opts: IRelationsRequestOpts = { dir: Direction.Backward },\n    ): Promise<IRelationsResponse> {\n        let params = opts as QueryDict;\n        if (Thread.hasServerSideFwdPaginationSupport === FeatureSupport.Experimental) {\n            params = replaceParam(\"dir\", \"org.matrix.msc3715.dir\", params);\n        }\n        if (this.canSupport.get(Feature.RelationsRecursion) === ServerSupport.Unstable) {\n            params = replaceParam(\"recurse\", \"org.matrix.msc3981.recurse\", params);\n        }\n        const queryString = utils.encodeParams(params);\n\n        let templatedUrl = \"/rooms/$roomId/relations/$eventId\";\n        if (relationType !== null) {\n            templatedUrl += \"/$relationType\";\n            if (eventType !== null) {\n                templatedUrl += \"/$eventType\";\n            }\n        } else if (eventType !== null) {\n            this.logger.warn(`eventType: ${eventType} ignored when fetching\n            relations as relationType is null`);\n            eventType = null;\n        }\n\n        const path = utils.encodeUri(templatedUrl + \"?\" + queryString, {\n            $roomId: roomId,\n            $eventId: eventId,\n            $relationType: relationType!,\n            $eventType: eventType!,\n        });\n        return this.http.authedRequest(Method.Get, path, undefined, undefined, {\n            prefix: ClientPrefix.V1,\n        });\n    }\n\n    /**\n     * @returns Promise which resolves: TODO\n     * @returns Rejects: with an error response.\n     */\n    public roomState(roomId: string): Promise<IStateEventWithRoomId[]> {\n        const path = utils.encodeUri(\"/rooms/$roomId/state\", { $roomId: roomId });\n        return this.http.authedRequest(Method.Get, path);\n    }\n\n    /**\n     * Get an event in a room by its event id.\n     *\n     * @returns Promise which resolves to an object containing the event.\n     * @returns Rejects: with an error response.\n     */\n    public fetchRoomEvent(roomId: string, eventId: string): Promise<Partial<IEvent>> {\n        const path = utils.encodeUri(\"/rooms/$roomId/event/$eventId\", {\n            $roomId: roomId,\n            $eventId: eventId,\n        });\n        return this.http.authedRequest(Method.Get, path);\n    }\n\n    /**\n     * @param includeMembership - the membership type to include in the response\n     * @param excludeMembership - the membership type to exclude from the response\n     * @param atEventId - the id of the event for which moment in the timeline the members should be returned for\n     * @returns Promise which resolves: dictionary of userid to profile information\n     * @returns Rejects: with an error response.\n     */\n    public members(\n        roomId: string,\n        includeMembership?: string,\n        excludeMembership?: string,\n        atEventId?: string,\n    ): Promise<{ [userId: string]: IStateEventWithRoomId[] }> {\n        const queryParams: Record<string, string> = {};\n        if (includeMembership) {\n            queryParams.membership = includeMembership;\n        }\n        if (excludeMembership) {\n            queryParams.not_membership = excludeMembership;\n        }\n        if (atEventId) {\n            queryParams.at = atEventId;\n        }\n\n        const queryString = utils.encodeParams(queryParams);\n\n        const path = utils.encodeUri(\"/rooms/$roomId/members?\" + queryString, { $roomId: roomId });\n        return this.http.authedRequest(Method.Get, path);\n    }\n\n    /**\n     * Upgrades a room to a new protocol version\n     * @param newVersion - The target version to upgrade to\n     * @param additionalCreators - an optional list of user IDs of users who\n     *        should have the same permissions as the user performing the\n     *        upgrade\n     * @returns Promise which resolves: Object with key 'replacement_room'\n     * @returns Rejects: with an error response.\n     */\n    public upgradeRoom(\n        roomId: string,\n        newVersion: string,\n        additionalCreators?: string[],\n    ): Promise<{ replacement_room: string }> {\n        // eslint-disable-line camelcase\n        const path = utils.encodeUri(\"/rooms/$roomId/upgrade\", { $roomId: roomId });\n        return this.http.authedRequest(Method.Post, path, undefined, {\n            new_version: newVersion,\n            additional_creators: additionalCreators,\n        });\n    }\n\n    /**\n     * Retrieve a state event.\n     * @returns Promise which resolves: TODO\n     * @returns Rejects: with an error response.\n     */\n    public getStateEvent(roomId: string, eventType: string, stateKey: string): Promise<Record<string, any>> {\n        const pathParams = {\n            $roomId: roomId,\n            $eventType: eventType,\n            $stateKey: stateKey,\n        };\n        let path = utils.encodeUri(\"/rooms/$roomId/state/$eventType\", pathParams);\n        if (stateKey !== undefined) {\n            path = utils.encodeUri(path + \"/$stateKey\", pathParams);\n        }\n        return this.http.authedRequest(Method.Get, path);\n    }\n\n    /**\n     * Send a state event into a room\n     * @param roomId - ID of the room to send the event into\n     * @param eventType - type of the state event to send\n     * @param content - content of the event to send\n     * @param stateKey - the stateKey to send into the room\n     * @param opts - Options for the request function.\n     * @returns Promise which resolves: TODO\n     * @returns Rejects: with an error response.\n     */\n    public async sendStateEvent<K extends keyof StateEvents>(\n        roomId: string,\n        eventType: K,\n        content: StateEvents[K],\n        stateKey = \"\",\n        opts: IRequestOpts = {},\n    ): Promise<ISendEventResponse> {\n        const room = this.getRoom(roomId);\n        const event = new MatrixEvent({\n            room_id: roomId,\n            type: eventType,\n            state_key: stateKey,\n            // Cast safety: StateEvents[K] is a stronger bound than IContent, which has [key: string]: any\n            content: content as IContent,\n        });\n\n        await this.encryptStateEventIfNeeded(event, room ?? undefined);\n\n        const pathParams = {\n            $roomId: roomId,\n            $eventType: event.getWireType(),\n            $stateKey: event.getWireStateKey(),\n        };\n        let path = utils.encodeUri(\"/rooms/$roomId/state/$eventType\", pathParams);\n        if (stateKey !== undefined) {\n            path = utils.encodeUri(path + \"/$stateKey\", pathParams);\n        }\n        return this.http.authedRequest(Method.Put, path, undefined, event.getWireContent(), opts);\n    }\n\n    private async encryptStateEventIfNeeded(event: MatrixEvent, room?: Room): Promise<void> {\n        if (!this.enableEncryptedStateEvents) {\n            return;\n        }\n\n        // If the room is unknown, we cannot encrypt for it\n        if (!room) return;\n\n        if (!this.cryptoBackend && this.usingExternalCrypto) {\n            // The client has opted to allow sending messages to encrypted\n            // rooms even if the room is encrypted, and we haven't set up\n            // crypto. This is useful for users of matrix-org/pantalaimon\n            return;\n        }\n\n        if (!this.cryptoBackend) {\n            throw new Error(\"This room is configured to use encryption, but your client does not support encryption.\");\n        }\n\n        // Check regular encryption conditions.\n        if (!(await this.shouldEncryptEventForRoom(event, room))) {\n            return;\n        }\n\n        // If the crypto impl thinks we shouldn't encrypt, then we shouldn't.\n        // Safety: we checked the crypto impl exists above.\n        if (!(await this.cryptoBackend!.isStateEncryptionEnabledInRoom(room.roomId))) {\n            return;\n        }\n\n        // Check if the event is excluded under MSC4362\n        if (\n            [\n                \"m.room.create\",\n                \"m.room.member\",\n                \"m.room.join_rules\",\n                \"m.room.power_levels\",\n                \"m.room.third_party_invite\",\n                \"m.room.history_visibility\",\n                \"m.room.guest_access\",\n                \"m.room.encryption\",\n            ].includes(event.getType())\n        ) {\n            return;\n        }\n\n        await this.cryptoBackend.encryptEvent(event, room);\n    }\n\n    /**\n     * @returns Promise which resolves: TODO\n     * @returns Rejects: with an error response.\n     */\n    public roomInitialSync(roomId: string, limit: number): Promise<IRoomInitialSyncResponse> {\n        const path = utils.encodeUri(\"/rooms/$roomId/initialSync\", { $roomId: roomId });\n\n        return this.http.authedRequest(Method.Get, path, { limit: limit?.toString() ?? \"30\" });\n    }\n\n    /**\n     * Set a marker to indicate the point in a room before which the user has read every\n     * event. This can be retrieved from room account data (the event type is `m.fully_read`)\n     * and displayed as a horizontal line in the timeline that is visually distinct to the\n     * position of the user's own read receipt.\n     * @param roomId - ID of the room that has been read\n     * @param rmEventId - ID of the event that has been read\n     * @param rrEventId - ID of the event tracked by the read receipt. This is here\n     * for convenience because the RR and the RM are commonly updated at the same time as\n     * each other. Optional.\n     * @param rpEventId - rpEvent the m.read.private read receipt event for when we\n     * don't want other users to see the read receipts. This is experimental. Optional.\n     * @returns Promise which resolves: the empty object, `{}`.\n     */\n    public async setRoomReadMarkersHttpRequest(\n        roomId: string,\n        rmEventId: string,\n        rrEventId?: string,\n        rpEventId?: string,\n    ): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/rooms/$roomId/read_markers\", {\n            $roomId: roomId,\n        });\n\n        const content: IContent = {\n            [ReceiptType.FullyRead]: rmEventId,\n            [ReceiptType.Read]: rrEventId,\n        };\n\n        if (\n            (await this.doesServerSupportUnstableFeature(\"org.matrix.msc2285.stable\")) ||\n            (await this.isVersionSupported(\"v1.4\"))\n        ) {\n            content[ReceiptType.ReadPrivate] = rpEventId;\n        }\n\n        return this.http.authedRequest(Method.Post, path, undefined, content);\n    }\n\n    /**\n     * @returns Promise which resolves: A list of the user's current rooms\n     * @returns Rejects: with an error response.\n     */\n    public getJoinedRooms(): Promise<IJoinedRoomsResponse> {\n        const path = utils.encodeUri(\"/joined_rooms\", {});\n        return this.http.authedRequest(Method.Get, path);\n    }\n\n    /**\n     * Retrieve membership info. for a room.\n     * @param roomId - ID of the room to get membership for\n     * @returns Promise which resolves: A list of currently joined users\n     *                                 and their profile data.\n     * @returns Rejects: with an error response.\n     */\n    public getJoinedRoomMembers(roomId: string): Promise<IJoinedMembersResponse> {\n        const path = utils.encodeUri(\"/rooms/$roomId/joined_members\", {\n            $roomId: roomId,\n        });\n        return this.http.authedRequest(Method.Get, path);\n    }\n\n    /**\n     * Get the public rooms list from the server. Supports pagination\n     * @param params - Options for this request\n     * @returns Promise which resolves: IPublicRoomsResponse\n     * @returns Rejects: with an error response.\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public publicRooms({\n        server,\n        limit,\n        since,\n        ...options\n    }: IRoomDirectoryOptions = {}): Promise<IPublicRoomsResponse> {\n        if (Object.keys(options).length === 0) {\n            const queryParams: QueryDict = { server, limit, since };\n            return this.http.authedRequest(Method.Get, \"/publicRooms\", queryParams);\n        } else {\n            const queryParams: QueryDict = { server };\n            const body = {\n                limit,\n                since,\n                ...options,\n            };\n            return this.http.authedRequest(Method.Post, \"/publicRooms\", queryParams, body);\n        }\n    }\n\n    /**\n     * Create an alias to room ID mapping.\n     * @param alias - The room alias to create.\n     * @param roomId - The room ID to link the alias to.\n     * @returns Promise which resolves: an empty object `{}`\n     * @returns Rejects: with an error response.\n     */\n    public createAlias(alias: string, roomId: string): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/directory/room/$alias\", {\n            $alias: alias,\n        });\n        const data = {\n            room_id: roomId,\n        };\n        return this.http.authedRequest(Method.Put, path, undefined, data);\n    }\n\n    /**\n     * Delete an alias to room ID mapping. This alias must be on your local server,\n     * and you must have sufficient access to do this operation.\n     * @param alias - The room alias to delete.\n     * @returns Promise which resolves: an empty object `{}`.\n     * @returns Rejects: with an error response.\n     */\n    public deleteAlias(alias: string): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/directory/room/$alias\", {\n            $alias: alias,\n        });\n        return this.http.authedRequest(Method.Delete, path);\n    }\n\n    /**\n     * Gets the local aliases for the room. Note: this includes all local aliases, unlike the\n     * curated list from the m.room.canonical_alias state event.\n     * @param roomId - The room ID to get local aliases for.\n     * @returns Promise which resolves: an object with an `aliases` property, containing an array of local aliases\n     * @returns Rejects: with an error response.\n     */\n    public getLocalAliases(roomId: string): Promise<{ aliases: string[] }> {\n        const path = utils.encodeUri(\"/rooms/$roomId/aliases\", { $roomId: roomId });\n        const prefix = ClientPrefix.V3;\n        return this.http.authedRequest(Method.Get, path, undefined, undefined, { prefix });\n    }\n\n    /**\n     * Get room info for the given alias.\n     * @param alias - The room alias to resolve.\n     * @returns Promise which resolves: Object with room_id and servers.\n     * @returns Rejects: with an error response.\n     */\n    public getRoomIdForAlias(alias: string): Promise<{ room_id: string; servers: string[] }> {\n        // eslint-disable-line camelcase\n        const path = utils.encodeUri(\"/directory/room/$alias\", {\n            $alias: alias,\n        });\n        return this.http.authedRequest(Method.Get, path);\n    }\n\n    /**\n     * Get the visibility of a room in the current HS's room directory\n     * @returns Promise which resolves: TODO\n     * @returns Rejects: with an error response.\n     */\n    public getRoomDirectoryVisibility(roomId: string): Promise<{ visibility: Visibility }> {\n        const path = utils.encodeUri(\"/directory/list/room/$roomId\", {\n            $roomId: roomId,\n        });\n        return this.http.authedRequest(Method.Get, path);\n    }\n\n    /**\n     * Set the visibility of a room in the current HS's room directory\n     * @param visibility - \"public\" to make the room visible\n     *                 in the public directory, or \"private\" to make\n     *                 it invisible.\n     * @returns Promise which resolves: to an empty object `{}`\n     * @returns Rejects: with an error response.\n     */\n    public setRoomDirectoryVisibility(roomId: string, visibility: Visibility): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/directory/list/room/$roomId\", {\n            $roomId: roomId,\n        });\n        return this.http.authedRequest(Method.Put, path, undefined, { visibility });\n    }\n\n    /**\n     * Query the user directory with a term matching user IDs, display names and domains.\n     * @param options\n     * @param options.term - the term with which to search.\n     * @param options.limit - the maximum number of results to return. The server will apply a limit if unspecified.\n     * @returns Promise which resolves: an array of results.\n     */\n    public searchUserDirectory({ term, limit }: { term: string; limit?: number }): Promise<IUserDirectoryResponse> {\n        const body: Body = {\n            search_term: term,\n        };\n\n        if (limit !== undefined) {\n            body.limit = limit;\n        }\n\n        return this.http.authedRequest(Method.Post, \"/user_directory/search\", undefined, body);\n    }\n\n    /**\n     * Upload a file to the media repository on the homeserver.\n     *\n     * @param file - The object to upload. On a browser, something that\n     *   can be sent to XMLHttpRequest.send (typically a File).  Under node.js,\n     *   a a Buffer, String or ReadStream.\n     *\n     * @param opts -  options object\n     *\n     * @returns Promise which resolves to response object, or rejects with an error (usually a MatrixError).\n     */\n    public uploadContent(file: FileType, opts?: UploadOpts): Promise<UploadResponse> {\n        return this.http.uploadContent(file, opts);\n    }\n\n    /**\n     * Cancel a file upload in progress\n     * @param upload - The object returned from uploadContent\n     * @returns true if canceled, otherwise false\n     */\n    public cancelUpload(upload: Promise<UploadResponse>): boolean {\n        return this.http.cancelUpload(upload);\n    }\n\n    /**\n     * Get a list of all file uploads in progress\n     * @returns Array of objects representing current uploads.\n     * Currently in progress is element 0. Keys:\n     *  - promise: The promise associated with the upload\n     *  - loaded: Number of bytes uploaded\n     *  - total: Total number of bytes to upload\n     */\n    public getCurrentUploads(): Upload[] {\n        return this.http.getCurrentUploads();\n    }\n\n    /**\n     * @param info - The kind of info to retrieve (e.g. 'displayname',\n     * 'avatar_url').\n     * @returns Promise which resolves: TODO\n     * @returns Rejects: with an error response.\n     */\n    public getProfileInfo(\n        userId: string,\n        info?: string,\n        // eslint-disable-next-line camelcase\n    ): Promise<{ avatar_url?: string; displayname?: string }> {\n        const path = info\n            ? utils.encodeUri(\"/profile/$userId/$info\", { $userId: userId, $info: info })\n            : utils.encodeUri(\"/profile/$userId\", { $userId: userId });\n        return this.http.authedRequest(Method.Get, path);\n    }\n\n    /**\n     * Determine if the server supports extended profiles, as described by MSC4133.\n     *\n     * @returns `true` if supported, otherwise `false`\n     */\n    public async doesServerSupportExtendedProfiles(): Promise<boolean> {\n        return (\n            (await this.isVersionSupported(\"v1.16\")) ||\n            (await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4133_EXTENDED_PROFILES)) ||\n            (await this.doesServerSupportUnstableFeature(STABLE_MSC4133_EXTENDED_PROFILES))\n        );\n    }\n\n    /**\n     * Get the prefix used for extended profile requests.\n     *\n     * @returns The prefix for use with `authedRequest`\n     */\n    private async getExtendedProfileRequestPrefix(): Promise<string> {\n        if (\n            (await this.isVersionSupported(\"v1.16\")) ||\n            (await this.doesServerSupportUnstableFeature(\"uk.tcpip.msc4133.stable\"))\n        ) {\n            return ClientPrefix.V3;\n        }\n        return \"/_matrix/client/unstable/uk.tcpip.msc4133\";\n    }\n\n    /**\n     * Fetch a user's *extended* profile, which may include additional keys.\n     * Always returns all available profile fields, irrespective of what profile fields are set\n     * in the sync filter.\n     *\n     * @see https://github.com/tcpipuk/matrix-spec-proposals/blob/main/proposals/4133-extended-profiles.md\n     * @param userId The user ID to fetch the profile of.\n     * @returns A set of keys to property values.\n     *\n     * @throws An error if the server does not support MSC4133.\n     * @throws A M_NOT_FOUND error if the profile could not be found.\n     */\n    public async getExtendedProfile(userId: string): Promise<Record<string, unknown>> {\n        if (!(await this.doesServerSupportExtendedProfiles())) {\n            throw new Error(\"Server does not support extended profiles\");\n        }\n\n        // Note that this does not look at the profile cache as this will only contain keys\n        // that we included in the sync filter and this function's purpose is to return the whole profile.\n\n        return this.http.authedRequest(\n            Method.Get,\n            utils.encodeUri(\"/profile/$userId\", { $userId: userId }),\n            undefined,\n            undefined,\n            {\n                prefix: await this.getExtendedProfileRequestPrefix(),\n            },\n        );\n    }\n\n    /**\n     * Fetch a specific key from the user's *extended* profile by checking local cache (which is updated from\n     * the sync) and querying the server if no data is cached locally.\n     *\n     * @see https://github.com/tcpipuk/matrix-spec-proposals/blob/main/proposals/4133-extended-profiles.md\n     * @param userId The user ID to fetch the profile of.\n     * @param key The key of the property to fetch.\n     * @returns The property value.\n     *\n     * @throws An error if the server does not support MSC4133.\n     * @throws A M_NOT_FOUND error if the key was not set OR the profile could not be found.\n     */\n    public async getExtendedProfileProperty(userId: string, key: string): Promise<unknown> {\n        if (!(await this.doesServerSupportExtendedProfiles())) {\n            throw new Error(\"Server does not support extended profiles\");\n        }\n        // NOTE: We only read individual keys from a cached profile as we don't have the full profile\n        // cached, only the keys that the user has configured via their sync filter.\n        const storedProfile = await this.store.getUserProfile(userId);\n        if (storedProfile?.[key] !== undefined) {\n            return storedProfile[key];\n        }\n        const profile = (await this.http.authedRequest(\n            Method.Get,\n            utils.encodeUri(\"/profile/$userId/$key\", { $userId: userId, $key: key }),\n            undefined,\n            undefined,\n            {\n                prefix: await this.getExtendedProfileRequestPrefix(),\n            },\n        )) as SyncUserProfile;\n\n        // write through to the cache\n        await this.store.storeUserProfiles(new Map([[userId, profile]]));\n\n        return profile[key];\n    }\n\n    /**\n     * Set a property on your *extended* profile.\n     *\n     * @see https://github.com/tcpipuk/matrix-spec-proposals/blob/main/proposals/4133-extended-profiles.md\n     * @param key The key of the property to set.\n     * @param value The value to set on the property.\n     *\n     * @throws An error if the server does not support MSC4133 OR the server disallows editing the user profile.\n     */\n    public async setExtendedProfileProperty(key: string, value: unknown): Promise<void> {\n        if (!(await this.doesServerSupportExtendedProfiles())) {\n            throw new Error(\"Server does not support extended profiles\");\n        }\n        const userId = this.getUserId();\n\n        await this.http.authedRequest(\n            Method.Put,\n            utils.encodeUri(\"/profile/$userId/$key\", { $userId: userId, $key: key }),\n            undefined,\n            { [key]: value },\n            {\n                prefix: await this.getExtendedProfileRequestPrefix(),\n            },\n        );\n    }\n\n    /**\n     * Delete a property on your *extended* profile.\n     *\n     * @see https://github.com/tcpipuk/matrix-spec-proposals/blob/main/proposals/4133-extended-profiles.md\n     * @param key The key of the property to delete.\n     *\n     * @throws An error if the server does not support MSC4133 OR the server disallows editing the user profile.\n     */\n    public async deleteExtendedProfileProperty(key: string): Promise<void> {\n        if (!(await this.doesServerSupportExtendedProfiles())) {\n            throw new Error(\"Server does not support extended profiles\");\n        }\n        const userId = this.getUserId();\n\n        await this.http.authedRequest(\n            Method.Delete,\n            utils.encodeUri(\"/profile/$userId/$key\", { $userId: userId, $key: key }),\n            undefined,\n            undefined,\n            {\n                prefix: await this.getExtendedProfileRequestPrefix(),\n            },\n        );\n    }\n\n    /**\n     * Update multiple properties on your *extended* profile. This will\n     * merge with any existing keys.\n     *\n     * @see https://github.com/tcpipuk/matrix-spec-proposals/blob/main/proposals/4133-extended-profiles.md\n     * @param profile The profile object to merge with the existing profile.\n     * @returns The newly merged profile.\n     *\n     * @throws An error if the server does not support MSC4133 OR the server disallows editing the user profile.\n     */\n    public async patchExtendedProfile(profile: Record<string, unknown>): Promise<Record<string, unknown>> {\n        if (!(await this.doesServerSupportExtendedProfiles())) {\n            throw new Error(\"Server does not support extended profiles\");\n        }\n        const userId = this.getUserId();\n\n        return this.http.authedRequest(\n            Method.Patch,\n            utils.encodeUri(\"/profile/$userId\", { $userId: userId }),\n            {},\n            profile,\n            {\n                prefix: await this.getExtendedProfileRequestPrefix(),\n            },\n        );\n    }\n\n    /**\n     * Set multiple properties on your *extended* profile. This will completely\n     * replace the existing profile, removing any unspecified keys.\n     *\n     * @see https://github.com/tcpipuk/matrix-spec-proposals/blob/main/proposals/4133-extended-profiles.md\n     * @param profile The profile object to set.\n     *\n     * @throws An error if the server does not support MSC4133 OR the server disallows editing the user profile.\n     */\n    public async setExtendedProfile(profile: Record<string, unknown>): Promise<void> {\n        if (!(await this.doesServerSupportExtendedProfiles())) {\n            throw new Error(\"Server does not support extended profiles\");\n        }\n        const userId = this.getUserId();\n\n        await this.http.authedRequest(\n            Method.Put,\n            utils.encodeUri(\"/profile/$userId\", { $userId: userId }),\n            {},\n            profile,\n            {\n                prefix: await this.getExtendedProfileRequestPrefix(),\n            },\n        );\n    }\n\n    /**\n     * @returns Promise which resolves to a list of the user's threepids.\n     * @returns Rejects: with an error response.\n     */\n    public getThreePids(): Promise<{ threepids: IThreepid[] }> {\n        return this.http.authedRequest(Method.Get, \"/account/3pid\");\n    }\n\n    /**\n     * Add a 3PID to your homeserver account. This API does not use an identity\n     * server, as the homeserver is expected to handle 3PID ownership validation.\n     *\n     * @param data - A object with 3PID validation data from having called\n     * `account/3pid/<medium>/requestToken` on the homeserver.\n     * @returns Promise which resolves: to an empty object `{}`\n     * @returns Rejects: with an error response.\n     */\n    public async addThreePidOnly(data: IAddThreePidOnlyBody): Promise<EmptyObject> {\n        const path = \"/account/3pid/add\";\n        return this.http.authedRequest(Method.Post, path, undefined, data);\n    }\n\n    /**\n     * Bind a 3PID for discovery onto an identity server via the homeserver. The\n     * identity server handles 3PID ownership validation and the homeserver records\n     * the new binding to track where all 3PIDs for the account are bound.\n     *\n     * @param data - A object with 3PID validation data from having called\n     * `validate/<medium>/requestToken` on the identity server. It should also\n     * contain `id_server` and `id_access_token` fields as well.\n     * @returns Promise which resolves: to an empty object `{}`\n     * @returns Rejects: with an error response.\n     */\n    public async bindThreePid(data: IBindThreePidBody): Promise<EmptyObject> {\n        const path = \"/account/3pid/bind\";\n        return this.http.authedRequest(Method.Post, path, undefined, data);\n    }\n\n    /**\n     * Unbind a 3PID for discovery on an identity server via the homeserver. The\n     * homeserver removes its record of the binding to keep an updated record of\n     * where all 3PIDs for the account are bound.\n     *\n     * @param medium - The threepid medium (eg. 'email')\n     * @param address - The threepid address (eg. 'bob\\@example.com')\n     *        this must be as returned by getThreePids.\n     * @returns Promise which resolves: on success\n     * @returns Rejects: with an error response.\n     */\n    public async unbindThreePid(\n        medium: string,\n        address: string,\n        // eslint-disable-next-line camelcase\n    ): Promise<{ id_server_unbind_result: IdServerUnbindResult }> {\n        const path = \"/account/3pid/unbind\";\n        const data = {\n            medium,\n            address,\n            id_server: this.getIdentityServerUrl(true),\n        };\n        return this.http.authedRequest(Method.Post, path, undefined, data);\n    }\n\n    /**\n     * @param medium - The threepid medium (eg. 'email')\n     * @param address - The threepid address (eg. 'bob\\@example.com')\n     *        this must be as returned by getThreePids.\n     * @returns Promise which resolves: The server response on success\n     *     (generally the empty JSON object)\n     * @returns Rejects: with an error response.\n     */\n    public deleteThreePid(\n        medium: string,\n        address: string,\n        // eslint-disable-next-line camelcase\n    ): Promise<{ id_server_unbind_result: IdServerUnbindResult }> {\n        const path = \"/account/3pid/delete\";\n        return this.http.authedRequest(Method.Post, path, undefined, { medium, address });\n    }\n\n    /**\n     * Make a request to change your password.\n     * @param newPassword - The new desired password.\n     * @param logoutDevices - Should all sessions be logged out after the password change. Defaults to true.\n     * @returns Promise which resolves: to an empty object `{}`\n     * @returns Rejects: with an error response.\n     */\n    public setPassword(authDict: AuthDict, newPassword: string, logoutDevices?: boolean): Promise<EmptyObject> {\n        const path = \"/account/password\";\n        const data = {\n            auth: authDict,\n            new_password: newPassword,\n            logout_devices: logoutDevices,\n        };\n\n        return this.http.authedRequest<EmptyObject>(Method.Post, path, undefined, data);\n    }\n\n    /**\n     * Gets all devices recorded for the logged-in user\n     * @returns Promise which resolves: result object\n     * @returns Rejects: with an error response.\n     */\n    public getDevices(): Promise<{ devices: IMyDevice[] }> {\n        return this.http.authedRequest(Method.Get, \"/devices\");\n    }\n\n    /**\n     * Gets specific device details for the logged-in user\n     * @param deviceId -  device to query\n     * @returns Promise which resolves: result object\n     * @returns Rejects: with an error response.\n     */\n    public getDevice(deviceId: string): Promise<IMyDevice> {\n        const path = utils.encodeUri(\"/devices/$device_id\", {\n            $device_id: deviceId,\n        });\n        return this.http.authedRequest(Method.Get, path);\n    }\n\n    /**\n     * Update the given device\n     *\n     * @param deviceId -  device to update\n     * @param body -       body of request\n     * @returns Promise which resolves: to an empty object `{}`\n     * @returns Rejects: with an error response.\n     */\n    // eslint-disable-next-line camelcase\n    public setDeviceDetails(deviceId: string, body: { display_name: string }): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/devices/$device_id\", {\n            $device_id: deviceId,\n        });\n\n        return this.http.authedRequest(Method.Put, path, undefined, body);\n    }\n\n    /**\n     * Delete the given device\n     *\n     * @param deviceId -  device to delete\n     * @param auth - Optional. Auth data to supply for User-Interactive auth.\n     * @returns Promise which resolves: result object\n     * @returns Rejects: with an error response.\n     */\n    public deleteDevice(deviceId: string, auth?: AuthDict): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/devices/$device_id\", {\n            $device_id: deviceId,\n        });\n\n        const body: Body = {};\n\n        if (auth) {\n            body.auth = auth;\n        }\n\n        return this.http.authedRequest(Method.Delete, path, undefined, body);\n    }\n\n    /**\n     * Delete multiple device\n     *\n     * @param devices - IDs of the devices to delete\n     * @param auth - Optional. Auth data to supply for User-Interactive auth.\n     * @returns Promise which resolves: result object\n     * @returns Rejects: with an error response.\n     */\n    public deleteMultipleDevices(devices: string[], auth?: AuthDict): Promise<EmptyObject> {\n        const body: Body = { devices };\n\n        if (auth) {\n            body.auth = auth;\n        }\n\n        const path = \"/delete_devices\";\n        return this.http.authedRequest(Method.Post, path, undefined, body);\n    }\n\n    /**\n     * Gets all pushers registered for the logged-in user\n     *\n     * @returns Promise which resolves: Array of objects representing pushers\n     * @returns Rejects: with an error response.\n     */\n    public async getPushers(): Promise<{ pushers: IPusher[] }> {\n        const response = await this.http.authedRequest<{ pushers: IPusher[] }>(Method.Get, \"/pushers\");\n\n        // Migration path for clients that connect to a homeserver that does not support\n        // MSC3881 yet, see https://github.com/matrix-org/matrix-spec-proposals/blob/kerry/remote-push-toggle/proposals/3881-remote-push-notification-toggling.md#migration\n        if (!(await this.doesServerSupportUnstableFeature(\"org.matrix.msc3881\"))) {\n            response.pushers = response.pushers.map((pusher) => {\n                if (!pusher.hasOwnProperty(PUSHER_ENABLED.name)) {\n                    pusher[PUSHER_ENABLED.name] = true;\n                }\n                return pusher;\n            });\n        }\n\n        return response;\n    }\n\n    /**\n     * Adds a new pusher or updates an existing pusher\n     *\n     * @param pusher - Object representing a pusher\n     * @returns Promise which resolves: Empty json object on success\n     * @returns Rejects: with an error response.\n     */\n    public setPusher(pusher: IPusherRequest): Promise<EmptyObject> {\n        const path = \"/pushers/set\";\n        return this.http.authedRequest(Method.Post, path, undefined, pusher);\n    }\n\n    /**\n     * Removes an existing pusher\n     * @param pushKey - pushkey of pusher to remove\n     * @param appId - app_id of pusher to remove\n     * @returns Promise which resolves: Empty json object on success\n     * @returns Rejects: with an error response.\n     */\n    public removePusher(pushKey: string, appId: string): Promise<EmptyObject> {\n        const path = \"/pushers/set\";\n        const body = {\n            pushkey: pushKey,\n            app_id: appId,\n            kind: null, // marks pusher for removal\n        };\n        return this.http.authedRequest(Method.Post, path, undefined, body);\n    }\n\n    /**\n     * Persists local notification settings\n     * @returns Promise which resolves: an empty object\n     * @returns Rejects: with an error response.\n     */\n    public setLocalNotificationSettings(\n        deviceId: string,\n        notificationSettings: LocalNotificationSettings,\n    ): Promise<EmptyObject> {\n        const key = `${LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${deviceId}` as const;\n        return this.setAccountData(key, notificationSettings);\n    }\n\n    /**\n     * Get the push rules for the account from the server.\n     * @returns Promise which resolves to the push rules.\n     * @returns Rejects: with an error response.\n     */\n    public getPushRules(): Promise<IPushRules> {\n        return this.http.authedRequest<IPushRules>(Method.Get, \"/pushrules/\").then((rules: IPushRules) => {\n            this.setPushRules(rules);\n            return this.pushRules!;\n        });\n    }\n\n    /**\n     * Update the push rules for the account. This should be called whenever\n     * updated push rules are available.\n     */\n    public setPushRules(rules: IPushRules): void {\n        // Fix-up defaults, if applicable.\n        this.pushRules = PushProcessor.rewriteDefaultRules(this.logger, rules, this.getUserId()!);\n        // Pre-calculate any necessary caches.\n        this.pushProcessor.updateCachedPushRuleKeys(this.pushRules);\n    }\n\n    /**\n     * @returns Promise which resolves: an empty object `{}`\n     * @returns Rejects: with an error response.\n     */\n    public addPushRule(\n        scope: string,\n        kind: PushRuleKind,\n        ruleId: Exclude<string, RuleId>,\n        body: Pick<IPushRule, \"actions\" | \"conditions\" | \"pattern\">,\n    ): Promise<EmptyObject> {\n        // NB. Scope not uri encoded because devices need the '/'\n        const path = utils.encodeUri(\"/pushrules/\" + scope + \"/$kind/$ruleId\", {\n            $kind: kind,\n            $ruleId: ruleId,\n        });\n        return this.http.authedRequest(Method.Put, path, undefined, body);\n    }\n\n    /**\n     * @returns Promise which resolves: an empty object `{}`\n     * @returns Rejects: with an error response.\n     */\n    public deletePushRule(scope: string, kind: PushRuleKind, ruleId: Exclude<string, RuleId>): Promise<EmptyObject> {\n        // NB. Scope not uri encoded because devices need the '/'\n        const path = utils.encodeUri(\"/pushrules/\" + scope + \"/$kind/$ruleId\", {\n            $kind: kind,\n            $ruleId: ruleId,\n        });\n        return this.http.authedRequest(Method.Delete, path);\n    }\n\n    /**\n     * Enable or disable a push notification rule.\n     * @returns Promise which resolves: to an empty object `{}`\n     * @returns Rejects: with an error response.\n     */\n    public setPushRuleEnabled(\n        scope: string,\n        kind: PushRuleKind,\n        ruleId: RuleId | string,\n        enabled: boolean,\n    ): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/pushrules/\" + scope + \"/$kind/$ruleId/enabled\", {\n            $kind: kind,\n            $ruleId: ruleId,\n        });\n        return this.http.authedRequest(Method.Put, path, undefined, { enabled: enabled });\n    }\n\n    /**\n     * Set the actions for a push notification rule.\n     * @returns Promise which resolves: to an empty object `{}`\n     * @returns Rejects: with an error response.\n     */\n    public setPushRuleActions(\n        scope: string,\n        kind: PushRuleKind,\n        ruleId: RuleId | string,\n        actions: PushRuleAction[],\n    ): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/pushrules/\" + scope + \"/$kind/$ruleId/actions\", {\n            $kind: kind,\n            $ruleId: ruleId,\n        });\n        return this.http.authedRequest(Method.Put, path, undefined, { actions: actions });\n    }\n\n    /**\n     * Perform a server-side search.\n     * @param params\n     * @param params.next_batch - the batch token to pass in the query string\n     * @param params.body - the JSON object to pass to the request body.\n     * @param abortSignal - optional signal used to cancel the http request.\n     * @returns Promise which resolves to the search response object.\n     * @returns Rejects: with an error response.\n     */\n    public search(\n        { body, next_batch: nextBatch }: { body: ISearchRequestBody; next_batch?: string },\n        abortSignal?: AbortSignal,\n    ): Promise<ISearchResponse> {\n        const queryParams: QueryDict = {};\n        if (nextBatch) {\n            queryParams.next_batch = nextBatch;\n        }\n        return this.http.authedRequest(Method.Post, \"/search\", queryParams, body, { abortSignal });\n    }\n\n    /**\n     * Upload keys\n     *\n     * @param content -  body of upload request\n     *\n     * @param opts - this method no longer takes any opts,\n     *  used to take opts.device_id but this was not removed from the spec as a redundant parameter\n     *\n     * @returns Promise which resolves: result object. Rejects: with\n     *     an error response ({@link MatrixError}).\n     */\n    public uploadKeysRequest(content: IUploadKeysRequest, opts?: void): Promise<IKeysUploadResponse> {\n        return this.http.authedRequest(Method.Post, \"/keys/upload\", undefined, content);\n    }\n\n    public uploadKeySignatures(content: KeySignatures): Promise<IUploadKeySignaturesResponse> {\n        return this.http.authedRequest(Method.Post, \"/keys/signatures/upload\", undefined, content);\n    }\n\n    /**\n     * Download device keys\n     *\n     * @param userIds -  list of users to get keys for\n     *\n     * @param token - sync token to pass in the query request, to help\n     *   the HS give the most recent results\n     *\n     * @returns Promise which resolves: result object. Rejects: with\n     *     an error response ({@link MatrixError}).\n     */\n    public downloadKeysForUsers(userIds: string[], { token }: { token?: string } = {}): Promise<IDownloadKeyResult> {\n        const content: IQueryKeysRequest = {\n            device_keys: {},\n        };\n        if (token !== undefined) {\n            content.token = token;\n        }\n        userIds.forEach((u) => {\n            content.device_keys[u] = [];\n        });\n\n        return this.http.authedRequest(Method.Post, \"/keys/query\", undefined, content);\n    }\n\n    /**\n     * Claim one-time keys\n     *\n     * @param devices -  a list of [userId, deviceId] pairs\n     *\n     * @param keyAlgorithm -  desired key type\n     *\n     * @param timeout - the time (in milliseconds) to wait for keys from remote\n     *     servers\n     *\n     * @returns Promise which resolves: result object. Rejects: with\n     *     an error response ({@link MatrixError}).\n     */\n    public claimOneTimeKeys(\n        devices: [string, string][],\n        keyAlgorithm = \"signed_curve25519\",\n        timeout?: number,\n    ): Promise<IClaimOTKsResult> {\n        const queries: Record<string, Record<string, string>> = {};\n\n        if (keyAlgorithm === undefined) {\n            keyAlgorithm = \"signed_curve25519\";\n        }\n\n        for (const [userId, deviceId] of devices) {\n            const query = queries[userId] || {};\n            safeSet(queries, userId, query);\n            safeSet(query, deviceId, keyAlgorithm);\n        }\n        const content: IClaimKeysRequest = { one_time_keys: queries };\n        if (timeout) {\n            content.timeout = timeout;\n        }\n        const path = \"/keys/claim\";\n        return this.http.authedRequest(Method.Post, path, undefined, content);\n    }\n\n    /**\n     * Ask the server for a list of users who have changed their device lists\n     * between a pair of sync tokens\n     *\n     *\n     * @returns Promise which resolves: result object. Rejects: with\n     *     an error response ({@link MatrixError}).\n     */\n    public getKeyChanges(oldToken: string, newToken: string): Promise<{ changed: string[]; left: string[] }> {\n        const qps = {\n            from: oldToken,\n            to: newToken,\n        };\n\n        return this.http.authedRequest(Method.Get, \"/keys/changes\", qps);\n    }\n\n    public uploadDeviceSigningKeys(auth?: AuthDict, keys?: CrossSigningKeys): Promise<EmptyObject> {\n        // API returns empty object\n        const data = Object.assign({}, keys);\n        if (auth) Object.assign(data, { auth });\n        return this.http.authedRequest(Method.Post, \"/keys/device_signing/upload\", undefined, data, {\n            prefix: ClientPrefix.Unstable,\n        });\n    }\n\n    /**\n     * Register with an identity server using the OpenID token from the user's\n     * Homeserver, which can be retrieved via\n     * {@link MatrixClient#getOpenIdToken}.\n     *\n     * Note that the `/account/register` endpoint (as well as IS authentication in\n     * general) was added as part of the v2 API version.\n     *\n     * @returns Promise which resolves: with object containing an Identity\n     * Server access token.\n     * @returns Rejects: with an error response.\n     */\n    public registerWithIdentityServer(hsOpenIdToken: IOpenIDToken): Promise<{\n        access_token: string;\n        token: string;\n    }> {\n        if (!this.idBaseUrl) {\n            throw new Error(\"No identity server base URL set\");\n        }\n\n        const uri = this.http.getUrl(\"/account/register\", undefined, IdentityPrefix.V2, this.idBaseUrl);\n        return this.http.requestOtherUrl(Method.Post, uri, hsOpenIdToken);\n    }\n\n    /**\n     * Requests an email verification token directly from an identity server.\n     *\n     * This API is used as part of binding an email for discovery on an identity\n     * server. The validation data that results should be passed to the\n     * `bindThreePid` method to complete the binding process.\n     *\n     * @param email - The email address to request a token for\n     * @param clientSecret - A secret binary string generated by the client.\n     *                 It is recommended this be around 16 ASCII characters.\n     * @param sendAttempt - If an identity server sees a duplicate request\n     *                 with the same sendAttempt, it will not send another email.\n     *                 To request another email to be sent, use a larger value for\n     *                 the sendAttempt param as was used in the previous request.\n     * @param nextLink - Optional If specified, the client will be redirected\n     *                 to this link after validation.\n     * @param identityAccessToken - The `access_token` field of the identity\n     * server `/account/register` response (see {@link registerWithIdentityServer}).\n     *\n     * @returns Promise which resolves: TODO\n     * @returns Rejects: with an error response.\n     * @throws Error if no identity server is set\n     */\n    public requestEmailToken(\n        email: string,\n        clientSecret: string,\n        sendAttempt: number,\n        nextLink?: string,\n        identityAccessToken?: string,\n    ): Promise<IRequestTokenResponse> {\n        const params: Record<string, string> = {\n            client_secret: clientSecret,\n            email: email,\n            send_attempt: sendAttempt?.toString(),\n        };\n        if (nextLink) {\n            params.next_link = nextLink;\n        }\n\n        return this.http.idServerRequest<IRequestTokenResponse>(\n            Method.Post,\n            \"/validate/email/requestToken\",\n            params,\n            IdentityPrefix.V2,\n            identityAccessToken,\n        );\n    }\n\n    /**\n     * Requests a MSISDN verification token directly from an identity server.\n     *\n     * This API is used as part of binding a MSISDN for discovery on an identity\n     * server. The validation data that results should be passed to the\n     * `bindThreePid` method to complete the binding process.\n     *\n     * @param phoneCountry - The ISO 3166-1 alpha-2 code for the country in\n     *                 which phoneNumber should be parsed relative to.\n     * @param phoneNumber - The phone number, in national or international\n     *                 format\n     * @param clientSecret - A secret binary string generated by the client.\n     *                 It is recommended this be around 16 ASCII characters.\n     * @param sendAttempt - If an identity server sees a duplicate request\n     *                 with the same sendAttempt, it will not send another SMS.\n     *                 To request another SMS to be sent, use a larger value for\n     *                 the sendAttempt param as was used in the previous request.\n     * @param nextLink - Optional If specified, the client will be redirected\n     *                 to this link after validation.\n     * @param identityAccessToken - The `access_token` field of the Identity\n     * Server `/account/register` response (see {@link registerWithIdentityServer}).\n     *\n     * @returns Promise which resolves to an object with a sid string\n     * @returns Rejects: with an error response.\n     * @throws Error if no identity server is set\n     */\n    public requestMsisdnToken(\n        phoneCountry: string,\n        phoneNumber: string,\n        clientSecret: string,\n        sendAttempt: number,\n        nextLink?: string,\n        identityAccessToken?: string,\n    ): Promise<IRequestMsisdnTokenResponse> {\n        const params: Record<string, string> = {\n            client_secret: clientSecret,\n            country: phoneCountry,\n            phone_number: phoneNumber,\n            send_attempt: sendAttempt?.toString(),\n        };\n        if (nextLink) {\n            params.next_link = nextLink;\n        }\n\n        return this.http.idServerRequest<IRequestMsisdnTokenResponse>(\n            Method.Post,\n            \"/validate/msisdn/requestToken\",\n            params,\n            IdentityPrefix.V2,\n            identityAccessToken,\n        );\n    }\n\n    /**\n     * Submits a MSISDN token to the identity server\n     *\n     * This is used when submitting the code sent by SMS to a phone number.\n     * The identity server has an equivalent API for email but the js-sdk does\n     * not expose this, since email is normally validated by the user clicking\n     * a link rather than entering a code.\n     *\n     * @param sid - The sid given in the response to requestToken\n     * @param clientSecret - A secret binary string generated by the client.\n     *                 This must be the same value submitted in the requestToken call.\n     * @param msisdnToken - The MSISDN token, as entered by the user.\n     * @param identityAccessToken - The `access_token` field of the Identity\n     * Server `/account/register` response (see {@link registerWithIdentityServer}).\n     * Some legacy identity servers had no authentication here.\n     *\n     * @returns Promise which resolves: Object, containing success boolean.\n     * @returns Rejects: with an error response.\n     * @throws Error if No identity server is set\n     */\n    public submitMsisdnToken(\n        sid: string,\n        clientSecret: string,\n        msisdnToken: string,\n        identityAccessToken: string | null,\n    ): Promise<{ success: boolean }> {\n        const params = {\n            sid: sid,\n            client_secret: clientSecret,\n            token: msisdnToken,\n        };\n\n        return this.http.idServerRequest(\n            Method.Post,\n            \"/validate/msisdn/submitToken\",\n            params,\n            IdentityPrefix.V2,\n            identityAccessToken ?? undefined,\n        );\n    }\n\n    /**\n     * Submits a MSISDN token to an arbitrary URL.\n     *\n     * This is used when submitting the code sent by SMS to a phone number in the\n     * newer 3PID flow where the homeserver validates 3PID ownership (as part of\n     * `requestAdd3pidMsisdnToken`). The homeserver response may include a\n     * `submit_url` to specify where the token should be sent, and this helper can\n     * be used to pass the token to this URL.\n     *\n     * @param url - The URL to submit the token to\n     * @param sid - The sid given in the response to requestToken\n     * @param clientSecret - A secret binary string generated by the client.\n     *                 This must be the same value submitted in the requestToken call.\n     * @param msisdnToken - The MSISDN token, as entered by the user.\n     *\n     * @returns Promise which resolves: Object, containing success boolean.\n     * @returns Rejects: with an error response.\n     */\n    public submitMsisdnTokenOtherUrl(\n        url: string,\n        sid: string,\n        clientSecret: string,\n        msisdnToken: string,\n    ): Promise<{ success: boolean }> {\n        const params = {\n            sid: sid,\n            client_secret: clientSecret,\n            token: msisdnToken,\n        };\n        return this.http.requestOtherUrl(Method.Post, url, params);\n    }\n\n    /**\n     * Gets the V2 hashing information from the identity server. Primarily useful for\n     * lookups.\n     * @param identityAccessToken - The access token for the identity server.\n     * @returns The hashing information for the identity server.\n     */\n    public getIdentityHashDetails(identityAccessToken: string): Promise<{\n        /**\n         * The algorithms the server supports. Must contain at least sha256.\n         */\n        algorithms: string[];\n        /**\n         * The pepper the client MUST use in hashing identifiers,\n         * and MUST supply to the /lookup endpoint when performing lookups.\n         */\n        lookup_pepper: string;\n    }> {\n        return this.http.idServerRequest(\n            Method.Get,\n            \"/hash_details\",\n            undefined,\n            IdentityPrefix.V2,\n            identityAccessToken,\n        );\n    }\n\n    /**\n     * Performs a hashed lookup of addresses against the identity server. This is\n     * only supported on identity servers which have at least the version 2 API.\n     * @param addressPairs - An array of 2 element arrays.\n     * The first element of each pair is the address, the second is the 3PID medium.\n     * Eg: `[\"email@example.org\", \"email\"]`\n     * @param identityAccessToken - The access token for the identity server.\n     * @returns A collection of address mappings to\n     * found MXIDs. Results where no user could be found will not be listed.\n     */\n    public async identityHashedLookup(\n        addressPairs: [string, string][],\n        identityAccessToken: string,\n    ): Promise<{ address: string; mxid: string }[]> {\n        const params: Record<string, string | string[]> = {\n            // addresses: [\"email@example.org\", \"10005550000\"],\n            // algorithm: \"sha256\",\n            // pepper: \"abc123\"\n        };\n\n        // Get hash information first before trying to do a lookup\n        const hashes = await this.getIdentityHashDetails(identityAccessToken);\n        if (!hashes || !hashes[\"lookup_pepper\"] || !hashes[\"algorithms\"]) {\n            throw new Error(\"Unsupported identity server: bad response\");\n        }\n\n        params[\"pepper\"] = hashes[\"lookup_pepper\"];\n\n        const localMapping: Record<string, string> = {\n            // hashed identifier => plain text address\n            // For use in this function's return format\n        };\n\n        // When picking an algorithm, we pick the hashed over no hashes\n        if (hashes[\"algorithms\"].includes(\"sha256\")) {\n            params[\"addresses\"] = await Promise.all(\n                addressPairs.map(async (p) => {\n                    const addr = p[0].toLowerCase(); // lowercase to get consistent hashes\n                    const med = p[1].toLowerCase();\n                    const hashBuffer = await sha256(`${addr} ${med} ${params[\"pepper\"]}`);\n                    const hashed = encodeUnpaddedBase64Url(hashBuffer);\n\n                    // Map the hash to a known (case-sensitive) address. We use the case\n                    // sensitive version because the caller might be expecting that.\n                    localMapping[hashed] = p[0];\n                    return hashed;\n                }),\n            );\n            params[\"algorithm\"] = \"sha256\";\n        } else if (hashes[\"algorithms\"].includes(\"none\")) {\n            params[\"addresses\"] = addressPairs.map((p) => {\n                const addr = p[0].toLowerCase(); // lowercase to get consistent hashes\n                const med = p[1].toLowerCase();\n                const unhashed = `${addr} ${med}`;\n                // Map the unhashed values to a known (case-sensitive) address. We use\n                // the case-sensitive version because the caller might be expecting that.\n                localMapping[unhashed] = p[0];\n                return unhashed;\n            });\n            params[\"algorithm\"] = \"none\";\n        } else {\n            throw new Error(\"Unsupported identity server: unknown hash algorithm\");\n        }\n\n        const response = await this.http.idServerRequest<{\n            mappings: { [address: string]: string };\n        }>(Method.Post, \"/lookup\", params, IdentityPrefix.V2, identityAccessToken);\n\n        if (!response?.[\"mappings\"]) return []; // no results\n\n        const foundAddresses: { address: string; mxid: string }[] = [];\n        for (const hashed of Object.keys(response[\"mappings\"])) {\n            const mxid = response[\"mappings\"][hashed];\n            const plainAddress = localMapping[hashed];\n            if (!plainAddress) {\n                throw new Error(\"Identity server returned more results than expected\");\n            }\n\n            foundAddresses.push({ address: plainAddress, mxid });\n        }\n        return foundAddresses;\n    }\n\n    /**\n     * Looks up the public Matrix ID mapping for a given 3rd party\n     * identifier from the identity server\n     *\n     * @param medium - The medium of the threepid, eg. 'email'\n     * @param address - The textual address of the threepid\n     * @param identityAccessToken - The `access_token` field of the Identity\n     * Server `/account/register` response (see {@link registerWithIdentityServer}).\n     *\n     * @returns Promise which resolves: A threepid mapping\n     *                                 object or the empty object if no mapping\n     *                                 exists\n     * @returns Rejects: with an error response.\n     */\n    public async lookupThreePid(\n        medium: string,\n        address: string,\n        identityAccessToken: string,\n    ): Promise<\n        | {\n              address: string;\n              medium: string;\n              mxid: string;\n          }\n        | EmptyObject\n    > {\n        // Note: we're using the V2 API by calling this function, but our\n        // function contract requires a V1 response. We therefore have to\n        // convert it manually.\n        const response = await this.identityHashedLookup([[address, medium]], identityAccessToken);\n        const result = response.find((p) => p.address === address);\n        if (!result) {\n            return {};\n        }\n\n        const mapping = {\n            address,\n            medium,\n            mxid: result.mxid,\n\n            // We can't reasonably fill these parameters:\n            // not_before\n            // not_after\n            // ts\n            // signatures\n        };\n\n        return mapping;\n    }\n\n    /**\n     * Looks up the public Matrix ID mappings for multiple 3PIDs.\n     *\n     * @param query - Array of arrays containing\n     * [medium, address]\n     * @param identityAccessToken - The `access_token` field of the Identity\n     * Server `/account/register` response (see {@link registerWithIdentityServer}).\n     *\n     * @returns Promise which resolves: Lookup results from IS.\n     * @returns Rejects: with an error response.\n     */\n    public async bulkLookupThreePids(\n        query: [string, string][],\n        identityAccessToken: string,\n    ): Promise<{\n        threepids: [medium: string, address: string, mxid: string][];\n    }> {\n        // Note: we're using the V2 API by calling this function, but our\n        // function contract requires a V1 response. We therefore have to\n        // convert it manually.\n        const response = await this.identityHashedLookup(\n            // We have to reverse the query order to get [address, medium] pairs\n            query.map((p) => [p[1], p[0]]),\n            identityAccessToken,\n        );\n\n        const v1results: [medium: string, address: string, mxid: string][] = [];\n        for (const mapping of response) {\n            const originalQuery = query.find((p) => p[1] === mapping.address);\n            if (!originalQuery) {\n                throw new Error(\"Identity sever returned unexpected results\");\n            }\n\n            v1results.push([\n                originalQuery[0], // medium\n                mapping.address,\n                mapping.mxid,\n            ]);\n        }\n\n        return { threepids: v1results };\n    }\n\n    /**\n     * Get account info from the identity server. This is useful as a neutral check\n     * to verify that other APIs are likely to approve access by testing that the\n     * token is valid, terms have been agreed, etc.\n     *\n     * @param identityAccessToken - The `access_token` field of the Identity\n     * Server `/account/register` response (see {@link registerWithIdentityServer}).\n     *\n     * @returns Promise which resolves: an object with account info.\n     * @returns Rejects: with an error response.\n     */\n    public getIdentityAccount(identityAccessToken: string): Promise<{ user_id: string }> {\n        return this.http.idServerRequest(Method.Get, \"/account\", undefined, IdentityPrefix.V2, identityAccessToken);\n    }\n\n    /**\n     * Send an event to a specific list of devices.\n     * This is a low-level API that simply wraps the HTTP API\n     * call to send to-device messages. We recommend using\n     * queueToDevice() which is a higher level API.\n     *\n     * @param eventType -  type of event to send\n     *    content to send. Map from user_id to device_id to content object.\n     * @param txnId -     transaction id. One will be made up if not\n     *    supplied.\n     * @returns Promise which resolves: to an empty object `{}`\n     */\n    public sendToDevice(eventType: string, contentMap: SendToDeviceContentMap, txnId?: string): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/sendToDevice/$eventType/$txnId\", {\n            $eventType: eventType,\n            $txnId: txnId ? txnId : this.makeTxnId(),\n        });\n\n        const body = {\n            messages: utils.recursiveMapToObject(contentMap),\n        };\n\n        const targets = new Map<string, string[]>();\n\n        for (const [userId, deviceMessages] of contentMap) {\n            targets.set(userId, Array.from(deviceMessages.keys()));\n        }\n\n        this.logger.debug(`PUT ${path}`, targets);\n\n        return this.http.authedRequest(Method.Put, path, undefined, body);\n    }\n\n    /**\n     * This will encrypt the payload for all devices in the list and will queue it.\n     * The type of the sent to-device message will be `m.room.encrypted`.\n     * @param eventType - The type of event to send\n     * @param devices - The list of devices to send the event to.\n     * @param payload - The payload to send. This will be encrypted.\n     * @returns Promise which resolves once queued there is no error feedback when sending fails.\n     */\n    public async encryptAndSendToDevice(\n        eventType: string,\n        devices: { userId: string; deviceId: string }[],\n        payload: ToDevicePayload,\n    ): Promise<void> {\n        if (!this.cryptoBackend) {\n            throw new Error(\"Cannot encrypt to device event, your client does not support encryption.\");\n        }\n        const batch = await this.cryptoBackend.encryptToDeviceMessages(eventType, devices, payload);\n\n        // TODO The batch mechanism removes all possibility to get error feedbacks..\n        // We might want instead to do the API call directly and pass the errors back.\n        await this.queueToDevice(batch);\n    }\n\n    /**\n     * Sends events directly to specific devices using Matrix's to-device\n     * messaging system. The batch will be split up into appropriately sized\n     * batches for sending and stored in the store so they can be retried\n     * later if they fail to send. Retries will happen automatically.\n     * @param batch - The to-device messages to send\n     */\n    public queueToDevice(batch: ToDeviceBatch): Promise<void> {\n        return this.toDeviceMessageQueue.queueBatch(batch);\n    }\n\n    /**\n     * Get the third party protocols that can be reached using\n     * this HS\n     * @returns Promise which resolves to the result object\n     */\n    public getThirdpartyProtocols(): Promise<{ [protocol: string]: IProtocol }> {\n        return this.http\n            .authedRequest<Record<string, IProtocol>>(Method.Get, \"/thirdparty/protocols\")\n            .then((response) => {\n                // sanity check\n                if (!response || typeof response !== \"object\") {\n                    throw new Error(`/thirdparty/protocols did not return an object: ${response}`);\n                }\n                return response;\n            });\n    }\n\n    /**\n     * Get information on how a specific place on a third party protocol\n     * may be reached.\n     * @param protocol - The protocol given in getThirdpartyProtocols()\n     * @param params - Protocol-specific parameters, as given in the\n     *                        response to getThirdpartyProtocols()\n     * @returns Promise which resolves to the result object\n     */\n    public getThirdpartyLocation(\n        protocol: string,\n        params: { searchFields?: string[] },\n    ): Promise<IThirdPartyLocation[]> {\n        const path = utils.encodeUri(\"/thirdparty/location/$protocol\", {\n            $protocol: protocol,\n        });\n\n        return this.http.authedRequest(Method.Get, path, params);\n    }\n\n    /**\n     * Get information on how a specific user on a third party protocol\n     * may be reached.\n     * @param protocol - The protocol given in getThirdpartyProtocols()\n     * @param params - Protocol-specific parameters, as given in the\n     *                        response to getThirdpartyProtocols()\n     * @returns Promise which resolves to the result object\n     */\n    public getThirdpartyUser(protocol: string, params?: QueryDict): Promise<IThirdPartyUser[]> {\n        const path = utils.encodeUri(\"/thirdparty/user/$protocol\", {\n            $protocol: protocol,\n        });\n\n        return this.http.authedRequest(Method.Get, path, params);\n    }\n\n    public getTerms(serviceType: SERVICE_TYPES, baseUrl: string): Promise<Terms> {\n        const url = this.termsUrlForService(serviceType, baseUrl);\n        return this.http.requestOtherUrl(Method.Get, url);\n    }\n\n    public agreeToTerms(\n        serviceType: SERVICE_TYPES,\n        baseUrl: string,\n        accessToken: string,\n        termsUrls: string[],\n    ): Promise<EmptyObject> {\n        const url = this.termsUrlForService(serviceType, baseUrl);\n        const headers = {\n            Authorization: \"Bearer \" + accessToken,\n        };\n        return this.http.requestOtherUrl(\n            Method.Post,\n            url,\n            {\n                user_accepts: termsUrls,\n            },\n            { headers },\n        );\n    }\n\n    /**\n     * Reports an event as inappropriate to the server, which may then notify the appropriate people.\n     * @param roomId - The room in which the event being reported is located.\n     * @param eventId - The event to report.\n     * @param score - The score to rate this content as where -100 is most offensive and 0 is inoffensive.\n     * @param reason - The reason the content is being reported. May be blank.\n     * @returns Promise which resolves to an empty object if successful\n     */\n    public reportEvent(roomId: string, eventId: string, score: number, reason: string): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/rooms/$roomId/report/$eventId\", {\n            $roomId: roomId,\n            $eventId: eventId,\n        });\n\n        return this.http.authedRequest(Method.Post, path, undefined, { score, reason });\n    }\n\n    /**\n     * Reports a room as inappropriate to the server, which may then notify the appropriate people.\n     *\n     * This API was introduced in Matrix v1.13.\n     *\n     * @param roomId - The room being reported.\n     * @param reason - The reason the room is being reported. May be blank.\n     * @returns Promise which resolves to an empty object if successful\n     */\n    public reportRoom(roomId: string, reason: string): Promise<EmptyObject> {\n        const path = utils.encodeUri(\"/rooms/$roomId/report\", {\n            $roomId: roomId,\n        });\n\n        return this.http.authedRequest(Method.Post, path, undefined, { reason });\n    }\n\n    /**\n     * Fetches or paginates a room hierarchy asmatrix-js-sdk/spec/unit/matrix-client.spec.ts defined by MSC2946.\n     * Falls back gracefully to sourcing its data from `getSpaceSummary` if this API is not yet supported by the server.\n     * @param roomId - The ID of the space-room to use as the root of the summary.\n     * @param limit - The maximum number of rooms to return per page.\n     * @param maxDepth - The maximum depth in the tree from the root room to return.\n     * @param suggestedOnly - Whether to only return rooms with suggested=true.\n     * @param fromToken - The opaque token to paginate a previous request.\n     * @returns the response, with next_batch & rooms fields.\n     */\n    public getRoomHierarchy(\n        roomId: string,\n        limit?: number,\n        maxDepth?: number,\n        suggestedOnly = false,\n        fromToken?: string,\n    ): Promise<IRoomHierarchy> {\n        const path = utils.encodeUri(\"/rooms/$roomId/hierarchy\", {\n            $roomId: roomId,\n        });\n\n        const queryParams: QueryDict = {\n            suggested_only: String(suggestedOnly),\n            max_depth: maxDepth?.toString(),\n            from: fromToken,\n            limit: limit?.toString(),\n        };\n\n        return this.http\n            .authedRequest<IRoomHierarchy>(Method.Get, path, queryParams, undefined, {\n                prefix: ClientPrefix.V1,\n            })\n            .catch((e) => {\n                if (e.errcode === \"M_UNRECOGNIZED\") {\n                    // fall back to the prefixed hierarchy API.\n                    return this.http.authedRequest<IRoomHierarchy>(Method.Get, path, queryParams, undefined, {\n                        prefix: \"/_matrix/client/unstable/org.matrix.msc2946\",\n                    });\n                }\n\n                throw e;\n            });\n    }\n\n    /**\n     * Creates a new file tree space with the given name. The client will pick\n     * defaults for how it expects to be able to support the remaining API offered\n     * by the returned class.\n     *\n     * Note that this is UNSTABLE and may have breaking changes without notice.\n     * @param name - The name of the tree space.\n     * @returns Promise which resolves to the created space.\n     */\n    public async unstableCreateFileTree(name: string): Promise<MSC3089TreeSpace> {\n        const { room_id: roomId } = await this.createRoom({\n            name: name,\n            preset: Preset.PrivateChat,\n            power_level_content_override: {\n                ...DEFAULT_TREE_POWER_LEVELS_TEMPLATE,\n                users: {\n                    [this.getUserId()!]: 100,\n                },\n            },\n            creation_content: {\n                [RoomCreateTypeField]: RoomType.Space,\n            },\n            initial_state: [\n                {\n                    type: UNSTABLE_MSC3088_PURPOSE.name,\n                    state_key: UNSTABLE_MSC3089_TREE_SUBTYPE.name,\n                    content: {\n                        [UNSTABLE_MSC3088_ENABLED.name]: true,\n                    },\n                },\n                {\n                    type: EventType.RoomEncryption,\n                    state_key: \"\",\n                    content: {\n                        algorithm: \"m.megolm.v1.aes-sha2\",\n                    },\n                },\n            ],\n        });\n        return new MSC3089TreeSpace(this, roomId);\n    }\n\n    /**\n     * Gets a reference to a tree space, if the room ID given is a tree space. If the room\n     * does not appear to be a tree space then null is returned.\n     *\n     * Note that this is UNSTABLE and may have breaking changes without notice.\n     * @param roomId - The room ID to get a tree space reference for.\n     * @returns The tree space, or null if not a tree space.\n     */\n    public unstableGetFileTreeSpace(roomId: string): MSC3089TreeSpace | null {\n        const room = this.getRoom(roomId);\n        if (room?.getMyMembership() !== KnownMembership.Join) return null;\n\n        const createEvent = room.currentState.getStateEvents(EventType.RoomCreate, \"\");\n        const purposeEvent = room.currentState.getStateEvents(\n            UNSTABLE_MSC3088_PURPOSE.name,\n            UNSTABLE_MSC3089_TREE_SUBTYPE.name,\n        );\n\n        if (!createEvent) throw new Error(\"Expected single room create event\");\n\n        if (!purposeEvent?.getContent()?.[UNSTABLE_MSC3088_ENABLED.name]) return null;\n        if (createEvent.getContent()?.[RoomCreateTypeField] !== RoomType.Space) return null;\n\n        return new MSC3089TreeSpace(this, roomId);\n    }\n\n    /**\n     * Perform a single MSC3575 sliding sync request.\n     * @param req - The request to make.\n     * @param proxyBaseUrl - The base URL for the sliding sync proxy.\n     * @param abortSignal - Optional signal to abort request mid-flight.\n     * @returns The sliding sync response, or a standard error.\n     * @throws on non 2xx status codes with an object with a field \"httpStatus\":number.\n     */\n    public slidingSync(\n        req: MSC3575SlidingSyncRequest,\n        proxyBaseUrl?: string,\n        abortSignal?: AbortSignal,\n    ): Promise<MSC3575SlidingSyncResponse> {\n        const qps: QueryDict = {};\n        if (req.pos) {\n            qps.pos = req.pos;\n            delete req.pos;\n        }\n        if (req.timeout) {\n            qps.timeout = req.timeout;\n            delete req.timeout;\n        }\n        const clientTimeout = req.clientTimeout;\n        delete req.clientTimeout;\n        return this.http.authedRequest<MSC3575SlidingSyncResponse>(Method.Post, \"/sync\", qps, req, {\n            prefix: \"/_matrix/client/unstable/org.matrix.simplified_msc3575\",\n            baseUrl: proxyBaseUrl,\n            localTimeoutMs: clientTimeout,\n            abortSignal,\n        });\n    }\n\n    /**\n     * A helper to determine thread support\n     * @returns a boolean to determine if threads are enabled\n     */\n    public supportsThreads(): boolean {\n        return this.clientOpts?.threadSupport || false;\n    }\n\n    /**\n     * A helper to determine intentional mentions support\n     * @returns a boolean to determine if intentional mentions are enabled on the server\n     * @experimental\n     */\n    public supportsIntentionalMentions(): boolean {\n        return this.canSupport.get(Feature.IntentionalMentions) !== ServerSupport.Unsupported;\n    }\n\n    /**\n     * Fetches the summary of a room as defined by an initial version of MSC3266 and implemented in Synapse\n     * Proposed at https://github.com/matrix-org/matrix-doc/pull/3266\n     * @param roomIdOrAlias - The ID or alias of the room to get the summary of.\n     * @param via - The list of servers which know about the room if only an ID was provided.\n     */\n    public async getRoomSummary(roomIdOrAlias: string, via?: string[]): Promise<RoomSummary> {\n        const paramOpts = {\n            prefix: \"/_matrix/client/unstable/im.nheko.summary\",\n        };\n        try {\n            const path = utils.encodeUri(\"/summary/$roomid\", { $roomid: roomIdOrAlias });\n            return await this.http.authedRequest(Method.Get, path, { via }, undefined, paramOpts);\n        } catch (e) {\n            if (e instanceof MatrixError && e.errcode === \"M_UNRECOGNIZED\") {\n                const path = utils.encodeUri(\"/rooms/$roomid/summary\", { $roomid: roomIdOrAlias });\n                return await this.http.authedRequest(Method.Get, path, { via }, undefined, paramOpts);\n            } else {\n                throw e;\n            }\n        }\n    }\n\n    /**\n     * Processes a list of threaded events and adds them to their respective timelines\n     * @param room - the room the adds the threaded events\n     * @param threadedEvents - an array of the threaded events\n     * @param toStartOfTimeline - the direction in which we want to add the events\n     */\n    public processThreadEvents(room: Room, threadedEvents: MatrixEvent[], toStartOfTimeline: boolean): void {\n        room.processThreadedEvents(threadedEvents, toStartOfTimeline);\n    }\n\n    /**\n     * Processes a list of thread roots and creates a thread model\n     * @param room - the room to create the threads in\n     * @param threadedEvents - an array of thread roots\n     * @param toStartOfTimeline - the direction\n     */\n    public processThreadRoots(room: Room, threadedEvents: MatrixEvent[], toStartOfTimeline: boolean): void {\n        if (!this.supportsThreads()) return;\n        room.processThreadRoots(threadedEvents, toStartOfTimeline);\n    }\n\n    public processBeaconEvents(room?: Room, events?: MatrixEvent[]): void {\n        this.processAggregatedTimelineEvents(room, events);\n    }\n\n    /**\n     * Calls aggregation functions for event types that are aggregated\n     * Polls and location beacons\n     * @param room - room the events belong to\n     * @param events - timeline events to be processed\n     * @returns\n     */\n    public processAggregatedTimelineEvents(room?: Room, events?: MatrixEvent[]): void {\n        if (!events?.length) return;\n        if (!room) return;\n\n        void room.currentState.processBeaconEvents(events, this);\n        void room.processPollEvents(events);\n    }\n\n    /**\n     * Fetches information about the user for the configured access token.\n     */\n    public async whoami(): Promise<IWhoamiResponse> {\n        return this.http.authedRequest(Method.Get, \"/account/whoami\");\n    }\n\n    /**\n     * Find the event_id closest to the given timestamp in the given direction.\n     * @returns Resolves: A promise of an object containing the event_id and\n     *    origin_server_ts of the closest event to the timestamp in the given direction\n     * @returns Rejects: when the request fails (module:http-api.MatrixError)\n     */\n    public async timestampToEvent(\n        roomId: string,\n        timestamp: number,\n        dir: Direction,\n    ): Promise<TimestampToEventResponse> {\n        const path = utils.encodeUri(\"/rooms/$roomId/timestamp_to_event\", {\n            $roomId: roomId,\n        });\n        const queryParams = {\n            ts: timestamp.toString(),\n            dir: dir,\n        };\n\n        try {\n            return await this.http.authedRequest(Method.Get, path, queryParams, undefined, {\n                prefix: ClientPrefix.V1,\n            });\n        } catch (err) {\n            // Fallback to the prefixed unstable endpoint. Since the stable endpoint is\n            // new, we should also try the unstable endpoint before giving up. We can\n            // remove this fallback request in a year (remove after 2023-11-28).\n            if (\n                (<MatrixError>err).errcode === \"M_UNRECOGNIZED\" &&\n                // XXX: The 400 status code check should be removed in the future\n                // when Synapse is compliant with MSC3743.\n                ((<MatrixError>err).httpStatus === 400 ||\n                    // This the correct standard status code for an unsupported\n                    // endpoint according to MSC3743. Not Found and Method Not Allowed\n                    // both indicate that this endpoint+verb combination is\n                    // not supported.\n                    (<MatrixError>err).httpStatus === 404 ||\n                    (<MatrixError>err).httpStatus === 405)\n            ) {\n                return await this.http.authedRequest(Method.Get, path, queryParams, undefined, {\n                    prefix: \"/_matrix/client/unstable/org.matrix.msc3030\",\n                });\n            }\n\n            throw err;\n        }\n    }\n\n    /**\n     * Discover and validate the auth metadata for the OAuth 2.0 API.\n     *\n     * When successful, validated metadata is returned.\n     *\n     * @returns validated authentication metadata\n     * @throws when delegated auth config is invalid or unreachable\n     */\n    public async getAuthMetadata(): Promise<ValidatedAuthMetadata> {\n        const useStable = await this.isVersionSupported(\"v1.15\");\n        const authMetadata = await this.http.request(Method.Get, \"/auth_metadata\", undefined, undefined, {\n            prefix: useStable ? ClientPrefix.V1 : ClientPrefix.Unstable + \"/org.matrix.msc2965\",\n        });\n\n        if (isValidAuthMetadata(authMetadata)) {\n            return authMetadata;\n        }\n\n        logger.error(\"Issuer configuration not valid\");\n        throw new Error(OAuth2Error.OpSupport);\n    }\n}\n\nfunction getUnstableDelayQueryOpts(delayOpts: SendDelayedEventRequestOpts): QueryDict {\n    return Object.fromEntries(\n        Object.entries(delayOpts).map(([k, v]) => [`${UNSTABLE_MSC4140_DELAYED_EVENTS}.${k}`, v]),\n    );\n}\n\n/**\n * recalculates an accurate notifications count on event decryption.\n * Servers do not have enough knowledge about encrypted events to calculate an\n * accurate notification_count\n */\nexport function fixNotificationCountOnDecryption(cli: MatrixClient, event: MatrixEvent): void {\n    const ourUserId = cli.getUserId();\n    const eventId = event.getId();\n\n    const room = cli.getRoom(event.getRoomId());\n    if (!room || !ourUserId || !eventId) return;\n\n    // Due to threads, we can get relation events (eg. edits & reactions) that never get\n    // added to a timeline and so cannot be found in their own room (their edit / reaction\n    // still applies to the event it needs to, so it doesn't matter too much). However, if\n    // we try to process notification about this event, we'll get very confused because we\n    // won't be able to find the event in the room, so will assume it must be unread, even\n    // if it's actually read. We therefore skip anything that isn't in the room. This isn't\n    // *great*, so if we can fix the homeless events (eg. with MSC4023) then we should probably\n    // remove this workaround.\n    if (!room.findEventById(eventId)) {\n        logger.info(`Decrypted event ${event.getId()} is not in room ${room.roomId}: ignoring`);\n        return;\n    }\n\n    const isThreadEvent = !!event.threadRootId && !event.isThreadRoot;\n\n    let hasReadEvent;\n    if (isThreadEvent) {\n        const thread = room.getThread(event.threadRootId);\n        hasReadEvent = thread\n            ? thread.hasUserReadEvent(ourUserId, eventId)\n            : // If the thread object does not exist in the room yet, we don't\n              // want to calculate notification for this event yet. We have not\n              // restored the read receipts yet and can't accurately calculate\n              // notifications at this stage.\n              //\n              // This issue can likely go away when MSC3874 is implemented\n              true;\n    } else {\n        hasReadEvent = room.hasUserReadEvent(ourUserId, eventId);\n    }\n\n    if (hasReadEvent) {\n        // If the event has been read, ignore it.\n        return;\n    }\n\n    const actions = cli.getPushActionsForEvent(event, true);\n\n    // Ensure the unread counts are kept up to date if the event is encrypted\n    // We also want to make sure that the notification count goes up if we already\n    // have encrypted events to avoid other code from resetting 'highlight' to zero.\n    const newHighlight = !!actions?.tweaks?.highlight;\n\n    if (newHighlight) {\n        // TODO: Handle mentions received while the client is offline\n        // See also https://github.com/vector-im/element-web/issues/9069\n        const newCount = room.getUnreadCountForEventContext(NotificationCountType.Highlight, event) + 1;\n        if (isThreadEvent) {\n            room.setThreadUnreadNotificationCount(event.threadRootId, NotificationCountType.Highlight, newCount);\n        } else {\n            room.setUnreadNotificationCount(NotificationCountType.Highlight, newCount);\n        }\n    }\n\n    // `notify` is used in practice for incrementing the total count\n    const newNotify = !!actions?.notify;\n\n    // The room total count is NEVER incremented by the server for encrypted rooms. We basically ignore\n    // the server here as it's always going to tell us to increment for encrypted events.\n    if (newNotify) {\n        // Total count is used to typically increment a room notification counter, but not loudly highlight it.\n        const newCount = room.getUnreadCountForEventContext(NotificationCountType.Total, event) + 1;\n        if (isThreadEvent) {\n            room.setThreadUnreadNotificationCount(event.threadRootId, NotificationCountType.Total, newCount);\n        } else {\n            room.setUnreadNotificationCount(NotificationCountType.Total, newCount);\n        }\n    }\n}\n\n/**\n * Given an event, figure out the thread ID we should use for it in a receipt.\n *\n * This will either be \"main\", or event.threadRootId. For the thread root, or\n * e.g. reactions to the thread root, this will be main. For events inside the\n * thread, or e.g. reactions to them, this will be event.threadRootId.\n *\n * (Exported for test.)\n */\nexport function threadIdForReceipt(event: MatrixEvent): string {\n    return inMainTimelineForReceipt(event) ? MAIN_ROOM_TIMELINE : event.threadRootId!;\n}\n\n/**\n * a) True for non-threaded messages, thread roots and non-thread relations to thread roots.\n * b) False for messages with thread relations to the thread root.\n * c) False for messages with any kind of relation to a message from case b.\n *\n * Note: true for redactions of messages that are in threads. Redacted messages\n * are not really in threads (because their relations are gone), so if they look\n * like they are in threads, that is a sign of a bug elsewhere. (At time of\n * writing, this bug definitely exists - messages are not moved to another\n * thread when they are redacted.)\n *\n * @returns true if this event is considered to be in the main timeline as far\n *               as receipts are concerned.\n */\nexport function inMainTimelineForReceipt(event: MatrixEvent): boolean {\n    if (!event.threadRootId) {\n        // Not in a thread: then it is in the main timeline\n        return true;\n    }\n\n    if (event.isThreadRoot) {\n        // Thread roots are in the main timeline. Note: the spec is ambiguous (or\n        // wrong) on this - see\n        // https://github.com/matrix-org/matrix-spec-proposals/pull/4037\n        return true;\n    }\n\n    if (!event.isRelation()) {\n        // If it's not related to anything, it can't be related via a chain of\n        // relations to a thread root.\n        //\n        // Note: this is a bug, because how does it have a threadRootId if it is\n        // neither a thread root, nor related to one?\n        logger.warn(`Event is not a relation or a thread root, but still has a threadRootId! id=${event.getId()}`);\n        return true;\n    }\n\n    if (event.isRelation(THREAD_RELATION_TYPE.name)) {\n        // It's a message in a thread - definitely not in the main timeline.\n        return false;\n    }\n\n    const isRelatedToRoot = event.relationEventId === event.threadRootId;\n\n    // If it's related to the thread root (and we already know it's not a thread\n    // relation) then it's in the main timeline. If it's related to something\n    // else, then it's in the thread (because it has a thread ID).\n    return isRelatedToRoot;\n}\n","/*\nCopyright 2018 - 2022 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type MBeaconEventContent, type MBeaconInfoContent, type MBeaconInfoEventContent } from \"./@types/beacon.ts\";\nimport { MsgType } from \"./@types/event.ts\";\nimport { type IMessageRendering, M_TEXT, REFERENCE_RELATION } from \"./@types/extensible_events.ts\";\nimport { isProvided } from \"./extensible_events_v1/utilities.ts\";\nimport {\n    M_ASSET,\n    LocationAssetType,\n    M_LOCATION,\n    M_TIMESTAMP,\n    type LocationEventWireContent,\n    type MLocationEventContent,\n    type MLocationContent,\n    type MAssetContent,\n    type LegacyLocationEventContent,\n} from \"./@types/location.ts\";\nimport { type MRoomTopicEventContent, type MTopicContent, M_TOPIC, type MTopicEvent } from \"./@types/topic.ts\";\nimport { type RoomMessageEventContent } from \"./@types/events.ts\";\n\n/**\n * Generates the content for a HTML Message event\n * @param body - the plaintext body of the message\n * @param htmlBody - the HTML representation of the message\n * @returns\n */\nexport function makeHtmlMessage(body: string, htmlBody: string): RoomMessageEventContent {\n    return {\n        msgtype: MsgType.Text,\n        format: \"org.matrix.custom.html\",\n        body: body,\n        formatted_body: htmlBody,\n    };\n}\n\n/**\n * Generates the content for a HTML Notice event\n * @param body - the plaintext body of the notice\n * @param htmlBody - the HTML representation of the notice\n * @returns\n */\nexport function makeHtmlNotice(body: string, htmlBody: string): RoomMessageEventContent {\n    return {\n        msgtype: MsgType.Notice,\n        format: \"org.matrix.custom.html\",\n        body: body,\n        formatted_body: htmlBody,\n    };\n}\n\n/**\n * Generates the content for a HTML Emote event\n * @param body - the plaintext body of the emote\n * @param htmlBody - the HTML representation of the emote\n * @returns\n */\nexport function makeHtmlEmote(body: string, htmlBody: string): RoomMessageEventContent {\n    return {\n        msgtype: MsgType.Emote,\n        format: \"org.matrix.custom.html\",\n        body: body,\n        formatted_body: htmlBody,\n    };\n}\n\n/**\n * Generates the content for a Plaintext Message event\n * @param body - the plaintext body of the emote\n * @returns\n */\nexport function makeTextMessage(body: string): RoomMessageEventContent {\n    return {\n        msgtype: MsgType.Text,\n        body: body,\n    };\n}\n\n/**\n * Generates the content for a Plaintext Notice event\n * @param body - the plaintext body of the notice\n * @returns\n */\nexport function makeNotice(body: string): RoomMessageEventContent {\n    return {\n        msgtype: MsgType.Notice,\n        body: body,\n    };\n}\n\n/**\n * Generates the content for a Plaintext Emote event\n * @param body - the plaintext body of the emote\n * @returns\n */\nexport function makeEmoteMessage(body: string): RoomMessageEventContent {\n    return {\n        msgtype: MsgType.Emote,\n        body: body,\n    };\n}\n\n/** Location content helpers */\n\nexport const getTextForLocationEvent = (\n    uri: string | undefined,\n    assetType: LocationAssetType,\n    timestamp?: number,\n    description?: string | null,\n): string => {\n    const date = `at ${new Date(timestamp!).toISOString()}`;\n    const assetName = assetType === LocationAssetType.Self ? \"User\" : undefined;\n    const quotedDescription = description ? `\"${description}\"` : undefined;\n\n    return [assetName, \"Location\", quotedDescription, uri, date].filter(Boolean).join(\" \");\n};\n\n/**\n * Generates the content for a Location event\n * @param uri - a geo:// uri for the location\n * @param timestamp - the timestamp when the location was correct (milliseconds since the UNIX epoch)\n * @param description - the (optional) label for this location on the map\n * @param assetType - the (optional) asset type of this location e.g. \"m.self\"\n * @param text - optional. A text for the location\n */\nexport const makeLocationContent = (\n    // this is first but optional\n    // to avoid a breaking change\n    text?: string,\n    uri?: string,\n    timestamp?: number,\n    description?: string | null,\n    assetType?: LocationAssetType,\n): LegacyLocationEventContent & MLocationEventContent => {\n    const defaultedText =\n        text ?? getTextForLocationEvent(uri, assetType || LocationAssetType.Self, timestamp, description);\n    const timestampEvent = timestamp ? { [M_TIMESTAMP.name]: timestamp } : {};\n    return {\n        msgtype: MsgType.Location,\n        body: defaultedText,\n        geo_uri: uri,\n        [M_LOCATION.name]: {\n            description,\n            uri,\n        },\n        [M_ASSET.name]: {\n            type: assetType || LocationAssetType.Self,\n        },\n        [M_TEXT.name]: defaultedText,\n        ...timestampEvent,\n    } as LegacyLocationEventContent & MLocationEventContent;\n};\n\n/**\n * Parse location event content and transform to\n * a backwards compatible modern m.location event format\n */\nexport const parseLocationEvent = (wireEventContent: LocationEventWireContent): MLocationEventContent => {\n    const location = M_LOCATION.findIn<MLocationContent>(wireEventContent);\n    const asset = M_ASSET.findIn<MAssetContent>(wireEventContent);\n    const timestamp = M_TIMESTAMP.findIn<number>(wireEventContent);\n    const text = M_TEXT.findIn<string>(wireEventContent);\n\n    const geoUri = location?.uri ?? wireEventContent?.geo_uri;\n    const description = location?.description;\n    const assetType = asset?.type ?? LocationAssetType.Self;\n    const fallbackText = text ?? wireEventContent.body;\n\n    return makeLocationContent(fallbackText, geoUri, timestamp ?? undefined, description, assetType);\n};\n\n/**\n * Topic event helpers\n */\nexport type MakeTopicContent = (topic: string | null | undefined, htmlTopic?: string) => MRoomTopicEventContent;\n\nexport const makeTopicContent: MakeTopicContent = (topic, htmlTopic) => {\n    const renderings = [];\n    // Put HTML first because clients will render the first type in\n    // the array that they understand\n    if (isProvided(htmlTopic)) {\n        renderings.push({ body: htmlTopic, mimetype: \"text/html\" });\n    }\n    if (isProvided(topic)) {\n        renderings.push({ body: topic, mimetype: \"text/plain\" });\n    }\n    return { topic, [M_TOPIC.name]: { \"m.text\": renderings } };\n};\n\nexport type TopicState = {\n    text?: string;\n    html?: string;\n};\n\nexport const parseTopicContent = (content: MRoomTopicEventContent): TopicState => {\n    const mtopicParent = M_TOPIC.findIn<MTopicContent | IMessageRendering[]>(content as MTopicEvent);\n    const mtopic = Array.isArray(mtopicParent) ? mtopicParent : mtopicParent?.[\"m.text\"];\n    // TODO remove support for the old malformed m.topic arrays after a few releases (only allow array in m.text)\n    //      https://github.com/matrix-org/matrix-js-sdk/pull/4984#pullrequestreview-3174251065\n    //const mtopic = M_TOPIC.findIn<MTopicContent>(content)?.[\"m.text\"];\n    if (!Array.isArray(mtopic)) {\n        return { text: content.topic ?? undefined };\n    }\n    const text =\n        mtopic?.find((r) => !isProvided(r.mimetype) || r.mimetype === \"text/plain\")?.body ?? content.topic ?? undefined;\n    const html = mtopic?.find((r) => r.mimetype === \"text/html\")?.body;\n    return { text, html };\n};\n\n/**\n * Beacon event helpers\n */\nexport type MakeBeaconInfoContent = (\n    timeout: number,\n    isLive?: boolean,\n    description?: string,\n    assetType?: LocationAssetType,\n    timestamp?: number,\n) => MBeaconInfoEventContent;\n\nexport const makeBeaconInfoContent: MakeBeaconInfoContent = (timeout, isLive, description, assetType, timestamp) => ({\n    description,\n    timeout,\n    live: isLive,\n    [M_TIMESTAMP.name]: timestamp || Date.now(),\n    [M_ASSET.name]: {\n        type: assetType ?? LocationAssetType.Self,\n    },\n});\n\nexport type BeaconInfoState = MBeaconInfoContent & {\n    assetType?: LocationAssetType;\n    timestamp?: number;\n};\n/**\n * Flatten beacon info event content\n */\nexport const parseBeaconInfoContent = (content: MBeaconInfoEventContent): BeaconInfoState => {\n    const { description, timeout, live } = content;\n    const timestamp = M_TIMESTAMP.findIn<number>(content) ?? undefined;\n    const asset = M_ASSET.findIn<MAssetContent>(content);\n\n    return {\n        description,\n        timeout,\n        live,\n        assetType: asset?.type,\n        timestamp,\n    };\n};\n\nexport type MakeBeaconContent = (\n    uri: string,\n    timestamp: number,\n    beaconInfoEventId: string,\n    description?: string,\n) => MBeaconEventContent;\n\nexport const makeBeaconContent: MakeBeaconContent = (uri, timestamp, beaconInfoEventId, description) => ({\n    [M_LOCATION.name]: {\n        description,\n        uri,\n    },\n    [M_TIMESTAMP.name]: timestamp,\n    \"m.relates_to\": {\n        rel_type: REFERENCE_RELATION.name,\n        event_id: beaconInfoEventId,\n    },\n});\n\nexport type BeaconLocationState = Omit<MLocationContent, \"uri\"> & {\n    uri?: string; // override from MLocationContent to allow optionals\n    timestamp?: number;\n};\n\nexport const parseBeaconContent = (content: MBeaconEventContent): BeaconLocationState => {\n    const location = M_LOCATION.findIn<MLocationContent>(content);\n    const timestamp = M_TIMESTAMP.findIn<number>(content) ?? undefined;\n\n    return {\n        description: location?.description,\n        uri: location?.uri,\n        timestamp,\n    };\n};\n","/*\n * Copyright 2024 The Matrix.org Foundation C.I.C.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport bs58 from \"bs58\";\n\n// picked arbitrarily but to try & avoid clashing with any bitcoin ones\n// (which are also base58 encoded, but bitcoin's involve a lot more hashing)\nconst OLM_RECOVERY_KEY_PREFIX = [0x8b, 0x01];\nconst KEY_SIZE = 32;\n\n/**\n * Encode a recovery key using the Matrix {@link https://spec.matrix.org/v1.11/appendices/#cryptographic-key-representation | Cryptographic key representation}\n * @param key\n */\nexport function encodeRecoveryKey(key: ArrayLike<number>): string | undefined {\n    const buf = new Uint8Array(OLM_RECOVERY_KEY_PREFIX.length + key.length + 1);\n    buf.set(OLM_RECOVERY_KEY_PREFIX, 0);\n    buf.set(key, OLM_RECOVERY_KEY_PREFIX.length);\n\n    let parity = 0;\n    for (let i = 0; i < buf.length - 1; ++i) {\n        parity ^= buf[i];\n    }\n    buf[buf.length - 1] = parity;\n    const base58key = bs58.encode(buf);\n\n    return base58key.match(/.{1,4}/g)?.join(\" \");\n}\n\n/**\n * Decode a recovery key encoded with the Matrix {@link https://spec.matrix.org/v1.11/appendices/#cryptographic-key-representation | Cryptographic key representation} encoding.\n * @param recoveryKey\n */\nexport function decodeRecoveryKey(recoveryKey: string): Uint8Array<ArrayBuffer> {\n    const result = bs58.decode(recoveryKey.replace(/ /g, \"\"));\n\n    let parity = 0;\n    for (const b of result) {\n        parity ^= b;\n    }\n    if (parity !== 0) {\n        throw new Error(\"Incorrect parity\");\n    }\n\n    for (let i = 0; i < OLM_RECOVERY_KEY_PREFIX.length; ++i) {\n        if (result[i] !== OLM_RECOVERY_KEY_PREFIX[i]) {\n            throw new Error(\"Incorrect prefix\");\n        }\n    }\n\n    if (result.length !== OLM_RECOVERY_KEY_PREFIX.length + KEY_SIZE + 1) {\n        throw new Error(\"Incorrect length\");\n    }\n\n    return Uint8Array.from(result.slice(OLM_RECOVERY_KEY_PREFIX.length, OLM_RECOVERY_KEY_PREFIX.length + KEY_SIZE));\n}\n","/*\nCopyright 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport type { SecretsBundle } from \"@matrix-org/matrix-sdk-crypto-wasm\";\nimport type { IMegolmSessionData } from \"../@types/crypto.ts\";\nimport type { ToDeviceBatch, ToDevicePayload } from \"../models/ToDeviceMessage.ts\";\nimport { type Room } from \"../models/room.ts\";\nimport { type DeviceMap } from \"../models/device.ts\";\nimport { type UIAuthCallback } from \"../interactive-auth.ts\";\nimport { type PassphraseInfo, type SecretStorageKey, type SecretStorageKeyDescription } from \"../secret-storage.ts\";\nimport { type VerificationRequest } from \"./verification.ts\";\nimport {\n    type BackupTrustInfo,\n    type KeyBackupCheck,\n    type KeyBackupInfo,\n    type KeyBackupRestoreOpts,\n    type KeyBackupRestoreResult,\n} from \"./keybackup.ts\";\nimport { type ISignatures } from \"../@types/signed.ts\";\nimport { type MatrixEvent } from \"../models/event.ts\";\n\n/**\n * `matrix-js-sdk/lib/crypto-api`: End-to-end encryption support.\n *\n * The most important type is {@link CryptoApi}, an instance of which can be retrieved via\n * {@link MatrixClient.getCrypto}.\n *\n * @packageDocumentation\n */\n\n/**\n * The options to start device dehydration.\n */\nexport interface StartDehydrationOpts {\n    /**\n     * Force creation of a new dehydration key, even if there is already an\n     * existing dehydration key. If `false`, and `onlyIfKeyCached` is `false`, a\n     * new key will be created if there is no existing dehydration key, whether\n     * already cached in our local storage or stored in Secret Storage.\n     *\n     * Checking for the presence of the key in Secret Storage may result in the\n     * `getSecretStorageKey` callback being called.\n     *\n     * Defaults to `false`.\n     */\n    createNewKey?: boolean;\n    /**\n     * Only start dehydration if we have a dehydration key cached in our local\n     * storage. If `true`, Secret Storage will not be checked. Defaults to\n     * `false`.\n     */\n    onlyIfKeyCached?: boolean;\n    /**\n     * Try to rehydrate a device before creating a new dehydrated device.\n     * Setting this to `false` may be useful for situations where the client is\n     * known to pre-date the dehydrated device, and so rehydration is\n     * unnecessary. Defaults to `true`.\n     */\n    rehydrate?: boolean;\n}\n\n/**\n * Public interface to the cryptography parts of the js-sdk\n */\nexport interface CryptoApi {\n    /**\n     * Global override for whether the client should ever send encrypted\n     * messages to unverified devices. This provides the default for rooms which\n     * do not specify a value.\n     *\n     * If true, all unverified devices will be blacklisted by default\n     */\n    globalBlacklistUnverifiedDevices: boolean;\n\n    /**\n     * The {@link DeviceIsolationMode} mode to use.\n     */\n    setDeviceIsolationMode(isolationMode: DeviceIsolationMode): void;\n\n    /**\n     * Return the current version of the crypto module.\n     * For example: `Rust SDK ${versions.matrix_sdk_crypto} (${versions.git_sha}), Vodozemac ${versions.vodozemac}`.\n     * @returns the formatted version\n     */\n    getVersion(): string;\n\n    /**\n     * Get the public part of the device keys for the current device.\n     *\n     * @returns The public device keys.\n     */\n    getOwnDeviceKeys(): Promise<OwnDeviceKeys>;\n\n    /**\n     * Check if we believe the given room to be encrypted.\n     *\n     * This method returns true if the room has been configured with encryption. The setting is persistent, so that\n     * even if the encryption event is removed from the room state, it still returns true. This helps to guard against\n     * a downgrade attack wherein a server admin attempts to remove encryption.\n     *\n     * @returns `true` if the room with the supplied ID is encrypted. `false` if the room is not encrypted, or is unknown to\n     * us.\n     */\n    isEncryptionEnabledInRoom(roomId: string): Promise<boolean>;\n\n    /**\n     * Check if we believe the given room supports encrypted state events.\n     */\n    isStateEncryptionEnabledInRoom(roomId: string): Promise<boolean>;\n\n    /**\n     * Perform any background tasks that can be done before a message is ready to\n     * send, in order to speed up sending of the message.\n     *\n     * @param room - the room the event is in\n     */\n    prepareToEncrypt(room: Room): void;\n\n    /**\n     * Discard any existing megolm session for the given room.\n     *\n     * This will ensure that a new session is created on the next call to {@link prepareToEncrypt},\n     * or the next time a message is sent.\n     *\n     * This should not normally be necessary: it should only be used as a debugging tool if there has been a\n     * problem with encryption.\n     *\n     * @param roomId - the room to discard sessions for\n     */\n    forceDiscardSession(roomId: string): Promise<void>;\n\n    /**\n     * Get a list containing all of the room keys\n     *\n     * This should be encrypted before returning it to the user.\n     *\n     * @returns a promise which resolves to a list of\n     *    session export objects\n     */\n    exportRoomKeys(): Promise<IMegolmSessionData[]>;\n\n    /**\n     * Get a JSON list containing all of the room keys\n     *\n     * This should be encrypted before returning it to the user.\n     *\n     * @returns a promise which resolves to a JSON string\n     *    encoding a list of session export objects,\n     *    each of which is an IMegolmSessionData\n     */\n    exportRoomKeysAsJson(): Promise<string>;\n\n    /**\n     * Import a list of room keys previously exported by exportRoomKeys\n     *\n     * @param keys - a list of session export objects\n     * @param opts - options object\n     * @returns a promise which resolves once the keys have been imported\n     */\n    importRoomKeys(keys: IMegolmSessionData[], opts?: ImportRoomKeysOpts): Promise<void>;\n\n    /**\n     * Import a JSON string encoding a list of room keys previously\n     * exported by exportRoomKeysAsJson\n     *\n     * @param keys - a JSON string encoding a list of session export\n     *    objects, each of which is an IMegolmSessionData\n     * @param opts - options object\n     * @returns a promise which resolves once the keys have been imported\n     */\n    importRoomKeysAsJson(keys: string, opts?: ImportRoomKeysOpts): Promise<void>;\n\n    /**\n     * Check if the given user has published cross-signing keys.\n     *\n     * - If the user is this user, a `/keys/query` request is made to update locally the cross signing keys.\n     * - If the user is tracked, any current `/keys/query` requests are awaited (with a timeout) and then\n     *   the locally cached information is used.\n     * - If the user is not tracked locally and downloadUncached is set to true,\n     *   a `/keys/query` request is made to the server to retrieve the cross signing keys.\n     * - Otherwise, return false\n     *\n     * @param userId - the user ID to check. Defaults to the local user.\n     * @param downloadUncached - If true, download the device list for users whose device list we are not\n     *    currently tracking. Defaults to false, in which case `false` will be returned for such users.\n     *\n     * @returns true if the cross signing keys are available.\n     */\n    userHasCrossSigningKeys(userId?: string, downloadUncached?: boolean): Promise<boolean>;\n\n    /**\n     * Get the device information for the given list of users.\n     *\n     * For any users whose device lists are cached (due to sharing an encrypted room with the user), the\n     * cached device data is returned, unless it is stale.\n     *\n     * If there are users with stale cached entries, wait (with some timeout) for any in-progress\n     * `/keys/query` request to complete.\n     *\n     * If there are uncached users, and the `downloadUncached` parameter is set to `true`,\n     * a `/keys/query` request is made to the server to retrieve these devices.\n     *\n     * @param userIds - The users to fetch.\n     * @param downloadUncached - If true, download the device list for users whose device list we are not\n     *    currently tracking. Defaults to false, in which case such users will not appear at all in the result map.\n     *\n     * @returns A map `{@link DeviceMap}`.\n     */\n    getUserDeviceInfo(userIds: string[], downloadUncached?: boolean): Promise<DeviceMap>;\n\n    /**\n     * Set whether to trust other user's signatures of their devices.\n     *\n     * If false, devices will only be considered 'verified' if we have\n     * verified that device individually (effectively disabling cross-signing).\n     *\n     * `true` by default.\n     *\n     * @param val - the new value\n     */\n    setTrustCrossSignedDevices(val: boolean): void;\n\n    /**\n     * Return whether we trust other user's signatures of their devices.\n     *\n     * @see {@link CryptoApi.setTrustCrossSignedDevices}\n     *\n     * @returns `true` if we trust cross-signed devices, otherwise `false`.\n     */\n    getTrustCrossSignedDevices(): boolean;\n\n    /**\n     * Get the verification status of a given user.\n     *\n     * @param userId - The ID of the user to check.\n     *\n     */\n    getUserVerificationStatus(userId: string): Promise<UserVerificationStatus>;\n\n    /**\n     * \"Pin\" the current identity of the given user, accepting it as genuine.\n     *\n     * This is useful if the user has changed identity since we first saw them (leading to\n     * {@link UserVerificationStatus.needsUserApproval}), and we are now accepting their new identity.\n     *\n     * Throws an error if called on our own user ID, or on a user ID that we don't have an identity for.\n     */\n    pinCurrentUserIdentity(userId: string): Promise<void>;\n\n    /**\n     * Remove the requirement for this identity to be verified, and pin it.\n     *\n     * This is useful if the user was previously verified but is not anymore\n     * ({@link UserVerificationStatus.wasCrossSigningVerified}) and it is not possible to verify him again now.\n     */\n    withdrawVerificationRequirement(userId: string): Promise<void>;\n\n    /**\n     * Get the given user's public cross-signing keys, if they have published any, and we have fetched them to\n     * our store.\n     *\n     * Normally this is only useful to applications as debug info, since the SDK handles keeping track of a\n     * user's identity, and whether their devices are verified.\n     */\n    getUserCrossSigningKeys(userId: string): Promise<Partial<CrossSigningKeys> | null>;\n\n    /**\n     * Get the verification status of a given device.\n     *\n     * @param userId - The ID of the user whose device is to be checked.\n     * @param deviceId - The ID of the device to check\n     *\n     * @returns `null` if the device is unknown, or has not published any encryption keys (implying it does not support\n     *     encryption); otherwise the verification status of the device.\n     */\n    getDeviceVerificationStatus(userId: string, deviceId: string): Promise<DeviceVerificationStatus | null>;\n\n    /**\n     * Mark the given device as locally verified.\n     *\n     * Marking a device as locally verified has much the same effect as completing the verification dance, or receiving\n     * a cross-signing signature for it.\n     *\n     * @param userId - owner of the device\n     * @param deviceId - unique identifier for the device.\n     * @param verified - whether to mark the device as verified. Defaults to 'true'.\n     *\n     * @throws an error if the device is unknown, or has not published any encryption keys.\n     */\n    setDeviceVerified(userId: string, deviceId: string, verified?: boolean): Promise<void>;\n\n    /**\n     * Cross-sign one of our own devices.\n     *\n     * This will create a signature for the device using our self-signing key, and publish that signature.\n     * Cross-signing a device indicates, to our other devices and to other users, that we have verified that it really\n     * belongs to us.\n     *\n     * Requires that cross-signing has been set up on this device (normally by calling {@link bootstrapCrossSigning}).\n     *\n     * *Note*: Do not call this unless you have verified, somehow, that the device is genuine!\n     *\n     * @param deviceId - ID of the device to be signed.\n     */\n    crossSignDevice(deviceId: string): Promise<void>;\n\n    /**\n     * Checks whether cross signing:\n     * - is enabled on this account and trusted by this device\n     * - has private keys either cached locally or stored in secret storage\n     *\n     * If this function returns false, {@link bootstrapCrossSigning()} can be used\n     * to fix things such that it returns true. That is to say, after\n     * `bootstrapCrossSigning()` completes successfully, this function should\n     * return true.\n     *\n     * @returns True if cross-signing is ready to be used on this device\n     *\n     * @throws May throw {@link matrix.ClientStoppedError} if the `MatrixClient` is stopped before or during the call.\n     */\n    isCrossSigningReady(): Promise<boolean>;\n\n    /**\n     * Get the ID of one of the user's cross-signing keys, if both private and matching\n     * public parts of that key are available (ie. cached in the local crypto store).\n     *\n     * The public part may not be available if a `/keys/query` request has not yet been\n     * performed, or if the device that created the keys failed to publish them.\n     *\n     * If either part of the keypair is not available, this will return `null`.\n     *\n     * @param type - The type of key to get the ID of.  One of `CrossSigningKey.Master`, `CrossSigningKey.SelfSigning`,\n     *     or `CrossSigningKey.UserSigning`.  Defaults to `CrossSigningKey.Master`.\n     *\n     * @returns If cross-signing has been initialised on this device, the ID of the given key. Otherwise, null\n     */\n    getCrossSigningKeyId(type?: CrossSigningKey): Promise<string | null>;\n\n    /**\n     * Bootstrap cross-signing by creating keys if needed.\n     *\n     * If everything is already set up, then no changes are made, so this is safe to run to ensure\n     * cross-signing is ready for use.\n     *\n     * This function:\n     * - creates new cross-signing keys if they are not found locally cached nor in\n     *   secret storage (if it has been set up)\n     * - publishes the public keys to the server if they are not already published\n     * - stores the private keys in secret storage if secret storage is set up.\n     *\n     * @param opts - options object\n     */\n    bootstrapCrossSigning(opts: BootstrapCrossSigningOpts): Promise<void>;\n\n    /**\n     * Checks whether secret storage:\n     * - is enabled on this account\n     * - is storing cross-signing private keys\n     * - is storing session backup key (if enabled)\n     *\n     * If this function returns false, {@link bootstrapSecretStorage()} can be used\n     * to fix things such that it returns true. That is to say, after\n     * `bootstrapSecretStorage()` completes successfully, this function should\n     * return true.\n     *\n     * @returns True if secret storage is ready to be used on this device\n     */\n    isSecretStorageReady(): Promise<boolean>;\n\n    /**\n     * Inspect the status of secret storage, in more detail than {@link isSecretStorageReady}.\n     */\n    getSecretStorageStatus(): Promise<SecretStorageStatus>;\n\n    /**\n     * Bootstrap [secret storage](https://spec.matrix.org/v1.12/client-server-api/#storage).\n     *\n     * - If secret storage is not already set up, or {@link CreateSecretStorageOpts.setupNewSecretStorage} is set:\n     *   * Calls {@link CreateSecretStorageOpts.createSecretStorageKey} to generate a new key.\n     *   * Stores the metadata of the new key in account data and sets it as the default secret storage key.\n     *   * Calls {@link CryptoCallbacks.cacheSecretStorageKey} if provided.\n     * - Stores the private cross signing keys in the secret storage if they are known, and they are not\n     *   already stored in secret storage.\n     * - If {@link CreateSecretStorageOpts.setupNewKeyBackup} is set, calls {@link CryptoApi.resetKeyBackup}; otherwise,\n     *   stores the key backup decryption key in secret storage if it is known, and it is not\n     *   already stored in secret storage.\n     *\n     * Note that there may be multiple accesses to secret storage during the course of this call, each of which will\n     * result in a call to {@link CryptoCallbacks.getSecretStorageKey}.\n     *\n     * @param opts - Options object.\n     */\n    bootstrapSecretStorage(opts: CreateSecretStorageOpts): Promise<void>;\n\n    /**\n     * Get the status of our cross-signing keys.\n     *\n     * @returns The current status of cross-signing keys: whether we have public and private keys cached locally, and\n     * whether the private keys are in secret storage.\n     *\n     * @throws May throw {@link matrix.ClientStoppedError} if the `MatrixClient` is stopped before or during the call.\n     */\n    getCrossSigningStatus(): Promise<CrossSigningStatus>;\n\n    /**\n     * Create a recovery key (ie, a key suitable for use with server-side secret storage).\n     *\n     * The key can either be based on a user-supplied passphrase, or just created randomly.\n     *\n     * @param password - Optional passphrase string to use to derive the key,\n     *      which can later be entered by the user as an alternative to entering the\n     *      recovery key itself. If omitted, a key is generated randomly.\n     *\n     * @returns Object including recovery key and server upload parameters.\n     *      The private key should be disposed of after displaying to the use.\n     */\n    createRecoveryKeyFromPassphrase(password?: string): Promise<GeneratedSecretStorageKey>;\n\n    /**\n     * Get information about the encryption of the given event.\n     *\n     * @param event - the event to get information for\n     *\n     * @returns `null` if the event is not encrypted, or has not (yet) been successfully decrypted. Otherwise, an\n     *      object with information about the encryption of the event.\n     */\n    getEncryptionInfoForEvent(event: MatrixEvent): Promise<EventEncryptionInfo | null>;\n\n    /**\n     * Encrypts a given payload object via Olm to-device messages to a given\n     * set of devices.\n     *\n     * @param eventType - the type of the event to send.\n     * @param devices - an array of devices to encrypt the payload for.\n     * @param payload - the payload to encrypt.\n     *\n     * @returns the batch of encrypted payloads which can then be sent via {@link matrix.MatrixClient#queueToDevice}.\n     */\n    encryptToDeviceMessages(\n        eventType: string,\n        devices: { userId: string; deviceId: string }[],\n        payload: ToDevicePayload,\n    ): Promise<ToDeviceBatch>;\n\n    /**\n     * Reset the encryption of the user by going through the following steps:\n     * - Remove the dehydrated device and stop the periodic creation of dehydrated devices.\n     * - Disable backing up room keys and delete any existing backups.\n     * - Remove the default secret storage key from the account data (ie: the recovery key).\n     * - Reset the cross-signing keys.\n     * - Create a new key backup.\n     *\n     * Note that the dehydrated device will be removed, but will not be replaced\n     * and it will not schedule creating new dehydrated devices.  To do this,\n     * {@link startDehydration} should be called after a new secret storage key\n     * is created.\n     *\n     * @param authUploadDeviceSigningKeys - Callback to authenticate the upload of device signing keys.\n     *      Used when resetting the cross signing keys.\n     *      See {@link BootstrapCrossSigningOpts#authUploadDeviceSigningKeys}.\n     */\n    resetEncryption(authUploadDeviceSigningKeys: UIAuthCallback<void>): Promise<void>;\n\n    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n    //\n    // Device/User verification\n    //\n    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Returns to-device verification requests that are already in progress for the given user id.\n     *\n     * @param userId - the ID of the user to query\n     *\n     * @returns the VerificationRequests that are in progress\n     */\n    getVerificationRequestsToDeviceInProgress(userId: string): VerificationRequest[];\n\n    /**\n     * Finds a DM verification request that is already in progress for the given room and user.\n     *\n     * @param roomId - the room to use for verification.\n     * @param userId - search for a verification request for the given user.\n     *\n     * @returns the VerificationRequest that is in progress, if any.\n     */\n    findVerificationRequestDMInProgress(roomId: string, userId?: string): VerificationRequest | undefined;\n\n    /**\n     * Request a key verification from another user, using a DM.\n     *\n     * @param userId - the user to request verification with.\n     * @param roomId - the room to use for verification.\n     *\n     * @returns resolves to a VerificationRequest when the request has been sent to the other party.\n     */\n    requestVerificationDM(userId: string, roomId: string): Promise<VerificationRequest>;\n\n    /**\n     * Send a verification request to our other devices.\n     *\n     * This is normally used when the current device is new, and we want to ask another of our devices to cross-sign.\n     *\n     * If an all-devices verification is already in flight, returns it. Otherwise, initiates a new one.\n     *\n     * To control the methods offered, set {@link matrix.ICreateClientOpts.verificationMethods} when creating the\n     * `MatrixClient`.\n     *\n     * @returns a VerificationRequest when the request has been sent to the other party.\n     */\n    requestOwnUserVerification(): Promise<VerificationRequest>;\n\n    /**\n     * Request an interactive verification with the given device.\n     *\n     * This is normally used on one of our own devices, when the current device is already cross-signed, and we want to\n     * validate another device.\n     *\n     * If a verification for this user/device is already in flight, returns it. Otherwise, initiates a new one.\n     *\n     * To control the methods offered, set {@link  matrix.ICreateClientOpts.verificationMethods} when creating the\n     * `MatrixClient`.\n     *\n     * @param userId - ID of the owner of the device to verify\n     * @param deviceId - ID of the device to verify\n     *\n     * @returns a VerificationRequest when the request has been sent to the other party.\n     */\n    requestDeviceVerification(userId: string, deviceId: string): Promise<VerificationRequest>;\n\n    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n    //\n    // Secure key backup\n    //\n    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Fetch the backup decryption key we have saved in our store.\n     *\n     * This can be used for gossiping the key to other devices.\n     *\n     * @returns the key, if any, or null\n     */\n    getSessionBackupPrivateKey(): Promise<Uint8Array | null>;\n\n    /**\n     * Store the backup decryption key.\n     *\n     * This should be called if the client has received the key from another device via secret sharing (gossiping).\n     * It is the responsability of the caller to check that the decryption key is valid for the given backup version.\n     *\n     * @param key - the backup decryption key\n     * @param version - the backup version corresponding to this decryption key\n     */\n    storeSessionBackupPrivateKey(key: Uint8Array, version: string): Promise<void>;\n\n    /**\n     * Attempt to fetch the backup decryption key from secret storage.\n     *\n     * If the key is found in secret storage, checks it against the latest backup on the server;\n     * if they match, stores the key in the crypto store by calling {@link storeSessionBackupPrivateKey},\n     * which enables automatic restore of individual keys when an Unable-to-decrypt error is encountered.\n     *\n     * If the backup decryption key from secret storage does not match the\n     * latest backup on the server, we throw a {@link DecryptionKeyDoesNotMatchError}.\n     *\n     * If we are unable to fetch the key from secret storage or there is no backup on the server,\n     * we throw an exception.\n     */\n    loadSessionBackupPrivateKeyFromSecretStorage(): Promise<void>;\n\n    /**\n     * Get the current status of key backup.\n     *\n     * @returns If automatic key backups are enabled, the `version` of the active backup. Otherwise, `null`.\n     */\n    getActiveSessionBackupVersion(): Promise<string | null>;\n\n    /**\n     * Determine if a key backup can be trusted.\n     *\n     * @param info - key backup info dict from {@link CryptoApi.getKeyBackupInfo}.\n     */\n    isKeyBackupTrusted(info: KeyBackupInfo): Promise<BackupTrustInfo>;\n\n    /**\n     * Return the details of the latest backup on the server, when we last checked.\n     *\n     * This normally returns a cached value, but if we haven't yet made a request to the server, it will fire one off.\n     * It will always return the details of the active backup if key backup is enabled.\n     *\n     * Return null if there is no backup.\n     *\n     * @returns the key backup information\n     */\n    getKeyBackupInfo(): Promise<KeyBackupInfo | null>;\n\n    /**\n     * Force a re-check of the key backup and enable/disable it as appropriate.\n     *\n     * Fetches the current backup information from the server. If there is a backup, and it is trusted, starts\n     * backing up to it; otherwise, disables backups.\n     *\n     * @returns `null` if there is no backup on the server. Otherwise, data on the backup as returned by the server,\n     *   and trust information (as returned by {@link isKeyBackupTrusted}).\n     */\n    checkKeyBackupAndEnable(): Promise<KeyBackupCheck | null>;\n\n    /**\n     * Creates a new key backup version.\n     *\n     * If there are existing backups they will be replaced.\n     *\n     * If secret storage is set up, the new decryption key will be saved (the {@link CryptoCallbacks.getSecretStorageKey}\n     * callback will be called to obtain the secret storage key).\n     *\n     * The backup engine will be started using the new backup version (i.e., {@link checkKeyBackupAndEnable} is called).\n     */\n    resetKeyBackup(): Promise<void>;\n\n    /**\n     * Disables server-side key storage and deletes server-side backups.\n     *  * Deletes the current key backup version, if any (but not any previous versions).\n     *  * Disables 4S, deleting the info for the default key, the default key pointer itself and any\n     *    known 4S data (cross-signing keys and the megolm key backup key).\n     *  * Deletes any dehydrated devices.\n     */\n    disableKeyStorage(): Promise<void>;\n\n    /**\n     * Deletes the given key backup.\n     *\n     * @param version - The backup version to delete.\n     */\n    deleteKeyBackupVersion(version: string): Promise<void>;\n\n    /**\n     * Download and restore the full key backup from the homeserver.\n     *\n     * Before calling this method, a decryption key, and the backup version to restore,\n     * must have been saved in the crypto store. This happens in one of the following ways:\n     *\n     * - When a new backup version is created with {@link CryptoApi.resetKeyBackup}, a new key is created and cached.\n     * - The key can be loaded from secret storage with {@link CryptoApi.loadSessionBackupPrivateKeyFromSecretStorage}.\n     * - The key can be received from another device via secret sharing, typically as part of the interactive verification flow.\n     * - The key and backup version can also be set explicitly via {@link CryptoApi.storeSessionBackupPrivateKey},\n     *   though this is not expected to be a common operation.\n     *\n     * Warning: the full key backup may be quite large, so this operation may take several hours to complete.\n     * Use of {@link KeyBackupRestoreOpts.progressCallback} is recommended.\n     *\n     * @param opts\n     */\n    restoreKeyBackup(opts?: KeyBackupRestoreOpts): Promise<KeyBackupRestoreResult>;\n\n    /**\n     * Restores a key backup using a passphrase.\n     * The decoded key (derived from the passphrase) is stored locally by calling {@link CryptoApi#storeSessionBackupPrivateKey}.\n     *\n     * @param passphrase - The passphrase to use to restore the key backup.\n     * @param opts\n     *\n     * @deprecated Deriving a backup key from a passphrase is not part of the matrix spec. Instead, a random key is generated and stored/shared via 4S.\n     */\n    restoreKeyBackupWithPassphrase(passphrase: string, opts?: KeyBackupRestoreOpts): Promise<KeyBackupRestoreResult>;\n\n    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n    //\n    // Dehydrated devices\n    //\n    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Returns whether MSC3814 dehydrated devices are supported by the crypto\n     * backend and by the server.\n     *\n     * This should be called before calling `startDehydration`, and if this\n     * returns `false`, `startDehydration` should not be called.\n     */\n    isDehydrationSupported(): Promise<boolean>;\n\n    /**\n     * Start using device dehydration.\n     *\n     * - Rehydrates a dehydrated device, if one is available and `opts.rehydrate`\n     *   is `true`.\n     * - Creates a new dehydration key, if necessary, and stores it in Secret\n     *   Storage.\n     *   - If `opts.createNewKey` is set to true, always creates a new key.\n     *   - If a dehydration key is not available, creates a new one.\n     * - Creates a new dehydrated device, and schedules periodically creating\n     *   new dehydrated devices.\n     *\n     * This function must not be called unless `isDehydrationSupported` returns\n     * `true`, and must not be called until after cross-signing and secret\n     * storage have been set up.\n     *\n     * @param opts - options for device dehydration. For backwards compatibility\n     *     with old code, a boolean can be given here, which will be treated as\n     *     the `createNewKey` option. However, this is deprecated.\n     */\n    startDehydration(opts?: StartDehydrationOpts | boolean): Promise<void>;\n\n    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n    //\n    // Import/export of secret keys\n    //\n    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    /**\n     * Export secrets bundle for transmitting to another device as part of OAuth2 QR login\n     */\n    exportSecretsBundle?(): Promise<Awaited<ReturnType<SecretsBundle[\"to_json\"]>>>;\n\n    /**\n     * Import secrets bundle transmitted from another device.\n     * @param secrets - The secrets bundle received from the other device\n     */\n    importSecretsBundle?(secrets: Awaited<ReturnType<SecretsBundle[\"to_json\"]>>): Promise<void>;\n}\n\n/** A reason code for a failure to decrypt an event. */\nexport enum DecryptionFailureCode {\n    /** Message was encrypted with a Megolm session whose keys have not been shared with us. */\n    MEGOLM_UNKNOWN_INBOUND_SESSION_ID = \"MEGOLM_UNKNOWN_INBOUND_SESSION_ID\",\n\n    /** A special case of {@link MEGOLM_UNKNOWN_INBOUND_SESSION_ID}: the sender has told us it is withholding the key. */\n    MEGOLM_KEY_WITHHELD = \"MEGOLM_KEY_WITHHELD\",\n\n    /** A special case of {@link MEGOLM_KEY_WITHHELD}: the sender has told us it is withholding the key, because the current device is unverified. */\n    MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE = \"MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE\",\n\n    /** Message was encrypted with a Megolm session which has been shared with us, but in a later ratchet state. */\n    OLM_UNKNOWN_MESSAGE_INDEX = \"OLM_UNKNOWN_MESSAGE_INDEX\",\n\n    /**\n     * Message was sent before the current device was created; there is no key backup on the server, so this\n     * decryption failure is expected.\n     */\n    HISTORICAL_MESSAGE_NO_KEY_BACKUP = \"HISTORICAL_MESSAGE_NO_KEY_BACKUP\",\n\n    /**\n     * Message was sent before the current device was created; there was a key backup on the server, but we don't\n     * seem to have access to the backup. (Probably we don't have the right key.)\n     */\n    HISTORICAL_MESSAGE_BACKUP_UNCONFIGURED = \"HISTORICAL_MESSAGE_BACKUP_UNCONFIGURED\",\n\n    /**\n     * Message was sent before the current device was created; there was a (usable) key backup on the server, but we\n     * still can't decrypt. (Either the session isn't in the backup, or we just haven't gotten around to checking yet.)\n     */\n    HISTORICAL_MESSAGE_WORKING_BACKUP = \"HISTORICAL_MESSAGE_WORKING_BACKUP\",\n\n    /**\n     * Message was sent when the user was not a member of the room.\n     */\n    HISTORICAL_MESSAGE_USER_NOT_JOINED = \"HISTORICAL_MESSAGE_USER_NOT_JOINED\",\n\n    /**\n     * The sender's identity is not verified, but was previously verified.\n     */\n    SENDER_IDENTITY_PREVIOUSLY_VERIFIED = \"SENDER_IDENTITY_PREVIOUSLY_VERIFIED\",\n\n    /**\n     * The sender device is not cross-signed.  This will only be used if the\n     * device isolation mode is set to `OnlySignedDevicesIsolationMode`.\n     */\n    UNSIGNED_SENDER_DEVICE = \"UNSIGNED_SENDER_DEVICE\",\n\n    /**\n     * We weren't able to link the message back to any known device.  This will\n     * only be used if the device isolation mode is set to `OnlySignedDevicesIsolationMode`.\n     */\n    UNKNOWN_SENDER_DEVICE = \"UNKNOWN_SENDER_DEVICE\",\n\n    /** Unknown or unclassified error. */\n    UNKNOWN_ERROR = \"UNKNOWN_ERROR\",\n}\n\n/** Base {@link DeviceIsolationMode} kind. */\nexport enum DeviceIsolationModeKind {\n    AllDevicesIsolationMode,\n    OnlySignedDevicesIsolationMode,\n}\n\n/**\n * A type of {@link DeviceIsolationMode}.\n *\n * Message encryption keys are shared with all devices in the room, except in case of\n * verified user problems (see {@link errorOnVerifiedUserProblems}).\n *\n * Events from all senders are always decrypted (and should be decorated with message shields in case\n * of authenticity warnings, see {@link EventEncryptionInfo}).\n *\n * `AllDevicesIsolationMode` is used in the legacy, non-'exclude insecure devices' mode in Element Web. It is not\n * recommended (see {@link https://github.com/matrix-org/matrix-spec-proposals/pull/4153 | MSC4153}).\n */\nexport class AllDevicesIsolationMode {\n    public readonly kind = DeviceIsolationModeKind.AllDevicesIsolationMode;\n\n    /**\n     *\n     * @param errorOnVerifiedUserProblems - Behavior when sharing keys to remote devices.\n     *\n     * If set to `true`, sharing keys will fail (i.e. message sending will fail) with an error if:\n     *   - The user was previously verified but is not anymore, or:\n     *   - A verified user has some unverified devices (not cross-signed).\n     *\n     * If `false`, the keys will be distributed as usual. In this case, the client UX should display\n     * warnings to inform the user about problematic devices/users, and stop them hitting this case.\n     */\n    public constructor(public readonly errorOnVerifiedUserProblems: boolean) {}\n}\n\n/**\n * A type of {@link DeviceIsolationMode}.\n *\n * Message encryption keys are only shared with devices that have been cross-signed by their owner.\n * Encryption will throw an error if a verified user replaces their identity.\n *\n * Events are decrypted only if they come from a cross-signed device. Other events will result in a decryption\n * failure. (To access the failure reason, see {@link MatrixEvent.decryptionFailureReason}.)\n *\n * `OnlySignedDevicesIsolationMode` corresponds to the 'Exclude insecure devices' mode in Element Web, which is\n * recommended by {@link https://github.com/matrix-org/matrix-spec-proposals/pull/4153 | MSC4153}.\n */\nexport class OnlySignedDevicesIsolationMode {\n    public readonly kind = DeviceIsolationModeKind.OnlySignedDevicesIsolationMode;\n}\n\n/**\n * DeviceIsolationMode represents the mode of device isolation used when encrypting or decrypting messages.\n * It can be one of two types: {@link AllDevicesIsolationMode} or {@link OnlySignedDevicesIsolationMode}.\n *\n * Only supported by rust Crypto.\n */\nexport type DeviceIsolationMode = AllDevicesIsolationMode | OnlySignedDevicesIsolationMode;\n\n/**\n * Options object for `CryptoApi.bootstrapCrossSigning`.\n */\nexport interface BootstrapCrossSigningOpts {\n    /** Optional. Reset the cross-signing keys even if keys already exist. */\n    setupNewCrossSigning?: boolean;\n\n    /**\n     * An application callback to collect the authentication data for uploading the keys. If not given, the keys\n     * will not be uploaded to the server (which seems like a bad thing?).\n     */\n    authUploadDeviceSigningKeys?: UIAuthCallback<void>;\n}\n\n/**\n * Represents the ways in which we trust a user\n */\nexport class UserVerificationStatus {\n    /**\n     * Indicates if we have saved a known identity for this user. Typically, this means that we share a\n     * room with them (or have done in the past).\n     *\n     * If this is `false`, then the other flags ({@link isCrossSigningVerified}, {@link wasCrossSigningVerified},\n     * {@link needsUserApproval}) will also be `false`. This means that we haven't seen this user before.\n     *\n     * If this is `true`, then there are further possibilities:\n     *\n     *  - If {@link isCrossSigningVerified} returns `true`, then we have cryptographically verified the current\n     *    identity of this user: that is the highest form of trust we have.\n     *\n     *  - If {@link needsUserApproval} is `true`, that means that the user has changed their identity.\n     *\n     *  - Otherwise, the user is \"TOFU trusted\": we have a record of their identity, and, typically, will share\n     *    encrypted content with them as long as they retain that identity.\n     */\n    public readonly known: boolean;\n\n    /**\n     * Indicates if the identity has changed in a way that needs user approval.\n     *\n     * This happens if the identity has changed since we first saw it, *unless* the new identity has also been verified\n     * by our user (eg via an interactive verification).\n     *\n     * To rectify this, either:\n     *\n     *  * Conduct a verification of the new identity via {@link CryptoApi.requestVerificationDM}.\n     *  * Pin the new identity, via {@link CryptoApi.pinCurrentUserIdentity}.\n     *\n     * @returns true if the identity has changed in a way that needs user approval.\n     */\n    public readonly needsUserApproval: boolean;\n\n    /** @internal */\n    public constructor(\n        private readonly crossSigningVerified: boolean,\n        private readonly crossSigningVerifiedBefore: boolean,\n        known: boolean,\n        needsUserApproval: boolean = false,\n    ) {\n        this.known = known;\n        this.needsUserApproval = needsUserApproval;\n    }\n\n    /**\n     * @returns true if this user is verified via any means\n     */\n    public isVerified(): boolean {\n        return this.isCrossSigningVerified();\n    }\n\n    /**\n     * @returns true if this user is verified via cross signing\n     */\n    public isCrossSigningVerified(): boolean {\n        return this.crossSigningVerified;\n    }\n\n    /**\n     * @returns true if we ever verified this user before (at least for\n     * the history of verifications observed by this device).\n     */\n    public wasCrossSigningVerified(): boolean {\n        return this.crossSigningVerifiedBefore;\n    }\n\n    /**\n     * @returns true if this user's key is trusted on first use\n     *\n     * @deprecated No longer supported, with the Rust crypto stack.\n     */\n    public isTofu(): boolean {\n        return false;\n    }\n}\n\nexport class DeviceVerificationStatus {\n    /**\n     * True if this device has been signed by its owner (and that signature verified).\n     *\n     * This doesn't necessarily mean that we have verified the device, since we may not have verified the\n     * owner's cross-signing key.\n     */\n    public readonly signedByOwner: boolean;\n\n    /**\n     * True if this device has been verified via cross signing.\n     *\n     * This does *not* take into account `trustCrossSignedDevices`.\n     */\n    public readonly crossSigningVerified: boolean;\n\n    /**\n     * TODO: tofu magic wtf does this do?\n     */\n    public readonly tofu: boolean;\n\n    /**\n     * True if the device has been marked as locally verified.\n     */\n    public readonly localVerified: boolean;\n\n    /**\n     * True if the client has been configured to trust cross-signed devices via {@link CryptoApi#setTrustCrossSignedDevices}.\n     */\n    private readonly trustCrossSignedDevices: boolean;\n\n    public constructor(\n        opts: Partial<DeviceVerificationStatus> & {\n            /**\n             * True if cross-signed devices should be considered verified for {@link DeviceVerificationStatus#isVerified}.\n             */\n            trustCrossSignedDevices?: boolean;\n        },\n    ) {\n        this.signedByOwner = opts.signedByOwner ?? false;\n        this.crossSigningVerified = opts.crossSigningVerified ?? false;\n        this.tofu = opts.tofu ?? false;\n        this.localVerified = opts.localVerified ?? false;\n        this.trustCrossSignedDevices = opts.trustCrossSignedDevices ?? false;\n    }\n\n    /**\n     * Check if we should consider this device \"verified\".\n     *\n     * A device is \"verified\" if either:\n     *  * it has been manually marked as such via {@link CryptoApi.setDeviceVerified}.\n     *  * it has been cross-signed with a verified signing key, **and** the client has been configured to trust\n     *    cross-signed devices via {@link CryptoApi.setTrustCrossSignedDevices}.\n     *\n     * @returns true if this device is verified via any means.\n     */\n    public isVerified(): boolean {\n        return this.localVerified || (this.trustCrossSignedDevices && this.crossSigningVerified);\n    }\n}\n\n/**\n * Enum representing the different stages of importing room keys.\n *\n * This is the type of the `stage` property of {@link ImportRoomKeyProgressData}.\n */\nexport enum ImportRoomKeyStage {\n    /**\n     * The stage where room keys are being fetched.\n     *\n     * @see {@link ImportRoomKeyFetchProgress}.\n     */\n    Fetch = \"fetch\",\n    /**\n     * The stage where room keys are being loaded.\n     *\n     * @see {@link ImportRoomKeyLoadProgress}.\n     */\n    LoadKeys = \"load_keys\",\n}\n\n/**\n * Type representing the progress during the 'fetch' stage of the room key import process.\n *\n * @see {@link ImportRoomKeyProgressData}.\n */\nexport type ImportRoomKeyFetchProgress = {\n    /**\n     * The current stage of the import process.\n     */\n    stage: ImportRoomKeyStage.Fetch;\n};\n\n/**\n * Type representing the progress during the 'load_keys' stage of the room key import process.\n *\n * @see {@link ImportRoomKeyProgressData}.\n */\nexport type ImportRoomKeyLoadProgress = {\n    /**\n     * The current stage of the import process.\n     */\n    stage: ImportRoomKeyStage.LoadKeys;\n\n    /**\n     * The number of successfully loaded room keys so far.\n     */\n    successes: number;\n\n    /**\n     * The number of room keys that failed to load so far.\n     */\n    failures: number;\n\n    /**\n     * The total number of room keys being loaded.\n     */\n    total: number;\n};\n\n/**\n * Room key import progress report.\n * Used when calling {@link CryptoApi#importRoomKeys},\n * {@link CryptoApi#importRoomKeysAsJson} or {@link CryptoApi#restoreKeyBackup} as the parameter of\n * the progressCallback. Used to display feedback.\n */\nexport type ImportRoomKeyProgressData = ImportRoomKeyFetchProgress | ImportRoomKeyLoadProgress;\n\n/**\n * Options object for {@link CryptoApi#importRoomKeys} and\n * {@link CryptoApi#importRoomKeysAsJson}.\n */\nexport interface ImportRoomKeysOpts {\n    /** Reports ongoing progress of the import process. Can be used for feedback. */\n    progressCallback?: (stage: ImportRoomKeyProgressData) => void;\n    /** @deprecated not useful externally */\n    source?: string;\n}\n\n/**\n * The result of a call to {@link CryptoApi.getCrossSigningStatus}.\n */\nexport interface CrossSigningStatus {\n    /**\n     * True if the public master, self signing and user signing keys are available on this device.\n     */\n    publicKeysOnDevice: boolean;\n    /**\n     * True if the private keys are stored in the secret storage.\n     */\n    privateKeysInSecretStorage: boolean;\n    /**\n     * True if the private keys are stored locally.\n     */\n    privateKeysCachedLocally: {\n        masterKey: boolean;\n        selfSigningKey: boolean;\n        userSigningKey: boolean;\n    };\n}\n\n/**\n * Crypto callbacks provided by the application\n */\nexport interface CryptoCallbacks {\n    /**\n     * Called to retrieve a secret storage encryption key.\n     *\n     * [Server-side secret storage](https://spec.matrix.org/v1.12/client-server-api/#key-storage)\n     * is, as the name implies, a mechanism for storing secrets which should be shared between\n     * clients on the server. For example, it is typically used for storing the\n     * [key backup decryption key](https://spec.matrix.org/v1.12/client-server-api/#decryption-key)\n     * and the private [cross-signing keys](https://spec.matrix.org/v1.12/client-server-api/#cross-signing).\n     *\n     * The secret storage mechanism encrypts the secrets before uploading them to the server using a\n     * secret storage key. The schema supports multiple keys, but in practice only one tends to be used\n     * at once; this is the \"default secret storage key\" and may be known as the \"recovery key\" (or, sometimes,\n     * the \"security key\").\n     *\n     * Secret storage can be set up by calling {@link CryptoApi.bootstrapSecretStorage}. Having done so, when\n     * the crypto stack needs to access secret storage (for example, when setting up a new device, or to\n     * store newly-generated secrets), it will use this callback (`getSecretStorageKey`).\n     *\n     * Note that the secret storage key may be needed several times in quick succession: it is recommended\n     * that applications use a temporary cache to avoid prompting the user multiple times for the key. See\n     * also {@link cacheSecretStorageKey} which is called when a new key is created.\n     *\n     * The helper method {@link deriveRecoveryKeyFromPassphrase} may be useful if the secret storage key\n     * was derived from a passphrase.\n     *\n     * @param opts - An options object.\n     *\n     * @param name - the name of the *secret* (NB: not the encryption key) being stored or retrieved.\n     *    When the item is stored in account data, it will have this `type`.\n     *\n     * @returns a pair [`keyId`, `privateKey`], where `keyId` is one of the keys from the `keys` parameter,\n     *    and `privateKey` is the raw private encryption key, as appropriate for the encryption algorithm.\n     *    (For `m.secret_storage.v1.aes-hmac-sha2`, it is the input to an HKDF as defined in the\n     *    [specification](https://spec.matrix.org/v1.6/client-server-api/#msecret_storagev1aes-hmac-sha2).)\n     *\n     *    Alternatively, if none of the keys are known, may return `null` — in which case the original\n     *     operation that requires access to a secret in secret storage may fail with an exception.\n     */\n    getSecretStorageKey?: (\n        opts: {\n            /**\n             * Details of the secret storage keys required: a map from the key ID\n             * (excluding the `m.secret_storage.key.` prefix) to details of the key.\n             *\n             * When storing a secret, `keys` will contain exactly one entry.\n             *\n             * For secret retrieval, `keys` may contain several entries, and the application can return\n             * any one of the requested keys. Unless your application specifically wants to offer the\n             * user the ability to have more than one secret storage key active at a time, it is recommended\n             * to call {@link matrix.SecretStorage.ServerSideSecretStorage.getDefaultKeyId | ServerSideSecretStorage.getDefaultKeyId}\n             * to figure out which is the current default key, and to return `null` if the default key is not listed in `keys`.\n             */\n            keys: Record<string, SecretStorageKeyDescription>;\n        },\n        name: string,\n    ) => Promise<[string, Uint8Array<ArrayBuffer>] | null>;\n\n    /**\n     * Called by {@link CryptoApi.bootstrapSecretStorage} when a new default secret storage key is created.\n     *\n     * Applications can use this to (temporarily) cache the secret storage key, for later return by\n     * {@link getSecretStorageKey}.\n     *\n     * @param keyId - secret storage key id\n     * @param keyInfo - secret storage key info\n     * @param key - private key to store\n     */\n    cacheSecretStorageKey?: (keyId: string, keyInfo: SecretStorageKeyDescription, key: Uint8Array<ArrayBuffer>) => void;\n}\n\n/**\n * The result of a call to {@link CryptoApi.getSecretStorageStatus}.\n */\nexport interface SecretStorageStatus {\n    /** Whether secret storage is fully populated. The same as {@link CryptoApi.isSecretStorageReady}. */\n    ready: boolean;\n\n    /** The ID of the current default secret storage key. */\n    defaultKeyId: string | null;\n\n    /**\n     * For each secret that we checked whether it is correctly stored in secret storage with the default secret storage key.\n     *\n     * Note that we will only check that the key backup key is stored if key backup is currently enabled (i.e. that\n     * {@link CryptoApi.getActiveSessionBackupVersion} returns non-null). `m.megolm_backup.v1` will only be present in that case.\n     *\n     * (This is an object rather than a `Map` so that it JSON.stringify()s nicely, since its main purpose is to end up\n     * in logs.)\n     */\n    secretStorageKeyValidityMap: {\n        [P in SecretStorageKey]?: boolean;\n    };\n}\n\n/**\n * Parameter of {@link CryptoApi#bootstrapSecretStorage}\n */\nexport interface CreateSecretStorageOpts {\n    /**\n     * Function called to await a secret storage key creation flow.\n     * @returns Promise resolving to an object with public key metadata, encoded private\n     *     recovery key which should be disposed of after displaying to the user,\n     *     and raw private key to avoid round tripping if needed.\n     */\n    createSecretStorageKey?: () => Promise<GeneratedSecretStorageKey>;\n\n    /**\n     * If true, a new key backup version will be\n     * created and the private key stored in the new SSSS store. Ignored if keyBackupInfo\n     * is supplied.\n     */\n    setupNewKeyBackup?: boolean;\n\n    /**\n     * Reset even if keys already exist.\n     */\n    setupNewSecretStorage?: boolean;\n}\n\n/** Types of cross-signing key */\nexport enum CrossSigningKey {\n    Master = \"master_key\",\n    SelfSigning = \"self_signing_key\",\n    UserSigning = \"user_signing_key\",\n}\n\n/** All of a user's cross-signing keys.\n * @see https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3keysdevice_signingupload\n */\nexport type CrossSigningKeys = Record<CrossSigningKey, CrossSigningKeyInfo>;\n\n/**\n * Information on one of the cross-signing keys.\n * @see https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3keysdevice_signingupload\n */\nexport interface CrossSigningKeyInfo {\n    keys: { [algorithm: string]: string };\n    signatures?: ISignatures;\n    usage: string[];\n    user_id: string;\n}\n\n/**\n * Recovery key created by {@link CryptoApi#createRecoveryKeyFromPassphrase} or {@link CreateSecretStorageOpts#createSecretStorageKey}.\n */\nexport interface GeneratedSecretStorageKey {\n    keyInfo?: {\n        /** If the key was derived from a passphrase, information (algorithm, salt, etc) on that derivation. */\n        passphrase?: PassphraseInfo;\n        /** Optional human-readable name for the key, to be stored in account_data. */\n        name?: string;\n    };\n    /** The raw generated private key. */\n    privateKey: Uint8Array<ArrayBuffer>;\n    /** The generated key, encoded for display to the user per https://spec.matrix.org/v1.7/client-server-api/#key-representation. */\n    encodedPrivateKey?: string;\n}\n\n/**\n *  Result type of {@link CryptoApi#getEncryptionInfoForEvent}.\n */\nexport interface EventEncryptionInfo {\n    /** \"Shield\" to be shown next to this event representing its verification status */\n    shieldColour: EventShieldColour;\n\n    /**\n     * `null` if `shieldColour` is `EventShieldColour.NONE`; otherwise a reason code for the shield in `shieldColour`.\n     */\n    shieldReason: EventShieldReason | null;\n}\n\n/**\n * Types of shield to be shown for {@link EventEncryptionInfo#shieldColour}.\n */\nexport enum EventShieldColour {\n    NONE,\n    GREY,\n    RED,\n}\n\n/**\n * Reason codes for {@link EventEncryptionInfo#shieldReason}.\n */\nexport enum EventShieldReason {\n    /** An unknown reason from the crypto library (if you see this, it is a bug in matrix-js-sdk). */\n    UNKNOWN,\n\n    /** \"Encrypted by an unverified user.\" */\n    UNVERIFIED_IDENTITY,\n\n    /** \"Encrypted by a device not verified by its owner.\" */\n    UNSIGNED_DEVICE,\n\n    /** \"Encrypted by an unknown or deleted device.\" */\n    UNKNOWN_DEVICE,\n\n    /**\n     * \"The authenticity of this encrypted message can't be guaranteed on this device.\"\n     *\n     * ie: the key has been forwarded, or retrieved from an insecure backup.\n     */\n    AUTHENTICITY_NOT_GUARANTEED,\n\n    /**\n     * The (deprecated) sender_key field in the event does not match the Ed25519 key of the device that sent us the\n     * decryption keys.\n     *\n     * @deprecated The sender_key field is not checked by matrix-sdk-crypto, and this value is therefore unused since\n     * the migration to matrix-sdk-crypto in v37.0.0.\n     */\n    MISMATCHED_SENDER_KEY,\n\n    /**\n     * The event was sent unencrypted in an encrypted room.\n     *\n     * @deprecated This has never been used. The fact it is here was due to a misunderstanding of the behaviour of\n     * `matrix-sdk-crypto`.\n     */\n    SENT_IN_CLEAR,\n\n    /**\n     * The sender was previously verified but changed their identity.\n     */\n    VERIFICATION_VIOLATION,\n\n    /**\n     * The `sender` field on the event does not match the owner of the device\n     * that established the Megolm session.\n     */\n    MISMATCHED_SENDER,\n}\n\n/** The result of a call to {@link CryptoApi.getOwnDeviceKeys} */\nexport interface OwnDeviceKeys {\n    /** Public part of the Ed25519 fingerprint key for the current device, base64 encoded. */\n    ed25519: string;\n    /** Public part of the Curve25519 identity key for the current device, base64 encoded. */\n    curve25519: string;\n}\n\n/**\n * Information about the encryption of a successfully decrypted to-device message.\n */\nexport interface OlmEncryptionInfo {\n    /** The user ID of the event sender, note this is untrusted data unless `isVerified` is true **/\n    sender: string;\n    /**\n     * The device ID of the device that sent us the event.\n     * Note this is untrusted data unless {@link senderVerified} is true.\n     * If the device ID is not known, this will be `null`.\n     **/\n    senderDevice?: string;\n    /** The sender device's public Curve25519 key, base64 encoded **/\n    senderCurve25519KeyBase64: string;\n    /**\n     *  If true, this message is guaranteed to be authentic as it is coming from a device belonging to a user that we have verified.\n     *  This is the state at the time of decryption (the user could be verified later).\n     */\n    senderVerified: boolean;\n}\n\n/**\n * An error thrown by loadSessionBackupPrivateKeyFromSecretStorage indicating\n * that the decryption key found in secret storage does not match the public key\n * of the latest backup.\n */\nexport class DecryptionKeyDoesNotMatchError extends Error {\n    public constructor(message: string) {\n        super(message);\n        this.name = \"DecryptionKeyDoesNotMatchError\";\n    }\n}\n\nexport * from \"./verification.ts\";\nexport type * from \"./keybackup.ts\";\nexport * from \"./recovery-key.ts\";\nexport * from \"./key-passphrase.ts\";\nexport * from \"./CryptoEvent.ts\";\nexport type * from \"./CryptoEventHandlerMap.ts\";\n","/*\nCopyright 2022 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nexport enum InvalidCryptoStoreState {\n    TooNew = \"TOO_NEW\",\n}\n\nexport class InvalidCryptoStoreError extends Error {\n    public static TOO_NEW = InvalidCryptoStoreState.TooNew;\n\n    public constructor(public readonly reason: InvalidCryptoStoreState) {\n        const message =\n            `Crypto store is invalid because ${reason}, ` +\n            `please stop the client, delete all data and start the client again`;\n        super(message);\n        this.name = \"InvalidCryptoStoreError\";\n    }\n}\n\nexport class KeySignatureUploadError extends Error {\n    public constructor(\n        message: string,\n        public readonly value: any,\n    ) {\n        super(message);\n    }\n}\n\n/**\n * It is invalid to call most methods once {@link MatrixClient#stopClient} has been called.\n *\n * This error will be thrown if you attempt to do so.\n *\n * {@link MatrixClient#stopClient} itself is an exception to this: it may safely be called multiple times on the same\n * instance.\n */\nexport class ClientStoppedError extends Error {\n    public constructor() {\n        super(\"MatrixClient has been stopped\");\n    }\n}\n\n/**\n * This error is thrown when the Homeserver does not support the delayed events endpoints.\n */\nexport class UnsupportedDelayedEventsEndpointError extends Error {\n    public constructor(\n        message: string,\n        public clientEndpoint:\n            | \"sendDelayedEvent\"\n            | \"updateDelayedEvent\"\n            | \"cancelScheduledDelayedEvent\"\n            | \"restartScheduledDelayedEvent\"\n            | \"sendScheduledDelayedEvent\"\n            | \"sendDelayedStateEvent\"\n            | \"getDelayedEvents\",\n    ) {\n        super(message);\n        this.name = \"UnsupportedDelayedEventsEndpointError\";\n    }\n}\n\n/**\n * This error is thrown when the Homeserver does not support the sticky events endpoints.\n */\nexport class UnsupportedStickyEventsEndpointError extends Error {\n    public constructor(\n        message: string,\n        public clientEndpoint: \"sendStickyEvent\" | \"sendStickyStateEvent\",\n    ) {\n        super(message);\n        this.name = \"UnsupportedStickyEventsEndpointError\";\n    }\n}\n","/*\nCopyright 2021 - 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type Optional } from \"matrix-events-sdk\";\n\n/**\n * Determines if the given optional was provided a value.\n * @param s - The optional to test.\n * @returns True if the value is defined.\n */\nexport function isProvided<T>(s: Optional<T>): s is T {\n    return s !== null && s !== undefined;\n}\n\n/**\n * Determines if the given optional string is a defined string.\n * @param s - The input string.\n * @returns True if the input is a defined string.\n */\nexport function isOptionalAString(s: Optional<string>): s is string {\n    return isProvided(s) && typeof s === \"string\";\n}\n","/*\nCopyright 2015 - 2021 Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type EventType, type RelationType } from \"./@types/event.ts\";\nimport { UNREAD_THREAD_NOTIFICATIONS } from \"./@types/sync.ts\";\nimport { FilterComponent, type IFilterComponent } from \"./filter-component.ts\";\nimport { type MatrixEvent } from \"./models/event.ts\";\nimport { NamespacedValue } from \"./NamespacedValue.ts\";\n\nconst profileFieldsFilterName = new NamespacedValue(\"profile_fields\", \"org.matrix.msc4429.profile_fields\");\n\n/**\n */\nfunction setProp(obj: Record<string, any>, keyNesting: string, val: any): void {\n    const nestedKeys = keyNesting.split(\".\") as [keyof typeof obj];\n    let currentObj = obj;\n    for (let i = 0; i < nestedKeys.length - 1; i++) {\n        if (!currentObj[nestedKeys[i]]) {\n            currentObj[nestedKeys[i]] = {};\n        }\n        currentObj = currentObj[nestedKeys[i]];\n    }\n    currentObj[nestedKeys[nestedKeys.length - 1]] = val;\n}\n\n/* eslint-disable camelcase */\nexport interface IFilterDefinition {\n    \"event_fields\"?: string[];\n    \"event_format\"?: \"client\" | \"federation\";\n    \"presence\"?: IFilterComponent;\n    \"account_data\"?: IFilterComponent;\n    \"room\"?: IRoomFilter;\n    \"profile_fields\"?: ProfileFieldsFilter;\n    \"org.matrix.msc4429.profile_fields\"?: ProfileFieldsFilter;\n}\n\nexport interface IRoomEventFilter extends IFilterComponent {\n    \"lazy_load_members\"?: boolean;\n    \"include_redundant_members\"?: boolean;\n    \"types\"?: Array<EventType | string>;\n    \"related_by_senders\"?: Array<RelationType | string>;\n    \"related_by_rel_types\"?: string[];\n    \"unread_thread_notifications\"?: boolean;\n    \"org.matrix.msc3773.unread_thread_notifications\"?: boolean;\n\n    // Unstable values\n    \"io.element.relation_senders\"?: Array<RelationType | string>;\n    \"io.element.relation_types\"?: string[];\n}\n\ninterface IStateFilter extends IRoomEventFilter {}\n\ninterface IRoomFilter {\n    not_rooms?: string[];\n    rooms?: string[];\n    ephemeral?: IRoomEventFilter;\n    include_leave?: boolean;\n    state?: IStateFilter;\n    timeline?: IRoomEventFilter;\n    account_data?: IRoomEventFilter;\n}\n\n/**\n * Filter section used for requesting a set of extended profile fields that will be sent down the sync stream.\n */\ninterface ProfileFieldsFilter {\n    ids: string[];\n}\n\n/* eslint-enable camelcase */\n\nexport class Filter {\n    public static LAZY_LOADING_MESSAGES_FILTER = {\n        lazy_load_members: true,\n    };\n\n    /**\n     * Create a filter from existing data.\n     */\n    public static fromJson(userId: string | undefined | null, filterId: string, jsonObj: IFilterDefinition): Filter {\n        const filter = new Filter(userId, filterId);\n        filter.setDefinition(jsonObj);\n        return filter;\n    }\n\n    private definition: IFilterDefinition = {};\n    private roomFilter?: FilterComponent;\n    private roomTimelineFilter?: FilterComponent;\n\n    /**\n     * Construct a new Filter.\n     * @param userId - The user ID for this filter.\n     * @param filterId - The filter ID if known.\n     */\n    public constructor(\n        public readonly userId: string | undefined | null,\n        public filterId?: string,\n    ) {}\n\n    /**\n     * Get the ID of this filter on your homeserver (if known)\n     * @returns The filter ID\n     */\n    public getFilterId(): string | undefined {\n        return this.filterId;\n    }\n\n    /**\n     * Get the JSON body of the filter.\n     * @returns The filter definition\n     */\n    public getDefinition(): IFilterDefinition {\n        return this.definition;\n    }\n\n    /**\n     * Set the JSON body of the filter\n     * @param definition - The filter definition\n     */\n    public setDefinition(definition: IFilterDefinition): void {\n        this.definition = definition;\n\n        // This is all ported from synapse's FilterCollection()\n\n        // definitions look something like:\n        // {\n        //   \"room\": {\n        //     \"rooms\": [\"!abcde:example.com\"],\n        //     \"not_rooms\": [\"!123456:example.com\"],\n        //     \"state\": {\n        //       \"types\": [\"m.room.*\"],\n        //       \"not_rooms\": [\"!726s6s6q:example.com\"],\n        //       \"lazy_load_members\": true,\n        //     },\n        //     \"timeline\": {\n        //       \"limit\": 10,\n        //       \"types\": [\"m.room.message\"],\n        //       \"not_rooms\": [\"!726s6s6q:example.com\"],\n        //       \"not_senders\": [\"@spam:example.com\"]\n        //       \"contains_url\": true\n        //     },\n        //     \"ephemeral\": {\n        //       \"types\": [\"m.receipt\", \"m.typing\"],\n        //       \"not_rooms\": [\"!726s6s6q:example.com\"],\n        //       \"not_senders\": [\"@spam:example.com\"]\n        //     }\n        //   },\n        //   \"presence\": {\n        //     \"types\": [\"m.presence\"],\n        //     \"not_senders\": [\"@alice:example.com\"]\n        //   },\n        //   \"event_format\": \"client\",\n        //   \"event_fields\": [\"type\", \"content\", \"sender\"]\n        // }\n\n        const roomFilterJson = definition.room;\n\n        // consider the top level rooms/not_rooms filter\n        const roomFilterFields: IRoomFilter = {};\n        if (roomFilterJson) {\n            if (roomFilterJson.rooms) {\n                roomFilterFields.rooms = roomFilterJson.rooms;\n            }\n            if (roomFilterJson.rooms) {\n                roomFilterFields.not_rooms = roomFilterJson.not_rooms;\n            }\n        }\n\n        this.roomFilter = new FilterComponent(roomFilterFields, this.userId);\n        this.roomTimelineFilter = new FilterComponent(roomFilterJson?.timeline || {}, this.userId);\n\n        // don't bother porting this from synapse yet:\n        // this._room_state_filter =\n        //     new FilterComponent(roomFilterJson.state || {});\n        // this._room_ephemeral_filter =\n        //     new FilterComponent(roomFilterJson.ephemeral || {});\n        // this._room_account_data_filter =\n        //     new FilterComponent(roomFilterJson.account_data || {});\n        // this._presence_filter =\n        //     new FilterComponent(definition.presence || {});\n        // this._account_data_filter =\n        //     new FilterComponent(definition.account_data || {});\n    }\n\n    /**\n     * Get the room.timeline filter component of the filter\n     * @returns room timeline filter component\n     */\n    public getRoomTimelineFilterComponent(): FilterComponent | undefined {\n        return this.roomTimelineFilter;\n    }\n\n    /**\n     * Filter the list of events based on whether they are allowed in a timeline\n     * based on this filter\n     * @param events -  the list of events being filtered\n     * @returns the list of events which match the filter\n     */\n    public filterRoomTimeline(events: MatrixEvent[]): MatrixEvent[] {\n        if (this.roomFilter) {\n            events = this.roomFilter.filter(events);\n        }\n        if (this.roomTimelineFilter) {\n            events = this.roomTimelineFilter.filter(events);\n        }\n        return events;\n    }\n\n    /**\n     * Set the max number of events to return for each room's timeline.\n     * @param limit - The max number of events to return for each room.\n     */\n    public setTimelineLimit(limit: number): void {\n        setProp(this.definition, \"room.timeline.limit\", limit);\n    }\n\n    /**\n     * Enable threads unread notification\n     */\n    public setUnreadThreadNotifications(enabled: boolean): void {\n        this.definition = {\n            ...this.definition,\n            room: {\n                ...this.definition?.room,\n                timeline: {\n                    ...this.definition?.room?.timeline,\n                    [UNREAD_THREAD_NOTIFICATIONS.name]: enabled,\n                },\n            },\n        };\n    }\n\n    public setLazyLoadMembers(enabled: boolean): void {\n        setProp(this.definition, \"room.state.lazy_load_members\", enabled);\n    }\n\n    /**\n     * Control whether left rooms should be included in responses.\n     * @param includeLeave - True to make rooms the user has left appear\n     * in responses.\n     */\n    public setIncludeLeaveRooms(includeLeave: boolean): void {\n        setProp(this.definition, \"room.include_leave\", includeLeave);\n    }\n\n    /**\n     * Set the list of fields to be included in the profile information sent down the sync stream.\n     * @param ids The field IDs to sync.\n     * @param stable Whether to use the stable or unstable versions of this filter.\n     * @experimental\n     */\n    public setUnstableMSC4429SyncUserProfiles(ids: string[], stable: boolean): void {\n        const field = stable\n            ? profileFieldsFilterName.name\n            : (profileFieldsFilterName.unstable ?? profileFieldsFilterName.name);\n        this.definition[field] = { ids };\n    }\n}\n","/*\nCopyright 2022 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { FetchHttpApi } from \"./fetch.ts\";\nimport { type FileType, type IHttpOpts, type Upload, type UploadOpts, type UploadResponse } from \"./interface.ts\";\nimport { MediaPrefix } from \"./prefix.ts\";\nimport { type QueryDict, removeElement } from \"../utils.ts\";\nimport * as callbacks from \"../realtime-callbacks.ts\";\nimport { Method } from \"./method.ts\";\nimport { ConnectionError } from \"./errors.ts\";\nimport { parseErrorResponse } from \"./utils.ts\";\n\nexport * from \"./interface.ts\";\nexport * from \"./prefix.ts\";\nexport * from \"./errors.ts\";\nexport * from \"./method.ts\";\nexport * from \"./utils.ts\";\n\nexport class MatrixHttpApi<O extends IHttpOpts> extends FetchHttpApi<O> {\n    private uploads: Upload[] = [];\n\n    /**\n     * Upload content to the homeserver\n     *\n     * @param file - The object to upload. On a browser, something that\n     *   can be sent to XMLHttpRequest.send (typically a File).  Under node.js,\n     *   a Buffer, String or ReadStream.\n     *\n     * @param opts - options object\n     *\n     * @returns Promise which resolves to response object, or rejects with an error (usually a MatrixError).\n     * @throws May throw a `MatrixSafetyError` if content is deemed unsafe.\n     * @see MatrixSafetyError\n     */\n    public uploadContent(file: FileType, opts: UploadOpts = {}): Promise<UploadResponse> {\n        const includeFilename = opts.includeFilename ?? true;\n        const abortController = opts.abortController ?? new AbortController();\n\n        // If the file doesn't have a mime type, use a default since the HS errors if we don't supply one.\n        const contentType = (opts.type ?? (file as File).type) || \"application/octet-stream\";\n        const fileName = opts.name ?? (file as File).name;\n\n        const upload = {\n            loaded: 0,\n            total: 0,\n            abortController,\n        } as Upload;\n        const uploadResolvers = Promise.withResolvers<UploadResponse>();\n\n        if (globalThis.XMLHttpRequest) {\n            const xhr = new globalThis.XMLHttpRequest();\n\n            const timeoutFn = function (): void {\n                xhr.abort();\n                uploadResolvers.reject(new Error(\"Timeout\"));\n            };\n\n            // set an initial timeout of 30s; we'll advance it each time we get a progress notification\n            let timeoutTimer = callbacks.setTimeout(timeoutFn, 30000);\n\n            xhr.onreadystatechange = function (): void {\n                switch (xhr.readyState) {\n                    case globalThis.XMLHttpRequest.DONE:\n                        callbacks.clearTimeout(timeoutTimer);\n                        try {\n                            if (xhr.status === 0) {\n                                throw new DOMException(xhr.statusText, \"AbortError\"); // mimic fetch API\n                            }\n                            if (!xhr.responseText) {\n                                throw new Error(\"No response body.\");\n                            }\n\n                            if (xhr.status >= 400) {\n                                uploadResolvers.reject(parseErrorResponse(xhr, xhr.responseText));\n                            } else {\n                                uploadResolvers.resolve(JSON.parse(xhr.responseText));\n                            }\n                        } catch (err) {\n                            if ((<Error>err).name === \"AbortError\") {\n                                uploadResolvers.reject(err);\n                                return;\n                            }\n                            uploadResolvers.reject(new ConnectionError(\"request failed\", <Error>err));\n                        }\n                        break;\n                }\n            };\n\n            xhr.upload.onprogress = (ev: ProgressEvent): void => {\n                callbacks.clearTimeout(timeoutTimer);\n                upload.loaded = ev.loaded;\n                upload.total = ev.total;\n                timeoutTimer = callbacks.setTimeout(timeoutFn, 30000);\n                opts.progressHandler?.({\n                    loaded: ev.loaded,\n                    total: ev.total,\n                });\n            };\n\n            const url = this.getUrl(\"/upload\", undefined, MediaPrefix.V3);\n\n            if (includeFilename && fileName) {\n                url.searchParams.set(\"filename\", encodeURIComponent(fileName));\n            }\n\n            if (!this.opts.useAuthorizationHeader && this.opts.accessToken) {\n                url.searchParams.set(\"access_token\", encodeURIComponent(this.opts.accessToken));\n            }\n\n            xhr.open(Method.Post, url.href);\n            if (this.opts.useAuthorizationHeader && this.opts.accessToken) {\n                xhr.setRequestHeader(\"Authorization\", \"Bearer \" + this.opts.accessToken);\n            }\n            xhr.setRequestHeader(\"Content-Type\", contentType);\n            xhr.send(file);\n\n            abortController.signal.addEventListener(\"abort\", () => {\n                xhr.abort();\n            });\n        } else {\n            const queryParams: QueryDict = {};\n            if (includeFilename && fileName) {\n                queryParams.filename = fileName;\n            }\n\n            const headers: Record<string, string> = { \"Content-Type\": contentType };\n\n            this.authedRequest<UploadResponse>(Method.Post, \"/upload\", queryParams, file, {\n                prefix: MediaPrefix.V3,\n                headers,\n                abortSignal: abortController.signal,\n            }).then(uploadResolvers.resolve, uploadResolvers.reject);\n        }\n\n        // remove the upload from the list on completion\n        upload.promise = uploadResolvers.promise.finally(() => {\n            removeElement(this.uploads, (elem) => elem === upload);\n        });\n        abortController.signal.addEventListener(\"abort\", () => {\n            removeElement(this.uploads, (elem) => elem === upload);\n            uploadResolvers.reject(new DOMException(\"Aborted\", \"AbortError\"));\n        });\n        this.uploads.push(upload);\n        return upload.promise;\n    }\n\n    public cancelUpload(promise: Promise<UploadResponse>): boolean {\n        const upload = this.uploads.find((u) => u.promise === promise);\n        if (upload) {\n            upload.abortController.abort();\n            return true;\n        }\n        return false;\n    }\n\n    public getCurrentUploads(): Upload[] {\n        return this.uploads;\n    }\n}\n","/*\nCopyright 2017 - 2021 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { LocalIndexedDBStoreBackend } from \"./indexeddb-local-backend.ts\";\nimport { logger } from \"../logger.ts\";\n\ninterface ICmd {\n    command: string;\n    seq: number;\n    args: any[];\n}\n\n/**\n * This class lives in the webworker and drives a LocalIndexedDBStoreBackend\n * controlled by messages from the main process.\n *\n * @example\n * It should be instantiated by a web worker script provided by the application\n * in a script, for example:\n * ```\n * import {IndexedDBStoreWorker} from 'matrix-js-sdk/lib/indexeddb-worker.js';\n * const remoteWorker = new IndexedDBStoreWorker(postMessage);\n * onmessage = remoteWorker.onMessage;\n * ```\n *\n * Note that it is advisable to import this class by referencing the file directly to\n * avoid a dependency on the whole js-sdk.\n *\n */\nexport class IndexedDBStoreWorker {\n    private backend?: LocalIndexedDBStoreBackend;\n\n    /**\n     * @param postMessage - The web worker postMessage function that\n     * should be used to communicate back to the main script.\n     */\n    public constructor(private readonly postMessage: InstanceType<typeof Worker>[\"postMessage\"]) {}\n\n    private onClose = (): void => {\n        this.postMessage.call(null, {\n            command: \"closed\",\n        });\n    };\n\n    /**\n     * Passes a message event from the main script into the class. This method\n     * can be directly assigned to the web worker `onmessage` variable.\n     *\n     * @param ev - The message event\n     */\n    public onMessage = (ev: MessageEvent): void => {\n        const msg: ICmd = ev.data;\n        let prom: Promise<any> | undefined;\n\n        switch (msg.command) {\n            case \"setupWorker\":\n                // this is the 'indexedDB' global (where global != window\n                // because it's a web worker and there is no window).\n                this.backend = new LocalIndexedDBStoreBackend(indexedDB, msg.args[0]);\n                prom = Promise.resolve();\n                break;\n            case \"connect\":\n                prom = this.backend?.connect(this.onClose);\n                break;\n            case \"isNewlyCreated\":\n                prom = this.backend?.isNewlyCreated();\n                break;\n            case \"clearDatabase\":\n                prom = this.backend?.clearDatabase();\n                break;\n            case \"getSavedSync\":\n                prom = this.backend?.getSavedSync(false);\n                break;\n            case \"setSyncData\":\n                prom = this.backend?.setSyncData(msg.args[0]);\n                break;\n            case \"syncToDatabase\":\n                prom = this.backend?.syncToDatabase(msg.args[0]);\n                break;\n            case \"getUserPresenceEvents\":\n                prom = this.backend?.getUserPresenceEvents();\n                break;\n            case \"getNextBatchToken\":\n                prom = this.backend?.getNextBatchToken();\n                break;\n            case \"getOutOfBandMembers\":\n                prom = this.backend?.getOutOfBandMembers(msg.args[0]);\n                break;\n            case \"clearOutOfBandMembers\":\n                prom = this.backend?.clearOutOfBandMembers(msg.args[0]);\n                break;\n            case \"setOutOfBandMembers\":\n                prom = this.backend?.setOutOfBandMembers(msg.args[0], msg.args[1]);\n                break;\n            case \"getClientOptions\":\n                prom = this.backend?.getClientOptions();\n                break;\n            case \"storeClientOptions\":\n                prom = this.backend?.storeClientOptions(msg.args[0]);\n                break;\n            case \"saveToDeviceBatches\":\n                prom = this.backend?.saveToDeviceBatches(msg.args[0]);\n                break;\n            case \"getOldestToDeviceBatch\":\n                prom = this.backend?.getOldestToDeviceBatch();\n                break;\n            case \"removeToDeviceBatch\":\n                prom = this.backend?.removeToDeviceBatch(msg.args[0]);\n                break;\n            case \"getUserProfile\":\n                prom = this.backend?.getUserProfile(msg.args[0]);\n                break;\n            case \"storeUserProfiles\":\n                prom = this.backend?.storeUserProfiles(msg.args[0]);\n                break;\n            case \"removeUserProfiles\":\n                prom = this.backend?.removeUserProfiles(msg.args[0]);\n                break;\n        }\n\n        if (prom === undefined) {\n            this.postMessage({\n                command: \"cmd_fail\",\n                seq: msg.seq,\n                // Can't be an Error because they're not structured cloneable\n                error: \"Unrecognised command\",\n            });\n            return;\n        }\n\n        prom.then(\n            (ret) => {\n                this.postMessage.call(null, {\n                    command: \"cmd_success\",\n                    seq: msg.seq,\n                    result: ret,\n                });\n            },\n            (err) => {\n                logger.error(\"Error running command: \" + msg.command, err);\n                this.postMessage.call(null, {\n                    command: \"cmd_fail\",\n                    seq: msg.seq,\n                    // Just send a string because Error objects aren't cloneable\n                    error: {\n                        message: err.message,\n                        name: err.name,\n                    },\n                });\n            },\n        );\n    };\n}\n","/*\nCopyright 2018 André Jaenisch\nCopyright 2019-2025 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport loglevel from \"loglevel\";\n\n/** Backwards-compatibility hack to expose `log` to applications that might still be relying on it. */\ninterface LoggerWithLogMethod extends Logger {\n    /**\n     * Output debug message to the logger.\n     *\n     * @param msg - Data to log.\n     *\n     * @deprecated prefer {@link Logger.debug}.\n     */\n    log(...msg: any[]): void;\n}\n\n/** Logger interface used within the js-sdk codebase */\nexport interface Logger extends BaseLogger {\n    /**\n     * Create a child logger.\n     *\n     * This child will use the `methodFactory` of the parent, so any log extensions applied to the parent\n     * at the time of calling `getChild` will be applied to the child as well.\n     * It will NOT apply changes to the parent's `methodFactory` after the child was created.\n     * Those changes need to be applied to the child manually.\n     *\n     * @param namespace - name to add to the current logger to generate the child. Some implementations of `Logger`\n     *    use this as a prefix; others use a different mechanism.\n     */\n    getChild(namespace: string): Logger;\n}\n\n/** The basic interface for a logger which doesn't support children */\nexport interface BaseLogger {\n    /**\n     * Output trace message to the logger, with stack trace.\n     *\n     * @param msg - Data to log.\n     */\n    trace(this: void, ...msg: any[]): void;\n\n    /**\n     * Output debug message to the logger.\n     *\n     * @param msg - Data to log.\n     */\n    debug(this: void, ...msg: any[]): void;\n\n    /**\n     * Output info message to the logger.\n     *\n     * @param msg - Data to log.\n     */\n    info(this: void, ...msg: any[]): void;\n\n    /**\n     * Output warn message to the logger.\n     *\n     * @param msg - Data to log.\n     */\n    warn(this: void, ...msg: any[]): void;\n\n    /**\n     * Output error message to the logger.\n     *\n     * @param msg - Data to log.\n     */\n    error(this: void, ...msg: any[]): void;\n}\n\n// This is to demonstrate, that you can use any namespace you want.\n// Namespaces allow you to turn on/off the logging for specific parts of the\n// application.\n// An idea would be to control this via an environment variable (on Node.js).\n// See https://www.npmjs.com/package/debug to see how this could be implemented\n// Part of #332 is introducing a logging library in the first place.\nconst DEFAULT_NAMESPACE = \"matrix\";\n\n// because rageshakes in react-sdk hijack the console log, also at module load time,\n// initializing the logger here races with the initialization of rageshakes.\n// to avoid the issue, we override the methodFactory of loglevel that binds to the\n// console methods at initialization time by a factory that looks up the console methods\n// when logging so we always get the current value of console methods.\nloglevel.methodFactory = function (methodName, logLevel, loggerName) {\n    return function (this: PrefixedLogger, ...args): void {\n        /* eslint-disable @typescript-eslint/no-invalid-this */\n        if (this.prefix) {\n            args.unshift(this.prefix);\n        }\n        /* eslint-enable @typescript-eslint/no-invalid-this */\n        const supportedByConsole =\n            methodName === \"error\" ||\n            methodName === \"warn\" ||\n            methodName === \"trace\" ||\n            methodName === \"info\" ||\n            methodName === \"debug\";\n        /* eslint-disable no-console */\n        if (supportedByConsole) {\n            return console[methodName](...args);\n        } else {\n            return console.log(...args);\n        }\n        /* eslint-enable no-console */\n    };\n};\n\n/**\n * Implementation of {@link Logger} based on `loglevel`.\n */\ninterface PrefixedLogger extends loglevel.Logger, LoggerWithLogMethod {\n    prefix?: string;\n}\n\n/**\n * Internal utility function: gets a {@link Logger} based on `loglevel`.\n *\n * Child loggers produced by {@link Logger.getChild} add the name of the child logger as a prefix on each log line.\n *\n * @param prefix Prefix to add to each logged line. If undefined, no prefix will be added.\n */\nfunction getPrefixedLogger(prefix?: string): PrefixedLogger {\n    const loggerName = DEFAULT_NAMESPACE + (prefix === undefined ? \"\" : `-${prefix}`);\n    const prefixLogger = loglevel.getLogger(loggerName) as PrefixedLogger;\n\n    if (prefixLogger.getChild === undefined) {\n        // This is a new loglevel Logger which has not been turned into a PrefixedLogger yet.\n        prefixLogger.prefix = prefix;\n        prefixLogger.getChild = (childPrefix): Logger => {\n            // create the new child logger\n            const childLogger = getPrefixedLogger((prefix ?? \"\") + childPrefix);\n            // Assign the methodFactory from the parent logger.\n            // This is useful if we add extensions to the parent logger that modifies\n            // its methodFactory. (An example extension is: storing each log to a rageshake db)\n            childLogger.methodFactory = prefixLogger.methodFactory;\n            // Rebuild the child logger with the new methodFactory.\n            childLogger.rebuild();\n            return childLogger;\n        };\n        prefixLogger.setLevel(loglevel.levels.DEBUG, false);\n    }\n\n    return prefixLogger;\n}\n\n/**\n * Drop-in replacement for `console` using {@link https://www.npmjs.com/package/loglevel|loglevel}.\n * Can be tailored down to specific use cases if needed.\n *\n * @deprecated avoid the use of this unless you are the constructor of `MatrixClient`: you should be using the logger\n *    associated with `MatrixClient`.\n */\nexport const logger = getPrefixedLogger() as LoggerWithLogMethod;\n\n/**\n * A \"span\" for grouping related log lines together.\n *\n * The current implementation just adds the name at the start of each log line.\n *\n * This offers a lighter-weight alternative to 'child' loggers returned by {@link Logger#getChild}. In particular,\n * it's not possible to apply individual filters to the LogSpan such as setting the verbosity level. On the other hand,\n * no reference to the LogSpan is retained in the logging framework, so it is safe to make lots of them over the course\n * of an application's life and just drop references to them when the job is done.\n */\nexport class LogSpan implements BaseLogger {\n    private readonly name;\n\n    public constructor(\n        private readonly parent: BaseLogger,\n        name: string,\n    ) {\n        this.name = name + \":\";\n    }\n\n    public trace(...msg: any[]): void {\n        this.parent.trace(this.name, ...msg);\n    }\n\n    public debug(...msg: any[]): void {\n        this.parent.debug(this.name, ...msg);\n    }\n\n    public info(...msg: any[]): void {\n        this.parent.info(this.name, ...msg);\n    }\n\n    public warn(...msg: any[]): void {\n        this.parent.warn(this.name, ...msg);\n    }\n\n    public error(...msg: any[]): void {\n        this.parent.error(this.name, ...msg);\n    }\n}\n\n/**\n * A simplification of the `Debugger` type exposed by the `debug` library. We reimplement the bits we need here\n * to avoid a dependency on `debug`.\n */\ninterface Debugger {\n    (formatter: any, ...args: any[]): void;\n    extend: (namespace: string, delimiter?: string) => Debugger;\n}\n\n/**\n * A `Logger` instance, suitable for use in {@link ICreateClientOpts.logger}, which will write to the `debug` library.\n *\n * @example\n * ```js\n *     import debug from \"debug\";\n *\n *     const client = createClient({\n *         baseUrl: homeserverUrl,\n *         userId: userId,\n *         accessToken: \"akjgkrgjs\",\n *         deviceId: \"xzcvb\",\n *         logger: new DebugLogger(debug(`matrix-js-sdk:${userId}`)),\n *     });\n * ```\n */\nexport class DebugLogger implements Logger {\n    public constructor(private debugInstance: Debugger) {}\n\n    public trace(...msg: any[]): void {\n        this.debugWithPrefix(\"[TRACE]\", ...msg);\n    }\n\n    public debug(...msg: any[]): void {\n        this.debugWithPrefix(\"[DEBUG]\", ...msg);\n    }\n\n    public info(...msg: any[]): void {\n        this.debugWithPrefix(\"[INFO]\", ...msg);\n    }\n\n    public warn(...msg: any[]): void {\n        this.debugWithPrefix(\"[WARN]\", ...msg);\n    }\n\n    public error(...msg: any[]): void {\n        this.debugWithPrefix(\"[ERROR]\", ...msg);\n    }\n\n    public getChild(namespace: string): DebugLogger {\n        return new DebugLogger(this.debugInstance.extend(namespace));\n    }\n\n    private debugWithPrefix(prefix: string, ...msg: any[]): void {\n        let formatter: string;\n\n        // Convert the first argument to a string, so that we can safely add a prefix. This is much the same logic that\n        // `debug()` uses.\n        if (msg.length === 0) {\n            formatter = \"\";\n        } else if (msg[0] instanceof Error) {\n            const err = msg.shift();\n            formatter = err.stack || err.message;\n        } else if (typeof msg[0] == \"string\") {\n            formatter = msg.shift();\n        } else {\n            formatter = \"%O\";\n        }\n        this.debugInstance(prefix + \" \" + formatter, ...msg);\n    }\n}\n","/*\nCopyright 2023-2026 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { deepCompare } from \"../utils.ts\";\nimport { type RTCCallIntent, type Transport, type SlotDescription } from \"./types.ts\";\nimport { type MatrixEvent } from \"../models/event.ts\";\nimport { type Logger, logger } from \"../logger.ts\";\nimport { computeSlotId, slotIdToDescription } from \"./utils.ts\";\nimport {\n    checkRtcMembershipData,\n    computeRtcIdentityRaw,\n    type RtcMembershipData,\n    checkSessionsMembershipData,\n    type SessionMembershipData,\n    MatrixRTCMembershipParseError,\n} from \"./membershipData/index.ts\";\nimport { EventType } from \"../@types/event.ts\";\n\n/**\n * The default duration in milliseconds that a membership is considered valid for.\n * Ordinarily the client responsible for the session will update the membership before it expires.\n * We use this duration as the fallback case where stale sessions are present for some reason.\n */\nexport const DEFAULT_EXPIRE_DURATION = 1000 * 60 * 60 * 4;\n\n/**\n * Describes the source event type that provided the membership data.\n */\nenum MembershipKind {\n    /**\n     * The modern MSC4143 format event.\n     */\n    RTC = \"rtc\",\n    /**\n     * The legacy call event type.\n     */\n    Session = \"session\",\n}\n\ntype MembershipData =\n    | { kind: MembershipKind.RTC; data: RtcMembershipData }\n    | { kind: MembershipKind.Session; data: SessionMembershipData };\n\ntype LimitedEvent = Pick<MatrixEvent, \"getId\" | \"getSender\" | \"getTs\" | \"getType\" | \"getContent\">;\n// TODO: Rename to RtcMembership once we removed the legacy SessionMembership is removed, to avoid confusion.\nexport class CallMembership {\n    /**\n     * Parse the membershipdata from a call membership event.\n     * @param matrixEvent The Matrix event to read.\n     * @returns MembershipData in either MembershipKind.RTC or MembershipKind.Session format.\n     * @throws If the content is neither format.\n     */\n    public static membershipDataFromMatrixEvent(matrixEvent: LimitedEvent): MembershipData {\n        const sender = matrixEvent.getSender();\n        const evType = matrixEvent.getType();\n        const data = matrixEvent.getContent();\n        if (sender === undefined) throw new Error(\"matrixEvent is missing sender field\");\n        try {\n            // Event types are strictly checked here.\n            if (evType === EventType.RTCMembership && checkRtcMembershipData(data, sender)) {\n                return { kind: MembershipKind.RTC, data };\n            } else if (evType === EventType.GroupCallMemberPrefix && checkSessionsMembershipData(data)) {\n                return { kind: MembershipKind.Session, data };\n            } else {\n                throw Error(`'${evType} is not a known call membership type`);\n            }\n        } catch (ex) {\n            if (ex instanceof MatrixRTCMembershipParseError) {\n                logger.debug(\"CallMembership.MatrixRTCMembershipParseError provided invalid data\", data);\n            }\n            throw ex;\n        }\n    }\n\n    /**\n     * Parse the contents of a MatrixEvent and create a CallMembership instance.\n     * @param matrixEvent The Matrix event to read.\n     */\n    public static async parseFromEvent(matrixEvent: LimitedEvent): Promise<CallMembership> {\n        const membershipData: MembershipData = this.membershipDataFromMatrixEvent(matrixEvent);\n        const rtcBackendIdentity =\n            membershipData.kind === MembershipKind.RTC\n                ? await computeRtcIdentityRaw(\n                      membershipData.data.member.user_id,\n                      membershipData.data.member.device_id,\n                      membershipData.data.member.id,\n                  )\n                : `${matrixEvent.getSender()}:${membershipData.data.device_id}`;\n        return new CallMembership(matrixEvent, membershipData, rtcBackendIdentity);\n    }\n\n    public static equal(a?: CallMembership, b?: CallMembership): boolean {\n        return deepCompare(a?.membershipData, b?.membershipData);\n    }\n\n    private logger: Logger;\n\n    /** The parsed data from the Matrix event.\n     * To access checked eventId and sender from the matrixEvent.\n     * Class construction will fail if these values cannot get obtained. */\n    private readonly matrixEventData: { eventId: string; sender: string };\n\n    /**\n     * Use `parseFromEvent`.\n     * Constructor should only be used by tests.\n     * @private\n     * @param matrixEvent\n     * @param membershipData\n     * @param rtcBackendIdentity\n     */\n    public constructor(\n        /** The Matrix event that this membership is based on */\n        private readonly matrixEvent: LimitedEvent,\n        private readonly membershipData: MembershipData,\n        public readonly rtcBackendIdentity: string,\n    ) {\n        const eventId = matrixEvent.getId();\n        const sender = matrixEvent.getSender();\n\n        if (eventId === undefined) throw new Error(\"parentEvent is missing eventId field\");\n        if (sender === undefined) throw new Error(\"parentEvent is missing sender field\");\n\n        this.logger = logger.getChild(`[CallMembership ${sender}:${this.deviceId}]`);\n        this.matrixEventData = { eventId, sender };\n    }\n\n    /** @deprecated use userId instead */\n    public get sender(): string {\n        return this.userId;\n    }\n\n    public get userId(): string {\n        const { kind, data } = this.membershipData;\n        switch (kind) {\n            case MembershipKind.RTC:\n                return data.member.user_id;\n            case MembershipKind.Session:\n            default:\n                return this.matrixEventData.sender;\n        }\n    }\n\n    public get eventId(): string {\n        return this.matrixEventData.eventId;\n    }\n\n    /**\n     * The ID of the MatrixRTC slot that this membership belongs to (format `{application}#{id}`).\n     * This is computed in case SessionMembershipData is used.\n     */\n    public get slotId(): string {\n        const { kind, data } = this.membershipData;\n        if (data.application === \"m.call\") {\n            switch (kind) {\n                case MembershipKind.RTC:\n                    return data.slot_id;\n                case MembershipKind.Session:\n                default: {\n                    const [application, id] = [data.application, data.call_id];\n\n                    // INFO_SLOT_ID_LEGACY_CASE  (search for all occurances of this INFO to get the full picture)\n                    // The spec got changed to use `\"ROOM\"` instead of `\"\"` empyt string for the implicit default call.\n                    // State events still are sent with `\"\"` however. To find other events that should end up in the same call,\n                    // we use the slotId.\n                    // Since the CallMembership is the public representation of a rtc.member event, we just pretend it is a\n                    // \"ROOM\" slotId/call_id.\n                    // This makes all the remote members work with just this simple trick.\n                    //\n                    // We of course now need to be careful when sending legacy events (state events)\n                    // They get a slotDescription containing \"ROOM\" since this is what we use starting at the time this comment\n                    // is commited.\n                    //\n                    // See the Other INFO_SLOT_ID_LEGACY_CASE comments to see where we revert back to \"\" just before sending the event.\n                    let compatibilityAdaptedId: string;\n                    if (id === \"\") {\n                        compatibilityAdaptedId = \"ROOM\";\n                        this.logger?.info(\"use slotId compat hack emptyString -> ROOM\");\n                    } else {\n                        compatibilityAdaptedId = id;\n                    }\n                    return computeSlotId({\n                        application,\n                        id: compatibilityAdaptedId,\n                    });\n                }\n            }\n        }\n\n        this.logger?.info(\"NOT using slotId compat hack emptyString -> ROOM\");\n        // This is what the function should look like for any other application that did not\n        // go through a `\"\"`=> `\"ROOM\"` rename\n        switch (kind) {\n            case MembershipKind.RTC:\n                return data.slot_id;\n            case MembershipKind.Session:\n            default:\n                return computeSlotId({ application: data.application, id: data.call_id });\n        }\n    }\n\n    public get deviceId(): string {\n        const { kind, data } = this.membershipData;\n        switch (kind) {\n            case MembershipKind.RTC:\n                return data.member.device_id;\n            case MembershipKind.Session:\n            default:\n                return data.device_id;\n        }\n    }\n\n    public get callIntent(): RTCCallIntent | undefined {\n        const intent = this.applicationData[\"m.call.intent\"];\n        if (typeof intent === \"string\") {\n            return intent;\n        }\n        this.logger.warn(\"RTC membership has invalid m.call.intent\");\n        return undefined;\n    }\n\n    /**\n     * Parsed `slot_id` (format `{application}#{id}`) into its components (application and id).\n     */\n    public get slotDescription(): SlotDescription {\n        const { kind, data } = this.membershipData;\n        if (kind === MembershipKind.RTC) {\n            const id = data.slot_id.slice(`${data.application.type}#`.length);\n            return { application: data.application.type, id };\n        }\n        return slotIdToDescription(this.slotId);\n    }\n\n    /**\n     * The application `type`.\n     * @deprecated Use @see applicationData\n     */\n    public get application(): string {\n        return this.applicationData.type;\n    }\n\n    /**\n     * Information about the application being used for the RTC session.\n     * May contain extra keys specific to the application.\n     */\n    public get applicationData(): { type: string; [key: string]: unknown } {\n        const { kind, data } = this.membershipData;\n        switch (kind) {\n            case MembershipKind.RTC:\n                return data.application;\n            case MembershipKind.Session:\n            default:\n                // SessionData does not have application data as such. We return specific\n                // properties in use by other getters in this class, for compatibility.\n                return { \"type\": data.application, \"m.call.intent\": data[\"m.call.intent\"] };\n        }\n    }\n\n    /** @deprecated scope is not used and will be removed in future versions. replaced by application specific types.*/\n    public get scope(): SessionMembershipData[\"scope\"] | undefined {\n        const { kind, data } = this.membershipData;\n        switch (kind) {\n            case MembershipKind.RTC:\n                return undefined;\n            case MembershipKind.Session:\n            default:\n                return data.scope;\n        }\n    }\n\n    /**\n     * This computes the membership ID for the membership.\n     * For the sticky event based rtcSessionData this is trivial it is `member.id`.\n     * This is not supposed to be used to identity on an rtc backend. This is just a nouance for\n     * a generated (sha256) anonymised identity. Only send `rtcBackendIdentity` to any rtc backend service.\n     *\n     * For the legacy sessionMemberEvents it is a bit more complex. Here we sometimes do not have this data\n     * in the event content and we expected the SFU and the client to use `${this.matrixEventData.sender}:${data.device_id}`.\n     *\n     * So if there is no membershipID we use the hard coded jwt id default (`${this.matrixEventData.sender}:${data.device_id}`)\n     * value (used until version 0.16.0)\n     *\n     * It is also possible for a session event to set a custom membershipID. in that case this will be used.\n     */\n    public get memberId(): string {\n        // the createdTs behaves equivalent to the membershipID.\n        // we only need the field for the legacy member events where we needed to update them\n        // synapse ignores sending state events if they have the same content.\n        const { kind, data } = this.membershipData;\n        switch (kind) {\n            case \"rtc\":\n                return data.member.id;\n            case \"session\":\n                return (\n                    // best case we have a client already publishing the right custom membershipId\n                    data.membershipID ??\n                    // alternativly we use the hard coded jwt id defuatl value (used until version 0.16.0)\n                    `${this.matrixEventData.sender}:${data.device_id}`\n                );\n            default:\n                throw Error(\"Not possible to get memberID without knowing the membership event kind\");\n        }\n    }\n\n    /**\n     * @deprecated renamed to `memberId`\n     */\n    public get membershipID(): string {\n        return this.memberId;\n    }\n\n    public createdTs(): number {\n        const { kind, data } = this.membershipData;\n        switch (kind) {\n            case MembershipKind.RTC:\n                // TODO we need to read the referenced (relation) event if available to get the real created_ts\n                return this.matrixEvent.getTs();\n            case MembershipKind.Session:\n            default:\n                return data.created_ts ?? this.matrixEvent.getTs();\n        }\n    }\n\n    /**\n     * Gets the absolute expiry timestamp of the membership.\n     * @returns The absolute expiry time of the membership as a unix timestamp in milliseconds or undefined if not applicable\n     */\n    public getAbsoluteExpiry(): number | undefined {\n        const { kind, data } = this.membershipData;\n        switch (kind) {\n            case MembershipKind.RTC:\n                return undefined;\n            case MembershipKind.Session:\n            default:\n                // TODO: calculate this from the MatrixRTCSession join configuration directly\n                return this.createdTs() + (data.expires ?? DEFAULT_EXPIRE_DURATION);\n        }\n    }\n\n    /**\n     * @returns The number of milliseconds until the membership expires or undefined if applicable\n     * @deprecated Not used by RTC events.\n     */\n    public getMsUntilExpiry(): number | undefined {\n        const { kind } = this.membershipData;\n        if (kind === MembershipKind.Session) {\n            const absExpiry = this.getAbsoluteExpiry();\n            if (absExpiry) {\n                // Assume that local clock is sufficiently in sync with other clocks in the distributed system.\n                // We used to try and adjust for the local clock being skewed, but there are cases where this is not accurate.\n                // The current implementation allows for the local clock to be -infinity to +MatrixRTCSession.MEMBERSHIP_EXPIRY_TIME/2\n                return absExpiry - Date.now();\n            }\n        }\n        return undefined;\n    }\n\n    /**\n     * @returns true if the membership has expired, otherwise false\n     */\n    public isExpired(): boolean {\n        const { kind } = this.membershipData;\n        switch (kind) {\n            case MembershipKind.RTC:\n                return false;\n            case MembershipKind.Session:\n            default:\n                return this.getMsUntilExpiry()! <= 0;\n        }\n    }\n\n    /**\n     * ## RTC Membership\n     * Gets the primary transport to use for this RTC membership (m.rtc.member).\n     * This will return the primary transport that is used by this call membership to publish their media.\n     * Directly relates to the `rtc_transports` field.\n     *\n     * ## Legacy session membership\n     * In case of a legacy session membership (m.call.member) this will return the selected transport where\n     * media is published. How this selection happens depends on the `focus_active` field of the session membership.\n     * If the `focus_selection` is `oldest_membership` this will return the transport of the oldest membership\n     * in the room (based on the `created_ts` field of the session membership).\n     * If the `focus_selection` is `multi_sfu` it will return the first transport of the `foci_preferred` list.\n     * (`multi_sfu` is equivalent to how `m.rtc.member` `rtc_transports` work).\n     * @param oldestMembership For backwards compatibility with session membership (legacy). Unused in case of RTC membership.\n     * Always required to make the consumer not care if it deals with RTC or session memberships.\n     * @returns The transport this membership uses to publish media or undefined if no transport is available.\n     */\n    public getTransport(oldestMembership: CallMembership): Transport | undefined {\n        const { kind, data } = this.membershipData;\n        switch (kind) {\n            case MembershipKind.RTC:\n                return data.rtc_transports[0];\n            case MembershipKind.Session:\n                switch (data.focus_active.focus_selection) {\n                    case \"oldest_membership\":\n                        if (CallMembership.equal(this, oldestMembership)) return data.foci_preferred[0];\n                        if (oldestMembership !== undefined) return oldestMembership.getTransport(oldestMembership);\n                        break;\n                    case \"multi_sfu\":\n                        return data.foci_preferred[0];\n                    default:\n                        // `focus_selection` not understood.\n                        return undefined;\n                }\n                break;\n            default:\n                return undefined;\n        }\n    }\n\n    /**\n     * The value of the `rtc_transports` field for RTC memberships (m.rtc.member).\n     * Or the value of the `foci_preferred` field for legacy session memberships (m.call.member).\n     */\n    public get transports(): Transport[] {\n        const { kind, data } = this.membershipData;\n        switch (kind) {\n            case MembershipKind.RTC:\n                return data.rtc_transports;\n            case MembershipKind.Session:\n            default:\n                return data.foci_preferred;\n        }\n    }\n}\n","/*\nCopyright 2023 - 2026 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type Logger, logger as rootLogger } from \"../logger.ts\";\nimport { TypedEventEmitter } from \"../models/typed-event-emitter.ts\";\nimport { EventTimeline } from \"../models/event-timeline.ts\";\nimport { type Room } from \"../models/room.ts\";\nimport { type MatrixClient } from \"../client.ts\";\nimport { EventType, RelationType } from \"../@types/event.ts\";\nimport { KnownMembership } from \"../@types/membership.ts\";\nimport { type ISendEventResponse } from \"../@types/requests.ts\";\nimport { CallMembership } from \"./CallMembership.ts\";\nimport { RoomStateEvent } from \"../models/room-state.ts\";\nimport { MembershipManager, StickyEventMembershipManager } from \"./MembershipManager.ts\";\nimport { type CallMembershipIdentityParts, type IEncryptionManager } from \"./EncryptionManager.ts\";\nimport { logDurationSync } from \"../utils.ts\";\nimport type {\n    Statistics,\n    RTCNotificationType,\n    Status,\n    IRTCNotificationContent,\n    RTCCallIntent,\n    Transport,\n    SlotDescription,\n} from \"./types.ts\";\nimport {\n    MembershipManagerEvent,\n    type MembershipManagerEventHandlerMap,\n    type IMembershipManager,\n} from \"./IMembershipManager.ts\";\nimport { RTCEncryptionManager } from \"./RTCEncryptionManager.ts\";\nimport { ToDeviceKeyTransport } from \"./ToDeviceKeyTransport.ts\";\nimport { TypedReEmitter } from \"../ReEmitter.ts\";\nimport { type IContent, type MatrixEvent } from \"../models/event.ts\";\nimport { RoomStickyEventsEvent, type RoomStickyEventsMap } from \"../models/room-sticky-events.ts\";\nimport { computeSlotId } from \"./utils.ts\";\n\n/**\n * Events emitted by MatrixRTCSession\n */\nexport enum MatrixRTCSessionEvent {\n    // A member joined, left, or updated a property of their membership.\n    MembershipsChanged = \"memberships_changed\",\n    // We joined or left the session: our own local idea of whether we are joined,\n    // separate from MembershipsChanged, ie. independent of whether our member event\n    // has successfully gone through.\n    JoinStateChanged = \"join_state_changed\",\n    // The key used to encrypt media has changed\n    EncryptionKeyChanged = \"encryption_key_changed\",\n    /** The membership manager had to shut down caused by an unrecoverable error */\n    MembershipManagerError = \"membership_manager_error\",\n    /** The RTCSession did send a call notification caused by joining the call as the first member */\n    DidSendCallNotification = \"did_send_call_notification\",\n}\n\nexport type MatrixRTCSessionEventHandlerMap = {\n    [MatrixRTCSessionEvent.MembershipsChanged]: (\n        oldMemberships: CallMembership[],\n        newMemberships: CallMembership[],\n    ) => void;\n    [MatrixRTCSessionEvent.JoinStateChanged]: (isJoined: boolean) => void;\n    [MatrixRTCSessionEvent.EncryptionKeyChanged]: (\n        key: Uint8Array<ArrayBuffer>,\n        encryptionKeyIndex: number,\n        membership: CallMembershipIdentityParts,\n        rtcBackendIdentity: string,\n    ) => void;\n    [MatrixRTCSessionEvent.MembershipManagerError]: (error: unknown) => void;\n    [MatrixRTCSessionEvent.DidSendCallNotification]: (\n        notificationContentNew: { event_id: string } & IRTCNotificationContent,\n    ) => void;\n};\n\nexport interface SessionConfig {\n    /**\n     * What kind of notification to send when starting the session.\n     * @default `undefined` (no notification)\n     */\n    notificationType?: RTCNotificationType;\n\n    /**\n     * Determines the kind of call this will be.\n     */\n    callIntent?: RTCCallIntent;\n}\n\n// The names follow these principles:\n// - we use the technical term delay if the option is related to delayed events.\n// - we use delayedLeaveEvent if the option is related to the delayed leave event.\n// - we use membershipEvent if the option is related to the rtc member state event.\n// - we use the technical term expiry if the option is related to the expiry field of the membership state event.\n// - we use a `Ms` postfix if the option is a duration to avoid using words like:\n//   `time`, `duration`, `delay`, `timeout`... that might be mistaken/confused with technical terms.\nexport interface MembershipConfig {\n    /**\n     * The timeout (in milliseconds) after we joined the call, that our membership should expire\n     * unless we have explicitly updated it.\n     *\n     * This is what goes into the m.rtc.member event expiry field and is typically set to a number of hours.\n     */\n    membershipEventExpiryMs?: number;\n\n    /**\n     * The time in (in milliseconds) which the manager will prematurely send the updated state event before the membership `expires` time to make sure it\n     * sends the updated state event early enough.\n     *\n     * A headroom of 1000ms and a `membershipExpiryTimeout` of 10000ms would result in the first membership event update after 9s and\n     * a membership event that would be considered expired after 10s.\n     *\n     * This value does not have an effect on the value of `SessionMembershipData.expires`.\n     */\n    membershipEventExpiryHeadroomMs?: number;\n\n    /**\n     * The timeout (in milliseconds) with which the deleayed leave event on the server is configured.\n     * After this time the server will set the event to the disconnected stat if it has not received a keep-alive from the client.\n     */\n    delayedLeaveEventDelayMs?: number;\n\n    /**\n     * The interval (in milliseconds) in which the client will send membership keep-alives to the server.\n     */\n    delayedLeaveEventRestartMs?: number;\n\n    /**\n     * The maximum number of retries that the manager will do for delayed event sending/updating and state event sending when a server rate limit has been hit.\n     */\n    maximumRateLimitRetryCount?: number;\n\n    /**\n     * The maximum number of retries that the manager will do for delayed event sending/updating and state event sending when a network error occurs.\n     */\n    maximumNetworkErrorRetryCount?: number;\n\n    /**\n     * The time (in milliseconds) after which we will retry a http request if it\n     * failed to send due to a network error. (send membership event, send delayed event, restart delayed event...)\n     */\n    networkErrorRetryMs?: number;\n\n    /**\n     * The time (in milliseconds) after which a we consider a delayed event restart http request to have failed.\n     * Setting this to a lower value will result in more frequent retries but also a higher chance of failiour.\n     *\n     * In the presence of network packet loss (hurting TCP connections), the custom delayedEventRestartLocalTimeoutMs\n     * helps by keeping more delayed event reset candidates in flight,\n     * improving the chances of a successful reset. (its is equivalent to the js-sdk `localTimeout` configuration,\n     * but only applies to calls to the `_unstable_restartScheduledDelayedEvent` endpoint\n     * or the `_unstable_updateDelayedEvent` endpoint with a body of `{action:\"restart\"}`.)\n     */\n    delayedLeaveEventRestartLocalTimeoutMs?: number;\n\n    /**\n     * Send membership using sticky events rather than state events.\n     * This also make the client use the new m.rtc.member MSC4354 event format. (instead of m.call.member)\n     *\n     * **WARNING**: This is an unstable feature and not all clients will support it.\n     */\n    unstableSendStickyEvents?: boolean;\n}\n\nexport interface EncryptionConfig {\n    /**\n     *  If true, generate and share a media key for this participant,\n     *  and emit MatrixRTCSessionEvent.EncryptionKeyChanged when\n     *  media keys for other participants become available.\n     */\n    manageMediaKeys?: boolean;\n    /**\n     * The minimum time (in milliseconds) between each attempt to send encryption key(s).\n     * e.g. if this is set to 1000, then we will send at most one key event every second.\n     * @deprecated - Not used by the new encryption manager.\n     */\n    updateEncryptionKeyThrottle?: number;\n\n    /**\n     * Sometimes it is necessary to rotate the encryption key after a membership update.\n     * For performance reasons we might not want to rotate the key immediately but allow future memberships to use the same key.\n     * If 5 people join in a row in less than 5 seconds, we don't want to rotate the key for each of them.\n     * If 5 people leave in a row in less than 5 seconds, we don't want to rotate the key for each of them.\n     * So we do share the key which was already used live for <5s to new joiners.\n     * This does result in a potential leak up to the configured time of call media.\n     * This has to be considered when choosing a value for this property.\n     */\n    keyRotationGracePeriodMs?: number;\n\n    /**\n     * The delay (in milliseconds) after a member leaves before we create and publish a new key, because people\n     * tend to leave calls at the same time.\n     * @deprecated - Not used by the new encryption manager.\n     */\n    makeKeyDelay?: number;\n    /**\n     * The delay (in milliseconds) between sending a new key and starting to encrypt with it. This\n     * gives others a chance to receive the new key to minimize the chance they get media they can't decrypt.\n     *\n     * The higher this value is, the better it is for existing members as they will have a smoother experience.\n     * But it impacts new joiners: They will always have to wait `useKeyDelay` before being able to decrypt the media\n     * (as it will be encrypted with the new key after the delay only), even if the key has already arrived before the delay.\n     */\n    useKeyDelay?: number;\n}\nexport type JoinSessionConfig = SessionConfig & MembershipConfig & EncryptionConfig;\n\ninterface SessionMembershipsForSlotOpts {\n    /**\n     * Listen for incoming sticky member events. If disabled, this session will\n     * ignore any incoming sticky events.\n     */\n    listenForStickyEvents: boolean;\n    /**\n     * Listen for incoming  member state events (legacy). If disabled, this session will\n     * ignore any incoming state events.\n     */\n    listenForMemberStateEvents: boolean;\n}\n\nconst DEFAULT_SESSION_MEMBERSHIPS_FOR_SLOT_OPTS: SessionMembershipsForSlotOpts = {\n    listenForStickyEvents: true,\n    listenForMemberStateEvents: true,\n};\n\n/**\n * A MatrixRTCSession manages the membership & properties of a MatrixRTC session.\n * This class doesn't deal with media at all, just membership & properties of a session.\n */\nexport class MatrixRTCSession extends TypedEventEmitter<\n    MatrixRTCSessionEvent | MembershipManagerEvent,\n    MatrixRTCSessionEventHandlerMap & MembershipManagerEventHandlerMap\n> {\n    private membershipManager?: IMembershipManager;\n    private encryptionManager?: IEncryptionManager;\n    private joinConfig?: SessionConfig;\n    private logger: Logger;\n\n    private pendingNotificationToSend: undefined | RTCNotificationType;\n    /**\n     * This timeout is responsible to track any expiration. We need to know when we have to start\n     * to ignore other call members. There is no callback for this. This timeout will always be configured to\n     * emit when the next membership expires.\n     */\n    private expiryTimeout?: ReturnType<typeof setTimeout>;\n\n    public memberships: CallMembership[] = [];\n\n    /**\n     * Resolves when the session has calculated the initial membership of the session.\n     */\n    public readonly initialMembershipCalculated: Promise<void>;\n    /**\n     * Does membership need to be recalculated? This is set to false upon\n     * recalculation.\n     */\n    private membershipNeedsRecalculation = false;\n\n    /**\n     * The statistics for this session.\n     */\n    public statistics: Statistics = {\n        counters: {\n            roomEventEncryptionKeysSent: 0,\n            roomEventEncryptionKeysReceived: 0,\n        },\n        totals: {\n            roomEventEncryptionKeysReceivedTotalAge: 0,\n        },\n    };\n\n    public get membershipStatus(): Status | undefined {\n        return this.membershipManager?.status;\n    }\n    public get probablyLeft(): boolean | undefined {\n        return this.membershipManager?.probablyLeft;\n    }\n    public get delayId(): string | undefined {\n        return this.membershipManager?.delayId;\n    }\n\n    /**\n     * The callId (sessionId) of the call.\n     *\n     * It can be undefined since the callId is only known once the first membership joins.\n     * The callId is the property that, per definition, groups memberships into one call.\n     * @deprecated use `slotId` instead.\n     */\n    public get callId(): string | undefined {\n        return this.slotDescription?.id;\n    }\n    /**\n     * The slotId of the call.\n     * `{application}#{appSpecificId}`\n     * It can be undefined since the slotId is only known once the first membership joins.\n     * The slotId is the property that, per definition, groups memberships into one call.\n     */\n    public get slotId(): string | undefined {\n        return computeSlotId(this.slotDescription);\n    }\n\n    /**\n     * Returns all the call memberships for a room that match the provided `sessionDescription`,\n     * oldest first.\n     *\n     * By default, this will return *both* sticky and member state events.\n     */\n    public static async sessionMembershipsForSlot(\n        room: Pick<Room, \"getLiveTimeline\" | \"roomId\" | \"hasMembershipState\" | \"_unstable_getStickyEvents\">,\n        slotDescription: SlotDescription,\n        // default both true this implied we combine sticky and state events for the final call state\n        // (prefer sticky events in case of a duplicate)\n        options: SessionMembershipsForSlotOpts = DEFAULT_SESSION_MEMBERSHIPS_FOR_SLOT_OPTS,\n    ): Promise<CallMembership[]> {\n        const logger = rootLogger.getChild(\n            `[MatrixRTCSession ${room.roomId} ${slotDescription.application}#${slotDescription.id}]`,\n        );\n        const callMemberEvents = collectMembersEvents(room, options, logger);\n\n        const callMemberships = await computeBackendIdentityAndVerifyMemberEvents(\n            room,\n            callMemberEvents,\n            slotDescription,\n            logger,\n        );\n\n        callMemberships.sort((a, b) => a.createdTs() - b.createdTs());\n        if (callMemberships.length > 1) {\n            logger.debug(\n                `Call memberships in room ${room.roomId}, in order: `,\n                callMemberships.map((m) => [m.createdTs(), m.userId]),\n            );\n        }\n\n        return callMemberships;\n    }\n\n    /**\n     * Return the MatrixRTC session for the room.\n     * This returned session can be used to find out if there are active sessions\n     * for the requested room and `slotDescription`.\n     */\n    public static sessionForSlot(\n        client: MatrixClient,\n        room: Room,\n        slotDescription: SlotDescription,\n        opts?: SessionMembershipsForSlotOpts,\n    ): MatrixRTCSession {\n        return new MatrixRTCSession(client, room, slotDescription, opts);\n    }\n\n    /**\n     * WARN: this can in theory only be a subset of the room with the properties required by\n     * this class.\n     * Outside of tests this most likely will be a full room, however.\n     * @deprecated Relying on a full Room object being available here is an anti-pattern. You should be tracking\n     * the room object in your own code and passing it in when needed.\n     */\n    public get room(): Room {\n        return this.roomSubset as Room;\n    }\n\n    /**\n     * This constructs a room session. When using MatrixRTC inside the js-sdk this is expected\n     * to be used with the MatrixRTCSessionManager exclusively.\n     *\n     * In cases where you don't use the js-sdk but build on top of another Matrix stack this class can be used standalone\n     * to manage a joined MatrixRTC session.\n     *\n     * @param client A subset of the {@link MatrixClient} that lets the session interact with the Matrix room.\n     * @param roomSubset The room this session is attached to. A subset of a js-sdk Room that the session needs.\n     * @param slotDescription The slot description is a virtual address where participants are allowed to meet.\n     * This session will only manage memberships that match this slot description.Sessions are distinct if any of\n     * those properties are distinct: `roomSubset.roomId`, `slotDescription.application`, `slotDescription.id`.\n     * @param calculateMembershipsOpts - Options to configure how memberships are calculated for this session.\n     */\n    public constructor(\n        private readonly client: Pick<\n            MatrixClient,\n            | \"getUserId\"\n            | \"getDeviceId\"\n            | \"sendEvent\"\n            | \"sendStateEvent\"\n            | \"_unstable_sendDelayedStateEvent\"\n            | \"_unstable_updateDelayedEvent\"\n            | \"_unstable_cancelScheduledDelayedEvent\"\n            | \"_unstable_restartScheduledDelayedEvent\"\n            | \"_unstable_sendScheduledDelayedEvent\"\n            | \"_unstable_sendStickyEvent\"\n            | \"_unstable_sendStickyDelayedEvent\"\n            | \"cancelPendingEvent\"\n            | \"encryptAndSendToDevice\"\n            | \"off\"\n            | \"on\"\n            | \"decryptEventIfNeeded\"\n        >,\n        private roomSubset: Pick<\n            Room,\n            | \"getLiveTimeline\"\n            | \"roomId\"\n            | \"getVersion\"\n            | \"hasMembershipState\"\n            | \"on\"\n            | \"off\"\n            | \"_unstable_getStickyEvents\"\n        >,\n\n        public readonly slotDescription: SlotDescription,\n        private readonly calculateMembershipsOpts?: SessionMembershipsForSlotOpts,\n    ) {\n        super();\n        this.logger = rootLogger.getChild(\n            `[MatrixRTCSession ${roomSubset.roomId} ${slotDescription.application}#${slotDescription.id}]`,\n        );\n\n        this.roomSubset.on(RoomStateEvent.Members, this.onRoomMemberUpdate);\n        this.roomSubset.on(RoomStickyEventsEvent.Update, this.onStickyEventUpdate);\n\n        this.initialMembershipCalculated = this.ensureRecalculateSessionMembers();\n        this.setExpiryTimer();\n    }\n    /*\n     * Returns true if we intend to be participating in the MatrixRTC session.\n     * This is determined by checking if the relativeExpiry has been set.\n     */\n    public isJoined(): boolean {\n        return this.membershipManager?.isJoined() ?? false;\n    }\n\n    /**\n     * Performs cleanup & removes timers for client shutdown\n     */\n    public async stop(): Promise<void> {\n        await this.membershipManager?.leave(1000);\n        if (this.expiryTimeout) {\n            clearTimeout(this.expiryTimeout);\n            this.expiryTimeout = undefined;\n        }\n\n        this.roomSubset.off(RoomStateEvent.Members, this.onRoomMemberUpdate);\n        this.roomSubset.off(RoomStickyEventsEvent.Update, this.onStickyEventUpdate);\n    }\n\n    private reEmitter = new TypedReEmitter<\n        MatrixRTCSessionEvent | MembershipManagerEvent,\n        MatrixRTCSessionEventHandlerMap & MembershipManagerEventHandlerMap\n    >(this);\n\n    /**\n     * Announces this user and device as joined to the MatrixRTC session,\n     * and continues to update the membership event to keep it valid until\n     * leaveRoomSession() is called\n     * This will not subscribe to updates: remember to call subscribe() separately if\n     * desired.\n     * This method will return immediately and the session will be joined in the background.\n     * @param ownMembershipIdentity the identity of the user and device joining the session.\n     * This will be put into the content.member.\n     * @param fociPreferred the list of preferred foci to use in the joined RTC membership event.\n     * If multiSfuFocus is set, this is only needed if this client wants to publish to multiple transports simultaneously.\n     * @param multiSfuFocus the active focus to use in the joined RTC membership event. Setting this implies the\n     * membership manager will operate in a multi-SFU connection mode. If `undefined`, an `oldest_membership`\n     * transport selection will be used instead.\n     * @param joinConfig - Additional configuration for the joined session.\n     */\n    public joinRTCSession(\n        ownMembershipIdentity: CallMembershipIdentityParts,\n        fociPreferred: Transport[],\n        multiSfuFocus?: Transport,\n        joinConfig?: JoinSessionConfig,\n    ): void {\n        if (this.isJoined()) {\n            this.logger.info(`Already joined to session in room ${this.roomSubset.roomId}: ignoring join call`);\n            return;\n        } else {\n            // Create MembershipManager and pass the RTCSession logger (with room id info)\n            this.membershipManager = joinConfig?.unstableSendStickyEvents\n                ? new StickyEventMembershipManager(\n                      joinConfig,\n                      this.roomSubset,\n                      this.client,\n                      this.slotDescription,\n                      ownMembershipIdentity.memberId,\n                      this.logger,\n                  )\n                : new MembershipManager(joinConfig, this.roomSubset, this.client, this.slotDescription, this.logger);\n\n            this.reEmitter.reEmit(this.membershipManager!, [\n                MembershipManagerEvent.ProbablyLeft,\n                MembershipManagerEvent.StatusChanged,\n                MembershipManagerEvent.DelayIdChanged,\n            ]);\n            // Create Encryption manager\n            const [room, client, statistics] = [this.roomSubset, this.client, this.statistics];\n            const transport = new ToDeviceKeyTransport(ownMembershipIdentity, room.roomId, client, statistics);\n            this.encryptionManager = new RTCEncryptionManager(\n                ownMembershipIdentity,\n                () => this.memberships,\n                transport,\n                (\n                    keyBin: Uint8Array<ArrayBuffer>,\n                    encryptionKeyIndex: number,\n                    membership: CallMembershipIdentityParts,\n                    rtcBackendIdentity: string,\n                ) => {\n                    this.emit(\n                        MatrixRTCSessionEvent.EncryptionKeyChanged,\n                        keyBin,\n                        encryptionKeyIndex,\n                        membership,\n                        rtcBackendIdentity,\n                    );\n                },\n                this.logger,\n            );\n        }\n\n        this.joinConfig = joinConfig;\n        this.pendingNotificationToSend = this.joinConfig?.notificationType;\n\n        // Join!\n        this.membershipManager!.join(fociPreferred, multiSfuFocus, (e) => {\n            this.logger.error(\"MembershipManager encountered an unrecoverable error: \", e);\n            this.emit(MatrixRTCSessionEvent.MembershipManagerError, e);\n            this.emit(MatrixRTCSessionEvent.JoinStateChanged, this.isJoined());\n        });\n        this.encryptionManager!.join(joinConfig);\n\n        this.emit(MatrixRTCSessionEvent.JoinStateChanged, true);\n    }\n\n    /**\n     *\n     * @param fociPreferred\n     * @param multiSfuFocus\n     * @param joinConfig\n     * @deprecated use the joinRTCSession method instead\n     */\n    public joinRoomSession(\n        fociPreferred: Transport[],\n        multiSfuFocus?: Transport,\n        joinConfig?: JoinSessionConfig,\n    ): void {\n        const [userId, deviceId] = [this.client.getUserId()!, this.client.getDeviceId()!];\n        // TODO this wants to become a UUID\n        const memberId = `${userId}:${deviceId}`;\n        this.joinRTCSession({ userId, deviceId, memberId }, fociPreferred, multiSfuFocus, joinConfig);\n    }\n\n    /**\n     * Announces this user and device as having left the MatrixRTC session\n     * and stops scheduled updates.\n     * This will not unsubscribe from updates: remember to call unsubscribe() separately if\n     * desired.\n     * The membership update required to leave the session will retry if it fails.\n     * Without network connection the promise will never resolve.\n     * A timeout can be provided so that there is a guarantee for the promise to resolve.\n     * @returns Whether the membership update was attempted and did not time out.\n     */\n    public async leaveRoomSession(timeout: number | undefined = undefined): Promise<boolean> {\n        if (!this.isJoined()) {\n            this.logger.info(`Not joined to session in room ${this.roomSubset.roomId}: ignoring leave call`);\n            return false;\n        }\n\n        this.logger.info(`Leaving call session in room ${this.roomSubset.roomId}`);\n\n        this.encryptionManager!.leave();\n\n        const leavePromise = this.membershipManager!.leave(timeout);\n        this.emit(MatrixRTCSessionEvent.JoinStateChanged, false);\n\n        return await leavePromise;\n    }\n    /**\n     * This returns the focus in use by the oldest membership.\n     * Do not use since this might be just the focus for the oldest membership. others might use a different focus.\n     * @deprecated use `member.getTransport(session.getOldestMembership())` instead for the specific member you want to get the focus for.\n     */\n    public getFocusInUse(): Transport | undefined {\n        const oldestMembership = this.getOldestMembership();\n        return oldestMembership?.getTransport(oldestMembership);\n    }\n\n    public getOldestMembership(): CallMembership | undefined {\n        return this.memberships[0];\n    }\n\n    /**\n     * Get the call intent for the current call, based on what members are advertising. If one or more\n     * members disagree on the current call intent, or nobody specifies one then `undefined` is returned.\n     *\n     * If all members that specify a call intent agree, that value is returned.\n     * @returns A call intent, or `undefined` if no consensus or not given.\n     */\n    public getConsensusCallIntent(): RTCCallIntent | undefined {\n        const getFirstCallIntent = this.memberships.find((m) => !!m.callIntent)?.callIntent;\n        if (!getFirstCallIntent) {\n            return undefined;\n        }\n        if (this.memberships.every((m) => !m.callIntent || m.callIntent === getFirstCallIntent)) {\n            return getFirstCallIntent;\n        }\n        return undefined;\n    }\n\n    public async updateCallIntent(callIntent: RTCCallIntent): Promise<void> {\n        const myMembership = this.membershipManager?.ownMembership;\n        if (!myMembership) {\n            throw Error(\"Not connected yet\");\n        }\n        await this.membershipManager?.updateCallIntent(callIntent);\n    }\n\n    /**\n     * Re-emit an EncryptionKeyChanged event for each tracked encryption key. This can be used to export\n     * the keys.\n     */\n    public reemitEncryptionKeys(): void {\n        this.encryptionManager?.getEncryptionKeys().forEach((keyRing, key) => {\n            keyRing.forEach((keyInfo) => {\n                this.emit(\n                    MatrixRTCSessionEvent.EncryptionKeyChanged,\n                    keyInfo.key,\n                    keyInfo.keyIndex,\n                    keyInfo.membership,\n                    keyInfo.rtcBackendIdentity,\n                );\n            });\n        });\n    }\n\n    /**\n     * Sets a timer for the soonest membership expiry\n     */\n    private setExpiryTimer(): void {\n        if (this.expiryTimeout) {\n            clearTimeout(this.expiryTimeout);\n            this.expiryTimeout = undefined;\n        }\n\n        let soonestExpiry;\n        for (const membership of this.memberships) {\n            const thisExpiry = membership.getMsUntilExpiry();\n            // If getMsUntilExpiry is undefined we have a MSC4143 (MatrixRTC) compliant event - it never expires\n            // but will be reliably resent on disconnect.\n            if (thisExpiry !== undefined && (soonestExpiry === undefined || thisExpiry < soonestExpiry)) {\n                soonestExpiry = thisExpiry;\n            }\n        }\n\n        if (soonestExpiry != undefined) {\n            this.expiryTimeout = setTimeout(() => void this.ensureRecalculateSessionMembers(), soonestExpiry);\n        }\n    }\n\n    /**\n     * Sends notification events to indiciate the call has started.\n     * Note: This does not return a promise, instead scheduling the notification events to be sent.\n     * @param parentEventId Event id linking to your RTC call membership event.\n     * @param notificationType The type of notification to send\n     * @param callIntent The type of call this is (e.g. \"audio\").\n     */\n    private sendCallNotify(\n        parentEventId: string,\n        notificationType: RTCNotificationType,\n        callIntent?: RTCCallIntent,\n    ): void {\n        const sendNotificationEvent = async (): Promise<{\n            response: ISendEventResponse;\n            content: IRTCNotificationContent;\n        }> => {\n            const content: IRTCNotificationContent = {\n                \"m.mentions\": { user_ids: [], room: true },\n                \"notification_type\": notificationType,\n                \"m.relates_to\": {\n                    event_id: parentEventId,\n                    rel_type: RelationType.Reference,\n                },\n                \"sender_ts\": Date.now(),\n                \"lifetime\": 30_000, // 30 seconds\n            };\n            if (callIntent) {\n                content[\"m.call.intent\"] = callIntent;\n            }\n            const response = await this.client.sendEvent(this.roomSubset.roomId, EventType.RTCNotification, content);\n            return { response, content };\n        };\n\n        void sendNotificationEvent()\n            .then((notification) => {\n                // Join event_id and origin event content\n                const newResult = { ...notification.response, ...notification.content };\n                this.emit(MatrixRTCSessionEvent.DidSendCallNotification, newResult);\n            })\n            .catch(([errorLegacy, errorNew]) =>\n                this.logger.error(\"Failed to send call notification\", errorLegacy, errorNew),\n            );\n    }\n\n    /**\n     * Call this when the Matrix room members have changed.\n     */\n    private readonly onRoomMemberUpdate = (): void => {\n        void this.ensureRecalculateSessionMembers();\n    };\n\n    /**\n     * Call this when a sticky event update has occured.\n     */\n    private readonly onStickyEventUpdate: RoomStickyEventsMap[RoomStickyEventsEvent.Update] = (\n        added,\n        updated,\n        removed,\n    ): void => {\n        if (\n            [...added, ...removed, ...updated.flatMap((v) => [v.current, v.previous])].some(\n                (e) => e.getType() === EventType.RTCMembership,\n            )\n        ) {\n            void this.ensureRecalculateSessionMembers();\n        }\n    };\n\n    /**\n     * Call this when something changed that may impacts the current MatrixRTC members in this session.\n     */\n    // We allow this name schema since this function should only be used for testing purposes.\n    // eslint-disable-next-line @typescript-eslint/naming-convention\n    public _onRTCSessionMemberUpdate = async (): Promise<void> => {\n        await this.recalculateSessionMembers();\n    };\n\n    // helper variables to make sure we do not have parallel running recalculations.\n    private recalculateSessionMembersPromise: Promise<void> = Promise.resolve();\n\n    /**\n     * Ensures that membership is recalculated when the state of the session may have changed.\n     * Also ensures that only one recalculation is made at a time.\n     * @returns A promise resolving when the state has been recalculated.\n     */\n    private ensureRecalculateSessionMembers(): Promise<void> {\n        if (this.membershipNeedsRecalculation) {\n            // We have already requested recalcuation, don't attempt a new one.\n            return this.recalculateSessionMembersPromise;\n        }\n        this.membershipNeedsRecalculation = true;\n        // Chain the recalculation.\n        this.recalculateSessionMembersPromise = this.recalculateSessionMembersPromise.then(\n            () => this.recalculateSessionMembers(),\n            () => this.recalculateSessionMembers(),\n        );\n        return this.recalculateSessionMembersPromise;\n    }\n\n    /**\n     * Call this when anything that could impact rtc memberships has changed: Room Members or RTC members.\n     *\n     * Examines the latest call memberships and handles any encryption key sending or rotation that is needed.\n     *\n     * This function should be called when the room members or call memberships might have changed.\n     */\n    private readonly recalculateSessionMembers = async (): Promise<void> => {\n        // Clear the flag.\n        this.membershipNeedsRecalculation = false;\n        const oldMemberships = this.memberships;\n\n        this.memberships = await MatrixRTCSession.sessionMembershipsForSlot(\n            this.room,\n            this.slotDescription,\n            this.calculateMembershipsOpts,\n        );\n\n        const changed =\n            oldMemberships.length != this.memberships.length ||\n            oldMemberships.some((m, i) => !CallMembership.equal(m, this.memberships[i]));\n\n        if (changed) {\n            this.logger.info(\n                `Memberships for call in room ${this.roomSubset.roomId} have changed: emitting (${this.memberships.length} members)`,\n            );\n            logDurationSync(this.logger, \"emit MatrixRTCSessionEvent.MembershipsChanged\", () => {\n                this.emit(MatrixRTCSessionEvent.MembershipsChanged, oldMemberships, this.memberships);\n            });\n\n            void this.membershipManager?.onRTCSessionMemberUpdate(this.memberships);\n            // The `ownMembership` will be set when calling `onRTCSessionMemberUpdate`.\n            const ownMembership = this.membershipManager?.ownMembership;\n            if (this.pendingNotificationToSend && ownMembership && oldMemberships.length === 0) {\n                // If we're the first member in the call, we're responsible for\n                // sending the notification event\n                if (ownMembership.eventId && this.joinConfig?.notificationType) {\n                    this.sendCallNotify(\n                        ownMembership.eventId,\n                        this.joinConfig.notificationType,\n                        ownMembership.callIntent,\n                    );\n                } else {\n                    this.logger.warn(\"Own membership eventId is undefined, cannot send call notification\");\n                }\n            }\n            // If anyone else joins the session it is no longer our responsibility to send the notification.\n            // (If we were the joiner we already did sent the notification in the block above.)\n            if (this.memberships.length > 0) this.pendingNotificationToSend = undefined;\n        } else {\n            this.logger.debug(`No membership changes detected for room ${this.roomSubset.roomId}`);\n        }\n        // This also needs to be done if `changed` = false\n        // A member might have updated their fingerprint (created_ts)\n        this.encryptionManager?.onMembershipsUpdate(oldMemberships);\n\n        this.setExpiryTimer();\n    };\n}\n\n/// Private helpers\nasync function computeBackendIdentityAndVerifyMemberEvents(\n    room: Pick<Room, \"hasMembershipState\">,\n    callMemberEvents: MatrixEvent[],\n    slotDescription: SlotDescription,\n    logger: Logger,\n): Promise<CallMembership[]> {\n    const callMemberships: CallMembership[] = [];\n\n    for (const memberEvent of callMemberEvents) {\n        const content = memberEvent.getContent();\n\n        // Quick filter to avoid unneeded processing of invalid events or left events.\n        if (!quickFilterNonRelevantContents(content, logger)) {\n            continue;\n        }\n\n        try {\n            const membership = await CallMembership.parseFromEvent(memberEvent);\n\n            if (isValidMembership(membership, room, slotDescription, logger)) {\n                callMemberships.push(membership);\n            }\n        } catch (e) {\n            logger.warn(\"Couldn't construct call membership: \", e);\n        }\n    }\n\n    return callMemberships;\n}\n\nfunction quickFilterNonRelevantContents(content: IContent, logger: Logger): boolean {\n    // Ignore sticky keys for the count\n    const eventKeysCount = Object.keys(content).filter((k) => k !== \"msc4354_sticky_key\").length;\n    // Don't even bother about empty events (saves us from costly type/\"key in\" checks in bigger rooms)\n    if (eventKeysCount === 0) return false;\n\n    // We first decide if it's a MSC4143 event (per device state key)\n    if (eventKeysCount > 1 && \"application\" in content) {\n        // We have a MSC4143 event membership event with a proper joined content\n        return true;\n    } else if (eventKeysCount === 1 && \"memberships\" in content) {\n        // Events used to have this format in the past, but are now deprecated.\n        // Given that state events ~cannot be deleted, there can be some remaining events in the room, just ignore them.\n        return false;\n    } else {\n        // Invalid or left content\n        return false;\n    }\n}\n\nfunction isValidMembership(\n    membership: CallMembership,\n    room: Pick<Room, \"hasMembershipState\">,\n    slotDescription: SlotDescription,\n    logger: Logger,\n): boolean {\n    if (membership.slotDescription.id !== slotDescription.id) {\n        logger.info(\n            `Ignoring membership of user ${membership.userId} for a different slot. Theirs: ${JSON.stringify(membership.slotDescription)}, Expected: ${JSON.stringify(slotDescription)}`,\n        );\n        return false;\n    }\n\n    if (membership.isExpired()) {\n        logger.info(`Ignoring expired device membership ${membership.userId}/${membership.deviceId}`);\n        return false;\n    }\n\n    if (!room.hasMembershipState(membership.userId ?? \"\", KnownMembership.Join)) {\n        logger.info(`Ignoring membership of user ${membership.userId} who is not in the room.`);\n        return false;\n    }\n\n    return true;\n}\n\n/**\n * Collects the raw member events from room state and sticky store.\n */\nfunction collectMembersEvents(\n    room: Pick<Room, \"getLiveTimeline\" | \"roomId\" | \"_unstable_getStickyEvents\">,\n    options: SessionMembershipsForSlotOpts,\n    logger: Logger,\n): MatrixEvent[] {\n    const { listenForStickyEvents, listenForMemberStateEvents } = options;\n    let callMemberEvents: MatrixEvent[] = [];\n    if (listenForStickyEvents) {\n        // prefill with sticky events\n        callMemberEvents = [...room._unstable_getStickyEvents()].filter((e) => e.getType() === EventType.RTCMembership);\n    }\n    if (listenForMemberStateEvents) {\n        const roomState = room.getLiveTimeline().getState(EventTimeline.FORWARDS);\n        if (!roomState) {\n            logger.warn(\"Couldn't get state for room \" + room.roomId + \"using empty membership array\");\n            return [];\n        }\n        const callMemberStateEvents = roomState.getStateEvents(EventType.GroupCallMemberPrefix);\n        callMemberEvents = callMemberEvents.concat(\n            callMemberStateEvents.filter(\n                (callMemberStateEvent) =>\n                    !callMemberEvents.some(\n                        // only care about state events which have keys which we have not yet seen in the sticky events.\n                        // TODO: I believe this can discard a joined state event if there is a matching left sticky event.\n                        (stickyEvent) =>\n                            stickyEvent.getContent().msc4354_sticky_key === callMemberStateEvent.getStateKey(),\n                    ),\n            ),\n        );\n    }\n    return callMemberEvents;\n}\n","/*\nCopyright 2023-2026 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type Logger } from \"../logger.ts\";\nimport { type MatrixClient, ClientEvent } from \"../client.ts\";\nimport { TypedEventEmitter } from \"../models/typed-event-emitter.ts\";\nimport { type Room } from \"../models/room.ts\";\nimport { RoomStateEvent } from \"../models/room-state.ts\";\nimport { type MatrixEvent } from \"../models/event.ts\";\nimport { MatrixRTCSession } from \"./MatrixRTCSession.ts\";\nimport { EventType } from \"../@types/event.ts\";\nimport { type SlotDescription } from \"./types.ts\";\nimport { computeSlotId } from \"./utils.ts\";\n\nexport enum MatrixRTCSessionManagerEvents {\n    // A member has joined the MatrixRTC session, creating an active session in a room where there wasn't previously\n    SessionStarted = \"session_started\",\n    // All participants have left a given MatrixRTC session.\n    SessionEnded = \"session_ended\",\n}\n\ntype EventHandlerMap = {\n    [MatrixRTCSessionManagerEvents.SessionStarted]: (roomId: string, session: MatrixRTCSession) => void;\n    [MatrixRTCSessionManagerEvents.SessionEnded]: (roomId: string, session: MatrixRTCSession) => void;\n};\n\n/**\n * Holds all active MatrixRTC session objects and creates new ones as events arrive.\n * One `MatrixRTCSessionManager` is required for each MatrixRTC sessionDescription (application, session id) that the client wants to support.\n * If no application type is specified in the constructor, the default is \"m.call\".\n *\n * This interface is UNSTABLE and may change without warning.\n */\nexport class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionManagerEvents, EventHandlerMap> {\n    // All the room-scoped sessions we know about. This will include any where the app\n    // has queried for the MatrixRTC sessions in a room, whether it's ever had any members\n    // or not). We keep a (lazily created) session object for every room to ensure that there\n    // is only ever one single room session object for any given room for the lifetime of the\n    // client: that way there can never be any code holding onto a stale object that is no\n    // longer the correct session object for the room.\n    private roomSessions = new Map<string, MatrixRTCSession>();\n\n    private readonly logger: Logger;\n\n    public constructor(\n        rootLogger: Logger,\n        private client: MatrixClient,\n        private readonly slotDescription: SlotDescription = { application: \"m.call\", id: \"ROOM\" }, // Default to the Matrix Call application\n    ) {\n        super();\n        this.logger = rootLogger.getChild(`[MatrixRTCSessionManager ${computeSlotId(slotDescription)}]`);\n    }\n\n    public start(): void {\n        // We shouldn't need to null-check here, but matrix-client.spec.ts mocks getRooms\n        // returning nothing, and breaks tests if you change it to return an empty array :'(\n        for (const room of this.client.getRooms() ?? []) {\n            const session = MatrixRTCSession.sessionForSlot(this.client, room, this.slotDescription);\n            if (session.memberships.length > 0) {\n                this.roomSessions.set(room.roomId, session);\n            }\n        }\n\n        this.client.on(ClientEvent.Room, this.onRoom);\n        this.client.on(ClientEvent.Event, this.onEvent);\n        this.client.on(RoomStateEvent.Events, this.onRoomState);\n    }\n\n    public stop(): void {\n        for (const sess of this.roomSessions.values()) {\n            void sess.stop();\n        }\n        this.roomSessions.clear();\n\n        this.client.off(ClientEvent.Room, this.onRoom);\n        this.client.off(ClientEvent.Event, this.onEvent);\n        this.client.off(RoomStateEvent.Events, this.onRoomState);\n    }\n\n    /**\n     * Gets the main MatrixRTC session for a room, or undefined if there is\n     * no current session\n     */\n    public getActiveRoomSession(room: Room): MatrixRTCSession | undefined {\n        return this.roomSessions.get(room.roomId)!;\n    }\n\n    /**\n     * Gets the main MatrixRTC session for a room, returning an empty session\n     * if no members are currently participating\n     */\n    public getRoomSession(room: Room): MatrixRTCSession {\n        if (!this.roomSessions.has(room.roomId)) {\n            this.roomSessions.set(\n                room.roomId,\n                MatrixRTCSession.sessionForSlot(this.client, room, this.slotDescription),\n            );\n        }\n\n        return this.roomSessions.get(room.roomId)!;\n    }\n\n    private onRoom = (room: Room): void => {\n        void this.refreshRoom(room);\n    };\n\n    private readonly onEvent = (event: MatrixEvent): void => {\n        if (!event.unstableStickyExpiresAt) return; // Not sticky, not interested.\n\n        if (event.getType() !== EventType.RTCMembership) return;\n\n        const room = this.client.getRoom(event.getRoomId());\n        if (!room) return;\n\n        void this.refreshRoom(room);\n    };\n\n    private readonly onRoomState = (event: MatrixEvent): void => {\n        if (event.getType() !== EventType.GroupCallMemberPrefix) {\n            return;\n        }\n        const room = this.client.getRoom(event.getRoomId());\n        if (!room) {\n            this.logger.error(`Got room state event for unknown room ${event.getRoomId()}!`);\n            return;\n        }\n\n        void this.refreshRoom(room);\n    };\n\n    private async refreshRoom(room: Room): Promise<void> {\n        const isNewSession = !this.roomSessions.has(room.roomId);\n        const session = this.getRoomSession(room);\n\n        const wasActiveAndKnown = session.memberships.length > 0 && !isNewSession;\n        // This needs to be here and the event listener cannot be setup in the MatrixRTCSession,\n        // because we need the update to happen between:\n        // wasActiveAndKnown = session.memberships.length > 0 and\n        // nowActive = session.memberships.length\n        // Alternatively we would need to setup some event emission when the RTC session ended.\n        // TODO we want to add the emission en session end. This makes the responsibility of the session manager more clear.\n\n        await session._onRTCSessionMemberUpdate().catch((error) => {\n            this.logger.error(`Error updating RTC session members for ${room.roomId}: ${error}`);\n        });\n\n        const nowActive = session.memberships.length > 0;\n\n        if (wasActiveAndKnown && !nowActive) {\n            this.logger.trace(`Session ended for ${room.roomId} (${session.memberships.length} members)`);\n            this.emit(MatrixRTCSessionManagerEvents.SessionEnded, room.roomId, this.roomSessions.get(room.roomId)!);\n        } else if (!wasActiveAndKnown && nowActive) {\n            this.logger.trace(`Session started for ${room.roomId} (${session.memberships.length} members)`);\n            this.emit(MatrixRTCSessionManagerEvents.SessionStarted, room.roomId, this.roomSessions.get(room.roomId)!);\n        }\n    }\n}\n","/*\nCopyright 2022 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type MBeaconEventContent } from \"../@types/beacon.ts\";\nimport {\n    type BeaconInfoState,\n    type BeaconLocationState,\n    parseBeaconContent,\n    parseBeaconInfoContent,\n} from \"../content-helpers.ts\";\nimport { type MatrixEvent } from \"./event.ts\";\nimport { sortEventsByLatestContentTimestamp } from \"../utils.ts\";\nimport { TypedEventEmitter } from \"./typed-event-emitter.ts\";\n\nexport enum BeaconEvent {\n    New = \"Beacon.new\",\n    Update = \"Beacon.update\",\n    LivenessChange = \"Beacon.LivenessChange\",\n    Destroy = \"Beacon.Destroy\",\n    LocationUpdate = \"Beacon.LocationUpdate\",\n}\n\nexport type BeaconEventHandlerMap = {\n    [BeaconEvent.Update]: (event: MatrixEvent, beacon: Beacon) => void;\n    [BeaconEvent.LivenessChange]: (isLive: boolean, beacon: Beacon) => void;\n    [BeaconEvent.Destroy]: (beaconIdentifier: string) => void;\n    [BeaconEvent.LocationUpdate]: (locationState: BeaconLocationState) => void;\n};\n\nexport const isTimestampInDuration = (startTimestamp: number, durationMs: number, timestamp: number): boolean =>\n    timestamp >= startTimestamp && startTimestamp + durationMs >= timestamp;\n\n// beacon info events are uniquely identified by\n// `<roomId>_<state_key>`\nexport type BeaconIdentifier = string;\nexport const getBeaconInfoIdentifier = (event: MatrixEvent): BeaconIdentifier =>\n    `${event.getRoomId()}_${event.getStateKey()}`;\n\n// https://github.com/matrix-org/matrix-spec-proposals/pull/3672\nexport class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.New>, BeaconEventHandlerMap> {\n    public readonly roomId: string;\n    // beaconInfo is assigned by setBeaconInfo in the constructor\n    // ! to make tsc believe it is definitely assigned\n    private _beaconInfo!: BeaconInfoState;\n    private _isLive?: boolean;\n    private livenessWatchTimeout?: ReturnType<typeof setTimeout>;\n    private _latestLocationEvent?: MatrixEvent;\n\n    public constructor(private rootEvent: MatrixEvent) {\n        super();\n        this.roomId = this.rootEvent.getRoomId()!;\n        this.setBeaconInfo(this.rootEvent);\n    }\n\n    public get isLive(): boolean {\n        return !!this._isLive;\n    }\n\n    public get identifier(): BeaconIdentifier {\n        return getBeaconInfoIdentifier(this.rootEvent);\n    }\n\n    public get beaconInfoId(): string {\n        return this.rootEvent.getId()!;\n    }\n\n    public get beaconInfoOwner(): string {\n        return this.rootEvent.getStateKey()!;\n    }\n\n    public get beaconInfoEventType(): string {\n        return this.rootEvent.getType();\n    }\n\n    public get beaconInfo(): BeaconInfoState {\n        return this._beaconInfo;\n    }\n\n    public get latestLocationState(): BeaconLocationState | undefined {\n        return this._latestLocationEvent && parseBeaconContent(this._latestLocationEvent.getContent());\n    }\n\n    public get latestLocationEvent(): MatrixEvent | undefined {\n        return this._latestLocationEvent;\n    }\n\n    public update(beaconInfoEvent: MatrixEvent): void {\n        if (getBeaconInfoIdentifier(beaconInfoEvent) !== this.identifier) {\n            throw new Error(\"Invalid updating event\");\n        }\n        // don't update beacon with an older event\n        if (beaconInfoEvent.getTs() < this.rootEvent.getTs()) {\n            return;\n        }\n        this.rootEvent = beaconInfoEvent;\n        this.setBeaconInfo(this.rootEvent);\n\n        this.emit(BeaconEvent.Update, beaconInfoEvent, this);\n        this.clearLatestLocation();\n    }\n\n    public destroy(): void {\n        if (this.livenessWatchTimeout) {\n            clearTimeout(this.livenessWatchTimeout);\n        }\n\n        this._isLive = false;\n        this.emit(BeaconEvent.Destroy, this.identifier);\n    }\n\n    /**\n     * Monitor liveness of a beacon\n     * Emits BeaconEvent.LivenessChange when beacon expires\n     */\n    public monitorLiveness(): void {\n        if (this.livenessWatchTimeout) {\n            clearTimeout(this.livenessWatchTimeout);\n        }\n\n        this.checkLiveness();\n        if (!this.beaconInfo) return;\n        if (this.isLive) {\n            const expiryInMs = this.beaconInfo.timestamp! + this.beaconInfo.timeout - Date.now();\n            if (expiryInMs > 1) {\n                this.livenessWatchTimeout = setTimeout(() => {\n                    this.monitorLiveness();\n                }, expiryInMs);\n            }\n        } else if (this.beaconInfo.timestamp! > Date.now()) {\n            // beacon start timestamp is in the future\n            // check liveness again then\n            this.livenessWatchTimeout = setTimeout(() => {\n                this.monitorLiveness();\n            }, this.beaconInfo.timestamp! - Date.now());\n        }\n    }\n\n    /**\n     * Process Beacon locations\n     * Emits BeaconEvent.LocationUpdate\n     */\n    public addLocations(beaconLocationEvents: MatrixEvent[]): void {\n        // discard locations for beacons that are not live\n        if (!this.isLive) {\n            return;\n        }\n\n        const validLocationEvents = beaconLocationEvents.filter((event) => {\n            const content = event.getContent<MBeaconEventContent>();\n            const parsed = parseBeaconContent(content);\n            if (!parsed.uri || !parsed.timestamp) return false; // we won't be able to process these\n            const { timestamp } = parsed;\n            return (\n                this._beaconInfo.timestamp &&\n                // only include positions that were taken inside the beacon's live period\n                isTimestampInDuration(this._beaconInfo.timestamp, this._beaconInfo.timeout, timestamp) &&\n                // ignore positions older than our current latest location\n                (!this.latestLocationState || timestamp > this.latestLocationState.timestamp!)\n            );\n        });\n        const latestLocationEvent = validLocationEvents.sort(sortEventsByLatestContentTimestamp)?.[0];\n\n        if (latestLocationEvent) {\n            this._latestLocationEvent = latestLocationEvent;\n            this.emit(BeaconEvent.LocationUpdate, this.latestLocationState!);\n        }\n    }\n\n    private clearLatestLocation = (): void => {\n        this._latestLocationEvent = undefined;\n        this.emit(BeaconEvent.LocationUpdate, this.latestLocationState!);\n    };\n\n    private setBeaconInfo(event: MatrixEvent): void {\n        this._beaconInfo = parseBeaconInfoContent(event.getContent());\n        this.checkLiveness();\n    }\n\n    private checkLiveness(): void {\n        const prevLiveness = this.isLive;\n\n        // element web sets a beacon's start timestamp to the senders local current time\n        // when Alice's system clock deviates slightly from Bob's a beacon Alice intended to be live\n        // may have a start timestamp in the future from Bob's POV\n        // handle this by adding 6min of leniency to the start timestamp when it is in the future\n        if (!this.beaconInfo) return;\n        const startTimestamp =\n            this.beaconInfo.timestamp! > Date.now()\n                ? this.beaconInfo.timestamp! - 360000 /* 6min */\n                : this.beaconInfo.timestamp;\n        this._isLive =\n            !!this._beaconInfo.live &&\n            !!startTimestamp &&\n            isTimestampInDuration(startTimestamp, this._beaconInfo.timeout, Date.now());\n\n        if (prevLiveness !== this.isLive) {\n            this.emit(BeaconEvent.LivenessChange, this.isLive, this);\n        }\n    }\n}\n","/*\nCopyright 2016 - 2021 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { EventTimeline, type IAddEventOptions } from \"./event-timeline.ts\";\nimport { type MatrixEvent } from \"./event.ts\";\nimport { logger } from \"../logger.ts\";\nimport { type Room, RoomEvent } from \"./room.ts\";\nimport { type Filter } from \"../filter.ts\";\nimport { type RoomState } from \"./room-state.ts\";\nimport { TypedEventEmitter } from \"./typed-event-emitter.ts\";\nimport { RelationsContainer } from \"./relations-container.ts\";\nimport { type MatrixClient } from \"../client.ts\";\nimport { type Thread, type ThreadFilterType } from \"./thread.ts\";\n\nconst DEBUG = true;\n\n/* istanbul ignore next */\nlet debuglog: (...args: any[]) => void;\nif (DEBUG) {\n    // using bind means that we get to keep useful line numbers in the console\n    debuglog = logger.log.bind(logger);\n} else {\n    /* istanbul ignore next */\n    debuglog = function (): void {};\n}\n\ninterface IOpts {\n    // Set to true to enable improved timeline support.\n    timelineSupport?: boolean;\n    // The filter object, if any, for this timelineSet.\n    filter?: Filter;\n    pendingEvents?: boolean;\n}\n\nexport enum DuplicateStrategy {\n    Ignore = \"ignore\",\n    Replace = \"replace\",\n}\n\nexport interface IRoomTimelineData {\n    // the timeline the event was added to/removed from\n    timeline: EventTimeline;\n    // true if the event was a real-time event added to the end of the live timeline\n    liveEvent?: boolean;\n}\n\nexport interface IAddEventToTimelineOptions extends Pick<\n    IAddEventOptions,\n    \"toStartOfTimeline\" | \"roomState\" | \"timelineWasEmpty\" | \"addToState\"\n> {\n    /** Whether the sync response came from cache */\n    fromCache?: boolean;\n}\n\nexport interface IAddLiveEventOptions extends Pick<\n    IAddEventToTimelineOptions,\n    \"fromCache\" | \"roomState\" | \"timelineWasEmpty\" | \"addToState\"\n> {\n    /** Applies to events in the timeline only. If this is 'replace' then if a\n     * duplicate is encountered, the event passed to this function will replace\n     * the existing event in the timeline. If this is not specified, or is\n     * 'ignore', then the event passed to this function will be ignored\n     * entirely, preserving the existing event in the timeline. Events are\n     * identical based on their event ID <b>only</b>. */\n    duplicateStrategy?: DuplicateStrategy;\n}\n\ntype EmittedEvents = RoomEvent.Timeline | RoomEvent.TimelineReset;\n\nexport type EventTimelineSetHandlerMap = {\n    /**\n     * Fires whenever the timeline in a room is updated.\n     * @param event - The matrix event which caused this event to fire.\n     * @param room - The room, if any, whose timeline was updated.\n     * @param toStartOfTimeline - True if this event was added to the start\n     * @param removed - True if this event has just been removed from the timeline\n     * (beginning; oldest) of the timeline e.g. due to pagination.\n     *\n     * @param data - more data about the event\n     *\n     * @example\n     * ```\n     * matrixClient.on(\"Room.timeline\",\n     *                 function(event, room, toStartOfTimeline, removed, data) {\n     *   if (!toStartOfTimeline && data.liveEvent) {\n     *     var messageToAppend = room.timeline.[room.timeline.length - 1];\n     *   }\n     * });\n     * ```\n     */\n    [RoomEvent.Timeline]: (\n        event: MatrixEvent,\n        room: Room | undefined,\n        toStartOfTimeline: boolean | undefined,\n        removed: boolean,\n        data: IRoomTimelineData,\n    ) => void;\n    /**\n     * Fires whenever the live timeline in a room is reset.\n     *\n     * When we get a 'limited' sync (for example, after a network outage), we reset\n     * the live timeline to be empty before adding the recent events to the new\n     * timeline. This event is fired after the timeline is reset, and before the\n     * new events are added.\n     *\n     * @param room - The room whose live timeline was reset, if any\n     * @param timelineSet - timelineSet room whose live timeline was reset\n     * @param resetAllTimelines - True if all timelines were reset.\n     */\n    [RoomEvent.TimelineReset]: (\n        room: Room | undefined,\n        timelineSet: EventTimelineSet,\n        resetAllTimelines: boolean,\n    ) => void;\n};\n\nexport class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTimelineSetHandlerMap> {\n    public readonly relations: RelationsContainer;\n    private readonly timelineSupport: boolean;\n    private readonly displayPendingEvents: boolean;\n    private liveTimeline: EventTimeline;\n    private timelines: EventTimeline[];\n    private _eventIdToTimeline = new Map<string, EventTimeline>();\n    private filter?: Filter;\n\n    /**\n     * Construct a set of EventTimeline objects, typically on behalf of a given\n     * room.  A room may have multiple EventTimelineSets for different levels\n     * of filtering.  The global notification list is also an EventTimelineSet, but\n     * lacks a room.\n     *\n     * <p>This is an ordered sequence of timelines, which may or may not\n     * be continuous. Each timeline lists a series of events, as well as tracking\n     * the room state at the start and the end of the timeline (if appropriate).\n     * It also tracks forward and backward pagination tokens, as well as containing\n     * links to the next timeline in the sequence.\n     *\n     * <p>There is one special timeline - the 'live' timeline, which represents the\n     * timeline to which events are being added in real-time as they are received\n     * from the /sync API. Note that you should not retain references to this\n     * timeline - even if it is the current timeline right now, it may not remain\n     * so if the server gives us a timeline gap in /sync.\n     *\n     * <p>In order that we can find events from their ids later, we also maintain a\n     * map from event_id to timeline and index.\n     *\n     * @param room - Room for this timelineSet. May be null for non-room cases, such as the\n     * notification timeline.\n     * @param opts - Options inherited from Room.\n     * @param client - the Matrix client which owns this EventTimelineSet,\n     * can be omitted if room is specified.\n     * @param thread - the thread to which this timeline set relates.\n     * @param threadListType - the type of thread list represented, if any\n     * (e.g., All threads or My threads)\n     */\n    public constructor(\n        public readonly room: Room | undefined,\n        opts: IOpts = {},\n        client?: MatrixClient,\n        public readonly thread?: Thread,\n        public readonly threadListType: ThreadFilterType | null = null,\n    ) {\n        super();\n\n        this.timelineSupport = Boolean(opts.timelineSupport);\n        this.liveTimeline = new EventTimeline(this);\n        this.displayPendingEvents = opts.pendingEvents !== false;\n\n        // just a list - *not* ordered.\n        this.timelines = [this.liveTimeline];\n        this._eventIdToTimeline = new Map<string, EventTimeline>();\n\n        this.filter = opts.filter;\n\n        this.relations = this.room?.relations ?? new RelationsContainer(room?.client ?? client!);\n    }\n\n    /**\n     * Get all the timelines in this set\n     * @returns the timelines in this set\n     */\n    public getTimelines(): EventTimeline[] {\n        return this.timelines;\n    }\n\n    /**\n     * Get the filter object this timeline set is filtered on, if any\n     * @returns the optional filter for this timelineSet\n     */\n    public getFilter(): Filter | undefined {\n        return this.filter;\n    }\n\n    /**\n     * Set the filter object this timeline set is filtered on\n     * (passed to the server when paginating via /messages).\n     * @param filter - the filter for this timelineSet\n     */\n    public setFilter(filter?: Filter): void {\n        this.filter = filter;\n    }\n\n    /**\n     * Get the list of pending sent events for this timelineSet's room, filtered\n     * by the timelineSet's filter if appropriate.\n     *\n     * @returns A list of the sent events\n     * waiting for remote echo.\n     *\n     * @throws If `opts.pendingEventOrdering` was not 'detached'\n     */\n    public getPendingEvents(): MatrixEvent[] {\n        if (!this.room || !this.displayPendingEvents) {\n            return [];\n        }\n\n        return this.room.getPendingEvents();\n    }\n    /**\n     * Get the live timeline for this room.\n     *\n     * @returns live timeline\n     */\n    public getLiveTimeline(): EventTimeline {\n        return this.liveTimeline;\n    }\n\n    /**\n     * Set the live timeline for this room.\n     *\n     * @returns live timeline\n     */\n    public setLiveTimeline(timeline: EventTimeline): void {\n        this.liveTimeline = timeline;\n    }\n\n    /**\n     * Return the timeline (if any) this event is in.\n     * @param eventId - the eventId being sought\n     * @returns timeline\n     */\n    public eventIdToTimeline(eventId: string): EventTimeline | undefined {\n        return this._eventIdToTimeline.get(eventId);\n    }\n\n    /**\n     * Track a new event as if it were in the same timeline as an old event,\n     * replacing it.\n     * @param oldEventId -  event ID of the original event\n     * @param newEventId -  event ID of the replacement event\n     */\n    public replaceEventId(oldEventId: string, newEventId: string): void {\n        const existingTimeline = this._eventIdToTimeline.get(oldEventId);\n        if (existingTimeline) {\n            this._eventIdToTimeline.delete(oldEventId);\n            this._eventIdToTimeline.set(newEventId, existingTimeline);\n        }\n    }\n\n    /**\n     * Reset the live timeline, and start a new one.\n     *\n     * <p>This is used when /sync returns a 'limited' timeline.\n     *\n     * @param backPaginationToken -   token for back-paginating the new timeline\n     * @param forwardPaginationToken - token for forward-paginating the old live timeline,\n     * if absent or null, all timelines are reset.\n     *\n     * @remarks\n     * Fires {@link RoomEvent.TimelineReset}\n     */\n    public resetLiveTimeline(backPaginationToken?: string, forwardPaginationToken?: string): void {\n        // Each EventTimeline has RoomState objects tracking the state at the start\n        // and end of that timeline. The copies at the end of the live timeline are\n        // special because they will have listeners attached to monitor changes to\n        // the current room state, so we move this RoomState from the end of the\n        // current live timeline to the end of the new one and, if necessary,\n        // replace it with a newly created one. We also make a copy for the start\n        // of the new timeline.\n\n        // if timeline support is disabled, forget about the old timelines\n        const resetAllTimelines = !this.timelineSupport || !forwardPaginationToken;\n\n        const oldTimeline = this.liveTimeline;\n        const newTimeline = resetAllTimelines\n            ? oldTimeline.forkLive(EventTimeline.FORWARDS)\n            : oldTimeline.fork(EventTimeline.FORWARDS);\n\n        if (resetAllTimelines) {\n            this.timelines = [newTimeline];\n            this._eventIdToTimeline = new Map<string, EventTimeline>();\n        } else {\n            this.timelines.push(newTimeline);\n        }\n\n        if (forwardPaginationToken) {\n            // Now set the forward pagination token on the old live timeline\n            // so it can be forward-paginated.\n            oldTimeline.setPaginationToken(forwardPaginationToken, EventTimeline.FORWARDS);\n        }\n\n        // make sure we set the pagination token before firing timelineReset,\n        // otherwise clients which start back-paginating will fail, and then get\n        // stuck without realising that they *can* back-paginate.\n        newTimeline.setPaginationToken(backPaginationToken ?? null, EventTimeline.BACKWARDS);\n\n        // Now we can swap the live timeline to the new one.\n        this.liveTimeline = newTimeline;\n        this.emit(RoomEvent.TimelineReset, this.room, this, resetAllTimelines);\n    }\n\n    /**\n     * Get the timeline which contains the given event, if any\n     *\n     * @param eventId -  event ID to look for\n     * @returns timeline containing\n     * the given event, or null if unknown\n     */\n    public getTimelineForEvent(eventId?: string): EventTimeline | null {\n        if (eventId === null || eventId === undefined) {\n            return null;\n        }\n        const res = this._eventIdToTimeline.get(eventId);\n        return res === undefined ? null : res;\n    }\n\n    /**\n     * Get an event which is stored in our timelines\n     *\n     * @param eventId -  event ID to look for\n     * @returns the given event, or undefined if unknown\n     */\n    public findEventById(eventId: string): MatrixEvent | undefined {\n        const tl = this.getTimelineForEvent(eventId);\n        if (!tl) {\n            return undefined;\n        }\n        return tl.getEvents().find(function (ev) {\n            return ev.getId() == eventId;\n        });\n    }\n\n    /**\n     * Add a new timeline to this timeline list\n     *\n     * @returns newly-created timeline\n     */\n    public addTimeline(): EventTimeline {\n        if (!this.timelineSupport) {\n            throw new Error(\n                \"timeline support is disabled. Set the 'timelineSupport'\" +\n                    \" parameter to true when creating MatrixClient to enable\" +\n                    \" it.\",\n            );\n        }\n\n        const timeline = new EventTimeline(this);\n        this.timelines.push(timeline);\n        return timeline;\n    }\n\n    /**\n     * Add events to a timeline\n     *\n     * <p>Will fire \"Room.timeline\" for each event added.\n     *\n     * @param events - A list of events to add.\n     *\n     * @param toStartOfTimeline -   True to add these events to the start\n     * (oldest) instead of the end (newest) of the timeline. If true, the oldest\n     * event will be the <b>last</b> element of 'events'.\n     *\n     * @param timeline -   timeline to\n     *    add events to.\n     *\n     * @param paginationToken -   token for the next batch of events\n     *\n     * @remarks\n     * Fires {@link RoomEvent.Timeline}\n     *\n     */\n    public addEventsToTimeline(\n        events: MatrixEvent[],\n        toStartOfTimeline: boolean,\n        addToState: boolean,\n        timeline: EventTimeline,\n        paginationToken?: string | null,\n    ): void {\n        if (!timeline) {\n            throw new Error(\"'timeline' not specified for EventTimelineSet.addEventsToTimeline\");\n        }\n\n        if (!toStartOfTimeline && timeline == this.liveTimeline) {\n            throw new Error(\n                \"EventTimelineSet.addEventsToTimeline cannot be used for adding events to \" +\n                    \"the live timeline - use Room.addLiveEvents instead\",\n            );\n        }\n\n        if (this.filter) {\n            events = this.filter.filterRoomTimeline(events);\n            if (!events.length) {\n                return;\n            }\n        }\n\n        const direction = toStartOfTimeline ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;\n        const inverseDirection = toStartOfTimeline ? EventTimeline.FORWARDS : EventTimeline.BACKWARDS;\n\n        // Adding events to timelines can be quite complicated. The following\n        // illustrates some of the corner-cases.\n        //\n        // Let's say we start by knowing about four timelines. timeline3 and\n        // timeline4 are neighbours:\n        //\n        //    timeline1    timeline2    timeline3    timeline4\n        //      [M]          [P]          [S] <------> [T]\n        //\n        // Now we paginate timeline1, and get the following events from the server:\n        // [M, N, P, R, S, T, U].\n        //\n        // 1. First, we ignore event M, since we already know about it.\n        //\n        // 2. Next, we append N to timeline 1.\n        //\n        // 3. Next, we don't add event P, since we already know about it,\n        //    but we do link together the timelines. We now have:\n        //\n        //    timeline1    timeline2    timeline3    timeline4\n        //      [M, N] <---> [P]          [S] <------> [T]\n        //\n        // 4. Now we add event R to timeline2:\n        //\n        //    timeline1    timeline2    timeline3    timeline4\n        //      [M, N] <---> [P, R]       [S] <------> [T]\n        //\n        //    Note that we have switched the timeline we are working on from\n        //    timeline1 to timeline2.\n        //\n        // 5. We ignore event S, but again join the timelines:\n        //\n        //    timeline1    timeline2    timeline3    timeline4\n        //      [M, N] <---> [P, R] <---> [S] <------> [T]\n        //\n        // 6. We ignore event T, and the timelines are already joined, so there\n        //    is nothing to do.\n        //\n        // 7. Finally, we add event U to timeline4:\n        //\n        //    timeline1    timeline2    timeline3    timeline4\n        //      [M, N] <---> [P, R] <---> [S] <------> [T, U]\n        //\n        // The important thing to note in the above is what happened when we\n        // already knew about a given event:\n        //\n        //   - if it was appropriate, we joined up the timelines (steps 3, 5).\n        //   - in any case, we started adding further events to the timeline which\n        //       contained the event we knew about (steps 3, 5, 6).\n        //\n        //\n        // So much for adding events to the timeline. But what do we want to do\n        // with the pagination token?\n        //\n        // In the case above, we will be given a pagination token which tells us how to\n        // get events beyond 'U' - in this case, it makes sense to store this\n        // against timeline4. But what if timeline4 already had 'U' and beyond? in\n        // that case, our best bet is to throw away the pagination token we were\n        // given and stick with whatever token timeline4 had previously. In short,\n        // we want to only store the pagination token if the last event we receive\n        // is one we didn't previously know about.\n        //\n        // We make an exception for this if it turns out that we already knew about\n        // *all* of the events, and we weren't able to join up any timelines. When\n        // that happens, it means our existing pagination token is faulty, since it\n        // is only telling us what we already know. Rather than repeatedly\n        // paginating with the same token, we might as well use the new pagination\n        // token in the hope that we eventually work our way out of the mess.\n\n        let didUpdate = false;\n        let lastEventWasNew = false;\n        for (const event of events) {\n            const eventId = event.getId()!;\n\n            const existingTimeline = this._eventIdToTimeline.get(eventId);\n\n            if (!existingTimeline) {\n                // we don't know about this event yet. Just add it to the timeline.\n                this.addEventToTimeline(event, timeline, {\n                    toStartOfTimeline,\n                    addToState,\n                });\n                lastEventWasNew = true;\n                didUpdate = true;\n                continue;\n            }\n\n            lastEventWasNew = false;\n\n            if (existingTimeline == timeline) {\n                debuglog(\"Event \" + eventId + \" already in timeline \" + timeline);\n                continue;\n            }\n\n            const neighbour = timeline.getNeighbouringTimeline(direction);\n            if (neighbour) {\n                // this timeline already has a neighbour in the relevant direction;\n                // let's assume the timelines are already correctly linked up, and\n                // skip over to it.\n                //\n                // there's probably some edge-case here where we end up with an\n                // event which is in a timeline a way down the chain, and there is\n                // a break in the chain somewhere. But I can't really imagine how\n                // that would happen, so I'm going to ignore it for now.\n                //\n                if (existingTimeline == neighbour) {\n                    debuglog(\"Event \" + eventId + \" in neighbouring timeline - \" + \"switching to \" + existingTimeline);\n                } else {\n                    debuglog(\"Event \" + eventId + \" already in a different \" + \"timeline \" + existingTimeline);\n                }\n                timeline = existingTimeline;\n                continue;\n            }\n\n            // time to join the timelines.\n            logger.info(\n                \"Already have timeline for \" + eventId + \" - joining timeline \" + timeline + \" to \" + existingTimeline,\n            );\n\n            // Variables to keep the line length limited below.\n            const existingIsLive = existingTimeline === this.liveTimeline;\n            const timelineIsLive = timeline === this.liveTimeline;\n\n            const backwardsIsLive = direction === EventTimeline.BACKWARDS && existingIsLive;\n            const forwardsIsLive = direction === EventTimeline.FORWARDS && timelineIsLive;\n\n            if (backwardsIsLive || forwardsIsLive) {\n                // The live timeline should never be spliced into a non-live position.\n                // We use independent logging to better discover the problem at a glance.\n                if (backwardsIsLive) {\n                    logger.warn(\n                        \"Refusing to set a preceding existingTimeLine on our \" +\n                            \"timeline as the existingTimeLine is live (\" +\n                            existingTimeline +\n                            \")\",\n                    );\n                }\n                if (forwardsIsLive) {\n                    logger.warn(\n                        \"Refusing to set our preceding timeline on a existingTimeLine \" +\n                            \"as our timeline is live (\" +\n                            timeline +\n                            \")\",\n                    );\n                }\n                continue; // abort splicing - try next event\n            }\n\n            timeline.setNeighbouringTimeline(existingTimeline, direction);\n            existingTimeline.setNeighbouringTimeline(timeline, inverseDirection);\n\n            timeline = existingTimeline;\n            didUpdate = true;\n        }\n\n        // see above - if the last event was new to us, or if we didn't find any\n        // new information, we update the pagination token for whatever\n        // timeline we ended up on.\n        if (lastEventWasNew || !didUpdate) {\n            if (direction === EventTimeline.FORWARDS && timeline === this.liveTimeline) {\n                logger.warn({ lastEventWasNew, didUpdate }); // for debugging\n                logger.warn(\n                    `Refusing to set forwards pagination token of live timeline ` + `${timeline} to ${paginationToken}`,\n                );\n                return;\n            }\n            timeline.setPaginationToken(paginationToken ?? null, direction);\n        }\n    }\n\n    /**\n     * Add an event to the end of this live timeline.\n     *\n     * @param event - Event to be added\n     * @param options - addLiveEvent options\n     */\n    public addLiveEvent(\n        event: MatrixEvent,\n        { duplicateStrategy, fromCache, roomState, timelineWasEmpty, addToState }: IAddLiveEventOptions,\n    ): void {\n        if (this.filter) {\n            const events = this.filter.filterRoomTimeline([event]);\n            if (!events.length) {\n                return;\n            }\n        }\n\n        const timeline = this._eventIdToTimeline.get(event.getId()!);\n        if (timeline) {\n            if (duplicateStrategy === DuplicateStrategy.Replace) {\n                debuglog(\"EventTimelineSet.addLiveEvent: replacing duplicate event \" + event.getId());\n                const tlEvents = timeline.getEvents();\n                for (let j = 0; j < tlEvents.length; j++) {\n                    if (tlEvents[j].getId() === event.getId()) {\n                        // still need to set the right metadata on this event\n                        if (!roomState) {\n                            roomState = timeline.getState(EventTimeline.FORWARDS);\n                        }\n                        EventTimeline.setEventMetadata(event, roomState!, false);\n                        tlEvents[j] = event;\n\n                        // XXX: we need to fire an event when this happens.\n                        break;\n                    }\n                }\n            } else {\n                debuglog(\"EventTimelineSet.addLiveEvent: ignoring duplicate event \" + event.getId());\n            }\n            return;\n        }\n\n        this.addEventToTimeline(event, this.liveTimeline, {\n            toStartOfTimeline: false,\n            fromCache,\n            roomState,\n            timelineWasEmpty,\n            addToState,\n        });\n    }\n\n    /**\n     * Add event to the given timeline, and emit Room.timeline. Assumes\n     * we have already checked we don't know about this event.\n     *\n     * Will fire \"Room.timeline\" for each event added.\n     *\n     * @param event - the event to add\n     * @param timeline - the timeline onto which to add it\n     * @param options - addEventToTimeline options\n     *\n     * @remarks\n     * Fires {@link RoomEvent.Timeline}\n     */\n    public addEventToTimeline(\n        event: MatrixEvent,\n        timeline: EventTimeline,\n        { toStartOfTimeline, fromCache = false, roomState, timelineWasEmpty, addToState }: IAddEventToTimelineOptions,\n    ): void {\n        if (timeline.getTimelineSet() !== this) {\n            throw new Error(`EventTimelineSet.addEventToTimeline: Timeline=${timeline.toString()} does not belong \" +\n                \"in timelineSet(threadId=${this.thread?.id})`);\n        }\n\n        const eventId = event.getId()!;\n        this.relations.aggregateParentEvent(event);\n        this.relations.aggregateChildEvent(event, this);\n\n        // Make sure events don't get mixed in timelines they shouldn't be in (e.g. a\n        // threaded message should not be in the main timeline).\n        //\n        // We can only run this check for timelines with a `room` because `canContain`\n        // requires it\n        if (this.room && !this.canContain(event)) {\n            let eventDebugString = `event=${eventId}`;\n            if (event.threadRootId) {\n                eventDebugString += `(belongs to thread=${event.threadRootId})`;\n            }\n            logger.warn(\n                `EventTimelineSet.addEventToTimeline: Ignoring ${eventDebugString} that does not belong ` +\n                    `in timeline=${timeline.toString()} timelineSet(threadId=${this.thread?.id})`,\n            );\n            return;\n        }\n\n        timeline.addEvent(event, {\n            toStartOfTimeline,\n            roomState,\n            timelineWasEmpty,\n            addToState,\n        });\n        this._eventIdToTimeline.set(eventId, timeline);\n\n        const data: IRoomTimelineData = {\n            timeline: timeline,\n            liveEvent: !toStartOfTimeline && timeline == this.liveTimeline && !fromCache,\n        };\n        this.emit(RoomEvent.Timeline, event, this.room, Boolean(toStartOfTimeline), false, data);\n    }\n\n    /**\n     * Insert event to the given timeline, and emit Room.timeline. Assumes\n     * we have already checked we don't know about this event.\n     *\n     * TEMPORARY: until we have recursive relations, we need this function\n     * to exist to allow us to insert events in timeline order, which is our\n     * best guess for Sync Order.\n     * This is a copy of addEventToTimeline above, modified to insert the event\n     * after the event it relates to, and before any event with a later\n     * timestamp. This is our best guess at Sync Order.\n     *\n     * Will fire \"Room.timeline\" for each event added.\n     *\n     * @internal\n     *\n     * @remarks\n     * Fires {@link RoomEvent.Timeline}\n     */\n    public insertEventIntoTimeline(\n        event: MatrixEvent,\n        timeline: EventTimeline,\n        roomState: RoomState,\n        addToState: boolean,\n    ): void {\n        if (timeline.getTimelineSet() !== this) {\n            throw new Error(`EventTimelineSet.insertEventIntoTimeline: Timeline=${timeline.toString()} does not belong \" +\n                \"in timelineSet(threadId=${this.thread?.id})`);\n        }\n\n        const eventId = event.getId()!;\n        this.relations.aggregateParentEvent(event);\n        this.relations.aggregateChildEvent(event, this);\n\n        // Make sure events don't get mixed in timelines they shouldn't be in (e.g. a\n        // threaded message should not be in the main timeline).\n        //\n        // We can only run this check for timelines with a `room` because `canContain`\n        // requires it\n        if (this.room && !this.canContain(event)) {\n            let eventDebugString = `event=${eventId}`;\n            if (event.threadRootId) {\n                eventDebugString += `(belongs to thread=${event.threadRootId})`;\n            }\n            logger.warn(\n                `EventTimelineSet.insertEventIntoTimeline: Ignoring ${eventDebugString} that does not belong ` +\n                    `in timeline=${timeline.toString()} timelineSet(threadId=${this.thread?.id})`,\n            );\n            return;\n        }\n\n        // Find the event that this event is related to - the \"parent\"\n        const parentEventId = event.relationEventId;\n        if (!parentEventId) {\n            // Not related to anything - we just append\n            this.addEventToTimeline(event, timeline, {\n                toStartOfTimeline: false,\n                fromCache: false,\n                timelineWasEmpty: false,\n                roomState,\n                addToState,\n            });\n            return;\n        }\n\n        const parentEvent = this.findEventById(parentEventId);\n\n        const timelineEvents = timeline.getEvents();\n\n        // Start searching from the parent event, or if it's not loaded, start\n        // at the beginning and insert purely using timestamp order.\n        const parentIndex = parentEvent !== undefined ? timelineEvents.indexOf(parentEvent) : 0;\n        let insertIndex = parentIndex;\n        for (; insertIndex < timelineEvents.length; insertIndex++) {\n            const nextEvent = timelineEvents[insertIndex];\n            if (nextEvent.getTs() > event.getTs()) {\n                // We found an event later than ours, so insert before that.\n                break;\n            }\n        }\n        // If we got to the end of the loop, insertIndex points at the end of\n        // the list.\n\n        timeline.insertEvent(event, insertIndex, roomState, addToState);\n        this._eventIdToTimeline.set(eventId, timeline);\n\n        const data: IRoomTimelineData = {\n            timeline: timeline,\n            // The purpose of this method is inserting events in the middle of the\n            // timeline, so the events are, by definition, not live (whether or not\n            // we're adding them to the live timeline).\n            liveEvent: false,\n        };\n        this.emit(RoomEvent.Timeline, event, this.room, false, false, data);\n    }\n\n    /**\n     * Replaces event with ID oldEventId with one with newEventId, if oldEventId is\n     * recognised.  Otherwise, add to the live timeline.  Used to handle remote echos.\n     *\n     * @param localEvent -     the new event to be added to the timeline\n     * @param oldEventId -          the ID of the original event\n     * @param newEventId -         the ID of the replacement event\n     *\n     * @remarks\n     * Fires {@link RoomEvent.Timeline}\n     */\n    public handleRemoteEcho(localEvent: MatrixEvent, oldEventId: string, newEventId: string): void {\n        // XXX: why don't we infer newEventId from localEvent?\n        const existingTimeline = this._eventIdToTimeline.get(oldEventId);\n        if (existingTimeline) {\n            this._eventIdToTimeline.delete(oldEventId);\n            this._eventIdToTimeline.set(newEventId, existingTimeline);\n        } else if (!this.filter || this.filter.filterRoomTimeline([localEvent]).length) {\n            this.addEventToTimeline(localEvent, this.liveTimeline, {\n                toStartOfTimeline: false,\n                addToState: false,\n            });\n        }\n    }\n\n    /**\n     * Removes a single event from this room.\n     *\n     * @param eventId -  The id of the event to remove\n     *\n     * @returns the removed event, or null if the event was not found\n     * in this room.\n     */\n    public removeEvent(eventId: string): MatrixEvent | null {\n        const timeline = this._eventIdToTimeline.get(eventId);\n        if (!timeline) {\n            return null;\n        }\n\n        const removed = timeline.removeEvent(eventId);\n        if (removed) {\n            this._eventIdToTimeline.delete(eventId);\n            const data = {\n                timeline: timeline,\n            };\n            this.emit(RoomEvent.Timeline, removed, this.room, undefined, true, data);\n        }\n        return removed;\n    }\n\n    /**\n     * Determine where two events appear in the timeline relative to one another\n     *\n     * @param eventId1 -   The id of the first event\n     * @param eventId2 -   The id of the second event\n\n     * @returns -1 if eventId1 precedes eventId2, and +1 eventId1 succeeds\n     * eventId2. 0 if they are the same event; null if we can't tell (either\n     * because we don't know about one of the events, or because they are in\n     * separate timelines which don't join up).\n     */\n    public compareEventOrdering(eventId1: string, eventId2: string): number | null {\n        if (eventId1 == eventId2) {\n            // optimise this case\n            return 0;\n        }\n\n        const timeline1 = this._eventIdToTimeline.get(eventId1);\n        const timeline2 = this._eventIdToTimeline.get(eventId2);\n\n        if (timeline1 === undefined) {\n            return null;\n        }\n        if (timeline2 === undefined) {\n            return null;\n        }\n\n        if (timeline1 === timeline2) {\n            // both events are in the same timeline - figure out their relative indices\n            let idx1: number | undefined = undefined;\n            let idx2: number | undefined = undefined;\n            const events = timeline1.getEvents();\n            for (let idx = 0; idx < events.length && (idx1 === undefined || idx2 === undefined); idx++) {\n                const evId = events[idx].getId();\n                if (evId == eventId1) {\n                    idx1 = idx;\n                }\n                if (evId == eventId2) {\n                    idx2 = idx;\n                }\n            }\n            const difference = idx1! - idx2!;\n\n            // Return the sign of difference.\n            if (difference < 0) {\n                return -1;\n            } else if (difference > 0) {\n                return 1;\n            } else {\n                return 0;\n            }\n        }\n\n        // the events are in different timelines. Iterate through the\n        // linkedlist to see which comes first.\n\n        // first work forwards from timeline1\n        let tl: EventTimeline | null = timeline1;\n        while (tl) {\n            if (tl === timeline2) {\n                // timeline1 is before timeline2\n                return -1;\n            }\n            tl = tl.getNeighbouringTimeline(EventTimeline.FORWARDS);\n        }\n\n        // now try backwards from timeline1\n        tl = timeline1;\n        while (tl) {\n            if (tl === timeline2) {\n                // timeline2 is before timeline1\n                return 1;\n            }\n            tl = tl.getNeighbouringTimeline(EventTimeline.BACKWARDS);\n        }\n\n        // the timelines are not contiguous.\n        return null;\n    }\n\n    /**\n     * Determine whether a given event can sanely be added to this event timeline set,\n     * for timeline sets relating to a thread, only return true for events in the same\n     * thread timeline, for timeline sets not relating to a thread only return true\n     * for events which should be shown in the main room timeline.\n     * Requires the `room` property to have been set at EventTimelineSet construction time.\n     *\n     * @param event - the event to check whether it belongs to this timeline set.\n     * @throws Error if `room` was not set when constructing this timeline set.\n     * @returns whether the event belongs to this timeline set.\n     */\n    public canContain(event: MatrixEvent): boolean {\n        if (!this.room) {\n            throw new Error(\n                \"Cannot call `EventTimelineSet::canContain without a `room` set. \" +\n                    \"Set the room when creating the EventTimelineSet to call this method.\",\n            );\n        }\n\n        const { threadId, shouldLiveInRoom, shouldLiveInThread } = this.room.eventShouldLiveIn(event);\n\n        if (this.thread) {\n            return this.thread.id === threadId;\n        }\n\n        if (!shouldLiveInRoom && !shouldLiveInThread) {\n            logger.warn(\n                `EventTimelineSet:canContain event encountered which cannot be added to any timeline roomId=${\n                    this.room?.roomId\n                } eventId=${event.getId()} threadId=${event.threadRootId}`,\n            );\n        }\n\n        return shouldLiveInRoom;\n    }\n}\n","/*\nCopyright 2016 - 2021 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type IMarkerFoundOptions, RoomState } from \"./room-state.ts\";\nimport { type EventTimelineSet } from \"./event-timeline-set.ts\";\nimport { type MatrixEvent } from \"./event.ts\";\nimport { type Filter } from \"../filter.ts\";\nimport { EventType } from \"../@types/event.ts\";\n\nexport interface IInitialiseStateOptions extends Pick<IMarkerFoundOptions, \"timelineWasEmpty\"> {\n    // This is a separate interface without any extra stuff currently added on\n    // top of `IMarkerFoundOptions` just because it feels like they have\n    // different concerns. One shouldn't necessarily look to add to\n    // `IMarkerFoundOptions` just because they want to add an extra option to\n    // `initialiseState`.\n}\n\nexport interface IAddEventOptions extends Pick<IMarkerFoundOptions, \"timelineWasEmpty\"> {\n    /** Whether to insert the new event at the start of the timeline where the\n     * oldest events are (timeline is in chronological order, oldest to most\n     * recent) */\n    toStartOfTimeline: boolean;\n    /** The state events to reconcile metadata from */\n    roomState?: RoomState;\n    /** Whether to add timeline events to the state as was done in legacy sync v2.\n     * If true then timeline events will be added to the state.\n     * In sync v2 with org.matrix.msc4222.use_state_after and simplified sliding sync,\n     * all state arrives explicitly and timeline events should not be added. */\n    addToState: boolean;\n}\n\nexport enum Direction {\n    Backward = \"b\",\n    Forward = \"f\",\n}\n\nexport class EventTimeline {\n    /**\n     * Symbolic constant for methods which take a 'direction' argument:\n     * refers to the start of the timeline, or backwards in time.\n     */\n    public static readonly BACKWARDS = Direction.Backward;\n\n    /**\n     * Symbolic constant for methods which take a 'direction' argument:\n     * refers to the end of the timeline, or forwards in time.\n     */\n    public static readonly FORWARDS = Direction.Forward;\n\n    /**\n     * Static helper method to set sender and target properties\n     *\n     * @param event -   the event whose metadata is to be set\n     * @param stateContext -  the room state to be queried\n     * @param toStartOfTimeline -  if true the event's forwardLooking flag is set false\n     */\n    public static setEventMetadata(event: MatrixEvent, stateContext: RoomState, toStartOfTimeline: boolean): void {\n        event.setMetadata(stateContext, toStartOfTimeline);\n    }\n\n    private readonly roomId: string | null;\n    private readonly name: string;\n    private events: MatrixEvent[] = [];\n    private baseIndex = 0;\n\n    private startState?: RoomState;\n    private endState?: RoomState;\n    // If we have a roomId then we delegate pagination token storage to the room state objects `startState` and\n    // `endState`, but for things like the notification timeline which mix multiple rooms we store the tokens ourselves.\n    private startToken: string | null = null;\n    private endToken: string | null = null;\n\n    private prevTimeline: EventTimeline | null = null;\n    private nextTimeline: EventTimeline | null = null;\n    public paginationRequests: Record<Direction, Promise<boolean> | null> = {\n        [Direction.Backward]: null,\n        [Direction.Forward]: null,\n    };\n\n    /**\n     * Construct a new EventTimeline\n     *\n     * <p>An EventTimeline represents a contiguous sequence of events in a room.\n     *\n     * <p>As well as keeping track of the events themselves, it stores the state of\n     * the room at the beginning and end of the timeline, and pagination tokens for\n     * going backwards and forwards in the timeline.\n     *\n     * <p>In order that clients can meaningfully maintain an index into a timeline,\n     * the EventTimeline object tracks a 'baseIndex'. This starts at zero, but is\n     * incremented when events are prepended to the timeline. The index of an event\n     * relative to baseIndex therefore remains constant.\n     *\n     * <p>Once a timeline joins up with its neighbour, they are linked together into a\n     * doubly-linked list.\n     *\n     * @param eventTimelineSet - the set of timelines this is part of\n     */\n    public constructor(private readonly eventTimelineSet: EventTimelineSet) {\n        this.roomId = eventTimelineSet.room?.roomId ?? null;\n        if (this.roomId) {\n            this.startState = new RoomState(this.roomId);\n            this.endState = new RoomState(this.roomId);\n        }\n\n        // this is used by client.js\n        this.paginationRequests = { b: null, f: null };\n\n        this.name = this.roomId + \":\" + new Date().toISOString();\n    }\n\n    /**\n     * Initialise the start and end state with the given events\n     *\n     * <p>This can only be called before any events are added.\n     *\n     * @param stateEvents - list of state events to initialise the\n     * state with.\n     * @throws Error if an attempt is made to call this after addEvent is called.\n     */\n    public initialiseState(stateEvents: MatrixEvent[], { timelineWasEmpty }: IInitialiseStateOptions = {}): void {\n        if (this.events.length > 0) {\n            throw new Error(\"Cannot initialise state after events are added\");\n        }\n\n        this.startState?.setStateEvents(stateEvents, { timelineWasEmpty });\n        this.endState?.setStateEvents(stateEvents, { timelineWasEmpty });\n    }\n\n    /**\n     * Forks the (live) timeline, taking ownership of the existing directional state of this timeline.\n     * All attached listeners will keep receiving state updates from the new live timeline state.\n     * The end state of this timeline gets replaced with an independent copy of the current RoomState,\n     * and will need a new pagination token if it ever needs to paginate forwards.\n\n     * @param direction -   EventTimeline.BACKWARDS to get the state at the\n     *   start of the timeline; EventTimeline.FORWARDS to get the state at the end\n     *   of the timeline.\n     *\n     * @returns the new timeline\n     */\n    public forkLive(direction: Direction): EventTimeline {\n        const forkState = this.getState(direction);\n        const timeline = new EventTimeline(this.eventTimelineSet);\n        timeline.startState = forkState?.clone();\n        // Now clobber the end state of the new live timeline with that from the\n        // previous live timeline. It will be identical except that we'll keep\n        // using the same RoomMember objects for the 'live' set of members with any\n        // listeners still attached\n        timeline.endState = forkState;\n        // Firstly, we just stole the current timeline's end state, so it needs a new one.\n        // Make an immutable copy of the state so back pagination will get the correct sentinels.\n        this.endState = forkState?.clone();\n        return timeline;\n    }\n\n    /**\n     * Creates an independent timeline, inheriting the directional state from this timeline.\n     *\n     * @param direction -   EventTimeline.BACKWARDS to get the state at the\n     *   start of the timeline; EventTimeline.FORWARDS to get the state at the end\n     *   of the timeline.\n     *\n     * @returns the new timeline\n     */\n    public fork(direction: Direction): EventTimeline {\n        const forkState = this.getState(direction);\n        const timeline = new EventTimeline(this.eventTimelineSet);\n        timeline.startState = forkState?.clone();\n        timeline.endState = forkState?.clone();\n        return timeline;\n    }\n\n    /**\n     * Get the ID of the room for this timeline\n     * @returns room ID\n     */\n    public getRoomId(): string | null {\n        return this.roomId;\n    }\n\n    /**\n     * Get the filter for this timeline's timelineSet (if any)\n     * @returns filter\n     */\n    public getFilter(): Filter | undefined {\n        return this.eventTimelineSet.getFilter();\n    }\n\n    /**\n     * Get the timelineSet for this timeline\n     * @returns timelineSet\n     */\n    public getTimelineSet(): EventTimelineSet {\n        return this.eventTimelineSet;\n    }\n\n    /**\n     * Get the base index.\n     *\n     * <p>This is an index which is incremented when events are prepended to the\n     * timeline. An individual event therefore stays at the same index in the array\n     * relative to the base index (although note that a given event's index may\n     * well be less than the base index, thus giving that event a negative relative\n     * index).\n     */\n    public getBaseIndex(): number {\n        return this.baseIndex;\n    }\n\n    /**\n     * Get the list of events in this context\n     *\n     * @returns An array of MatrixEvents\n     */\n    public getEvents(): MatrixEvent[] {\n        return this.events;\n    }\n\n    /**\n     * Get the room state at the start/end of the timeline\n     *\n     * @param direction -   EventTimeline.BACKWARDS to get the state at the\n     *   start of the timeline; EventTimeline.FORWARDS to get the state at the end\n     *   of the timeline.\n     *\n     * @returns state at the start/end of the timeline\n     */\n    public getState(direction: Direction): RoomState | undefined {\n        if (direction == EventTimeline.BACKWARDS) {\n            return this.startState;\n        } else if (direction == EventTimeline.FORWARDS) {\n            return this.endState;\n        } else {\n            throw new Error(\"Invalid direction '\" + direction + \"'\");\n        }\n    }\n\n    /**\n     * Get a pagination token\n     *\n     * @param direction - EventTimeline.BACKWARDS to get the pagination\n     *   token for going backwards in time; EventTimeline.FORWARDS to get the\n     *   pagination token for going forwards in time.\n     *\n     * @returns pagination token\n     */\n    public getPaginationToken(direction: Direction): string | null {\n        if (this.roomId) {\n            return this.getState(direction)!.paginationToken;\n        } else if (direction === Direction.Backward) {\n            return this.startToken;\n        } else {\n            return this.endToken;\n        }\n    }\n\n    /**\n     * Set a pagination token\n     *\n     * @param token -       pagination token\n     *\n     * @param direction -    EventTimeline.BACKWARDS to set the pagination\n     *   token for going backwards in time; EventTimeline.FORWARDS to set the\n     *   pagination token for going forwards in time.\n     */\n    public setPaginationToken(token: string | null, direction: Direction): void {\n        if (this.roomId) {\n            this.getState(direction)!.paginationToken = token;\n        } else if (direction === Direction.Backward) {\n            this.startToken = token;\n        } else {\n            this.endToken = token;\n        }\n    }\n\n    /**\n     * Get the next timeline in the series\n     *\n     * @param direction - EventTimeline.BACKWARDS to get the previous\n     *   timeline; EventTimeline.FORWARDS to get the next timeline.\n     *\n     * @returns previous or following timeline, if they have been\n     * joined up.\n     */\n    public getNeighbouringTimeline(direction: Direction): EventTimeline | null {\n        if (direction == EventTimeline.BACKWARDS) {\n            return this.prevTimeline;\n        } else if (direction == EventTimeline.FORWARDS) {\n            return this.nextTimeline;\n        } else {\n            throw new Error(\"Invalid direction '\" + direction + \"'\");\n        }\n    }\n\n    /**\n     * Set the next timeline in the series\n     *\n     * @param neighbour - previous/following timeline\n     *\n     * @param direction - EventTimeline.BACKWARDS to set the previous\n     *   timeline; EventTimeline.FORWARDS to set the next timeline.\n     *\n     * @throws Error if an attempt is made to set the neighbouring timeline when\n     * it is already set.\n     */\n    public setNeighbouringTimeline(neighbour: EventTimeline, direction: Direction): void {\n        if (this.getNeighbouringTimeline(direction)) {\n            throw new Error(\n                \"timeline already has a neighbouring timeline - \" +\n                    \"cannot reset neighbour (direction: \" +\n                    direction +\n                    \")\",\n            );\n        }\n\n        if (direction == EventTimeline.BACKWARDS) {\n            this.prevTimeline = neighbour;\n        } else if (direction == EventTimeline.FORWARDS) {\n            this.nextTimeline = neighbour;\n        } else {\n            throw new Error(\"Invalid direction '\" + direction + \"'\");\n        }\n\n        // make sure we don't try to paginate this timeline\n        this.setPaginationToken(null, direction);\n    }\n\n    /**\n     * Add a new event to the timeline, and update the state\n     *\n     * @param event - new event\n     * @param options - addEvent options\n     */\n    public addEvent(\n        event: MatrixEvent,\n        { toStartOfTimeline, roomState, timelineWasEmpty, addToState }: IAddEventOptions,\n    ): void {\n        if (!roomState) {\n            roomState = toStartOfTimeline ? this.startState : this.endState;\n        }\n\n        const timelineSet = this.getTimelineSet();\n\n        if (timelineSet.room) {\n            EventTimeline.setEventMetadata(event, roomState!, toStartOfTimeline);\n\n            // modify state but only on unfiltered timelineSets\n            if (addToState && event.isState() && timelineSet.room.getUnfilteredTimelineSet() === timelineSet) {\n                roomState?.setStateEvents([event], { timelineWasEmpty });\n                // it is possible that the act of setting the state event means we\n                // can set more metadata (specifically sender/target props), so try\n                // it again if the prop wasn't previously set. It may also mean that\n                // the sender/target is updated (if the event set was a room member event)\n                // so we want to use the *updated* member (new avatar/name) instead.\n                //\n                // However, we do NOT want to do this on member events if we're going\n                // back in time, else we'll set the .sender value for BEFORE the given\n                // member event, whereas we want to set the .sender value for the ACTUAL\n                // member event itself.\n                if (!event.sender || (event.getType() === EventType.RoomMember && !toStartOfTimeline)) {\n                    EventTimeline.setEventMetadata(event, roomState!, toStartOfTimeline);\n                }\n            }\n        }\n\n        let insertIndex: number;\n\n        if (toStartOfTimeline) {\n            insertIndex = 0;\n        } else {\n            insertIndex = this.events.length;\n        }\n\n        this.events.splice(insertIndex, 0, event); // insert element\n        if (toStartOfTimeline) {\n            this.baseIndex++;\n        }\n    }\n\n    /**\n     * Insert a new event into the timeline, and update the state.\n     *\n     * TEMPORARY: until we have recursive relations, we need this function\n     * to exist to allow us to insert events in timeline order, which is our\n     * best guess for Sync Order.\n     * This is a copy of addEvent above, modified to allow inserting an event at\n     * a specific index.\n     *\n     * @internal\n     */\n    public insertEvent(event: MatrixEvent, insertIndex: number, roomState: RoomState, addToState: boolean): void {\n        const timelineSet = this.getTimelineSet();\n\n        if (timelineSet.room) {\n            EventTimeline.setEventMetadata(event, roomState, false);\n\n            // modify state but only on unfiltered timelineSets\n            if (addToState && event.isState() && timelineSet.room.getUnfilteredTimelineSet() === timelineSet) {\n                roomState.setStateEvents([event], {});\n                // it is possible that the act of setting the state event means we\n                // can set more metadata (specifically sender/target props), so try\n                // it again if the prop wasn't previously set. It may also mean that\n                // the sender/target is updated (if the event set was a room member event)\n                // so we want to use the *updated* member (new avatar/name) instead.\n                //\n                // However, we do NOT want to do this on member events if we're going\n                // back in time, else we'll set the .sender value for BEFORE the given\n                // member event, whereas we want to set the .sender value for the ACTUAL\n                // member event itself.\n                if (!event.sender || event.getType() === EventType.RoomMember) {\n                    EventTimeline.setEventMetadata(event, roomState, false);\n                }\n            }\n        }\n\n        this.events.splice(insertIndex, 0, event); // insert element\n    }\n\n    /**\n     * Remove an event from the timeline\n     *\n     * @param eventId -  ID of event to be removed\n     * @returns removed event, or null if not found\n     */\n    public removeEvent(eventId: string): MatrixEvent | null {\n        for (let i = this.events.length - 1; i >= 0; i--) {\n            const ev = this.events[i];\n            if (ev.getId() == eventId) {\n                this.events.splice(i, 1);\n                if (i < this.baseIndex) {\n                    this.baseIndex--;\n                }\n                return ev;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Return a string to identify this timeline, for debugging\n     *\n     * @returns name for this timeline\n     */\n    public toString(): string {\n        return this.name;\n    }\n}\n","/*\nCopyright 2015 - 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n/**\n * This is an internal module. See {@link MatrixEvent} and {@link RoomEvent} for\n * the public classes.\n */\n\nimport { type ExtensibleEvent, ExtensibleEvents } from \"matrix-events-sdk\";\n\nimport { logger } from \"../logger.ts\";\nimport {\n    EVENT_VISIBILITY_CHANGE_TYPE,\n    EventType,\n    type MsgType,\n    RelationType,\n    ToDeviceMessageId,\n    UNSIGNED_THREAD_ID_FIELD,\n    UNSIGNED_MEMBERSHIP_FIELD,\n} from \"../@types/event.ts\";\nimport { deepSortedObjectEntries, internaliseString } from \"../utils.ts\";\nimport { type RoomMember } from \"./room-member.ts\";\nimport { type Thread, THREAD_RELATION_TYPE, ThreadEvent, type ThreadEventHandlerMap } from \"./thread.ts\";\nimport { type IActionsObject } from \"../pushprocessor.ts\";\nimport { TypedReEmitter } from \"../ReEmitter.ts\";\nimport { type MatrixError } from \"../http-api/index.ts\";\nimport { TypedEventEmitter } from \"./typed-event-emitter.ts\";\nimport { type EventStatus } from \"./event-status.ts\";\nimport { type CryptoBackend, DecryptionError, type EventDecryptionResult } from \"../common-crypto/CryptoBackend.ts\";\nimport { type IAnnotatedPushRule } from \"../@types/PushRules.ts\";\nimport { type Room } from \"./room.ts\";\nimport { EventTimeline } from \"./event-timeline.ts\";\nimport { type Membership } from \"../@types/membership.ts\";\nimport { DecryptionFailureCode } from \"../crypto-api/index.ts\";\nimport { type RoomState } from \"./room-state.ts\";\nimport { type EmptyObject } from \"../@types/common.ts\";\n\nexport { EventStatus } from \"./event-status.ts\";\n\n/* eslint-disable camelcase */\nexport interface IContent {\n    [key: string]: any;\n    \"msgtype\"?: MsgType | string;\n    \"membership\"?: Membership;\n    \"avatar_url\"?: string;\n    \"displayname\"?: string;\n    \"m.relates_to\"?: IEventRelation;\n\n    \"m.mentions\"?: IMentions;\n}\n\ntype StrippedState = Required<Pick<IEvent, \"content\" | \"state_key\" | \"type\" | \"sender\">>;\n\nexport interface IUnsigned {\n    [key: string]: any;\n    \"age\"?: number;\n    \"prev_sender\"?: string;\n    \"prev_content\"?: IContent;\n    \"redacted_because\"?: IEvent;\n    \"replaces_state\"?: string;\n    \"transaction_id\"?: string;\n    \"invite_room_state\"?: StrippedState[];\n    \"m.relations\"?: Record<RelationType | string, any>; // No common pattern for aggregated relations\n    \"msc4354_sticky_duration_ttl_ms\"?: number;\n    [UNSIGNED_THREAD_ID_FIELD.name]?: string;\n    \"membership\"?: Membership;\n    \"io.element.msc4115.membership\"?: Membership;\n}\n\nexport interface IThreadBundledRelationship {\n    latest_event: IEvent;\n    count: number;\n    current_user_participated?: boolean;\n}\n\nexport interface IEvent {\n    event_id: string;\n    type: string;\n    content: IContent;\n    sender: string;\n    room_id?: string;\n    origin_server_ts: number;\n    txn_id?: string;\n    state_key?: string;\n    membership?: Membership;\n    unsigned: IUnsigned;\n    redacts?: string;\n    msc4354_sticky?: { duration_ms: number };\n}\n\nexport interface IAggregatedRelation {\n    origin_server_ts: number;\n    event_id?: string;\n    sender?: string;\n    type?: string;\n    count?: number;\n    key?: string;\n}\n\nexport interface IEventRelation {\n    \"rel_type\"?: RelationType | string;\n    \"event_id\"?: string;\n    \"is_falling_back\"?: boolean;\n    \"m.in_reply_to\"?: {\n        event_id?: string;\n    };\n    \"key\"?: string;\n}\n\nexport interface IMentions {\n    user_ids?: string[];\n    room?: boolean;\n}\n\nexport interface PushDetails {\n    rule?: IAnnotatedPushRule;\n    actions?: IActionsObject;\n}\n\n/**\n * When an event is a visibility change event, as per MSC3531,\n * the visibility change implied by the event.\n */\nexport interface IVisibilityChange {\n    /**\n     * If `true`, the target event should be made visible.\n     * Otherwise, it should be hidden.\n     */\n    visible: boolean;\n\n    /**\n     * The event id affected.\n     */\n    eventId: string;\n\n    /**\n     * Optionally, a human-readable reason explaining why\n     * the event was hidden. Ignored if the event was made\n     * visible.\n     */\n    reason: string | null;\n}\n\nexport interface IMarkedUnreadEvent {\n    unread: boolean;\n}\n\nexport interface IClearEvent {\n    room_id?: string;\n    type: string;\n    state_key?: string;\n    content: Omit<IContent, \"membership\" | \"avatar_url\" | \"displayname\" | \"m.relates_to\">;\n    unsigned?: IUnsigned;\n}\n/* eslint-enable camelcase */\n\ninterface IKeyRequestRecipient {\n    userId: string;\n    deviceId: \"*\" | string;\n}\n\nexport interface IDecryptOptions {\n    /** Whether to emit {@link MatrixEventEvent.Decrypted} events on successful decryption. Defaults to true.\n     */\n    emit?: boolean;\n\n    /**\n     * True if this is a retry, after receiving an update to the session key. (Enables more logging.)\n     *\n     * This is only intended for use within the js-sdk.\n     *\n     * @internal\n     */\n    isRetry?: boolean;\n\n    /**\n     * @deprecated does nothing\n     */\n    forceRedecryptIfUntrusted?: boolean;\n}\n\n/**\n * Message hiding, as specified by https://github.com/matrix-org/matrix-doc/pull/3531.\n */\nexport type MessageVisibility = IMessageVisibilityHidden | IMessageVisibilityVisible;\n/**\n * Variant of `MessageVisibility` for the case in which the message should be displayed.\n */\nexport interface IMessageVisibilityVisible {\n    readonly visible: true;\n}\n/**\n * Variant of `MessageVisibility` for the case in which the message should be hidden.\n */\nexport interface IMessageVisibilityHidden {\n    readonly visible: false;\n    /**\n     * Optionally, a human-readable reason to show to the user indicating why the\n     * message has been hidden (e.g. \"Message Pending Moderation\").\n     */\n    readonly reason: string | null;\n}\n// A singleton implementing `IMessageVisibilityVisible`.\nconst MESSAGE_VISIBLE: IMessageVisibilityVisible = Object.freeze({ visible: true });\nexport const MAX_STICKY_DURATION_MS = 3600000;\n\nexport enum MatrixEventEvent {\n    /**\n     * An event has been decrypted, or we have failed to decrypt it.\n     *\n     * The payload consists of:\n     *\n     *  * `event` - The {@link MatrixEvent} which we attempted to decrypt.\n     *\n     *  * `err` - The error that occurred during decryption, or `undefined` if no error occurred.\n     *     Avoid use of this: {@link MatrixEvent.decryptionFailureReason} is more useful.\n     */\n    Decrypted = \"Event.decrypted\",\n\n    BeforeRedaction = \"Event.beforeRedaction\",\n    VisibilityChange = \"Event.visibilityChange\",\n    LocalEventIdReplaced = \"Event.localEventIdReplaced\",\n    Status = \"Event.status\",\n    Replaced = \"Event.replaced\",\n    RelationsCreated = \"Event.relationsCreated\",\n    SentinelUpdated = \"Event.sentinelUpdated\",\n}\n\nexport type MatrixEventEmittedEvents = MatrixEventEvent | ThreadEvent.Update;\n\nexport type MatrixEventHandlerMap = {\n    [MatrixEventEvent.Decrypted]: (event: MatrixEvent, err?: Error) => void;\n    [MatrixEventEvent.BeforeRedaction]: (event: MatrixEvent, redactionEvent: MatrixEvent) => void;\n    [MatrixEventEvent.VisibilityChange]: (event: MatrixEvent, visible: boolean) => void;\n    [MatrixEventEvent.LocalEventIdReplaced]: (event: MatrixEvent) => void;\n    [MatrixEventEvent.Status]: (event: MatrixEvent, status: EventStatus | null) => void;\n    [MatrixEventEvent.Replaced]: (event: MatrixEvent) => void;\n    [MatrixEventEvent.RelationsCreated]: (relationType: string, eventType: string) => void;\n    [MatrixEventEvent.SentinelUpdated]: () => void;\n} & Pick<ThreadEventHandlerMap, ThreadEvent.Update>;\n\nexport class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, MatrixEventHandlerMap> {\n    // applied push rule and action for this event\n    private pushDetails: PushDetails = {};\n    private _replacingEvent: MatrixEvent | null = null;\n    private _localRedactionEvent: MatrixEvent | null = null;\n    private _isCancelled = false;\n    private clearEvent?: IClearEvent;\n\n    /* Message hiding, as specified by https://github.com/matrix-org/matrix-doc/pull/3531.\n\n    Note: We're returning this object, so any value stored here MUST be frozen.\n    */\n    private visibility: MessageVisibility = MESSAGE_VISIBLE;\n\n    // Not all events will be extensible-event compatible, so cache a flag in\n    // addition to a falsy cached event value. We check the flag later on in\n    // a public getter to decide if the cache is valid.\n    private _hasCachedExtEv = false;\n    private _cachedExtEv?: ExtensibleEvent = undefined;\n\n    /** If we failed to decrypt this event, the reason for the failure. Otherwise, `null`. */\n    private _decryptionFailureReason: DecryptionFailureCode | null = null;\n\n    /* curve25519 key which we believe belongs to the sender of the event. See\n     * getSenderKey()\n     */\n    private senderCurve25519Key: string | null = null;\n\n    /* ed25519 key which the sender of this event (for olm) or the creator of\n     * the megolm session (for megolm) claims to own. See getClaimedEd25519Key()\n     */\n    private claimedEd25519Key: string | null = null;\n\n    /**\n     * If another user forwarded the key to this message\n     * (eg via [MSC4268](https://github.com/matrix-org/matrix-spec-proposals/pull/4268)),\n     * the ID of that user.\n     */\n    private keyForwardedBy?: string;\n\n    /* if we have a process decrypting this event, a Promise which resolves\n     * when it is finished. Normally null.\n     */\n    private decryptionPromise: Promise<void> | null = null;\n\n    /* flag to indicate if we should retry decrypting this event after the\n     * first attempt (eg, we have received new data which means that a second\n     * attempt may succeed)\n     */\n    private retryDecryption = false;\n\n    /* The txnId with which this event was sent if it was during this session,\n     * allows for a unique ID which does not change when the event comes back down sync.\n     */\n    private txnId?: string;\n\n    /**\n     * A reference to the thread this event belongs to\n     */\n    private thread?: Thread;\n    private threadId?: string;\n\n    /* Set an approximate timestamp for the event relative the local clock.\n     * This will inherently be approximate because it doesn't take into account\n     * the time between the server putting the 'age' field on the event as it sent\n     * it to us and the time we're now constructing this event, but that's better\n     * than assuming the local clock is in sync with the origin HS's clock.\n     */\n    public localTimestamp: number;\n\n    /**\n     * The room member who sent this event, or null e.g.\n     * this is a presence event. This is only guaranteed to be set for events that\n     * appear in a timeline, ie. do not guarantee that it will be set on state\n     * events.\n     * @privateRemarks\n     * Should be read-only\n     */\n    public sender: RoomMember | null = null;\n\n    /**\n     * The room member who is the target of this event, e.g.\n     * the invitee, the person being banned, etc.\n     * @privateRemarks\n     * Should be read-only\n     */\n    public target: RoomMember | null = null;\n\n    /**\n     * Update the sentinels and forwardLooking flag for this event.\n     * @param stateContext -  the room state to be queried\n     * @param toStartOfTimeline -  if true the event's forwardLooking flag is set false\n     * @internal\n     */\n    public setMetadata(stateContext: RoomState, toStartOfTimeline: boolean): void {\n        // If an event is an m.room.member state event then we should set the sentinels again in case setEventMetadata\n        // was already called before the state was applied and thus the sentinel points at the member from before this event.\n        const affectsSelf =\n            this.isState() && this.getType() === EventType.RoomMember && this.getSender() === this.getStateKey();\n\n        let changed = false;\n        // When we try to generate a sentinel member before we have that member\n        // in the members object, we still generate a sentinel but it doesn't\n        // have a membership event, so test to see if events.member is set. We\n        // check this to avoid overriding non-sentinel members by sentinel ones\n        // when adding the event to a filtered timeline\n        if (affectsSelf || !this.sender?.events?.member) {\n            const newSender = stateContext.getSentinelMember(this.getSender()!);\n            if (newSender !== this.sender) changed = true;\n            this.sender = newSender;\n        }\n        if (affectsSelf || (!this.target?.events?.member && this.getType() === EventType.RoomMember)) {\n            const newTarget = stateContext.getSentinelMember(this.getStateKey()!);\n            if (newTarget !== this.target) changed = true;\n            this.target = newTarget;\n        }\n\n        if (this.isState()) {\n            // room state has no concept of 'old' or 'current', but we want the\n            // room state to regress back to previous values if toStartOfTimeline\n            // is set, which means inspecting prev_content if it exists. This\n            // is done by toggling the forwardLooking flag.\n            if (toStartOfTimeline) {\n                this.forwardLooking = false;\n            }\n        }\n\n        if (changed) {\n            this.emit(MatrixEventEvent.SentinelUpdated);\n        }\n    }\n\n    /**\n     * The sending status of the event.\n     * @privateRemarks\n     * Should be read-only\n     */\n    public status: EventStatus | null = null;\n    /**\n     * most recent error associated with sending the event, if any\n     * @privateRemarks\n     * Should be read-only. May not be a MatrixError.\n     */\n    public error: MatrixError | null = null;\n    /**\n     * True if this event is 'forward looking', meaning\n     * that getDirectionalContent() will return event.content and not event.prev_content.\n     * Only state events may be backwards looking\n     * Default: true. <strong>This property is experimental and may change.</strong>\n     * @privateRemarks\n     * Should be read-only\n     */\n    public forwardLooking = true;\n\n    private readonly reEmitter: TypedReEmitter<MatrixEventEmittedEvents, MatrixEventHandlerMap>;\n\n    /**\n     * The timestamp for when this event should expire, in milliseconds.\n     * Prefers using the server-provided value, but will fall back to local calculation.\n     *\n     * This value is **safe** to use, as malicious start time and duration are appropriately capped.\n     *\n     * If the event is not a sticky event (or not supported by the server),\n     * then this returns `undefined`.\n     */\n    public readonly unstableStickyExpiresAt: number | undefined;\n\n    /**\n     * Construct a Matrix Event object\n     *\n     * @param event - The raw (possibly encrypted) event. <b>Do not access\n     * this property</b> directly unless you absolutely have to. Prefer the getter\n     * methods defined on this class. Using the getter methods shields your app\n     * from changes to event JSON between Matrix versions.\n     */\n    public constructor(public event: Partial<IEvent> = {}) {\n        super();\n\n        // intern the values of matrix events to force share strings and reduce the\n        // amount of needless string duplication. This can save moderate amounts of\n        // memory (~10% on a 350MB heap).\n        // 'membership' at the event level (rather than the content level) is a legacy\n        // field that Element never otherwise looks at, but it will still take up a lot\n        // of space if we don't intern it.\n        ([\"state_key\", \"type\", \"sender\", \"room_id\", \"membership\"] as const).forEach((prop) => {\n            if (typeof event[prop] !== \"string\") return;\n            event[prop] = internaliseString(event[prop]!);\n        });\n\n        ([\"membership\", \"avatar_url\", \"displayname\"] as const).forEach((prop) => {\n            if (typeof event.content?.[prop] !== \"string\") return;\n            event.content[prop] = internaliseString(event.content[prop]!);\n        });\n\n        ([\"rel_type\"] as const).forEach((prop) => {\n            if (typeof event.content?.[\"m.relates_to\"]?.[prop] !== \"string\") return;\n            event.content[\"m.relates_to\"][prop] = internaliseString(event.content[\"m.relates_to\"][prop]!);\n        });\n\n        this.txnId = event.txn_id;\n        // The localTimestamp is calculated using the age.\n        // Some events lack an `age` property, either because they are EDUs such as typing events,\n        // or due to server-side bugs such as https://github.com/matrix-org/synapse/issues/8429.\n        // The fallback in these cases will be to use the origin_server_ts.\n        // For EDUs, the origin_server_ts also is not defined so we use Date.now().\n        const age = this.getAge();\n        const now = Date.now();\n        this.localTimestamp = age !== undefined ? now - age : (this.getTs() ?? now);\n        this.reEmitter = new TypedReEmitter(this);\n        if (this.unstableStickyInfo) {\n            if (this.unstableStickyInfo.duration_ttl_ms) {\n                this.unstableStickyExpiresAt = now + this.unstableStickyInfo.duration_ttl_ms;\n            } else {\n                // Bound the timestamp so it doesn't come from the future.\n                this.unstableStickyExpiresAt = Math.min(now, this.getTs()) + this.unstableStickyInfo.duration_ms;\n            }\n        }\n    }\n\n    /**\n     * Unstable getter to try and get an extensible event. Note that this might\n     * return a falsy value if the event could not be parsed as an extensible\n     * event.\n     *\n     * @deprecated Use stable functions where possible.\n     */\n    public get unstableExtensibleEvent(): ExtensibleEvent | undefined {\n        if (!this._hasCachedExtEv) {\n            this._cachedExtEv = ExtensibleEvents.parse(this.getEffectiveEvent()) ?? undefined;\n        }\n        return this._cachedExtEv;\n    }\n\n    private invalidateExtensibleEvent(): void {\n        // just reset the flag - that'll trick the getter into parsing a new event\n        this._hasCachedExtEv = false;\n    }\n\n    /**\n     * Gets the event as it would appear if it had been sent unencrypted.\n     *\n     * If the event is encrypted, we attempt to mock up an event as it would have looked had the sender not encrypted it.\n     * If the event is not encrypted, a copy of it is simply returned as-is.\n     *\n     * @returns A shallow copy of the event, in wire format, as it would have been had it not been encrypted.\n     */\n    public getEffectiveEvent(): IEvent {\n        const content = Object.assign({}, this.getContent()); // clone for mutation\n\n        if (this.getWireType() === EventType.RoomMessageEncrypted) {\n            // Encrypted events sometimes aren't symmetrical on the `content` so we'll copy\n            // that over too, but only for missing properties. We don't copy over mismatches\n            // between the plain and decrypted copies of `content` because we assume that the\n            // app is relying on the decrypted version, so we want to expose that as a source\n            // of truth here too.\n            for (const [key, value] of Object.entries(this.getWireContent())) {\n                // Skip fields from the encrypted event schema though - we don't want to leak\n                // these.\n                if ([\"algorithm\", \"ciphertext\", \"device_id\", \"sender_key\", \"session_id\"].includes(key)) {\n                    continue;\n                }\n\n                if (content[key] === undefined) content[key] = value;\n            }\n        }\n\n        // clearEvent doesn't have all the fields, so we'll copy what we can from this.event.\n        // We also copy over our \"fixed\" content key.\n        return Object.assign({}, this.event, this.clearEvent, { content }) as IEvent;\n    }\n\n    /**\n     * Get the event_id for this event.\n     * @returns The event ID, e.g. <code>$143350589368169JsLZx:localhost\n     * </code>\n     */\n    public getId(): string | undefined {\n        return this.event.event_id;\n    }\n\n    /**\n     * Get the user_id for this event.\n     * @returns The user ID, e.g. `@alice:matrix.org`\n     */\n    public getSender(): string | undefined {\n        return this.event.sender; // v2 / v1\n    }\n\n    /**\n     * Get the (decrypted, if necessary) type of event.\n     *\n     * @returns The event type, e.g. `m.room.message`\n     */\n    public getType(): EventType | string {\n        if (this.clearEvent) {\n            return this.clearEvent.type;\n        }\n        return this.event.type!;\n    }\n\n    /**\n     * Get the (possibly encrypted) type of the event that will be sent to the\n     * homeserver.\n     *\n     * @returns The event type.\n     */\n    public getWireType(): EventType | string {\n        return this.event.type!;\n    }\n\n    /**\n     * Get the room_id for this event. This will return `undefined`\n     * for `m.presence` events.\n     * @returns The room ID, e.g. <code>!cURbafjkfsMDVwdRDQ:matrix.org\n     * </code>\n     */\n    public getRoomId(): string | undefined {\n        return this.event.room_id;\n    }\n\n    /**\n     * Get the timestamp of this event.\n     * @returns The event timestamp, e.g. `1433502692297`\n     */\n    public getTs(): number {\n        return this.event.origin_server_ts!;\n    }\n\n    /**\n     * Get the timestamp of this event, as a Date object.\n     * @returns The event date, e.g. `new Date(1433502692297)`\n     */\n    public getDate(): Date | null {\n        return this.event.origin_server_ts ? new Date(this.event.origin_server_ts) : null;\n    }\n\n    /**\n     * Get a string containing details of this event\n     *\n     * This is intended for logging, to help trace errors. Example output:\n     *\n     * @example\n     * ```\n     * id=$HjnOHV646n0SjLDAqFrgIjim7RCpB7cdMXFrekWYAn type=m.room.encrypted\n     * sender=@user:example.com room=!room:example.com ts=2022-10-25T17:30:28.404Z\n     * ```\n     */\n    public getDetails(): string {\n        const room = this.getRoomId();\n        if (room) {\n            // in-room event\n            return `id=${this.getId()} type=${this.getWireType()} sender=${this.getSender()} room=${room} ts=${this.getDate()?.toISOString()}`;\n        } else {\n            // to-device event\n            const msgid = this.getContent()[ToDeviceMessageId];\n            return `msgid=${msgid} type=${this.getWireType()} sender=${this.getSender()}`;\n        }\n    }\n\n    /**\n     * Get the (decrypted, if necessary) event content JSON, even if the event\n     * was replaced by another event.\n     *\n     * @returns The event content JSON, or an empty object.\n     */\n    public getOriginalContent<T = IContent>(): T {\n        if (this._localRedactionEvent) {\n            return {} as T;\n        }\n        if (this.clearEvent) {\n            return (this.clearEvent.content ?? {}) as T;\n        }\n        return (this.event.content ?? {}) as T;\n    }\n\n    /**\n     * Get the (decrypted, if necessary) event content JSON,\n     * or the content from the replacing event, if any.\n     * See `makeReplaced`.\n     *\n     * @returns The event content JSON, or an empty object.\n     */\n    public getContent<T extends IContent = IContent>(): T {\n        if (this._localRedactionEvent) {\n            return {} as T;\n        } else if (this._replacingEvent) {\n            return this._replacingEvent.getContent()[\"m.new_content\"] ?? {};\n        } else {\n            return this.getOriginalContent();\n        }\n    }\n\n    /**\n     * Get the (possibly encrypted) event content JSON that will be sent to the\n     * homeserver.\n     *\n     * @returns The event content JSON, or an empty object.\n     */\n    public getWireContent(): IContent {\n        return this.event.content || {};\n    }\n\n    /**\n     * Get the event ID of the thread head\n     */\n    public get threadRootId(): string | undefined {\n        // don't allow state events to be threaded as per the spec\n        if (this.isState()) {\n            return undefined;\n        }\n        const relatesTo = this.getWireContent()?.[\"m.relates_to\"];\n        if (relatesTo?.rel_type === THREAD_RELATION_TYPE.name) {\n            return relatesTo.event_id;\n        }\n        if (this.thread) {\n            return this.thread.id;\n        }\n        if (this.threadId !== undefined) {\n            return this.threadId;\n        }\n        const unsigned = this.getUnsigned();\n        if (typeof unsigned[UNSIGNED_THREAD_ID_FIELD.name] === \"string\") {\n            return unsigned[UNSIGNED_THREAD_ID_FIELD.name];\n        }\n        return undefined;\n    }\n\n    /**\n     * A helper to check if an event is a thread's head or not\n     */\n    public get isThreadRoot(): boolean {\n        // don't allow state events to be threaded as per the spec\n        if (this.isState()) {\n            return false;\n        }\n\n        const threadDetails = this.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);\n\n        // Bundled relationships only returned when the sync response is limited\n        // hence us having to check both bundled relation and inspect the thread\n        // model\n        return !!threadDetails || this.threadRootId === this.getId();\n    }\n\n    public get replyEventId(): string | undefined {\n        return this.getWireContent()[\"m.relates_to\"]?.[\"m.in_reply_to\"]?.event_id;\n    }\n\n    public get relationEventId(): string | undefined {\n        return this.getWireContent()?.[\"m.relates_to\"]?.event_id;\n    }\n\n    /**\n     * Get the previous event content JSON. This will only return something for\n     * state events which exist in the timeline.\n     * @returns The previous event content JSON, or an empty object.\n     */\n    public getPrevContent(): IContent {\n        // v2 then v1 then default\n        return this.getUnsigned().prev_content || {};\n    }\n\n    /**\n     * Get either 'content' or 'prev_content' depending on if this event is\n     * 'forward-looking' or not. This can be modified via event.forwardLooking.\n     * In practice, this means we get the chronologically earlier content value\n     * for this event (this method should surely be called getEarlierContent)\n     * <strong>This method is experimental and may change.</strong>\n     * @returns event.content if this event is forward-looking, else\n     * event.prev_content.\n     */\n    public getDirectionalContent(): IContent {\n        return this.forwardLooking ? this.getContent() : this.getPrevContent();\n    }\n\n    /**\n     * Get the age of this event. This represents the age of the event when the\n     * event arrived at the device, and not the age of the event when this\n     * function was called.\n     * Can only be returned once the server has echo'ed back\n     * @returns The age of this event in milliseconds.\n     */\n    public getAge(): number | undefined {\n        return this.getUnsigned().age;\n    }\n\n    /**\n     * Get the age of the event when this function was called.\n     * This is the 'age' field adjusted according to how long this client has\n     * had the event.\n     * @returns The age of this event in milliseconds.\n     */\n    public getLocalAge(): number {\n        return Date.now() - this.localTimestamp;\n    }\n\n    /**\n     * Get the event state_key if it has one. If necessary, this will perform\n     * string-unpacking on the state key, as per MSC4362. This will return\n     * <code>undefined</code> for message events.\n     * @returns The event's `state_key`.\n     */\n    public getStateKey(): string | undefined {\n        if (this.clearEvent) {\n            return this.clearEvent.state_key;\n        }\n        return this.event.state_key;\n    }\n\n    /**\n     * Get the raw event state_key if it has one. This may be string-packed as per\n     * MSC4362 if the state event is encrypted. This will return <code>undefined\n     * </code> for message events.\n     * @returns The event's `state_key`.\n     */\n    public getWireStateKey(): string | undefined {\n        return this.event.state_key;\n    }\n\n    /**\n     * Check if this event is a state event.\n     * @returns True if this is a state event.\n     */\n    public isState(): boolean {\n        return this.event.state_key !== undefined;\n    }\n\n    /**\n     * Get the user's room membership at the time the event was sent, as reported\n     * by the server.  This uses MSC4115.\n     *\n     * @returns The user's room membership, or `undefined` if the server does\n     *   not report it.\n     */\n    public getMembershipAtEvent(): Membership | string | undefined {\n        const unsigned = this.getUnsigned();\n        return UNSIGNED_MEMBERSHIP_FIELD.findIn<Membership>(unsigned);\n    }\n\n    /**\n     * Replace the content of this event with encrypted versions.\n     * (This is used when sending an event; it should not be used by applications).\n     *\n     * @internal\n     *\n     * @param cryptoType - type of the encrypted event - typically\n     * <tt>\"m.room.encrypted\"</tt>\n     *\n     * @param cryptoContent - raw 'content' for the encrypted event.\n     *\n     * @param senderCurve25519Key - curve25519 key to record for the\n     *   sender of this event.\n     *   See {@link MatrixEvent#getSenderKey}.\n     *\n     * @param claimedEd25519Key - claimed ed25519 key to record for the\n     *   sender if this event.\n     *   See {@link MatrixEvent#getClaimedEd25519Key}\n     */\n    public makeEncrypted(\n        cryptoType: string,\n        cryptoContent: object,\n        senderCurve25519Key: string,\n        claimedEd25519Key: string,\n    ): void {\n        // keep the plain-text data for 'view source'\n        this.clearEvent = {\n            type: this.event.type!,\n            content: this.event.content!,\n            state_key: this.event.state_key,\n        };\n        this.event.type = cryptoType;\n        this.event.content = cryptoContent;\n        this.senderCurve25519Key = senderCurve25519Key;\n        this.claimedEd25519Key = claimedEd25519Key;\n\n        // if this is a state event, pack cleartext type and statekey\n        if (this.isState()) {\n            this.event.state_key = `${this.clearEvent!.type}:${this.clearEvent!.state_key}`;\n        }\n    }\n\n    /**\n     * Check if this event is currently being decrypted.\n     *\n     * @returns True if this event is currently being decrypted, else false.\n     */\n    public isBeingDecrypted(): boolean {\n        return this.decryptionPromise != null;\n    }\n\n    public getDecryptionPromise(): Promise<void> | null {\n        return this.decryptionPromise;\n    }\n\n    /**\n     * Check if this event is an encrypted event which we failed to decrypt\n     *\n     * (This implies that we might retry decryption at some point in the future)\n     *\n     * @returns True if this event is an encrypted event which we\n     *     couldn't decrypt.\n     */\n    public isDecryptionFailure(): boolean {\n        return this._decryptionFailureReason !== null;\n    }\n\n    /** If we failed to decrypt this event, the reason for the failure. Otherwise, `null`. */\n    public get decryptionFailureReason(): DecryptionFailureCode | null {\n        return this._decryptionFailureReason;\n    }\n\n    public shouldAttemptDecryption(): boolean {\n        if (this.isRedacted()) return false;\n        if (this.isBeingDecrypted()) return false;\n        if (this.clearEvent) return false;\n        if (!this.isEncrypted()) return false;\n\n        return true;\n    }\n\n    /**\n     * Start the process of trying to decrypt this event.\n     *\n     * (This is used within the SDK: it isn't intended for use by applications)\n     *\n     * @internal\n     *\n     * @param crypto - crypto module\n     *\n     * @returns promise which resolves (to undefined) when the decryption\n     * attempt is completed.\n     */\n    public async attemptDecryption(crypto: CryptoBackend, options: IDecryptOptions = {}): Promise<void> {\n        // start with a couple of sanity checks.\n        if (!this.isEncrypted()) {\n            throw new Error(\"Attempt to decrypt event which isn't encrypted\");\n        }\n\n        const alreadyDecrypted = this.clearEvent && !this.isDecryptionFailure();\n        if (alreadyDecrypted) {\n            // we may want to just ignore this? let's start with rejecting it.\n            throw new Error(\"Attempt to decrypt event which has already been decrypted\");\n        }\n\n        // if we already have a decryption attempt in progress, then it may\n        // fail because it was using outdated info. We now have reason to\n        // succeed where it failed before, but we don't want to have multiple\n        // attempts going at the same time, so just set a flag that says we have\n        // new info.\n        //\n        if (this.decryptionPromise) {\n            logger.log(`Event ${this.getId()} already being decrypted; queueing a retry`);\n            this.retryDecryption = true;\n            return this.decryptionPromise;\n        }\n\n        this.decryptionPromise = this.decryptionLoop(crypto, options);\n        return this.decryptionPromise;\n    }\n\n    /**\n     * Calculate the recipients for keyshare requests.\n     *\n     * @param userId - the user who received this event.\n     *\n     * @returns array of recipients\n     */\n    public getKeyRequestRecipients(userId: string): IKeyRequestRecipient[] {\n        // send the request to all of our own devices\n        const recipients = [\n            {\n                userId,\n                deviceId: \"*\",\n            },\n        ];\n\n        return recipients;\n    }\n\n    private async decryptionLoop(crypto: CryptoBackend, options: IDecryptOptions = {}): Promise<void> {\n        // make sure that this method never runs completely synchronously.\n        // (doing so would mean that we would clear decryptionPromise *before*\n        // it is set in attemptDecryption - and hence end up with a stuck\n        // `decryptionPromise`).\n        await Promise.resolve();\n\n        // eslint-disable-next-line no-constant-condition\n        while (true) {\n            this.retryDecryption = false;\n\n            let err: Error | undefined = undefined;\n            try {\n                const res = await crypto.decryptEvent(this);\n                if (options.isRetry === true) {\n                    logger.info(`Decrypted event on retry (${this.getDetails()})`);\n                }\n                this.setClearData(res);\n                this._decryptionFailureReason = null;\n            } catch (e) {\n                const detailedError = e instanceof DecryptionError ? (<DecryptionError>e).detailedString : String(e);\n\n                err = e as Error;\n\n                // see if we have a retry queued.\n                //\n                // NB: make sure to keep this check in the same tick of the\n                //   event loop as `decryptionPromise = null` below - otherwise we\n                //   risk a race:\n                //\n                //   * A: we check retryDecryption here and see that it is\n                //        false\n                //   * B: we get a second call to attemptDecryption, which sees\n                //        that decryptionPromise is set so sets\n                //        retryDecryption\n                //   * A: we continue below, clear decryptionPromise, and\n                //        never do the retry.\n                //\n                if (this.retryDecryption) {\n                    // decryption error, but we have a retry queued.\n                    logger.log(`Error decrypting event (${this.getDetails()}), but retrying: ${detailedError}`);\n                    continue;\n                }\n\n                // decryption error, no retries queued. Warn about the error and\n                // set it to m.bad.encrypted.\n                //\n                // the detailedString already includes the name and message of the error, and the stack isn't much use,\n                // so we don't bother to log `e` separately.\n                logger.warn(`Error decrypting event (${this.getDetails()}): ${detailedError}`);\n\n                this.setClearDataForDecryptionFailure(String(e));\n                this._decryptionFailureReason =\n                    e instanceof DecryptionError ? (<DecryptionError>e).code : DecryptionFailureCode.UNKNOWN_ERROR;\n            }\n\n            // Make sure we clear 'decryptionPromise' before sending the 'Event.decrypted' event,\n            // otherwise the app will be confused to see `isBeingDecrypted` still set when\n            // there isn't an `Event.decrypted` on the way.\n            //\n            // see also notes on retryDecryption above.\n            //\n            this.decryptionPromise = null;\n            this.retryDecryption = false;\n\n            // Before we emit the event, clear the push actions so that they can be recalculated\n            // by relevant code. We do this because the clear event has now changed, making it\n            // so that existing rules can be re-run over the applicable properties. Stuff like\n            // highlighting when the user's name is mentioned rely on this happening. We also want\n            // to set the push actions before emitting so that any notification listeners don't\n            // pick up the wrong contents.\n            this.setPushDetails();\n\n            if (options.emit !== false) {\n                this.emit(MatrixEventEvent.Decrypted, this, err);\n            }\n\n            return;\n        }\n    }\n\n    /**\n     * Update the cleartext data on this event.\n     *\n     * (This is used after decrypting an event; it should not be used by applications).\n     *\n     * @internal\n     *\n     * @param decryptionResult - the decryption result, including the plaintext and some key info\n     */\n    private setClearData(decryptionResult: EventDecryptionResult): void {\n        this.clearEvent = decryptionResult.clearEvent;\n        this.senderCurve25519Key = decryptionResult.senderCurve25519Key ?? null;\n        this.claimedEd25519Key = decryptionResult.claimedEd25519Key ?? null;\n        this.keyForwardedBy = decryptionResult.keyForwardedBy;\n        this.invalidateExtensibleEvent();\n    }\n\n    /**\n     * Update the cleartext data on this event after a decryption failure.\n     *\n     * @param reason - the textual reason for the failure\n     */\n    private setClearDataForDecryptionFailure(reason: string): void {\n        this.clearEvent = {\n            type: EventType.RoomMessage,\n            content: {\n                msgtype: \"m.bad.encrypted\",\n                body: `** Unable to decrypt: ${reason} **`,\n            },\n        };\n        this.senderCurve25519Key = null;\n        this.claimedEd25519Key = null;\n        this.invalidateExtensibleEvent();\n    }\n\n    /**\n     * Gets the cleartext content for this event. If the event is not encrypted,\n     * or encryption has not been completed, this will return null.\n     *\n     * @returns The cleartext (decrypted) content for the event\n     */\n    public getClearContent(): IContent | null {\n        return this.clearEvent ? this.clearEvent.content : null;\n    }\n\n    /**\n     * Check if the event is encrypted.\n     * @returns True if this event is encrypted.\n     */\n    public isEncrypted(): boolean {\n        return this.event.type === EventType.RoomMessageEncrypted;\n    }\n\n    /**\n     * The curve25519 key for the device that we think sent this event\n     *\n     * For an Olm-encrypted event, this is inferred directly from the DH\n     * exchange at the start of the session: the curve25519 key is involved in\n     * the DH exchange, so only a device which holds the private part of that\n     * key can establish such a session.\n     *\n     * For a megolm-encrypted event, it is inferred from the Olm message which\n     * established the megolm session\n     */\n    public getSenderKey(): string | null {\n        return this.senderCurve25519Key;\n    }\n\n    /**\n     * The additional keys the sender of this encrypted event claims to possess.\n     *\n     * Just a wrapper for #getClaimedEd25519Key (q.v.)\n     */\n    public getKeysClaimed(): Partial<Record<\"ed25519\", string>> {\n        if (!this.claimedEd25519Key) return {};\n\n        return {\n            ed25519: this.claimedEd25519Key,\n        };\n    }\n\n    /**\n     * Get the ed25519 the sender of this event claims to own.\n     *\n     * For Olm messages, this claim is encoded directly in the plaintext of the\n     * event itself. For megolm messages, it is implied by the m.room_key event\n     * which established the megolm session.\n     *\n     * Until we download the device list of the sender, it's just a claim: the\n     * device list gives a proof that the owner of the curve25519 key used for\n     * this event (and returned by #getSenderKey) also owns the ed25519 key by\n     * signing the public curve25519 key with the ed25519 key.\n     *\n     * In general, applications should not use this method directly, but should\n     * instead use {@link crypto-api!CryptoApi#getEncryptionInfoForEvent}.\n     */\n    public getClaimedEd25519Key(): string | null {\n        return this.claimedEd25519Key;\n    }\n\n    /**\n     *  Returns an empty array.\n     *\n     * Previously, this returned the chain of Curve25519 keys through which\n     * this session was forwarded, via `m.forwarded_room_key` events.\n     * However, that is not cryptographically reliable, and clients should not\n     * be using it.\n     *\n     * @see https://github.com/matrix-org/matrix-spec/issues/1089\n     * @deprecated\n     */\n    public getForwardingCurve25519KeyChain(): string[] {\n        return [];\n    }\n\n    /**\n     * @deprecated always returns false\n     */\n    public isKeySourceUntrusted(): false {\n        return false;\n    }\n\n    /**\n     * If another user forwarded the key to this message\n     * (eg via [MSC4268](https://github.com/matrix-org/matrix-spec-proposals/pull/4268)),\n     * get the ID of that user.\n     */\n    public getKeyForwardingUser(): string | undefined {\n        return this.keyForwardedBy;\n    }\n\n    public getUnsigned(): IUnsigned {\n        return this.event.unsigned || {};\n    }\n\n    public setUnsigned(unsigned: IUnsigned): void {\n        this.event.unsigned = unsigned;\n    }\n\n    public unmarkLocallyRedacted(): boolean {\n        const value = this._localRedactionEvent;\n        this._localRedactionEvent = null;\n        if (this.event.unsigned) {\n            this.event.unsigned.redacted_because = undefined;\n        }\n        return !!value;\n    }\n\n    public markLocallyRedacted(redactionEvent: MatrixEvent): void {\n        if (this._localRedactionEvent) return;\n        this.emit(MatrixEventEvent.BeforeRedaction, this, redactionEvent);\n        this._localRedactionEvent = redactionEvent;\n        if (!this.event.unsigned) {\n            this.event.unsigned = {};\n        }\n        this.event.unsigned.redacted_because = redactionEvent.event as IEvent;\n    }\n\n    /**\n     * Change the visibility of an event, as per https://github.com/matrix-org/matrix-doc/pull/3531 .\n     *\n     * @param visibilityChange - event holding a hide/unhide payload, or nothing\n     *   if the event is being reset to its original visibility (presumably\n     *   by a visibility event being redacted).\n     *\n     * @remarks\n     * Fires {@link MatrixEventEvent.VisibilityChange} if `visibilityEvent`\n     *   caused a change in the actual visibility of this event, either by making it\n     *   visible (if it was hidden), by making it hidden (if it was visible) or by\n     *   changing the reason (if it was hidden).\n     */\n    public applyVisibilityEvent(visibilityChange?: IVisibilityChange): void {\n        const visible = visibilityChange?.visible ?? true;\n        const reason = visibilityChange?.reason ?? null;\n        let change = false;\n        if (this.visibility.visible !== visible) {\n            change = true;\n        } else if (!this.visibility.visible && this.visibility[\"reason\"] !== reason) {\n            change = true;\n        }\n        if (change) {\n            if (visible) {\n                this.visibility = MESSAGE_VISIBLE;\n            } else {\n                this.visibility = Object.freeze({\n                    visible: false,\n                    reason,\n                });\n            }\n            this.emit(MatrixEventEvent.VisibilityChange, this, visible);\n        }\n    }\n\n    /**\n     * Return instructions to display or hide the message.\n     *\n     * @returns Instructions determining whether the message\n     * should be displayed.\n     */\n    public messageVisibility(): MessageVisibility {\n        // Note: We may return `this.visibility` without fear, as\n        // this is a shallow frozen object.\n        return this.visibility;\n    }\n\n    /**\n     * Update the content of an event in the same way it would be by the server\n     * if it were redacted before it was sent to us\n     *\n     * @param redactionEvent - event causing the redaction\n     * @param room - the room in which the event exists\n     */\n    public makeRedacted(redactionEvent: MatrixEvent, room: Room): void {\n        // quick sanity-check\n        if (!redactionEvent.event) {\n            throw new Error(\"invalid redactionEvent in makeRedacted\");\n        }\n\n        this._localRedactionEvent = null;\n\n        this.emit(MatrixEventEvent.BeforeRedaction, this, redactionEvent);\n\n        this._replacingEvent = null;\n        // we attempt to replicate what we would see from the server if\n        // the event had been redacted before we saw it.\n        //\n        // The server removes (most of) the content of the event, and adds a\n        // \"redacted_because\" key to the unsigned section containing the\n        // redacted event.\n        if (!this.event.unsigned) {\n            this.event.unsigned = {};\n        }\n        this.event.unsigned.redacted_because = redactionEvent.event as IEvent;\n\n        for (const key in this.event) {\n            if (this.event.hasOwnProperty(key) && !REDACT_KEEP_KEYS.has(key)) {\n                delete this.event[key as keyof IEvent];\n            }\n        }\n\n        // If the event is encrypted prune the decrypted bits\n        if (this.isEncrypted()) {\n            this.clearEvent = undefined;\n        }\n\n        const keeps =\n            this.getType() in REDACT_KEEP_CONTENT_MAP\n                ? REDACT_KEEP_CONTENT_MAP[this.getType() as keyof typeof REDACT_KEEP_CONTENT_MAP]\n                : {};\n        const content = this.getContent();\n        for (const key in content) {\n            if (content.hasOwnProperty(key) && !keeps[key]) {\n                delete content[key];\n            }\n        }\n\n        // If the redacted event was in a thread (but not thread root), move it\n        // to the main timeline. This will change if MSC3389 is merged.\n        if (!this.isThreadRoot && this.threadRootId && this.threadRootId !== this.getId()) {\n            this.moveAllRelatedToMainTimeline(room);\n            redactionEvent.moveToMainTimeline(room);\n        }\n\n        this.invalidateExtensibleEvent();\n    }\n\n    private moveAllRelatedToMainTimeline(room: Room): void {\n        const thread = this.thread;\n        this.moveToMainTimeline(room);\n\n        // If we dont have access to the thread, we can only move this\n        // event, not things related to it.\n        if (thread) {\n            for (const event of thread.events) {\n                if (event.getRelation()?.event_id === this.getId()) {\n                    event.moveAllRelatedToMainTimeline(room);\n                }\n            }\n        }\n    }\n\n    private moveToMainTimeline(room: Room): void {\n        // Remove it from its thread\n        this.thread?.timelineSet.removeEvent(this.getId()!);\n        this.setThread(undefined);\n\n        // And insert it into the main timeline\n        const timeline = room.getLiveTimeline();\n        // We use insertEventIntoTimeline to insert it in timestamp order,\n        // because we don't know where it should go (until we have MSC4033).\n        timeline\n            .getTimelineSet()\n            .insertEventIntoTimeline(this, timeline, timeline.getState(EventTimeline.FORWARDS)!, false);\n    }\n\n    /**\n     * Check if this event has been redacted\n     *\n     * @returns True if this event has been redacted\n     */\n    public isRedacted(): boolean {\n        return Boolean(this.getUnsigned().redacted_because);\n    }\n\n    /**\n     * Check if this event is a redaction of another event\n     *\n     * @returns True if this event is a redaction\n     */\n    public isRedaction(): boolean {\n        return this.getType() === EventType.RoomRedaction;\n    }\n\n    /**\n     * Return the visibility change caused by this event,\n     * as per https://github.com/matrix-org/matrix-doc/pull/3531.\n     *\n     * @returns If the event is a well-formed visibility change event,\n     * an instance of `IVisibilityChange`, otherwise `null`.\n     */\n    public asVisibilityChange(): IVisibilityChange | null {\n        if (!EVENT_VISIBILITY_CHANGE_TYPE.matches(this.getType())) {\n            // Not a visibility change event.\n            return null;\n        }\n        const relation = this.getRelation();\n        if (!relation || relation.rel_type != \"m.reference\") {\n            // Ill-formed, ignore this event.\n            return null;\n        }\n        const eventId = relation.event_id;\n        if (!eventId) {\n            // Ill-formed, ignore this event.\n            return null;\n        }\n        const content = this.getWireContent();\n        const visible = !!content.visible;\n        const reason = content.reason;\n        if (reason && typeof reason != \"string\") {\n            // Ill-formed, ignore this event.\n            return null;\n        }\n        // Well-formed visibility change event.\n        return {\n            visible,\n            reason,\n            eventId,\n        };\n    }\n\n    /**\n     * Check if this event alters the visibility of another event,\n     * as per https://github.com/matrix-org/matrix-doc/pull/3531.\n     *\n     * @returns True if this event alters the visibility\n     * of another event.\n     */\n    public isVisibilityEvent(): boolean {\n        return EVENT_VISIBILITY_CHANGE_TYPE.matches(this.getType());\n    }\n\n    /**\n     * Get the (decrypted, if necessary) redaction event JSON\n     * if event was redacted\n     *\n     * @returns The redaction event JSON, or an empty object\n     */\n    public getRedactionEvent(): IEvent | EmptyObject | null {\n        if (!this.isRedacted()) return null;\n\n        if (this.clearEvent?.unsigned) {\n            return this.clearEvent?.unsigned.redacted_because ?? null;\n        } else if (this.event.unsigned?.redacted_because) {\n            return this.event.unsigned.redacted_because;\n        } else {\n            return {};\n        }\n    }\n\n    /**\n     * Get the push actions, if known, for this event\n     *\n     * @returns push actions\n     */\n    public getPushActions(): IActionsObject | null {\n        return this.pushDetails.actions || null;\n    }\n\n    /**\n     * Get the push details, if known, for this event\n     *\n     * @returns push actions\n     */\n    public getPushDetails(): PushDetails {\n        return this.pushDetails;\n    }\n\n    /**\n     * Set the push details for this event.\n     *\n     * @param pushActions - push actions\n     * @param rule - the executed push rule\n     */\n    public setPushDetails(pushActions?: IActionsObject, rule?: IAnnotatedPushRule): void {\n        this.pushDetails = {\n            actions: pushActions,\n            rule,\n        };\n    }\n\n    /**\n     * Replace the `event` property and recalculate any properties based on it.\n     * @param event - the object to assign to the `event` property\n     */\n    public handleRemoteEcho(event: object): void {\n        const oldUnsigned = this.getUnsigned();\n        const oldId = this.getId();\n        this.event = event;\n        // if this event was redacted before it was sent, it's locally marked as redacted.\n        // At this point, we've received the remote echo for the event, but not yet for\n        // the redaction that we are sending ourselves. Preserve the locally redacted\n        // state by copying over redacted_because so we don't get a flash of\n        // redacted, not-redacted, redacted as remote echos come in\n        if (oldUnsigned.redacted_because) {\n            if (!this.event.unsigned) {\n                this.event.unsigned = {};\n            }\n            this.event.unsigned.redacted_because = oldUnsigned.redacted_because;\n        }\n        // successfully sent.\n        this.setStatus(null);\n        if (this.getId() !== oldId) {\n            // emit the event if it changed\n            this.emit(MatrixEventEvent.LocalEventIdReplaced, this);\n        }\n\n        this.localTimestamp = Date.now() - (this.getAge() ?? 0);\n    }\n\n    /**\n     * Whether the event is in any phase of sending, send failure, waiting for\n     * remote echo, etc.\n     */\n    public isSending(): boolean {\n        return !!this.status;\n    }\n\n    /**\n     * Update the event's sending status and emit an event as well.\n     *\n     * @param status - The new status\n     */\n    public setStatus(status: EventStatus | null): void {\n        this.status = status;\n        this.emit(MatrixEventEvent.Status, this, status);\n    }\n\n    public replaceLocalEventId(eventId: string): void {\n        this.event.event_id = eventId;\n        this.emit(MatrixEventEvent.LocalEventIdReplaced, this);\n    }\n\n    /**\n     * Get whether the event is a relation event, and of a given type if\n     * `relType` is passed in. State events cannot be relation events\n     *\n     * @param relType - if given, checks that the relation is of the\n     * given type\n     */\n    public isRelation(relType?: string): boolean {\n        // Relation info is lifted out of the encrypted content when sent to\n        // encrypted rooms, so we have to check `getWireContent` for this.\n        const relation = this.getWireContent()?.[\"m.relates_to\"];\n        if (\n            this.isState() &&\n            !!relation?.rel_type &&\n            ([RelationType.Replace, RelationType.Thread] as string[]).includes(relation.rel_type)\n        ) {\n            // State events cannot be m.replace or m.thread relations\n            return false;\n        }\n        return !!(relation?.rel_type && relation.event_id && (relType ? relation.rel_type === relType : true));\n    }\n\n    /**\n     * Get relation info for the event, if any.\n     */\n    public getRelation(): IEventRelation | null {\n        if (!this.isRelation()) {\n            return null;\n        }\n        return this.getWireContent()[\"m.relates_to\"] ?? null;\n    }\n\n    /**\n     * Set an event that replaces the content of this event, through an m.replace relation.\n     *\n     * @param newEvent - the event with the replacing content, if any.\n     *\n     * @remarks\n     * Fires {@link MatrixEventEvent.Replaced}\n     */\n    public makeReplaced(newEvent?: MatrixEvent): void {\n        // don't allow redacted events to be replaced.\n        // if newEvent is null we allow to go through though,\n        // as with local redaction, the replacing event might get\n        // cancelled, which should be reflected on the target event.\n        if (this.isRedacted() && newEvent) {\n            return;\n        }\n        // don't allow state events to be replaced using this mechanism as per MSC2676\n        if (this.isState()) {\n            return;\n        }\n        if (this._replacingEvent !== newEvent) {\n            this._replacingEvent = newEvent ?? null;\n            this.emit(MatrixEventEvent.Replaced, this);\n            this.invalidateExtensibleEvent();\n        }\n    }\n\n    /**\n     * Returns the status of any associated edit or redaction\n     * (not for reactions/annotations as their local echo doesn't affect the original event),\n     * or else the status of the event.\n     */\n    public getAssociatedStatus(): EventStatus | null {\n        if (this._replacingEvent) {\n            return this._replacingEvent.status;\n        } else if (this._localRedactionEvent) {\n            return this._localRedactionEvent.status;\n        }\n        return this.status;\n    }\n\n    public getServerAggregatedRelation<T>(relType: RelationType | string): T | undefined {\n        return this.getUnsigned()[\"m.relations\"]?.[relType];\n    }\n\n    /**\n     * Returns the event ID of the event replacing the content of this event, if any.\n     */\n    public replacingEventId(): string | undefined {\n        const replaceRelation = this.getServerAggregatedRelation<IAggregatedRelation>(RelationType.Replace);\n        if (replaceRelation) {\n            return replaceRelation.event_id;\n        } else if (this._replacingEvent) {\n            return this._replacingEvent.getId();\n        }\n    }\n\n    /**\n     * Returns the event replacing the content of this event, if any.\n     * Replacements are aggregated on the server, so this would only\n     * return an event in case it came down the sync, or for local echo of edits.\n     */\n    public replacingEvent(): MatrixEvent | null {\n        return this._replacingEvent;\n    }\n\n    /**\n     * Returns the origin_server_ts of the event replacing the content of this event, if any.\n     */\n    public replacingEventDate(): Date | undefined {\n        const replaceRelation = this.getServerAggregatedRelation<IAggregatedRelation>(RelationType.Replace);\n        if (replaceRelation) {\n            const ts = replaceRelation.origin_server_ts;\n            if (Number.isFinite(ts)) {\n                return new Date(ts);\n            }\n        } else if (this._replacingEvent) {\n            return this._replacingEvent.getDate() ?? undefined;\n        }\n    }\n\n    /**\n     * Returns the event that wants to redact this event, but hasn't been sent yet.\n     * @returns the event\n     */\n    public localRedactionEvent(): MatrixEvent | null {\n        return this._localRedactionEvent;\n    }\n\n    /**\n     * For relations and redactions, returns the event_id this event is referring to.\n     */\n    public getAssociatedId(): string | undefined {\n        const relation = this.getRelation();\n        if (this.replyEventId) {\n            return this.replyEventId;\n        } else if (relation) {\n            return relation.event_id;\n        } else if (this.isRedaction()) {\n            return this.event.redacts;\n        }\n    }\n\n    /**\n     * Checks if this event is associated with another event. See `getAssociatedId`.\n     */\n    public hasAssociation(): boolean {\n        return !!this.getAssociatedId();\n    }\n\n    /**\n     * Update the related id with a new one.\n     *\n     * Used to replace a local id with remote one before sending\n     * an event with a related id.\n     *\n     * @param eventId - the new event id\n     */\n    public updateAssociatedId(eventId: string): void {\n        const relation = this.getRelation();\n        if (relation) {\n            relation.event_id = eventId;\n        } else if (this.isRedaction()) {\n            this.event.redacts = eventId;\n        }\n    }\n\n    /**\n     * Flags an event as cancelled due to future conditions. For example, a verification\n     * request event in the same sync transaction may be flagged as cancelled to warn\n     * listeners that a cancellation event is coming down the same pipe shortly.\n     * @param cancelled - Whether the event is to be cancelled or not.\n     */\n    public flagCancelled(cancelled = true): void {\n        this._isCancelled = cancelled;\n    }\n\n    /**\n     * Gets whether or not the event is flagged as cancelled. See flagCancelled() for\n     * more information.\n     * @returns True if the event is cancelled, false otherwise.\n     */\n    public isCancelled(): boolean {\n        return this._isCancelled;\n    }\n\n    /**\n     * Get a copy/snapshot of this event. The returned copy will be loosely linked\n     * back to this instance, though will have \"frozen\" event information. Other\n     * properties of this MatrixEvent instance will be copied verbatim, which can\n     * mean they are in reference to this instance despite being on the copy too.\n     * The reference the snapshot uses does not change, however members aside from\n     * the underlying event will not be deeply cloned, thus may be mutated internally.\n     * For example, the sender profile will be copied over at snapshot time, and\n     * the sender profile internally may mutate without notice to the consumer.\n     *\n     * This is meant to be used to snapshot the event details themselves, not the\n     * features (such as sender) surrounding the event.\n     * @returns A snapshot of this event.\n     */\n    public toSnapshot(): MatrixEvent {\n        const ev = new MatrixEvent(JSON.parse(JSON.stringify(this.event)));\n        for (const [p, v] of Object.entries(this)) {\n            if (p !== \"event\") {\n                // exclude the thing we just cloned\n                // @ts-ignore - XXX: this is just nasty\n                ev[p as keyof MatrixEvent] = v;\n            }\n        }\n        return ev;\n    }\n\n    /**\n     * Determines if this event is equivalent to the given event. This only checks\n     * the event object itself, not the other properties of the event. Intended for\n     * use with toSnapshot() to identify events changing.\n     * @param otherEvent - The other event to check against.\n     * @returns True if the events are the same, false otherwise.\n     */\n    public isEquivalentTo(otherEvent?: MatrixEvent): boolean {\n        if (!otherEvent) return false;\n        if (otherEvent === this) return true;\n        const myProps = deepSortedObjectEntries(this.event);\n        const theirProps = deepSortedObjectEntries(otherEvent.event);\n        return JSON.stringify(myProps) === JSON.stringify(theirProps);\n    }\n\n    /**\n     * Summarise the event as JSON.\n     *\n     * If encrypted, include both the decrypted and encrypted view of the event.\n     *\n     * This is named `toJSON` for use with `JSON.stringify` which checks objects\n     * for functions named `toJSON` and will call them to customise the output\n     * if they are defined.\n     *\n     * **WARNING** Do not log the result of this method; otherwise, it will end up\n     * in rageshakes, leading to a privacy violation.\n     *\n     * @deprecated Prefer to use {@link MatrixEvent#getEffectiveEvent} or similar.\n     * This method will be removed soon; it is too easy to use it accidentally\n     * and cause a privacy violation (cf https://github.com/vector-im/element-web/issues/26380).\n     * In any case, the value it returns is not a faithful serialization of the object.\n     */\n    public toJSON(): object {\n        const event = this.getEffectiveEvent();\n\n        if (!this.isEncrypted()) {\n            return event;\n        }\n\n        return {\n            decrypted: event,\n            encrypted: this.event,\n        };\n    }\n\n    public setTxnId(txnId: string): void {\n        this.txnId = txnId;\n    }\n\n    public getTxnId(): string | undefined {\n        return this.txnId;\n    }\n\n    /**\n     * Set the instance of a thread associated with the current event\n     * @param thread - the thread\n     */\n    public setThread(thread?: Thread): void {\n        // don't allow state events to be threaded as per the spec\n        if (this.isState()) {\n            return;\n        }\n        if (this.thread) {\n            this.reEmitter.stopReEmitting(this.thread, [ThreadEvent.Update]);\n        }\n        this.thread = thread;\n        this.setThreadId(thread?.id);\n        if (thread) {\n            this.reEmitter.reEmit(thread, [ThreadEvent.Update]);\n        }\n    }\n\n    /**\n     * Get the instance of the thread associated with the current event\n     */\n    public getThread(): Thread | undefined {\n        return this.thread;\n    }\n\n    public setThreadId(threadId?: string): void {\n        this.threadId = threadId;\n    }\n\n    /**\n     * Unstable getter to try and get the sticky information for the event.\n     * If the event is not a sticky event (or not supported by the server),\n     * then this returns `undefined`.\n     *\n     * `duration_ms` is safely bounded to a hour.\n     */\n    public get unstableStickyInfo(): { duration_ms: number; duration_ttl_ms?: number } | undefined {\n        if (!this.event.msc4354_sticky?.duration_ms) {\n            return undefined;\n        }\n        return {\n            duration_ms: Math.min(MAX_STICKY_DURATION_MS, this.event.msc4354_sticky.duration_ms),\n            // This is assumed to be bounded server-side.\n            duration_ttl_ms: this.event.unsigned?.msc4354_sticky_duration_ttl_ms,\n        };\n    }\n}\n\n/* REDACT_KEEP_KEYS gives the keys we keep when an event is redacted\n *\n * This is specified here:\n *  http://matrix.org/speculator/spec/HEAD/client_server/latest.html#redactions\n *\n * Also:\n *  - We keep 'unsigned' since that is created by the local server\n *  - We keep user_id for backwards-compat with v1\n */\nconst REDACT_KEEP_KEYS = new Set([\n    \"event_id\",\n    \"type\",\n    \"room_id\",\n    \"user_id\",\n    \"sender\",\n    \"state_key\",\n    \"prev_state\",\n    \"content\",\n    \"unsigned\",\n    \"origin_server_ts\",\n]);\n\n// a map from state event type to the .content keys we keep when an event is redacted\nconst REDACT_KEEP_CONTENT_MAP: Record<string, Record<string, 1>> = {\n    [EventType.RoomMember]: { membership: 1 },\n    [EventType.RoomJoinRules]: { join_rule: 1 },\n    [EventType.RoomPowerLevels]: {\n        ban: 1,\n        events: 1,\n        events_default: 1,\n        kick: 1,\n        redact: 1,\n        state_default: 1,\n        users: 1,\n        users_default: 1,\n    },\n} as const;\n","/*\nCopyright 2022 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type MatrixClient } from \"../client.ts\";\nimport { type IContent, type MatrixEvent } from \"./event.ts\";\nimport { EventTimeline } from \"./event-timeline.ts\";\nimport { Preset } from \"../@types/partials.ts\";\nimport { globToRegexp } from \"../utils.ts\";\nimport { type Room } from \"./room.ts\";\nimport { EventType, type StateEvents } from \"../@types/event.ts\";\nimport {\n    IGNORE_INVITES_ACCOUNT_EVENT_KEY,\n    POLICIES_ACCOUNT_EVENT_TYPE,\n    PolicyRecommendation,\n    PolicyScope,\n} from \"./invites-ignorer-types.ts\";\n\nexport { IGNORE_INVITES_ACCOUNT_EVENT_KEY, POLICIES_ACCOUNT_EVENT_TYPE, PolicyRecommendation, PolicyScope };\n\nconst scopeToEventTypeMap: Record<PolicyScope, keyof StateEvents> = {\n    [PolicyScope.User]: EventType.PolicyRuleUser,\n    [PolicyScope.Room]: EventType.PolicyRuleRoom,\n    [PolicyScope.Server]: EventType.PolicyRuleServer,\n};\n\n/**\n * A container for ignored invites.\n *\n * # Performance\n *\n * This implementation is extremely naive. It expects that we are dealing\n * with a very short list of sources (e.g. only one). If real-world\n * applications turn out to require longer lists, we may need to rework\n * our data structures.\n */\nexport class IgnoredInvites {\n    public constructor(private readonly client: MatrixClient) {}\n\n    /**\n     * Add a new rule.\n     *\n     * @param scope - The scope for this rule.\n     * @param entity - The entity covered by this rule. Globs are supported.\n     * @param reason - A human-readable reason for introducing this new rule.\n     * @returns The event id for the new rule.\n     */\n    public async addRule(scope: PolicyScope, entity: string, reason: string): Promise<string> {\n        const target = await this.getOrCreateTargetRoom();\n        const response = await this.client.sendStateEvent(target.roomId, scopeToEventTypeMap[scope], {\n            entity,\n            reason,\n            recommendation: PolicyRecommendation.Ban,\n        });\n        return response.event_id;\n    }\n\n    /**\n     * Remove a rule.\n     */\n    public async removeRule(event: MatrixEvent): Promise<void> {\n        await this.client.redactEvent(event.getRoomId()!, event.getId()!);\n    }\n\n    /**\n     * Add a new room to the list of sources. If the user isn't a member of the\n     * room, attempt to join it.\n     *\n     * @param roomId - A valid room id. If this room is already in the list\n     * of sources, it will not be duplicated.\n     * @returns `true` if the source was added, `false` if it was already present.\n     * @throws If `roomId` isn't the id of a room that the current user is already\n     * member of or can join.\n     *\n     * # Safety\n     *\n     * This method will rewrite the `Policies` object in the user's account data.\n     * This rewrite is inherently racy and could overwrite or be overwritten by\n     * other concurrent rewrites of the same object.\n     */\n    public async addSource(roomId: string): Promise<boolean> {\n        // We attempt to join the room *before* calling\n        // `await this.getOrCreateSourceRooms()` to decrease the duration\n        // of the racy section.\n        await this.client.joinRoom(roomId);\n        // Race starts.\n        const sources = (await this.getOrCreateSourceRooms()).map((room) => room.roomId);\n        if (sources.includes(roomId)) {\n            return false;\n        }\n        sources.push(roomId);\n        await this.withIgnoreInvitesPolicies((ignoreInvitesPolicies) => {\n            ignoreInvitesPolicies.sources = sources;\n        });\n\n        // Race ends.\n        return true;\n    }\n\n    /**\n     * Find out whether an invite should be ignored.\n     *\n     * @param params\n     * @param params.sender - The user id for the user who issued the invite.\n     * @param params.roomId - The room to which the user is invited.\n     * @returns A rule matching the entity, if any was found, `null` otherwise.\n     */\n    public async getRuleForInvite({\n        sender,\n        roomId,\n    }: {\n        sender: string;\n        roomId: string;\n    }): Promise<Readonly<MatrixEvent | null>> {\n        // In this implementation, we perform a very naive lookup:\n        // - search in each policy room;\n        // - turn each (potentially glob) rule entity into a regexp.\n        //\n        // Real-world testing will tell us whether this is performant enough.\n        // In the (unfortunately likely) case it isn't, there are several manners\n        // in which we could optimize this:\n        // - match several entities per go;\n        // - pre-compile each rule entity into a regexp;\n        // - pre-compile entire rooms into a single regexp.\n        const policyRooms = await this.getOrCreateSourceRooms();\n        const senderServer = sender.split(\":\")[1];\n        const roomServer = roomId.split(\":\")[1];\n        for (const room of policyRooms) {\n            const state = room.getUnfilteredTimelineSet().getLiveTimeline().getState(EventTimeline.FORWARDS)!;\n\n            for (const { scope, entities } of [\n                { scope: PolicyScope.Room, entities: [roomId] },\n                { scope: PolicyScope.User, entities: [sender] },\n                { scope: PolicyScope.Server, entities: [senderServer, roomServer] },\n            ]) {\n                const events = state.getStateEvents(scopeToEventTypeMap[scope]);\n                for (const event of events) {\n                    const content = event.getContent();\n                    if (content?.recommendation != PolicyRecommendation.Ban) {\n                        // Ignoring invites only looks at `m.ban` recommendations.\n                        continue;\n                    }\n                    const glob = content?.entity;\n                    if (!glob) {\n                        // Invalid event.\n                        continue;\n                    }\n                    let regexp: RegExp;\n                    try {\n                        regexp = new RegExp(globToRegexp(glob));\n                    } catch {\n                        // Assume invalid event.\n                        continue;\n                    }\n                    for (const entity of entities) {\n                        if (entity && regexp.test(entity)) {\n                            return event;\n                        }\n                    }\n                    // No match.\n                }\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Get the target room, i.e. the room in which any new rule should be written.\n     *\n     * If there is no target room setup, a target room is created.\n     *\n     * Note: This method is public for testing reasons. Most clients should not need\n     * to call it directly.\n     *\n     * # Safety\n     *\n     * This method will rewrite the `Policies` object in the user's account data.\n     * This rewrite is inherently racy and could overwrite or be overwritten by\n     * other concurrent rewrites of the same object.\n     */\n    public async getOrCreateTargetRoom(): Promise<Room> {\n        const ignoreInvitesPolicies = this.getIgnoreInvitesPolicies();\n        let target = ignoreInvitesPolicies.target;\n        // Validate `target`. If it is invalid, trash out the current `target`\n        // and create a new room.\n        if (typeof target !== \"string\") {\n            target = null;\n        }\n        if (target) {\n            // Check that the room exists and is valid.\n            const room = this.client.getRoom(target);\n            if (room) {\n                return room;\n            } else {\n                target = null;\n            }\n        }\n        // We need to create our own policy room for ignoring invites.\n        target = (\n            await this.client.createRoom({\n                name: \"Individual Policy Room\",\n                preset: Preset.PrivateChat,\n            })\n        ).room_id;\n        await this.withIgnoreInvitesPolicies((ignoreInvitesPolicies) => {\n            ignoreInvitesPolicies.target = target;\n        });\n\n        // Since we have just called `createRoom`, `getRoom` should not be `null`.\n        return this.client.getRoom(target)!;\n    }\n\n    /**\n     * Get the list of source rooms, i.e. the rooms from which rules need to be read.\n     *\n     * If no source rooms are setup, the target room is used as sole source room.\n     *\n     * Note: This method is public for testing reasons. Most clients should not need\n     * to call it directly.\n     *\n     * # Safety\n     *\n     * This method will rewrite the `Policies` object in the user's account data.\n     * This rewrite is inherently racy and could overwrite or be overwritten by\n     * other concurrent rewrites of the same object.\n     */\n    public async getOrCreateSourceRooms(): Promise<Room[]> {\n        const ignoreInvitesPolicies = this.getIgnoreInvitesPolicies();\n        let sources: string[] = ignoreInvitesPolicies.sources;\n\n        // Validate `sources`. If it is invalid, trash out the current `sources`\n        // and create a new list of sources from `target`.\n        let hasChanges = false;\n        if (!Array.isArray(sources)) {\n            // `sources` could not be an array.\n            hasChanges = true;\n            sources = [];\n        }\n        let sourceRooms = sources\n            // `sources` could contain non-string / invalid room ids\n            .filter((roomId) => typeof roomId === \"string\")\n            .map((roomId) => this.client.getRoom(roomId))\n            .filter((room) => !!room) as Room[];\n        if (sourceRooms.length != sources.length) {\n            hasChanges = true;\n        }\n        if (sourceRooms.length == 0) {\n            // `sources` could be empty (possibly because we've removed\n            // invalid content)\n            const target = await this.getOrCreateTargetRoom();\n            hasChanges = true;\n            sourceRooms = [target];\n        }\n        if (hasChanges) {\n            // Reload `policies`/`ignoreInvitesPolicies` in case it has been changed\n            // during or by our call to `this.getTargetRoom()`.\n            await this.withIgnoreInvitesPolicies((ignoreInvitesPolicies) => {\n                ignoreInvitesPolicies.sources = sources;\n            });\n        }\n        return sourceRooms;\n    }\n\n    /**\n     * Fetch the `IGNORE_INVITES_POLICIES` object from account data.\n     *\n     * If both an unstable prefix version and a stable prefix version are available,\n     * it will return the stable prefix version preferentially.\n     *\n     * The result is *not* validated but is guaranteed to be a non-null object.\n     *\n     * @returns A non-null object.\n     */\n    private getIgnoreInvitesPolicies(): { [key: string]: any } {\n        return this.getPoliciesAndIgnoreInvitesPolicies().ignoreInvitesPolicies;\n    }\n\n    /**\n     * Modify in place the `IGNORE_INVITES_POLICIES` object from account data.\n     */\n    private async withIgnoreInvitesPolicies(\n        cb: (ignoreInvitesPolicies: { [key: string]: any }) => void,\n    ): Promise<void> {\n        const { policies, ignoreInvitesPolicies } = this.getPoliciesAndIgnoreInvitesPolicies();\n        cb(ignoreInvitesPolicies);\n        policies[IGNORE_INVITES_ACCOUNT_EVENT_KEY.name] = ignoreInvitesPolicies;\n        await this.client.setAccountData(POLICIES_ACCOUNT_EVENT_TYPE.name, policies);\n    }\n\n    /**\n     * As `getIgnoreInvitesPolicies` but also return the `POLICIES_ACCOUNT_EVENT_TYPE`\n     * object.\n     */\n    private getPoliciesAndIgnoreInvitesPolicies(): {\n        policies: { [key: string]: any };\n        ignoreInvitesPolicies: { [key: string]: any };\n    } {\n        let policies: IContent = {};\n        for (const key of [POLICIES_ACCOUNT_EVENT_TYPE.name, POLICIES_ACCOUNT_EVENT_TYPE.altName]) {\n            if (!key) {\n                continue;\n            }\n            const value = this.client.getAccountData(key)?.getContent();\n            if (value) {\n                policies = value;\n                break;\n            }\n        }\n\n        let ignoreInvitesPolicies = {};\n        let hasIgnoreInvitesPolicies = false;\n        for (const key of [IGNORE_INVITES_ACCOUNT_EVENT_KEY.name, IGNORE_INVITES_ACCOUNT_EVENT_KEY.altName]) {\n            if (!key) {\n                continue;\n            }\n            const value = policies[key];\n            if (value && typeof value == \"object\") {\n                ignoreInvitesPolicies = value;\n                hasIgnoreInvitesPolicies = true;\n                break;\n            }\n        }\n        if (!hasIgnoreInvitesPolicies) {\n            policies[IGNORE_INVITES_ACCOUNT_EVENT_KEY.name] = ignoreInvitesPolicies;\n        }\n\n        return { policies, ignoreInvitesPolicies };\n    }\n}\n","/*\nCopyright 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { M_POLL_START } from \"matrix-events-sdk\";\n\nimport { M_POLL_END, M_POLL_RESPONSE } from \"../@types/polls.ts\";\nimport { type MatrixClient } from \"../client.ts\";\nimport { type PollStartEvent } from \"../extensible_events_v1/PollStartEvent.ts\";\nimport { type MatrixEvent } from \"./event.ts\";\nimport { Relations } from \"./relations.ts\";\nimport { type Room } from \"./room.ts\";\nimport { TypedEventEmitter } from \"./typed-event-emitter.ts\";\n\nexport enum PollEvent {\n    New = \"Poll.new\",\n    End = \"Poll.end\",\n    Update = \"Poll.update\",\n    Responses = \"Poll.Responses\",\n    Destroy = \"Poll.Destroy\",\n    UndecryptableRelations = \"Poll.UndecryptableRelations\",\n}\n\nexport type PollEventHandlerMap = {\n    [PollEvent.Update]: (event: MatrixEvent, poll: Poll) => void;\n    [PollEvent.Destroy]: (pollIdentifier: string) => void;\n    [PollEvent.End]: () => void;\n    [PollEvent.Responses]: (responses: Relations) => void;\n    [PollEvent.UndecryptableRelations]: (count: number) => void;\n};\n\nconst filterResponseRelations = (\n    relationEvents: MatrixEvent[],\n    pollEndTimestamp: number,\n): {\n    responseEvents: MatrixEvent[];\n} => {\n    const responseEvents = relationEvents.filter((event) => {\n        if (event.isDecryptionFailure()) {\n            return;\n        }\n        return (\n            M_POLL_RESPONSE.matches(event.getType()) &&\n            // From MSC3381:\n            // \"Votes sent on or before the end event's timestamp are valid votes\"\n            event.getTs() <= pollEndTimestamp\n        );\n    });\n\n    return { responseEvents };\n};\n\nexport class Poll extends TypedEventEmitter<Exclude<PollEvent, PollEvent.New>, PollEventHandlerMap> {\n    public readonly roomId: string;\n    public readonly pollEvent: PollStartEvent;\n    private _isFetchingResponses = false;\n    private relationsNextBatch: string | undefined;\n    private responses: null | Relations = null;\n    private endEvent: MatrixEvent | undefined;\n    /**\n     * Keep track of undecryptable relations\n     * As incomplete result sets affect poll results\n     */\n    private undecryptableRelationEventIds = new Set<string>();\n\n    public constructor(\n        public readonly rootEvent: MatrixEvent,\n        private matrixClient: MatrixClient,\n        private room: Room,\n    ) {\n        super();\n        if (!this.rootEvent.getRoomId() || !this.rootEvent.getId()) {\n            throw new Error(\"Invalid poll start event.\");\n        }\n        this.roomId = this.rootEvent.getRoomId()!;\n        this.pollEvent = this.rootEvent.unstableExtensibleEvent as unknown as PollStartEvent;\n    }\n\n    public get pollId(): string {\n        return this.rootEvent.getId()!;\n    }\n\n    public get endEventId(): string | undefined {\n        return this.endEvent?.getId();\n    }\n\n    public get isEnded(): boolean {\n        return !!this.endEvent;\n    }\n\n    public get isFetchingResponses(): boolean {\n        return this._isFetchingResponses;\n    }\n\n    public get undecryptableRelationsCount(): number {\n        return this.undecryptableRelationEventIds.size;\n    }\n\n    public async getResponses(): Promise<Relations> {\n        // if we have already fetched some responses\n        // just return them\n        if (this.responses) {\n            return this.responses;\n        }\n\n        // if there is no fetching in progress\n        // start fetching\n        if (!this.isFetchingResponses) {\n            await this.fetchResponses();\n        }\n        // return whatever responses we got from the first page\n        return this.responses!;\n    }\n\n    /**\n     *\n     * @param event - event with a relation to the rootEvent\n     * @returns void\n     */\n    public onNewRelation(event: MatrixEvent): void {\n        if (M_POLL_END.matches(event.getType()) && this.validateEndEvent(event)) {\n            this.endEvent = event;\n            this.refilterResponsesOnEnd();\n            this.emit(PollEvent.End);\n        }\n\n        // wait for poll responses to be initialised\n        if (!this.responses) {\n            return;\n        }\n\n        const pollEndTimestamp = this.endEvent?.getTs() || Number.MAX_SAFE_INTEGER;\n        const { responseEvents } = filterResponseRelations([event], pollEndTimestamp);\n\n        this.countUndecryptableEvents([event]);\n\n        if (responseEvents.length) {\n            responseEvents.forEach((event) => {\n                this.responses!.addEvent(event);\n            });\n\n            this.emit(PollEvent.Responses, this.responses);\n        }\n    }\n\n    private async fetchResponses(): Promise<void> {\n        this._isFetchingResponses = true;\n\n        // we want:\n        // - stable and unstable M_POLL_RESPONSE\n        // - stable and unstable M_POLL_END\n        // so make one api call and filter by event type client side\n        const allRelations = await this.matrixClient.relations(\n            this.roomId,\n            this.rootEvent.getId()!,\n            \"m.reference\",\n            undefined,\n            {\n                from: this.relationsNextBatch || undefined,\n            },\n        );\n\n        await Promise.all(allRelations.events.map((event) => this.matrixClient.decryptEventIfNeeded(event)));\n\n        const responses =\n            this.responses ||\n            new Relations(\"m.reference\", M_POLL_RESPONSE.name, this.matrixClient, [M_POLL_RESPONSE.altName!]);\n\n        const pollEndEvent = allRelations.events.find((event) => M_POLL_END.matches(event.getType()));\n\n        if (this.validateEndEvent(pollEndEvent)) {\n            this.endEvent = pollEndEvent;\n            this.refilterResponsesOnEnd();\n            this.emit(PollEvent.End);\n        }\n\n        const pollCloseTimestamp = this.endEvent?.getTs() || Number.MAX_SAFE_INTEGER;\n\n        const { responseEvents } = filterResponseRelations(allRelations.events, pollCloseTimestamp);\n\n        responseEvents.forEach((event) => {\n            responses.addEvent(event);\n        });\n\n        this.relationsNextBatch = allRelations.nextBatch ?? undefined;\n        this.responses = responses;\n        this.countUndecryptableEvents(allRelations.events);\n\n        // while there are more pages of relations\n        // fetch them\n        if (this.relationsNextBatch) {\n            // don't await\n            // we want to return the first page as soon as possible\n            this.fetchResponses();\n        } else {\n            // no more pages\n            this._isFetchingResponses = false;\n        }\n\n        // emit after updating _isFetchingResponses state\n        this.emit(PollEvent.Responses, this.responses);\n    }\n\n    /**\n     * Only responses made before the poll ended are valid\n     * Refilter after an end event is recieved\n     * To ensure responses are valid\n     */\n    private refilterResponsesOnEnd(): void {\n        if (!this.responses) {\n            return;\n        }\n\n        const pollEndTimestamp = this.endEvent?.getTs() || Number.MAX_SAFE_INTEGER;\n        this.responses.getRelations().forEach((event) => {\n            if (event.getTs() > pollEndTimestamp) {\n                this.responses?.removeEvent(event);\n            }\n        });\n\n        this.emit(PollEvent.Responses, this.responses);\n    }\n\n    private countUndecryptableEvents = (events: MatrixEvent[]): void => {\n        const undecryptableEventIds = events\n            .filter((event) => event.isDecryptionFailure())\n            .map((event) => event.getId()!);\n\n        const previousCount = this.undecryptableRelationsCount;\n        this.undecryptableRelationEventIds = new Set([...this.undecryptableRelationEventIds, ...undecryptableEventIds]);\n\n        if (this.undecryptableRelationsCount !== previousCount) {\n            this.emit(PollEvent.UndecryptableRelations, this.undecryptableRelationsCount);\n        }\n    };\n\n    private validateEndEvent(endEvent?: MatrixEvent): boolean {\n        if (!endEvent) {\n            return false;\n        }\n        /**\n         * Repeated end events are ignored -\n         * only the first (valid) closure event by origin_server_ts is counted.\n         */\n        if (this.endEvent && this.endEvent.getTs() < endEvent.getTs()) {\n            return false;\n        }\n\n        /**\n         * MSC3381\n         * If a m.poll.end event is received from someone other than the poll creator or user with permission to redact\n         * others' messages in the room, the event must be ignored by clients due to being invalid.\n         */\n        const roomCurrentState = this.room.currentState;\n        const endEventSender = endEvent.getSender();\n        return (\n            !!endEventSender &&\n            (endEventSender === this.rootEvent.getSender() ||\n                roomCurrentState.maySendRedactionForEvent(this.rootEvent, endEventSender))\n        );\n    }\n}\n\n/**\n * Tests whether the event is a start, response or end poll event.\n *\n * @param event - Event to test\n * @returns true if the event is a poll event, else false\n */\nexport const isPollEvent = (event: MatrixEvent): boolean => {\n    const eventType = event.getType();\n    return M_POLL_START.matches(eventType) || M_POLL_RESPONSE.matches(eventType) || M_POLL_END.matches(eventType);\n};\n","/*\nCopyright 2019, 2021, 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { EventStatus, type IAggregatedRelation, type MatrixEvent, MatrixEventEvent } from \"./event.ts\";\nimport { logger } from \"../logger.ts\";\nimport { RelationType } from \"../@types/event.ts\";\nimport { TypedEventEmitter } from \"./typed-event-emitter.ts\";\nimport { type MatrixClient } from \"../client.ts\";\nimport { Room } from \"./room.ts\";\nimport { type CryptoBackend } from \"../common-crypto/CryptoBackend.ts\";\n\nexport enum RelationsEvent {\n    Add = \"Relations.add\",\n    Remove = \"Relations.remove\",\n    Redaction = \"Relations.redaction\",\n}\n\nexport type EventHandlerMap = {\n    [RelationsEvent.Add]: (event: MatrixEvent) => void;\n    [RelationsEvent.Remove]: (event: MatrixEvent) => void;\n    [RelationsEvent.Redaction]: (event: MatrixEvent) => void;\n};\n\nconst matchesEventType = (eventType: string, targetEventType: string, altTargetEventTypes: string[] = []): boolean =>\n    [targetEventType, ...altTargetEventTypes].includes(eventType);\n\n/**\n * A container for relation events that supports easy access to common ways of\n * aggregating such events. Each instance holds events that of a single relation\n * type and event type. All of the events also relate to the same original event.\n *\n * The typical way to get one of these containers is via\n * EventTimelineSet#getRelationsForEvent.\n */\nexport class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap> {\n    private relationEventIds = new Set<string>();\n    private relations = new Set<MatrixEvent>();\n    private annotationsByKey: Record<string, Set<MatrixEvent>> = {};\n    private annotationsBySender: Record<string, Set<MatrixEvent>> = {};\n    private sortedAnnotationsByKey: [string, Set<MatrixEvent>][] = [];\n    private targetEvent: MatrixEvent | null = null;\n    private creationEmitted = false;\n    private replacementUpdateId = 0;\n    private readonly client: MatrixClient;\n\n    /**\n     * @param relationType - The type of relation involved, such as \"m.annotation\", \"m.reference\", \"m.replace\", etc.\n     * @param eventType - The relation event's type, such as \"m.reaction\", etc.\n     * @param client - The client which created this instance. For backwards compatibility also accepts a Room.\n     * @param altEventTypes - alt event types for relation events, for example to support unstable prefixed event types\n     */\n    public constructor(\n        public readonly relationType: RelationType | string,\n        public readonly eventType: string,\n        client: MatrixClient | Room,\n        public readonly altEventTypes?: string[],\n    ) {\n        super();\n        this.client = client instanceof Room ? client.client : client;\n    }\n\n    /**\n     * Add relation events to this collection.\n     *\n     * @param event - The new relation event to be added.\n     */\n    public async addEvent(event: MatrixEvent): Promise<void> {\n        if (this.relationEventIds.has(event.getId()!)) {\n            return;\n        }\n\n        const relation = event.getRelation();\n        if (!relation) {\n            logger.error(\"Event must have relation info\");\n            return;\n        }\n\n        const relationType = relation.rel_type;\n        const eventType = event.getType();\n\n        if (this.relationType !== relationType || !matchesEventType(eventType, this.eventType, this.altEventTypes)) {\n            logger.error(\"Event relation info doesn't match this container\");\n            return;\n        }\n\n        // If the event is in the process of being sent, listen for cancellation\n        // so we can remove the event from the collection.\n        if (event.isSending()) {\n            event.on(MatrixEventEvent.Status, this.onEventStatus);\n        }\n\n        this.relations.add(event);\n        this.relationEventIds.add(event.getId()!);\n\n        if (this.relationType === RelationType.Annotation) {\n            this.addAnnotationToAggregation(event);\n        } else if (this.relationType === RelationType.Replace) {\n            await this.updateTargetEventReplacement();\n        }\n\n        event.on(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);\n\n        this.emit(RelationsEvent.Add, event);\n\n        this.maybeEmitCreated();\n    }\n\n    /**\n     * Remove relation event from this collection.\n     *\n     * @param event - The relation event to remove.\n     */\n    public async removeEvent(event: MatrixEvent): Promise<void> {\n        if (!this.relations.has(event)) {\n            return;\n        }\n\n        this.relations.delete(event);\n\n        if (this.relationType === RelationType.Annotation) {\n            this.removeAnnotationFromAggregation(event);\n        } else if (this.relationType === RelationType.Replace) {\n            await this.updateTargetEventReplacement();\n        }\n\n        this.emit(RelationsEvent.Remove, event);\n    }\n\n    /**\n     * Listens for event status changes to remove cancelled events.\n     *\n     * @param event - The event whose status has changed\n     * @param status - The new status\n     */\n    private onEventStatus = (event: MatrixEvent, status: EventStatus | null): void => {\n        if (!event.isSending()) {\n            // Sending is done, so we don't need to listen anymore\n            event.removeListener(MatrixEventEvent.Status, this.onEventStatus);\n            return;\n        }\n        if (status !== EventStatus.CANCELLED) {\n            return;\n        }\n        // Event was cancelled, remove from the collection\n        event.removeListener(MatrixEventEvent.Status, this.onEventStatus);\n        this.removeEvent(event);\n    };\n\n    /**\n     * Get all relation events in this collection.\n     *\n     * These are currently in the order of insertion to this collection, which\n     * won't match timeline order in the case of scrollback.\n     * TODO: Tweak `addEvent` to insert correctly for scrollback.\n     *\n     * Relation events in insertion order.\n     */\n    public getRelations(): MatrixEvent[] {\n        return [...this.relations];\n    }\n\n    private addAnnotationToAggregation(event: MatrixEvent): void {\n        const { key } = event.getRelation() ?? {};\n        if (!key) return;\n\n        let eventsForKey = this.annotationsByKey[key];\n        if (!eventsForKey) {\n            eventsForKey = this.annotationsByKey[key] = new Set();\n            this.sortedAnnotationsByKey.push([key, eventsForKey]);\n        }\n        // Add the new event to the set for this key\n        eventsForKey.add(event);\n        // Re-sort the [key, events] pairs in descending order of event count\n        this.sortedAnnotationsByKey.sort((a, b) => {\n            const aEvents = a[1];\n            const bEvents = b[1];\n            return bEvents.size - aEvents.size;\n        });\n\n        const sender = event.getSender()!;\n        let eventsFromSender = this.annotationsBySender[sender];\n        if (!eventsFromSender) {\n            eventsFromSender = this.annotationsBySender[sender] = new Set();\n        }\n        // Add the new event to the set for this sender\n        eventsFromSender.add(event);\n    }\n\n    private removeAnnotationFromAggregation(event: MatrixEvent): void {\n        const { key } = event.getRelation() ?? {};\n        if (!key) return;\n\n        const eventsForKey = this.annotationsByKey[key];\n        if (eventsForKey) {\n            eventsForKey.delete(event);\n\n            // Re-sort the [key, events] pairs in descending order of event count\n            this.sortedAnnotationsByKey.sort((a, b) => {\n                const aEvents = a[1];\n                const bEvents = b[1];\n                return bEvents.size - aEvents.size;\n            });\n        }\n\n        const sender = event.getSender()!;\n        const eventsFromSender = this.annotationsBySender[sender];\n        if (eventsFromSender) {\n            eventsFromSender.delete(event);\n        }\n    }\n\n    /**\n     * For relations that have been redacted, we want to remove them from\n     * aggregation data sets and emit an update event.\n     *\n     * To do so, we listen for `Event.beforeRedaction`, which happens:\n     *   - after the server accepted the redaction and remote echoed back to us\n     *   - before the original event has been marked redacted in the client\n     *\n     * @param redactedEvent - The original relation event that is about to be redacted.\n     */\n    private onBeforeRedaction = async (redactedEvent: MatrixEvent): Promise<void> => {\n        if (!this.relations.has(redactedEvent)) {\n            return;\n        }\n\n        this.relations.delete(redactedEvent);\n\n        if (this.relationType === RelationType.Annotation) {\n            // Remove the redacted annotation from aggregation by key\n            this.removeAnnotationFromAggregation(redactedEvent);\n        } else if (this.relationType === RelationType.Replace) {\n            await this.updateTargetEventReplacement();\n        }\n\n        redactedEvent.removeListener(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);\n\n        this.emit(RelationsEvent.Redaction, redactedEvent);\n    };\n\n    /**\n     * Get all events in this collection grouped by key and sorted by descending\n     * event count in each group.\n     *\n     * This is currently only supported for the annotation relation type.\n     *\n     * An array of [key, events] pairs sorted by descending event count.\n     * The events are stored in a Set (which preserves insertion order).\n     */\n    public getSortedAnnotationsByKey(): [string, Set<MatrixEvent>][] | null {\n        if (this.relationType !== RelationType.Annotation) {\n            // Other relation types are not grouped currently.\n            return null;\n        }\n\n        return this.sortedAnnotationsByKey;\n    }\n\n    /**\n     * Get all events in this collection grouped by sender.\n     *\n     * This is currently only supported for the annotation relation type.\n     *\n     * An object with each relation sender as a key and the matching Set of\n     * events for that sender as a value.\n     */\n    public getAnnotationsBySender(): Record<string, Set<MatrixEvent>> | null {\n        if (this.relationType !== RelationType.Annotation) {\n            // Other relation types are not grouped currently.\n            return null;\n        }\n\n        return this.annotationsBySender;\n    }\n\n    /**\n     * Returns the most recent (and allowed) m.replace relation, if any.\n     *\n     * This is currently only supported for the m.replace relation type,\n     * once the target event is known, see `addEvent`.\n     */\n    public async getLastReplacement(): Promise<MatrixEvent | null> {\n        if (this.relationType !== RelationType.Replace) {\n            // Aggregating on last only makes sense for this relation type\n            return null;\n        }\n        if (!this.targetEvent) {\n            // Don't know which replacements to accept yet.\n            // This method shouldn't be called before the original\n            // event is known anyway.\n            return null;\n        }\n\n        // the all-knowning server tells us that the event at some point had\n        // this timestamp for its replacement, so any following replacement should definitely not be less\n        const replaceRelation = this.targetEvent.getServerAggregatedRelation<IAggregatedRelation>(RelationType.Replace);\n        const minTs = replaceRelation?.origin_server_ts;\n\n        const lastReplacement = this.getRelations().reduce<MatrixEvent | null>((last, event) => {\n            if (event.getSender() !== this.targetEvent!.getSender()) {\n                return last;\n            }\n            if (minTs && minTs > event.getTs()) {\n                return last;\n            }\n            if (last && last.getTs() > event.getTs()) {\n                return last;\n            }\n            return event;\n        }, null);\n\n        if (lastReplacement?.shouldAttemptDecryption() && this.client.getCrypto()) {\n            // Dirty but we are expecting to pass the cryptoBackend which is not accessible here\n            await lastReplacement.attemptDecryption(this.client.getCrypto() as CryptoBackend);\n        } else if (lastReplacement?.isBeingDecrypted()) {\n            await lastReplacement.getDecryptionPromise();\n        }\n\n        return lastReplacement;\n    }\n\n    /*\n     * @param targetEvent - the event the relations are related to.\n     */\n    public async setTargetEvent(event: MatrixEvent): Promise<void> {\n        if (this.targetEvent) {\n            return;\n        }\n        this.targetEvent = event;\n\n        if (this.relationType === RelationType.Replace) {\n            await this.updateTargetEventReplacement();\n        }\n\n        this.maybeEmitCreated();\n    }\n\n    /**\n     * Updates the target event with the latest replacement.\n     *\n     * Multiple replacement updates can be triggered concurrently (for example\n     * while edits are still being decrypted). A monotonic update counter guards\n     * against older async resolutions overriding newer replacement selections.\n     */\n    private async updateTargetEventReplacement(): Promise<void> {\n        if (!this.targetEvent || this.targetEvent.isState()) {\n            return;\n        }\n\n        const targetEvent = this.targetEvent;\n        const updateId = ++this.replacementUpdateId;\n        const lastReplacement = await this.getLastReplacement();\n\n        // If a newer update started while we were awaiting, discard this stale result.\n        if (updateId !== this.replacementUpdateId || this.targetEvent !== targetEvent) {\n            return;\n        }\n\n        // Avoid emitting Event.replaced when there is no replacement and none currently set.\n        if (!lastReplacement && !targetEvent.replacingEvent()) {\n            return;\n        }\n\n        targetEvent.makeReplaced(lastReplacement ?? undefined);\n    }\n\n    private maybeEmitCreated(): void {\n        if (this.creationEmitted) {\n            return;\n        }\n        // Only emit we're \"created\" once we have a target event instance _and_\n        // at least one related event.\n        if (!this.targetEvent || !this.relations.size) {\n            return;\n        }\n        this.creationEmitted = true;\n        this.targetEvent.emit(MatrixEventEvent.RelationsCreated, this.relationType, this.eventType);\n    }\n}\n","/*\nCopyright 2015 - 2021 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { getHttpUriForMxc } from \"../content-repo.ts\";\nimport { removeDirectionOverrideChars, removeHiddenChars } from \"../utils.ts\";\nimport { type User } from \"./user.ts\";\nimport { type MatrixEvent } from \"./event.ts\";\nimport { type RoomState } from \"./room-state.ts\";\nimport { logger } from \"../logger.ts\";\nimport { TypedEventEmitter } from \"./typed-event-emitter.ts\";\nimport { EventType } from \"../@types/event.ts\";\nimport { KnownMembership, type Membership } from \"../@types/membership.ts\";\n\nexport enum RoomMemberEvent {\n    Membership = \"RoomMember.membership\",\n    Name = \"RoomMember.name\",\n    PowerLevel = \"RoomMember.powerLevel\",\n    Typing = \"RoomMember.typing\",\n}\n\nexport type RoomMemberEventHandlerMap = {\n    /**\n     * Fires whenever any room member's membership state changes.\n     * @param event - The matrix event which caused this event to fire.\n     * @param member - The member whose RoomMember.membership changed.\n     * @param oldMembership - The previous membership state. Null if it's a new member.\n     * @example\n     * ```\n     * matrixClient.on(\"RoomMember.membership\", function(event, member, oldMembership){\n     *   var newState = member.membership;\n     * });\n     * ```\n     */\n    [RoomMemberEvent.Membership]: (event: MatrixEvent, member: RoomMember, oldMembership?: string) => void;\n    /**\n     * Fires whenever any room member's name changes.\n     * @param event - The matrix event which caused this event to fire.\n     * @param member - The member whose RoomMember.name changed.\n     * @param oldName - The previous name. Null if the member didn't have a name previously.\n     * @example\n     * ```\n     * matrixClient.on(\"RoomMember.name\", function(event, member){\n     *   var newName = member.name;\n     * });\n     * ```\n     */\n    [RoomMemberEvent.Name]: (event: MatrixEvent, member: RoomMember, oldName: string | null) => void;\n    /**\n     * Fires whenever any room member's power level changes.\n     * @param event - The matrix event which caused this event to fire.\n     * @param member - The member whose RoomMember.powerLevel changed.\n     * @example\n     * ```\n     * matrixClient.on(\"RoomMember.powerLevel\", function(event, member){\n     *   var newPowerLevel = member.powerLevel;\n     * });\n     * ```\n     */\n    [RoomMemberEvent.PowerLevel]: (event: MatrixEvent, member: RoomMember) => void;\n    /**\n     * Fires whenever any room member's typing state changes.\n     * @param event - The matrix event which caused this event to fire.\n     * @param member - The member whose RoomMember.typing changed.\n     * @example\n     * ```\n     * matrixClient.on(\"RoomMember.typing\", function(event, member){\n     *   var isTyping = member.typing;\n     * });\n     * ```\n     */\n    [RoomMemberEvent.Typing]: (event: MatrixEvent, member: RoomMember) => void;\n};\n\nexport class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEventHandlerMap> {\n    private _isOutOfBand = false;\n    private modified = -1;\n    public requestedProfileInfo = false; // used by sync.ts\n\n    // XXX these should be read-only\n    /**\n     * True if the room member is currently typing.\n     */\n    public typing = false;\n\n    /**\n     * The human-readable name for this room member. Similar to {@link rawDisplayName}, but\n     * disambiguated with a suffix of \" (\\@user_id:matrix.org)\" if another member shares the\n     * same displayname.\n     */\n    public name: string;\n\n    /**\n     * The ambiguous displayname of this room member, with some preprocessing:\n     *\n     *  * Direction override characters (RTO and LRO) are removed.\n     *  * If the displayname is empty, or contains only blank, non-printing, or diacritcic characters, it is\n     *    replaced with the user ID.\n     */\n    public rawDisplayName: string;\n\n    /**\n     * The power level for this room member.\n     */\n    public powerLevel = 0;\n    /**\n     * The User object for this room member, if one exists.\n     */\n    public user?: User;\n    /**\n     * The membership state for this room member e.g. 'join'.\n     */\n    public membership?: Membership;\n    /**\n     * True if the member's name is disambiguated.\n     */\n    public disambiguate = false;\n    /**\n     * The events describing this RoomMember.\n     */\n    public events: {\n        /**\n         * The m.room.member event for this RoomMember.\n         */\n        member?: MatrixEvent;\n    } = {};\n\n    /**\n     * Construct a new room member.\n     *\n     * @param roomId - The room ID of the member.\n     * @param userId - The user ID of the member.\n     */\n    public constructor(\n        public readonly roomId: string,\n        public readonly userId: string,\n    ) {\n        super();\n\n        this.name = userId;\n        this.rawDisplayName = userId;\n        this.updateModifiedTime();\n    }\n\n    /**\n     * Mark the member as coming from a channel that is not sync\n     */\n    public markOutOfBand(): void {\n        this._isOutOfBand = true;\n    }\n\n    /**\n     * @returns does the member come from a channel that is not sync?\n     * This is used to store the member seperately\n     * from the sync state so it available across browser sessions.\n     */\n    public isOutOfBand(): boolean {\n        return this._isOutOfBand;\n    }\n\n    /**\n     * Update this room member's membership event. May fire \"RoomMember.name\" if\n     * this event updates this member's name.\n     * @param event - The `m.room.member` event\n     * @param roomState - Optional. The room state to take into account\n     * when calculating (e.g. for disambiguating users with the same name).\n     *\n     * @remarks\n     * Fires {@link RoomMemberEvent.Name}\n     * Fires {@link RoomMemberEvent.Membership}\n     */\n    public setMembershipEvent(event: MatrixEvent, roomState?: RoomState): void {\n        const displayName = event.getDirectionalContent().displayname ?? \"\";\n\n        if (event.getType() !== EventType.RoomMember) {\n            return;\n        }\n\n        this._isOutOfBand = false;\n\n        this.events.member = event;\n\n        const oldMembership = this.membership;\n        this.membership = event.getDirectionalContent().membership;\n        if (this.membership === undefined) {\n            // logging to diagnose https://github.com/vector-im/element-web/issues/20962\n            // (logs event content, although only of membership events)\n            logger.trace(\n                `membership event with membership undefined (forwardLooking: ${event.forwardLooking})!`,\n                event.getContent(),\n                `prevcontent is `,\n                event.getPrevContent(),\n            );\n        }\n\n        this.disambiguate = shouldDisambiguate(this.userId, displayName, roomState);\n\n        const oldName = this.name;\n        this.name = calculateDisplayName(this.userId, displayName, this.disambiguate);\n\n        // not quite raw: we strip direction override chars so it can safely be inserted into\n        // blocks of text without breaking the text direction\n        this.rawDisplayName = removeDirectionOverrideChars(event.getDirectionalContent().displayname ?? \"\");\n        if (!this.rawDisplayName || !removeHiddenChars(this.rawDisplayName)) {\n            this.rawDisplayName = this.userId;\n        }\n\n        if (oldMembership !== this.membership) {\n            this.updateModifiedTime();\n            this.emit(RoomMemberEvent.Membership, event, this, oldMembership);\n        }\n        if (oldName !== this.name) {\n            this.updateModifiedTime();\n            this.emit(RoomMemberEvent.Name, event, this, oldName);\n        }\n    }\n\n    /**\n     * Recalculate the disambiguation flag for this member based on current room state.\n     * This should be called when another member's display name changes and may affect\n     * whether this member needs disambiguation.\n     *\n     * @param roomState - The current room state to use for disambiguation check\n     * @returns true if the member's name changed as a result of the disambiguation update\n     *\n     * @remarks\n     * Fires {@link RoomMemberEvent.Name}\n     */\n    public recalculateDisambiguatedName(roomState: RoomState): boolean {\n        if (!this.events.member) {\n            return false;\n        }\n\n        const displayName = this.events.member.getDirectionalContent().displayname ?? \"\";\n        const newDisambiguate = shouldDisambiguate(this.userId, displayName, roomState);\n\n        if (newDisambiguate === this.disambiguate) {\n            return false;\n        }\n\n        this.disambiguate = newDisambiguate;\n        const oldName = this.name;\n        this.name = calculateDisplayName(this.userId, displayName, this.disambiguate);\n\n        if (oldName !== this.name) {\n            this.updateModifiedTime();\n            this.emit(RoomMemberEvent.Name, this.events.member, this, oldName);\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Update this room member's power level event. Will fire\n     * \"RoomMember.powerLevel\" if the new power level is different\n     * @param powerLevel - The power level of the room member.\n     *\n     * @remarks\n     * Fires {@link RoomMemberEvent.PowerLevel}\n     */\n    public setPowerLevel(powerLevel: number, powerLevelEvent: MatrixEvent): void {\n        const oldPowerLevel = this.powerLevel;\n        this.powerLevel = powerLevel;\n\n        if (oldPowerLevel !== this.powerLevel) {\n            this.updateModifiedTime();\n            this.emit(RoomMemberEvent.PowerLevel, powerLevelEvent, this);\n        }\n    }\n\n    /**\n     * Update this room member's typing event. May fire \"RoomMember.typing\" if\n     * this event changes this member's typing state.\n     * @param event - The typing event\n     *\n     * @remarks\n     * Fires {@link RoomMemberEvent.Typing}\n     */\n    public setTypingEvent(event: MatrixEvent): void {\n        if (event.getType() !== \"m.typing\") {\n            return;\n        }\n        const oldTyping = this.typing;\n        this.typing = false;\n        const typingList = event.getContent().user_ids;\n        if (!Array.isArray(typingList)) {\n            // malformed event :/ bail early. TODO: whine?\n            return;\n        }\n        if (typingList.indexOf(this.userId) !== -1) {\n            this.typing = true;\n        }\n        if (oldTyping !== this.typing) {\n            this.updateModifiedTime();\n            this.emit(RoomMemberEvent.Typing, event, this);\n        }\n    }\n\n    /**\n     * Update the last modified time to the current time.\n     */\n    private updateModifiedTime(): void {\n        this.modified = Date.now();\n    }\n\n    /**\n     * Get the timestamp when this RoomMember was last updated. This timestamp is\n     * updated when properties on this RoomMember are updated.\n     * It is updated <i>before</i> firing events.\n     * @returns The timestamp\n     */\n    public getLastModifiedTime(): number {\n        return this.modified;\n    }\n\n    public isKicked(): boolean {\n        return (\n            this.membership === KnownMembership.Leave &&\n            this.events.member !== undefined &&\n            this.events.member.getSender() !== this.events.member.getStateKey()\n        );\n    }\n\n    /**\n     * If this member was invited with the is_direct flag set, return\n     * the user that invited this member\n     * @returns user id of the inviter\n     */\n    public getDMInviter(): string | undefined {\n        // when not available because that room state hasn't been loaded in,\n        // we don't really know, but more likely to not be a direct chat\n        if (this.events.member) {\n            // TODO: persist the is_direct flag on the member as more member events\n            //       come in caused by displayName changes.\n\n            // the is_direct flag is set on the invite member event.\n            // This is copied on the prev_content section of the join member event\n            // when the invite is accepted.\n\n            const memberEvent = this.events.member;\n            let memberContent = memberEvent.getContent();\n            let inviteSender: string | undefined = memberEvent.getSender();\n\n            if (memberContent.membership === KnownMembership.Join) {\n                memberContent = memberEvent.getPrevContent();\n                inviteSender = memberEvent.getUnsigned().prev_sender;\n            }\n\n            if (memberContent.membership === KnownMembership.Invite && memberContent.is_direct) {\n                return inviteSender;\n            }\n        }\n    }\n\n    /**\n     * Get the avatar URL for a room member.\n     * @param baseUrl - The base homeserver URL See\n     * {@link MatrixClient#getHomeserverUrl}.\n     * @param width - The desired width of the thumbnail.\n     * @param height - The desired height of the thumbnail.\n     * @param resizeMethod - The thumbnail resize method to use, either\n     * \"crop\" or \"scale\".\n     * @param allowDefault - (optional) Passing false causes this method to\n     * return null if the user has no avatar image. Otherwise, a default image URL\n     * will be returned. Default: true. (Deprecated)\n     * @param allowDirectLinks - (optional) If true, the avatar URL will be\n     * returned even if it is a direct hyperlink rather than a matrix content URL.\n     * If false, any non-matrix content URLs will be ignored. Setting this option to\n     * true will expose URLs that, if fetched, will leak information about the user\n     * to anyone who they share a room with.\n     * @param useAuthentication - (optional) If true, the caller supports authenticated\n     * media and wants an authentication-required URL. Note that server support for\n     * authenticated media will not be checked - it is the caller's responsibility\n     * to do so before calling this function. Note also that useAuthentication\n     * implies allowRedirects. Defaults to false (unauthenticated endpoints).\n     * @returns the avatar URL or null.\n     */\n    public getAvatarUrl(\n        baseUrl: string,\n        width: number,\n        height: number,\n        resizeMethod: string,\n        allowDefault = true,\n        allowDirectLinks: boolean,\n        useAuthentication: boolean = false,\n    ): string | null {\n        const rawUrl = this.getMxcAvatarUrl();\n\n        if (!rawUrl && !allowDefault) {\n            return null;\n        }\n        const httpUrl = getHttpUriForMxc(\n            baseUrl,\n            rawUrl,\n            width,\n            height,\n            resizeMethod,\n            allowDirectLinks,\n            undefined,\n            useAuthentication,\n        );\n        if (httpUrl) {\n            return httpUrl;\n        }\n        return null;\n    }\n\n    /**\n     * get the mxc avatar url, either from a state event, or from a lazily loaded member\n     * @returns the mxc avatar url\n     */\n    public getMxcAvatarUrl(): string | undefined {\n        if (this.events.member) {\n            return this.events.member.getDirectionalContent().avatar_url;\n        } else if (this.user) {\n            return this.user.avatarUrl;\n        }\n    }\n}\n\nexport const MXID_PATTERN = /@.+:.+/;\nconst LTR_RTL_PATTERN = /[\\u200E\\u200F\\u202A-\\u202F]/;\n\nfunction shouldDisambiguate(selfUserId: string, displayName?: string, roomState?: RoomState): boolean {\n    if (!displayName || displayName === selfUserId) return false;\n    if (!roomState) return false;\n\n    const strippedDisplayName = removeHiddenChars(displayName);\n\n    // First check if the displayname is something we consider truthy\n    // after stripping it of zero width characters and padding spaces\n    if (!strippedDisplayName) return false;\n\n    // Next check if the name contains something that look like a mxid\n    // If it does, it may be someone trying to impersonate someone else\n    // Show full mxid in this case\n    if (MXID_PATTERN.test(strippedDisplayName)) return true;\n\n    // Also show mxid if the display name contains any LTR/RTL characters as these\n    // make it very difficult for us to find similar *looking* display names\n    // E.g \"Mark\" could be cloned by writing \"kraM\" but in RTL.\n    if (LTR_RTL_PATTERN.test(displayName)) return true;\n\n    // Also show mxid if there are other people with the same or similar\n    // displayname, after hidden character removal.\n    const userIds = roomState.getUserIdsWithDisplayName(displayName);\n    if (userIds.some((u) => u !== selfUserId)) return true;\n\n    return false;\n}\n\nfunction calculateDisplayName(selfUserId: string, displayName: string | undefined, disambiguate: boolean): string {\n    if (!displayName || displayName === selfUserId) return selfUserId;\n\n    if (disambiguate) return removeDirectionOverrideChars(displayName) + \" (\" + selfUserId + \")\";\n\n    // First check if the displayname is something we consider truthy\n    // after stripping it of zero width characters and padding spaces\n    if (!removeHiddenChars(displayName)) return selfUserId;\n\n    // We always strip the direction override characters (LRO and RLO).\n    // These override the text direction for all subsequent characters\n    // in the paragraph so if display names contained these, they'd\n    // need to be wrapped in something to prevent this from leaking out\n    // (which we can do in HTML but not text) or we'd need to add\n    // control characters to the string to reset any overrides (eg.\n    // adding PDF characters at the end). As far as we can see,\n    // there should be no reason these would be necessary - rtl display\n    // names should flip into the correct direction automatically based on\n    // the characters, and you can still embed rtl in ltr or vice versa\n    // with the embed chars or marker chars.\n    return removeDirectionOverrideChars(displayName);\n}\n","/*\nCopyright 2015 - 2021 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { RoomMember } from \"./room-member.ts\";\nimport { logger } from \"../logger.ts\";\nimport { isNumber, removeHiddenChars } from \"../utils.ts\";\nimport { EventType, UNSTABLE_MSC2716_MARKER } from \"../@types/event.ts\";\nimport { type IEvent, type MatrixEvent, MatrixEventEvent } from \"./event.ts\";\nimport { type MatrixClient } from \"../client.ts\";\nimport { GuestAccess, HistoryVisibility, JoinRule } from \"../@types/partials.ts\";\nimport { TypedEventEmitter } from \"./typed-event-emitter.ts\";\nimport {\n    Beacon,\n    BeaconEvent,\n    type BeaconEventHandlerMap,\n    getBeaconInfoIdentifier,\n    type BeaconIdentifier,\n} from \"./beacon.ts\";\nimport { TypedReEmitter } from \"../ReEmitter.ts\";\nimport { M_BEACON, M_BEACON_INFO } from \"../@types/beacon.ts\";\nimport { KnownMembership } from \"../@types/membership.ts\";\nimport { type RoomJoinRulesEventContent } from \"../@types/state_events.ts\";\nimport { shouldUseHydraForRoomVersion } from \"../utils/roomVersion.ts\";\n\nexport interface IMarkerFoundOptions {\n    /** Whether the timeline was empty before the marker event arrived in the\n     *  room. This could be happen in a variety of cases:\n     *  1. From the initial sync\n     *  2. It's the first state we're seeing after joining the room\n     *  3. Or whether it's coming from `syncFromCache`\n     *\n     * A marker event refers to `UNSTABLE_MSC2716_MARKER` and indicates that\n     * history was imported somewhere back in time. It specifically points to an\n     * MSC2716 insertion event where the history was imported at. Marker events\n     * are sent as state events so they are easily discoverable by clients and\n     * homeservers and don't get lost in timeline gaps.\n     */\n    timelineWasEmpty?: boolean;\n}\n\n// possible statuses for out-of-band member loading\nenum OobStatus {\n    NotStarted,\n    InProgress,\n    Finished,\n}\n\nexport interface IPowerLevelsContent {\n    users?: Record<string, number>;\n    events?: Record<string, number>;\n    notifications?: Partial<Record<\"room\", number>>;\n    // eslint-disable-next-line camelcase\n    users_default?: number;\n    // eslint-disable-next-line camelcase\n    events_default?: number;\n    // eslint-disable-next-line camelcase\n    state_default?: number;\n    ban?: number;\n    invite?: number;\n    kick?: number;\n    redact?: number;\n}\n\nexport enum RoomStateEvent {\n    Events = \"RoomState.events\",\n    Members = \"RoomState.members\",\n    NewMember = \"RoomState.newMember\",\n    Update = \"RoomState.update\", // signals batches of updates without specificity\n    BeaconLiveness = \"RoomState.BeaconLiveness\",\n    Marker = \"RoomState.Marker\",\n}\n\nexport type RoomStateEventHandlerMap = {\n    /**\n     * Fires whenever the event dictionary in room state is updated.\n     * This does not guarantee that any related objects (like RoomMember) have been updated.\n     * Use RoomStateEvent.Update for that.\n     * @param event - The matrix event which caused this event to fire.\n     * @param state - The room state whose RoomState.events dictionary\n     * was updated.\n     * @param prevEvent - The event being replaced by the new state, if\n     * known. Note that this can differ from `getPrevContent()` on the new state event\n     * as this is the store's view of the last state, not the previous state provided\n     * by the server.\n     * @example\n     * ```\n     * matrixClient.on(\"RoomState.events\", function(event, state, prevEvent){\n     *   var newStateEvent = event;\n     * });\n     * ```\n     */\n    [RoomStateEvent.Events]: (event: MatrixEvent, state: RoomState, prevEvent: MatrixEvent | null) => void;\n    /**\n     * Fires whenever a member in the members dictionary is updated in any way.\n     * @param event - The matrix event which caused this event to fire.\n     * @param state - The room state whose RoomState.members dictionary\n     * was updated.\n     * @param member - The room member that was updated.\n     * @example\n     * ```\n     * matrixClient.on(\"RoomState.members\", function(event, state, member){\n     *   var newMembershipState = member.membership;\n     * });\n     * ```\n     */\n    [RoomStateEvent.Members]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void;\n    /**\n     * Fires whenever a member is added to the members dictionary. The RoomMember\n     * will not be fully populated yet (e.g. no membership state) but will already\n     * be available in the members dictionary.\n     * @param event - The matrix event which caused this event to fire.\n     * @param state - The room state whose RoomState.members dictionary\n     * was updated with a new entry.\n     * @param member - The room member that was added.\n     * @example\n     * ```\n     * matrixClient.on(\"RoomState.newMember\", function(event, state, member){\n     *   // add event listeners on 'member'\n     * });\n     * ```\n     */\n    [RoomStateEvent.NewMember]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void;\n    [RoomStateEvent.Update]: (state: RoomState) => void;\n    [RoomStateEvent.BeaconLiveness]: (state: RoomState, hasLiveBeacons: boolean) => void;\n    [RoomStateEvent.Marker]: (event: MatrixEvent, setStateOptions?: IMarkerFoundOptions) => void;\n    [BeaconEvent.New]: (event: MatrixEvent, beacon: Beacon) => void;\n};\n\ntype EmittedEvents = RoomStateEvent | BeaconEvent;\ntype EventHandlerMap = RoomStateEventHandlerMap & BeaconEventHandlerMap;\n\ntype KeysMatching<T, V> = { [K in keyof T]-?: T[K] extends V ? K : never }[keyof T];\n\nexport class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {\n    public readonly reEmitter = new TypedReEmitter<EmittedEvents, EventHandlerMap>(this);\n    private sentinels: Record<string, RoomMember> = {}; // userId: RoomMember\n    // stores fuzzy matches to a list of userIDs (applies utils.removeHiddenChars to keys)\n    private displayNameToUserIds = new Map<string, string[]>();\n    private userIdsToDisplayNames: Record<string, string> = {};\n    private tokenToInvite: Record<string, MatrixEvent> = {}; // 3pid invite state_key to m.room.member invite\n    private joinedMemberCount: number | null = null; // cache of the number of joined members\n    // joined members count from summary api\n    // once set, we know the server supports the summary api\n    // and we should only trust that\n    // we could also only trust that before OOB members\n    // are loaded but doesn't seem worth the hassle atm\n    private summaryJoinedMemberCount: number | null = null;\n    // same for invited member count\n    private invitedMemberCount: number | null = null;\n    private summaryInvitedMemberCount: number | null = null;\n    private modified = -1;\n\n    // XXX: Should be read-only\n    // The room member dictionary, keyed on the user's ID.\n    public members: Record<string, RoomMember> = {}; // userId: RoomMember\n    // The state events dictionary, keyed on the event type and then the state_key value.\n    public events = new Map<string, Map<string, MatrixEvent>>(); // Map<eventType, Map<stateKey, MatrixEvent>>\n    // The pagination token for this state.\n    public paginationToken: string | null = null;\n\n    public readonly beacons = new Map<BeaconIdentifier, Beacon>();\n    private _liveBeaconIds: BeaconIdentifier[] = [];\n\n    // We only wants to print warnings about bad room state once.\n    private getVersionWarning = false;\n\n    /**\n     * Construct room state.\n     *\n     * Room State represents the state of the room at a given point.\n     * It can be mutated by adding state events to it.\n     * There are two types of room member associated with a state event:\n     * normal member objects (accessed via getMember/getMembers) which mutate\n     * with the state to represent the current state of that room/user, e.g.\n     * the object returned by `getMember('@bob:example.com')` will mutate to\n     * get a different display name if Bob later changes his display name\n     * in the room.\n     * There are also 'sentinel' members (accessed via getSentinelMember).\n     * These also represent the state of room members at the point in time\n     * represented by the RoomState object, but unlike objects from getMember,\n     * sentinel objects will always represent the room state as at the time\n     * getSentinelMember was called, so if Bob subsequently changes his display\n     * name, a room member object previously acquired with getSentinelMember\n     * will still have his old display name. Calling getSentinelMember again\n     * after the display name change will return a new RoomMember object\n     * with Bob's new display name.\n     *\n     * @param roomId - Optional. The ID of the room which has this state.\n     * If none is specified it just tracks paginationTokens, useful for notifTimelineSet\n     * @param oobMemberFlags - Optional. The state of loading out of bound members.\n     * As the timeline might get reset while they are loading, this state needs to be inherited\n     * and shared when the room state is cloned for the new timeline.\n     * This should only be passed from clone.\n     */\n    public constructor(\n        public readonly roomId: string,\n        private oobMemberFlags = { status: OobStatus.NotStarted },\n    ) {\n        super();\n        this.updateModifiedTime();\n    }\n\n    /**\n     * Gets the version of the room\n     * @returns The version of the room\n     */\n    public getRoomVersion(): string {\n        const createEvent = this.getStateEvents(EventType.RoomCreate, \"\");\n        if (!createEvent) {\n            if (!this.getVersionWarning) {\n                logger.warn(\"[getVersion] Room \" + this.roomId + \" does not have an m.room.create event\");\n                this.getVersionWarning = true;\n            }\n            return \"1\";\n        }\n        return createEvent.getContent()[\"room_version\"] ?? \"1\";\n    }\n\n    /**\n     * Returns the number of joined members in this room\n     * This method caches the result.\n     * @returns The number of members in this room whose membership is 'join'\n     */\n    public getJoinedMemberCount(): number {\n        if (this.summaryJoinedMemberCount !== null) {\n            return this.summaryJoinedMemberCount;\n        }\n        if (this.joinedMemberCount === null) {\n            this.joinedMemberCount = this.getMembers().reduce((count, m) => {\n                return m.membership === KnownMembership.Join ? count + 1 : count;\n            }, 0);\n        }\n        return this.joinedMemberCount;\n    }\n\n    /**\n     * Set the joined member count explicitly (like from summary part of the sync response)\n     * @param count - the amount of joined members\n     */\n    public setJoinedMemberCount(count: number): void {\n        this.summaryJoinedMemberCount = count;\n    }\n\n    /**\n     * Returns the number of invited members in this room\n     * @returns The number of members in this room whose membership is 'invite'\n     */\n    public getInvitedMemberCount(): number {\n        if (this.summaryInvitedMemberCount !== null) {\n            return this.summaryInvitedMemberCount;\n        }\n        if (this.invitedMemberCount === null) {\n            this.invitedMemberCount = this.getMembers().reduce((count, m) => {\n                return m.membership === KnownMembership.Invite ? count + 1 : count;\n            }, 0);\n        }\n        return this.invitedMemberCount;\n    }\n\n    /**\n     * Set the amount of invited members in this room\n     * @param count - the amount of invited members\n     */\n    public setInvitedMemberCount(count: number): void {\n        this.summaryInvitedMemberCount = count;\n    }\n\n    /**\n     * Get all RoomMembers in this room.\n     * @returns A list of RoomMembers.\n     */\n    public getMembers(): RoomMember[] {\n        return Object.values(this.members);\n    }\n\n    /**\n     * Get all RoomMembers in this room, excluding the user IDs provided.\n     * @param excludedIds - The user IDs to exclude.\n     * @returns A list of RoomMembers.\n     */\n    public getMembersExcept(excludedIds: string[]): RoomMember[] {\n        return this.getMembers().filter((m) => !excludedIds.includes(m.userId));\n    }\n\n    /**\n     * Get a room member by their user ID.\n     * @param userId - The room member's user ID.\n     * @returns The member or null if they do not exist.\n     */\n    public getMember(userId: string): RoomMember | null {\n        return this.members[userId] || null;\n    }\n\n    /**\n     * Get a room member whose properties will not change with this room state. You\n     * typically want this if you want to attach a RoomMember to a MatrixEvent which\n     * may no longer be represented correctly by Room.currentState or Room.oldState.\n     * The term 'sentinel' refers to the fact that this RoomMember is an unchanging\n     * guardian for state at this particular point in time.\n     * @param userId - The room member's user ID.\n     * @returns The member or null if they do not exist.\n     */\n    public getSentinelMember(userId: string): RoomMember | null {\n        if (!userId) return null;\n        let sentinel = this.sentinels[userId];\n\n        if (sentinel === undefined) {\n            sentinel = new RoomMember(this.roomId, userId);\n            const member = this.members[userId];\n            if (member?.events.member) {\n                sentinel.setMembershipEvent(member.events.member, this);\n            }\n            this.sentinels[userId] = sentinel;\n        }\n        return sentinel;\n    }\n\n    /**\n     * Get state events from the state of the room.\n     * @param eventType - The event type of the state event.\n     * @returns A list of events\n     */\n    public getStateEvents(eventType: EventType | string): MatrixEvent[];\n    /**\n     * Get state events from the state of the room.\n     * @param eventType - The event type of the state event.\n     * @param stateKey - The state_key of the state event.\n     * @returns A single event (or null if no match found).\n     */\n    public getStateEvents(eventType: EventType | string, stateKey: string): MatrixEvent | null;\n    public getStateEvents(eventType: EventType | string, stateKey?: string): MatrixEvent[] | MatrixEvent | null {\n        if (!this.events.has(eventType)) {\n            // no match\n            return stateKey === undefined ? [] : null;\n        }\n        if (stateKey === undefined) {\n            // return all values\n            return Array.from(this.events.get(eventType)!.values());\n        }\n        const event = this.events.get(eventType)!.get(stateKey);\n        return event ? event : null;\n    }\n\n    public get hasLiveBeacons(): boolean {\n        return !!this.liveBeaconIds?.length;\n    }\n\n    public get liveBeaconIds(): BeaconIdentifier[] {\n        return this._liveBeaconIds;\n    }\n\n    /**\n     * Creates a copy of this room state so that mutations to either won't affect the other.\n     * @returns the copy of the room state\n     */\n    public clone(): RoomState {\n        const copy = new RoomState(this.roomId, this.oobMemberFlags);\n\n        // Ugly hack: because setStateEvents will mark\n        // members as susperseding future out of bound members\n        // if loading is in progress (through oobMemberFlags)\n        // since these are not new members, we're merely copying them\n        // set the status to not started\n        // after copying, we set back the status\n        const status = this.oobMemberFlags.status;\n        this.oobMemberFlags.status = OobStatus.NotStarted;\n\n        Array.from(this.events.values()).forEach((eventsByStateKey) => {\n            copy.setStateEvents(Array.from(eventsByStateKey.values()));\n        });\n\n        // Ugly hack: see above\n        this.oobMemberFlags.status = status;\n\n        if (this.summaryInvitedMemberCount !== null) {\n            copy.setInvitedMemberCount(this.getInvitedMemberCount());\n        }\n        if (this.summaryJoinedMemberCount !== null) {\n            copy.setJoinedMemberCount(this.getJoinedMemberCount());\n        }\n\n        // copy out of band flags if needed\n        if (this.oobMemberFlags.status == OobStatus.Finished) {\n            // copy markOutOfBand flags\n            this.getMembers().forEach((member) => {\n                if (member.isOutOfBand()) {\n                    copy.getMember(member.userId)?.markOutOfBand();\n                }\n            });\n        }\n\n        return copy;\n    }\n\n    /**\n     * Add previously unknown state events.\n     * When lazy loading members while back-paginating,\n     * the relevant room state for the timeline chunk at the end\n     * of the chunk can be set with this method.\n     * @param events - state events to prepend\n     */\n    public setUnknownStateEvents(events: MatrixEvent[]): void {\n        const unknownStateEvents = events.filter((event) => {\n            return !this.events.has(event.getType()) || !this.events.get(event.getType())!.has(event.getStateKey()!);\n        });\n\n        this.setStateEvents(unknownStateEvents);\n    }\n\n    /**\n     * Add an array of one or more state MatrixEvents, overwriting any existing\n     * state with the same `{type, stateKey}` tuple. Will fire \"RoomState.events\"\n     * for every event added. May fire \"RoomState.members\" if there are\n     * `m.room.member` events. May fire \"RoomStateEvent.Marker\" if there are\n     * `UNSTABLE_MSC2716_MARKER` events.\n     * @param stateEvents - a list of state events for this room.\n     *\n     * @remarks\n     * Fires {@link RoomStateEvent.Members}\n     * Fires {@link RoomStateEvent.NewMember}\n     * Fires {@link RoomStateEvent.Events}\n     * Fires {@link RoomStateEvent.Marker}\n     */\n    public setStateEvents(stateEvents: MatrixEvent[], markerFoundOptions?: IMarkerFoundOptions): void {\n        this.updateModifiedTime();\n\n        // update the core event dict\n        // Track display names that change so we can recalculate disambiguation\n        const affectedDisplayNames = new Set<string>();\n        // Track userIds whose membership events we process so we don't emit duplicate events\n        const processedMemberUserIds = new Set<string>();\n\n        stateEvents.forEach((event) => {\n            if (event.getRoomId() !== this.roomId || !event.isState()) return;\n\n            if (M_BEACON_INFO.matches(event.getType())) {\n                this.setBeacon(event);\n            }\n\n            const lastStateEvent = this.getStateEventMatching(event);\n            this.setStateEvent(event);\n            if (event.getType() === EventType.RoomMember) {\n                const userId = event.getStateKey()!;\n                processedMemberUserIds.add(userId);\n                const newDisplayName = event.getContent().displayname ?? \"\";\n                const oldDisplayName = this.userIdsToDisplayNames[userId];\n\n                // Track both old and new display names for disambiguation recalculation\n                if (oldDisplayName) {\n                    const strippedOld = removeHiddenChars(oldDisplayName);\n                    if (strippedOld) affectedDisplayNames.add(strippedOld);\n                }\n                if (newDisplayName) {\n                    const strippedNew = removeHiddenChars(newDisplayName);\n                    if (strippedNew) affectedDisplayNames.add(strippedNew);\n                }\n\n                this.updateDisplayNameCache(userId, newDisplayName);\n                this.updateThirdPartyTokenCache(event);\n            }\n            this.emit(RoomStateEvent.Events, event, this, lastStateEvent);\n        });\n\n        this.onBeaconLivenessChange();\n        // update higher level data structures. This needs to be done AFTER the\n        // core event dict as these structures may depend on other state events in\n        // the given array (e.g. disambiguating display names in one go to do both\n        // clashing names rather than progressively which only catches 1 of them).\n        stateEvents.forEach((event) => {\n            if (event.getRoomId() !== this.roomId || !event.isState()) return;\n\n            if (event.getType() === EventType.RoomMember) {\n                const userId = event.getStateKey()!;\n\n                // leave events apparently elide the displayname or avatar_url,\n                // so let's fake one up so that we don't leak user ids\n                // into the timeline\n                if (\n                    event.getContent().membership === KnownMembership.Leave ||\n                    event.getContent().membership === KnownMembership.Ban\n                ) {\n                    event.getContent().avatar_url = event.getContent().avatar_url || event.getPrevContent().avatar_url;\n                    event.getContent().displayname =\n                        event.getContent().displayname || event.getPrevContent().displayname;\n                }\n\n                const member = this.getOrCreateMember(userId, event);\n                member.setMembershipEvent(event, this);\n                this.updateMember(member);\n                this.emit(RoomStateEvent.Members, event, this, member);\n            } else if (event.getType() === EventType.RoomPowerLevels) {\n                // events with unknown state keys should be ignored\n                // and should not aggregate onto members power levels\n                if (event.getStateKey() !== \"\") {\n                    return;\n                }\n                const members = Object.values(this.members);\n\n                const createEvent = this.getStateEvents(EventType.RoomCreate, \"\");\n                const creators = getCreators(this.getRoomVersion(), createEvent);\n\n                members.forEach((member) => {\n                    // We only propagate `RoomState.members` event if the\n                    // power levels has been changed\n                    // large room suffer from large re-rendering especially when not needed\n                    const oldLastModified = member.getLastModifiedTime();\n\n                    if (createEvent) {\n                        const pl = powerLevelForUserId(member.userId, event, creators);\n                        member.setPowerLevel(pl, event);\n                    }\n                    if (oldLastModified !== member.getLastModifiedTime()) {\n                        this.emit(RoomStateEvent.Members, event, this, member);\n                    }\n                });\n\n                // assume all our sentinels are now out-of-date\n                this.sentinels = {};\n            } else if (UNSTABLE_MSC2716_MARKER.matches(event.getType())) {\n                this.emit(RoomStateEvent.Marker, event, markerFoundOptions);\n            }\n        });\n\n        // Recalculate disambiguation for all members whose display names were affected.\n        // This ensures that when a user changes their name to match (or stop matching)\n        // another user, all affected users' disambiguation flags are updated correctly.\n        if (affectedDisplayNames.size > 0) {\n            // Collect all affected user IDs first to avoid duplicate processing\n            const affectedUserIds = new Set<string>();\n            for (const displayName of affectedDisplayNames) {\n                const userIds = this.displayNameToUserIds.get(displayName) ?? [];\n                userIds.forEach((id) => affectedUserIds.add(id));\n            }\n\n            // Process each affected member once, excluding those whose membership\n            // events were already processed (they already got their events emitted)\n            for (const userId of affectedUserIds) {\n                if (processedMemberUserIds.has(userId)) {\n                    continue;\n                }\n                const member = this.members[userId];\n                if (member?.events.member) {\n                    const nameChanged = member.recalculateDisambiguatedName(this);\n                    if (nameChanged) {\n                        this.emit(RoomStateEvent.Members, member.events.member, this, member);\n                    }\n                }\n            }\n        }\n\n        this.emit(RoomStateEvent.Update, this);\n    }\n\n    public async processBeaconEvents(events: MatrixEvent[], matrixClient: MatrixClient): Promise<void> {\n        if (\n            !events.length ||\n            // discard locations if we have no beacons\n            !this.beacons.size\n        ) {\n            return;\n        }\n\n        const beaconByEventIdDict = [...this.beacons.values()].reduce<Record<string, Beacon>>((dict, beacon) => {\n            dict[beacon.beaconInfoId] = beacon;\n            return dict;\n        }, {});\n\n        const processBeaconRelation = (beaconInfoEventId: string, event: MatrixEvent): void => {\n            if (!M_BEACON.matches(event.getType())) {\n                return;\n            }\n\n            const beacon = beaconByEventIdDict[beaconInfoEventId];\n\n            if (beacon) {\n                beacon.addLocations([event]);\n            }\n        };\n\n        for (const event of events) {\n            const relatedToEventId = event.getRelation()?.event_id;\n            // not related to a beacon we know about; discard\n            if (!relatedToEventId || !beaconByEventIdDict[relatedToEventId]) return;\n            if (!M_BEACON.matches(event.getType()) && !event.isEncrypted()) return;\n\n            try {\n                await matrixClient.decryptEventIfNeeded(event);\n                processBeaconRelation(relatedToEventId, event);\n            } catch {\n                if (event.isDecryptionFailure()) {\n                    // add an event listener for once the event is decrypted.\n                    event.once(MatrixEventEvent.Decrypted, async () => {\n                        processBeaconRelation(relatedToEventId, event);\n                    });\n                }\n            }\n        }\n    }\n\n    /**\n     * Looks up a member by the given userId, and if it doesn't exist,\n     * create it and emit the `RoomState.newMember` event.\n     * This method makes sure the member is added to the members dictionary\n     * before emitting, as this is done from setStateEvents and setOutOfBandMember.\n     * @param userId - the id of the user to look up\n     * @param event - the membership event for the (new) member. Used to emit.\n     * @returns the member, existing or newly created.\n     *\n     * @remarks\n     * Fires {@link RoomStateEvent.NewMember}\n     */\n    private getOrCreateMember(userId: string, event: MatrixEvent): RoomMember {\n        let member = this.members[userId];\n        if (!member) {\n            member = new RoomMember(this.roomId, userId);\n            // add member to members before emitting any events,\n            // as event handlers often lookup the member\n            this.members[userId] = member;\n            this.emit(RoomStateEvent.NewMember, event, this, member);\n        }\n        return member;\n    }\n\n    private setStateEvent(event: MatrixEvent): void {\n        if (!this.events.has(event.getType())) {\n            this.events.set(event.getType(), new Map());\n        }\n        this.events.get(event.getType())!.set(event.getStateKey()!, event);\n    }\n\n    /**\n     * @experimental\n     */\n    private setBeacon(event: MatrixEvent): void {\n        const beaconIdentifier = getBeaconInfoIdentifier(event);\n\n        if (this.beacons.has(beaconIdentifier)) {\n            const beacon = this.beacons.get(beaconIdentifier)!;\n\n            if (event.isRedacted()) {\n                if (beacon.beaconInfoId === (<IEvent>event.getRedactionEvent())?.redacts) {\n                    beacon.destroy();\n                    this.beacons.delete(beaconIdentifier);\n                }\n                return;\n            }\n\n            return beacon.update(event);\n        }\n\n        if (event.isRedacted()) {\n            return;\n        }\n\n        const beacon = new Beacon(event);\n\n        this.reEmitter.reEmit<BeaconEvent, BeaconEvent>(beacon, [\n            BeaconEvent.New,\n            BeaconEvent.Update,\n            BeaconEvent.Destroy,\n            BeaconEvent.LivenessChange,\n        ]);\n\n        this.emit(BeaconEvent.New, event, beacon);\n        beacon.on(BeaconEvent.LivenessChange, this.onBeaconLivenessChange.bind(this));\n        beacon.on(BeaconEvent.Destroy, this.onBeaconLivenessChange.bind(this));\n\n        this.beacons.set(beacon.identifier, beacon);\n    }\n\n    /**\n     * @experimental\n     * Check liveness of room beacons\n     * emit RoomStateEvent.BeaconLiveness event\n     */\n    private onBeaconLivenessChange(): void {\n        this._liveBeaconIds = Array.from(this.beacons.values())\n            .filter((beacon) => beacon.isLive)\n            .map((beacon) => beacon.identifier);\n\n        this.emit(RoomStateEvent.BeaconLiveness, this, this.hasLiveBeacons);\n    }\n\n    private getStateEventMatching(event: MatrixEvent): MatrixEvent | null {\n        return this.events.get(event.getType())?.get(event.getStateKey()!) ?? null;\n    }\n\n    private updateMember(member: RoomMember): void {\n        // this member may have a power level already, so set it.\n        const createEvent = this.getStateEvents(EventType.RoomCreate, \"\");\n        const pwrLvlEvent = this.getStateEvents(EventType.RoomPowerLevels, \"\");\n        if (pwrLvlEvent && createEvent) {\n            const powerLevel = powerLevelForUserId(\n                member.userId,\n                pwrLvlEvent,\n                getCreators(this.getRoomVersion(), createEvent),\n            );\n\n            member.setPowerLevel(powerLevel, pwrLvlEvent);\n        }\n\n        // blow away the sentinel which is now outdated\n        delete this.sentinels[member.userId];\n\n        this.members[member.userId] = member;\n        this.joinedMemberCount = null;\n        this.invitedMemberCount = null;\n    }\n\n    /**\n     * Get the out-of-band members loading state, whether loading is needed or not.\n     * Note that loading might be in progress and hence isn't needed.\n     * @returns whether or not the members of this room need to be loaded\n     */\n    public needsOutOfBandMembers(): boolean {\n        return this.oobMemberFlags.status === OobStatus.NotStarted;\n    }\n\n    /**\n     * Check if loading of out-of-band-members has completed\n     *\n     * @returns true if the full membership list of this room has been loaded. False if it is not started or is in\n     *    progress.\n     */\n    public outOfBandMembersReady(): boolean {\n        return this.oobMemberFlags.status === OobStatus.Finished;\n    }\n\n    /**\n     * Mark this room state as waiting for out-of-band members,\n     * ensuring it doesn't ask for them to be requested again\n     * through needsOutOfBandMembers\n     */\n    public markOutOfBandMembersStarted(): void {\n        if (this.oobMemberFlags.status !== OobStatus.NotStarted) {\n            return;\n        }\n        this.oobMemberFlags.status = OobStatus.InProgress;\n    }\n\n    /**\n     * Mark this room state as having failed to fetch out-of-band members\n     */\n    public markOutOfBandMembersFailed(): void {\n        if (this.oobMemberFlags.status !== OobStatus.InProgress) {\n            return;\n        }\n        this.oobMemberFlags.status = OobStatus.NotStarted;\n    }\n\n    /**\n     * Clears the loaded out-of-band members\n     */\n    public clearOutOfBandMembers(): void {\n        let count = 0;\n        Object.keys(this.members).forEach((userId) => {\n            const member = this.members[userId];\n            if (member.isOutOfBand()) {\n                ++count;\n                delete this.members[userId];\n            }\n        });\n        logger.log(`LL: RoomState removed ${count} members...`);\n        this.oobMemberFlags.status = OobStatus.NotStarted;\n    }\n\n    /**\n     * Sets the loaded out-of-band members.\n     * @param stateEvents - array of membership state events\n     */\n    public setOutOfBandMembers(stateEvents: MatrixEvent[]): void {\n        logger.log(`LL: RoomState about to set ${stateEvents.length} OOB members ...`);\n        if (this.oobMemberFlags.status !== OobStatus.InProgress) {\n            return;\n        }\n        logger.log(`LL: RoomState put in finished state ...`);\n        this.oobMemberFlags.status = OobStatus.Finished;\n        stateEvents.forEach((e) => this.setOutOfBandMember(e));\n        this.emit(RoomStateEvent.Update, this);\n    }\n\n    /**\n     * Sets a single out of band member, used by both setOutOfBandMembers and clone\n     * @param stateEvent - membership state event\n     */\n    private setOutOfBandMember(stateEvent: MatrixEvent): void {\n        if (stateEvent.getType() !== EventType.RoomMember) {\n            return;\n        }\n        const userId = stateEvent.getStateKey()!;\n        const existingMember = this.getMember(userId);\n        // never replace members received as part of the sync\n        if (existingMember && !existingMember.isOutOfBand()) {\n            return;\n        }\n\n        const member = this.getOrCreateMember(userId, stateEvent);\n        member.setMembershipEvent(stateEvent, this);\n        // needed to know which members need to be stored seperately\n        // as they are not part of the sync accumulator\n        // this is cleared by setMembershipEvent so when it's updated through /sync\n        member.markOutOfBand();\n\n        this.updateDisplayNameCache(member.userId, member.name);\n\n        this.setStateEvent(stateEvent);\n        this.updateMember(member);\n        this.emit(RoomStateEvent.Members, stateEvent, this, member);\n    }\n\n    /**\n     * Set the current typing event for this room.\n     * @param event - The typing event\n     */\n    public setTypingEvent(event: MatrixEvent): void {\n        Object.values(this.members).forEach(function (member) {\n            member.setTypingEvent(event);\n        });\n    }\n\n    /**\n     * Get the m.room.member event which has the given third party invite token.\n     *\n     * @param token - The token\n     * @returns The m.room.member event or null\n     */\n    public getInviteForThreePidToken(token: string): MatrixEvent | null {\n        return this.tokenToInvite[token] || null;\n    }\n\n    /**\n     * Update the last modified time to the current time.\n     */\n    private updateModifiedTime(): void {\n        this.modified = Date.now();\n    }\n\n    /**\n     * Get the timestamp when this room state was last updated. This timestamp is\n     * updated when this object has received new state events.\n     * @returns The timestamp\n     */\n    public getLastModifiedTime(): number {\n        return this.modified;\n    }\n\n    /**\n     * Get user IDs with the specified or similar display names.\n     * @param displayName - The display name to get user IDs from.\n     * @returns An array of user IDs or an empty array.\n     */\n    public getUserIdsWithDisplayName(displayName: string): string[] {\n        return this.displayNameToUserIds.get(removeHiddenChars(displayName)) ?? [];\n    }\n\n    /**\n     * Returns true if userId is in room, event is not redacted and either sender of\n     * mxEvent or has power level sufficient to redact events other than their own.\n     * @param mxEvent - The event to test permission for\n     * @param userId - The user ID of the user to test permission for\n     * @returns true if the given used ID can redact given event\n     */\n    public maySendRedactionForEvent(mxEvent: MatrixEvent, userId: string): boolean {\n        const member = this.getMember(userId);\n        if (!member || member.membership === KnownMembership.Leave) return false;\n\n        if (mxEvent.status || mxEvent.isRedacted()) return false;\n\n        // The user may have been the sender, but they can't redact their own message\n        // if redactions are blocked.\n        const canRedact = this.maySendEvent(EventType.RoomRedaction, userId);\n\n        if (!canRedact) return false;\n        if (mxEvent.getSender() === userId) return true;\n\n        return this.hasSufficientPowerLevelFor(\"redact\", member.powerLevel);\n    }\n\n    /**\n     * Returns true if the given power level is sufficient for action\n     * @param action - The type of power level to check\n     * @param powerLevel - The power level of the member\n     * @returns true if the given power level is sufficient\n     */\n    public hasSufficientPowerLevelFor(\n        action: KeysMatching<Required<IPowerLevelsContent>, number>,\n        powerLevel: number,\n    ): boolean {\n        const powerLevelsEvent = this.getStateEvents(EventType.RoomPowerLevels, \"\");\n\n        let powerLevels: IPowerLevelsContent = {};\n        if (powerLevelsEvent) {\n            powerLevels = powerLevelsEvent.getContent();\n        }\n\n        let requiredLevel = 50;\n        if (isNumber(powerLevels[action])) {\n            requiredLevel = powerLevels[action]!;\n        }\n\n        return powerLevel >= requiredLevel;\n    }\n\n    /**\n     * Short-form for maySendEvent('m.room.message', userId)\n     * @param userId - The user ID of the user to test permission for\n     * @returns true if the given user ID should be permitted to send\n     *                   message events into the given room.\n     */\n    public maySendMessage(userId: string): boolean {\n        return this.maySendEventOfType(EventType.RoomMessage, userId, false);\n    }\n\n    /**\n     * Returns true if the given user ID has permission to send a normal\n     * event of type `eventType` into this room.\n     * @param eventType - The type of event to test\n     * @param userId - The user ID of the user to test permission for\n     * @returns true if the given user ID should be permitted to send\n     *                        the given type of event into this room,\n     *                        according to the room's state.\n     */\n    public maySendEvent(eventType: EventType | string, userId: string): boolean {\n        return this.maySendEventOfType(eventType, userId, false);\n    }\n\n    /**\n     * Returns true if the given MatrixClient has permission to send a state\n     * event of type `stateEventType` into this room.\n     * @param stateEventType - The type of state events to test\n     * @param cli - The client to test permission for\n     * @returns true if the given client should be permitted to send\n     *                        the given type of state event into this room,\n     *                        according to the room's state.\n     */\n    public mayClientSendStateEvent(stateEventType: EventType | string, cli: MatrixClient): boolean {\n        if (cli.isGuest() || !cli.credentials.userId) {\n            return false;\n        }\n        return this.maySendStateEvent(stateEventType, cli.credentials.userId);\n    }\n\n    /**\n     * Returns true if the given user ID has permission to send a state\n     * event of type `stateEventType` into this room.\n     * @param stateEventType - The type of state events to test\n     * @param userId - The user ID of the user to test permission for\n     * @returns true if the given user ID should be permitted to send\n     *                        the given type of state event into this room,\n     *                        according to the room's state.\n     */\n    public maySendStateEvent(stateEventType: EventType | string, userId: string): boolean {\n        return this.maySendEventOfType(stateEventType, userId, true);\n    }\n\n    /**\n     * Returns true if the given user ID has permission to send a normal or state\n     * event of type `eventType` into this room.\n     * @param eventType - The type of event to test\n     * @param userId - The user ID of the user to test permission for\n     * @param state - If true, tests if the user may send a state\n     event of this type. Otherwise tests whether\n     they may send a regular event.\n     * @returns true if the given user ID should be permitted to send\n     *                        the given type of event into this room,\n     *                        according to the room's state.\n     */\n    private maySendEventOfType(eventType: EventType | string, userId: string, state: boolean): boolean {\n        const powerLevelsEvent = this.getStateEvents(EventType.RoomPowerLevels, \"\");\n\n        let powerLevels: IPowerLevelsContent;\n        let eventsLevels: Record<EventType | string, number> = {};\n\n        let stateDefault = 0;\n        let eventsDefault = 0;\n        if (powerLevelsEvent) {\n            powerLevels = powerLevelsEvent.getContent();\n            eventsLevels = powerLevels.events || {};\n\n            if (Number.isSafeInteger(powerLevels.state_default)) {\n                stateDefault = powerLevels.state_default!;\n            } else {\n                stateDefault = 50;\n            }\n\n            if (Number.isSafeInteger(powerLevels.events_default)) {\n                eventsDefault = powerLevels.events_default!;\n            }\n        }\n\n        let requiredLevel = state ? stateDefault : eventsDefault;\n        if (Number.isSafeInteger(eventsLevels[eventType])) {\n            requiredLevel = eventsLevels[eventType];\n        }\n\n        const roomMember = this.getMember(userId);\n        const userPowerLevel = roomMember?.powerLevel ?? 0;\n\n        return userPowerLevel >= requiredLevel;\n    }\n\n    /**\n     * Returns true if the given user ID has permission to trigger notification\n     * of type `notifLevelKey`\n     * @param notifLevelKey - The level of notification to test (eg. 'room')\n     * @param userId - The user ID of the user to test permission for\n     * @returns true if the given user ID has permission to trigger a\n     *                        notification of this type.\n     */\n    public mayTriggerNotifOfType(notifLevelKey: string, userId: string): boolean {\n        const member = this.getMember(userId);\n        if (!member) {\n            return false;\n        }\n\n        const powerLevelsEvent = this.getStateEvents(EventType.RoomPowerLevels, \"\");\n\n        let notifLevel = 50;\n        if (\n            powerLevelsEvent &&\n            powerLevelsEvent.getContent() &&\n            powerLevelsEvent.getContent().notifications &&\n            isNumber(powerLevelsEvent.getContent().notifications[notifLevelKey])\n        ) {\n            notifLevel = powerLevelsEvent.getContent().notifications[notifLevelKey];\n        }\n\n        return member.powerLevel >= notifLevel;\n    }\n\n    /**\n     * Returns the join rule based on the m.room.join_rule state event, defaulting to `invite`.\n     * @returns the join_rule applied to this room\n     */\n    public getJoinRule(): JoinRule {\n        const joinRuleEvent = this.getStateEvents(EventType.RoomJoinRules, \"\");\n        const joinRuleContent: Partial<RoomJoinRulesEventContent> = joinRuleEvent?.getContent() ?? {};\n        return joinRuleContent[\"join_rule\"] || JoinRule.Invite;\n    }\n\n    /**\n     * Returns the history visibility based on the m.room.history_visibility state event, defaulting to `shared`.\n     * @returns the history_visibility applied to this room\n     */\n    public getHistoryVisibility(): HistoryVisibility {\n        const historyVisibilityEvent = this.getStateEvents(EventType.RoomHistoryVisibility, \"\");\n        const historyVisibilityContent = historyVisibilityEvent?.getContent() ?? {};\n        return historyVisibilityContent[\"history_visibility\"] || HistoryVisibility.Shared;\n    }\n\n    /**\n     * Returns the guest access based on the m.room.guest_access state event, defaulting to `shared`.\n     * @returns the guest_access applied to this room\n     */\n    public getGuestAccess(): GuestAccess {\n        const guestAccessEvent = this.getStateEvents(EventType.RoomGuestAccess, \"\");\n        const guestAccessContent = guestAccessEvent?.getContent() ?? {};\n        return guestAccessContent[\"guest_access\"] || GuestAccess.Forbidden;\n    }\n\n    /**\n     * Find the predecessor room based on this room state.\n     *\n     * @param msc3946ProcessDynamicPredecessor - if true, look for an\n     * m.room.predecessor state event and use it if found (MSC3946).\n     * @returns null if this room has no predecessor. Otherwise, returns\n     * the roomId, last eventId and viaServers of the predecessor room.\n     *\n     * If msc3946ProcessDynamicPredecessor is true, use m.predecessor events\n     * as well as m.room.create events to find predecessors.\n     *\n     * Note: if an m.predecessor event is used, eventId may be undefined\n     * since last_known_event_id is optional.\n     *\n     * Note: viaServers may be undefined, and will definitely be undefined if\n     * this predecessor comes from a RoomCreate event (rather than a\n     * RoomPredecessor, which has the optional via_servers property).\n     */\n    public findPredecessor(\n        msc3946ProcessDynamicPredecessor = false,\n    ): { roomId: string; eventId?: string; viaServers?: string[] } | null {\n        // Note: the tests for this function are against Room.findPredecessor,\n        // which just calls through to here.\n\n        if (msc3946ProcessDynamicPredecessor) {\n            const predecessorEvent = this.getStateEvents(EventType.RoomPredecessor, \"\");\n            if (predecessorEvent) {\n                const content = predecessorEvent.getContent<{\n                    predecessor_room_id: string;\n                    last_known_event_id?: string;\n                    via_servers?: string[];\n                }>();\n                const roomId = content.predecessor_room_id;\n                let eventId = content.last_known_event_id;\n                if (typeof eventId !== \"string\") {\n                    eventId = undefined;\n                }\n                let viaServers = content.via_servers;\n                if (!Array.isArray(viaServers)) {\n                    viaServers = undefined;\n                }\n                if (typeof roomId === \"string\") {\n                    return { roomId, eventId, viaServers };\n                }\n            }\n        }\n\n        const createEvent = this.getStateEvents(EventType.RoomCreate, \"\");\n        if (createEvent) {\n            const predecessor = createEvent.getContent<{\n                predecessor?: Partial<{\n                    room_id: string;\n                    event_id: string;\n                }>;\n            }>()[\"predecessor\"];\n            if (predecessor) {\n                const roomId = predecessor[\"room_id\"];\n                if (typeof roomId === \"string\") {\n                    let eventId = predecessor[\"event_id\"];\n                    if (typeof eventId !== \"string\" || eventId === \"\") {\n                        eventId = undefined;\n                    }\n                    return { roomId, eventId };\n                }\n            }\n        }\n        return null;\n    }\n\n    private updateThirdPartyTokenCache(memberEvent: MatrixEvent): void {\n        if (!memberEvent.getContent().third_party_invite) {\n            return;\n        }\n        const token = (memberEvent.getContent().third_party_invite.signed || {}).token;\n        if (!token) {\n            return;\n        }\n        const threePidInvite = this.getStateEvents(EventType.RoomThirdPartyInvite, token);\n        if (!threePidInvite) {\n            return;\n        }\n        this.tokenToInvite[token] = memberEvent;\n    }\n\n    private updateDisplayNameCache(userId: string, displayName: string): void {\n        const oldName = this.userIdsToDisplayNames[userId];\n\n        delete this.userIdsToDisplayNames[userId];\n        if (oldName) {\n            // Remove the old name from the cache.\n            // We clobber the user_id > name lookup but the name -> [user_id] lookup\n            // means we need to remove that user ID from that array rather than nuking\n            // the lot.\n            const strippedOldName = removeHiddenChars(oldName);\n\n            const existingUserIds = this.displayNameToUserIds.get(strippedOldName);\n            if (existingUserIds) {\n                // remove this user ID from this array\n                const filteredUserIDs = existingUserIds.filter((id) => id !== userId);\n                this.displayNameToUserIds.set(strippedOldName, filteredUserIDs);\n            }\n        }\n\n        this.userIdsToDisplayNames[userId] = displayName;\n\n        const strippedDisplayname = displayName && removeHiddenChars(displayName);\n        // an empty stripped displayname (undefined/'') will be set to MXID in room-member.js\n        if (strippedDisplayname) {\n            const arr = this.displayNameToUserIds.get(strippedDisplayname) ?? [];\n            arr.push(userId);\n            this.displayNameToUserIds.set(strippedDisplayname, arr);\n        }\n    }\n}\n\n/**\n * Get the set of creator user IDs for a room: empty if the room is not a 'hydra' room, otherwise\n * computed from the sender of the m.room.create event plus the additional_creators field.\n * @param roomVersion The version of the room\n * @param roomCreateEvent The m.room.create event for the room\n * @returns A set of user IDs of the creators of the room.\n */\nfunction getCreators(roomVersion: string, roomCreateEvent: MatrixEvent | null): Set<string> {\n    const creators = new Set<string>();\n    if (shouldUseHydraForRoomVersion(roomVersion) && roomCreateEvent) {\n        const roomCreateSender = roomCreateEvent.getSender();\n        if (roomCreateSender) creators.add(roomCreateSender);\n        const additionalCreators = roomCreateEvent.getDirectionalContent().additional_creators;\n        if (Array.isArray(additionalCreators)) additionalCreators.forEach((c) => creators.add(c));\n    }\n    return creators;\n}\n\n/**\n *\n * @param userId The user ID to compute the power level for\n * @param powerLevelEvents The power level event for the room\n * @param creators The set of creator user IDs for the room if the room is a 'hydra' room, otherwise the empty set.\n */\nfunction powerLevelForUserId(userId: string, powerLevelEvent: MatrixEvent, creators: Set<string>): number {\n    if (creators.has(userId)) {\n        // As of \"Hydra\", If the user is a creator, they always have the highest power level\n        return Infinity;\n    } else {\n        const evContent = powerLevelEvent.getDirectionalContent();\n\n        const users: { [userId: string]: number } = evContent.users || {};\n\n        if (users[userId] !== undefined && Number.isInteger(users[userId])) {\n            return users[userId];\n        } else if (evContent.users_default !== undefined) {\n            return evContent.users_default;\n        } else {\n            return 0;\n        }\n    }\n}\n","import { logger as loggerInstance } from \"../logger.ts\";\nimport { type MatrixEvent } from \"./event.ts\";\nimport { TypedEventEmitter } from \"./typed-event-emitter.ts\";\n\nconst logger = loggerInstance.getChild(\"RoomStickyEvents\");\n\nexport enum RoomStickyEventsEvent {\n    Update = \"RoomStickyEvents.Update\",\n}\n\nexport type StickyMatrixEvent = MatrixEvent & { unstableStickyExpiresAt: number };\n\nexport type RoomStickyEventsMap = {\n    /**\n     * Fires when any sticky event changes happen in a room.\n     * @param added Any new sticky events with no predecessor events (matching sender, type, and sticky_key)\n     * @param updated Any sticky events that supersede an existing event (matching sender, type, and sticky_key)\n     * @param removed The events that were removed from the map due to expiry.\n     */\n    [RoomStickyEventsEvent.Update]: (\n        added: StickyMatrixEvent[],\n        updated: { current: StickyMatrixEvent; previous: StickyMatrixEvent }[],\n        removed: StickyMatrixEvent[],\n    ) => void;\n};\n\ntype UserId = `@${string}`;\n\nfunction assertIsUserId(value: unknown): asserts value is UserId {\n    if (typeof value !== \"string\") throw new Error(\"Not a string\");\n    if (!value.startsWith(\"@\")) throw new Error(\"Not a userId\");\n}\n\n/**\n * Tracks sticky events on behalf of one room, and fires an event\n * whenever a sticky event is updated or replaced.\n */\nexport class RoomStickyEventsStore extends TypedEventEmitter<RoomStickyEventsEvent, RoomStickyEventsMap> {\n    /**\n     * Sticky event map is a nested map of:\n     *  eventType -> `content.sticky_key sender` -> StickyMatrixEvent[]\n     *\n     * The events are ordered in latest to earliest expiry, so that the first event\n     * in the array will always be the \"current\" one.\n     */\n    private readonly stickyEventsMap = new Map<string, Map<string, StickyMatrixEvent[]>>();\n    /**\n     * These are sticky events that have no sticky key and therefore exist outside the tuple\n     * system above. They are just held in this Set until they expire.\n     */\n    private readonly unkeyedStickyEvents = new Set<StickyMatrixEvent>();\n\n    private stickyEventTimer?: ReturnType<typeof setTimeout>;\n    private nextStickyEventExpiryTs: number = Number.MAX_SAFE_INTEGER;\n\n    /**\n     * Sort two sticky events by order of expiry. This assumes the sticky events have the same\n     * `type`, `sticky_key` and `sender`.\n     * @returns A positive value if event A will expire sooner, or a negative value if event B will expire sooner.\n     */\n    private static sortStickyEvent(eventA: StickyMatrixEvent, eventB: StickyMatrixEvent): number {\n        // Sticky events with the same key have to use the same expiration duration.\n        // Hence, comparing via `origin_server_ts` yields the exact same result as comparing their expiration time.\n        if (eventB.getTs() !== eventA.getTs()) {\n            return eventB.getTs() - eventA.getTs();\n        }\n\n        if ((eventB.getId() ?? \"\") > (eventA.getId() ?? \"\")) {\n            return 1;\n        }\n\n        // This should fail as we've got corruption in our sticky array.\n        throw Error(\"Comparing two sticky events with the same event ID is not allowed.\");\n    }\n\n    /**\n     * Generate the correct key for an event to be found in the inner maps of `stickyEventsMap`.\n     * @param stickyKey The sticky key of an event.\n     * @param sender The sender of the event.\n     */\n    private static stickyMapKey(stickyKey: string, sender: UserId): string {\n        return `${stickyKey}${sender}`;\n    }\n\n    /**\n     * Get all sticky events that are currently active.\n     * @returns An iterable set of events.\n     * @yields {@link StickyMatrixEvent}\n     */\n    public *getStickyEvents(): Iterable<StickyMatrixEvent> {\n        yield* this.unkeyedStickyEvents;\n        for (const innerMap of this.stickyEventsMap.values()) {\n            // Inner map contains a map of sender+stickykeys => all sticky events\n            for (const events of innerMap.values()) {\n                // The first sticky event is the \"current\" one in the sticky map.\n                yield events[0];\n            }\n        }\n    }\n\n    /**\n     * Get an active sticky event that match the given `type`, `sender`, and `stickyKey`\n     * @param type The event `type`.\n     * @param sender The sender of the sticky event.\n     * @param stickyKey The sticky key used by the event.\n     * @returns A matching active sticky event, or undefined.\n     */\n    public getKeyedStickyEvent(sender: string, type: string, stickyKey: string): StickyMatrixEvent | undefined {\n        assertIsUserId(sender);\n        return this.stickyEventsMap.get(type)?.get(RoomStickyEventsStore.stickyMapKey(stickyKey, sender))?.[0];\n    }\n\n    /**\n     * Get active sticky events without a sticky key that match the given `type` and `sender`.\n     * @param type The event `type`.\n     * @param sender The sender of the sticky event.\n     * @returns An array of matching sticky events.\n     */\n    public getUnkeyedStickyEvent(sender: string, type: string): StickyMatrixEvent[] {\n        return [...this.unkeyedStickyEvents].filter((ev) => ev.getType() === type && ev.getSender() === sender);\n    }\n\n    /**\n     * Adds a sticky event into the local sticky event map.\n     *\n     * NOTE: This will not cause `RoomEvent.StickyEvents` to be emitted.\n     *\n     * @throws If the `event` does not contain valid sticky data.\n     * @param event The MatrixEvent that contains sticky data.\n     * @returns An object describing whether the event was added to the map,\n     *          and the previous event it may have replaced.\n     */\n    private addStickyEvent(event: MatrixEvent): { added: true; prevEvent?: StickyMatrixEvent } | { added: false } {\n        const stickyKey = event.getContent().msc4354_sticky_key;\n        if (typeof stickyKey !== \"string\" && stickyKey !== undefined) {\n            throw new Error(`${event.getId()} is missing msc4354_sticky_key`);\n        }\n\n        // With this we have the guarantee, that all events in stickyEventsMap are correctly formatted\n        if (event.unstableStickyExpiresAt === undefined) {\n            throw new Error(`${event.getId()} is missing msc4354_sticky.duration_ms`);\n        }\n        const sender = event.getSender();\n        const type = event.getType();\n        assertIsUserId(sender);\n        if (event.unstableStickyExpiresAt <= Date.now()) {\n            logger.info(\"ignored sticky event with older expiration time than current time\", stickyKey);\n            return { added: false };\n        }\n\n        // While we fully expect the server to always provide the correct value,\n        // this is just insurance to protect against attacks on our Map.\n        if (!sender.startsWith(\"@\")) {\n            throw new Error(\"Expected sender to start with @\");\n        }\n\n        const stickyEvent = event as StickyMatrixEvent;\n\n        if (stickyKey === undefined) {\n            this.unkeyedStickyEvents.add(stickyEvent);\n            // Recalculate the next expiry time.\n            this.nextStickyEventExpiryTs = Math.min(event.unstableStickyExpiresAt, this.nextStickyEventExpiryTs);\n\n            this.scheduleStickyTimer();\n            return { added: true };\n        }\n\n        // Why this is safe:\n        // A type may contain anything but the *sender* is tightly\n        // constrained so that a key will always end with a @<user_id>\n        // E.g. Where a malicious event type might be \"rtc.member.event@foo:bar\" the key becomes:\n        // \"rtc.member.event.@foo:bar@bar:baz\"\n        const innerMapKey = RoomStickyEventsStore.stickyMapKey(stickyKey, sender);\n        const currentEventSet = [stickyEvent, ...(this.stickyEventsMap.get(type)?.get(innerMapKey) ?? [])].sort(\n            RoomStickyEventsStore.sortStickyEvent,\n        );\n        if (!this.stickyEventsMap.has(type)) {\n            this.stickyEventsMap.set(type, new Map());\n        }\n        this.stickyEventsMap.get(type)?.set(innerMapKey, currentEventSet);\n\n        // Recalculate the next expiry time.\n        this.nextStickyEventExpiryTs = Math.min(stickyEvent.unstableStickyExpiresAt, this.nextStickyEventExpiryTs);\n\n        this.scheduleStickyTimer();\n        return {\n            added: currentEventSet[0] === stickyEvent,\n            prevEvent: currentEventSet?.[1],\n        };\n    }\n\n    /**\n     * Add a series of sticky events, emitting `RoomEvent.StickyEvents` if any\n     * changes were made.\n     * @param events A set of new sticky events.\n     */\n    public addStickyEvents(events: MatrixEvent[]): void {\n        const added: StickyMatrixEvent[] = [];\n        const updated: { current: StickyMatrixEvent; previous: StickyMatrixEvent }[] = [];\n        for (const event of events) {\n            try {\n                const result = this.addStickyEvent(event);\n                if (result.added) {\n                    if (result.prevEvent) {\n                        // e is validated as a StickyMatrixEvent by virtue of `addStickyEvent` returning added: true.\n                        updated.push({ current: event as StickyMatrixEvent, previous: result.prevEvent });\n                    } else {\n                        added.push(event as StickyMatrixEvent);\n                    }\n                }\n            } catch (ex) {\n                logger.warn(\"ignored invalid sticky event\", ex);\n            }\n        }\n        if (added.length || updated.length) this.emit(RoomStickyEventsEvent.Update, added, updated, []);\n        this.scheduleStickyTimer();\n    }\n\n    /**\n     * Schedule the sticky event expiry timer. The timer will\n     * run immediately if an event has already expired.\n     */\n    private scheduleStickyTimer(): void {\n        if (this.stickyEventTimer) {\n            clearTimeout(this.stickyEventTimer);\n            this.stickyEventTimer = undefined;\n        }\n        if (this.nextStickyEventExpiryTs === Number.MAX_SAFE_INTEGER) {\n            // We have no events due to expire.\n            return;\n        } // otherwise, schedule in the future\n        this.stickyEventTimer = setTimeout(this.cleanExpiredStickyEvents, this.nextStickyEventExpiryTs - Date.now());\n    }\n\n    /**\n     * Clean out any expired sticky events.\n     */\n    private readonly cleanExpiredStickyEvents = (): void => {\n        const now = Date.now();\n        const removedEvents: StickyMatrixEvent[] = [];\n\n        // We will recalculate this as we check all events.\n        this.nextStickyEventExpiryTs = Number.MAX_SAFE_INTEGER;\n        for (const [eventType, innerEvents] of this.stickyEventsMap.entries()) {\n            for (const [innerMapKey, [currentEvent, ...previousEvents]] of innerEvents) {\n                // we only added items with `sticky` into this map so we can assert non-null here\n                if (now >= currentEvent.unstableStickyExpiresAt) {\n                    logger.debug(\"Expiring sticky event\", currentEvent.getId());\n                    removedEvents.push(currentEvent);\n                    this.stickyEventsMap.get(eventType)!.delete(innerMapKey);\n                } else {\n                    // Ensure we remove any previous events which have now expired, to avoid unbounded memory consumption.\n                    this.stickyEventsMap\n                        .get(eventType)!\n                        .set(innerMapKey, [\n                            currentEvent,\n                            ...previousEvents.filter((e) => e.unstableStickyExpiresAt <= now),\n                        ]);\n                    // If not removing the event, check to see if it's the next lowest expiry.\n                    this.nextStickyEventExpiryTs = Math.min(\n                        this.nextStickyEventExpiryTs,\n                        currentEvent.unstableStickyExpiresAt,\n                    );\n                }\n            }\n            // Clean up map after use.\n            if (this.stickyEventsMap.get(eventType)?.size === 0) {\n                this.stickyEventsMap.delete(eventType);\n            }\n        }\n        for (const event of this.unkeyedStickyEvents) {\n            if (now >= event.unstableStickyExpiresAt) {\n                logger.debug(\"Expiring sticky event\", event.getId());\n                this.unkeyedStickyEvents.delete(event);\n                removedEvents.push(event);\n            } else {\n                // If not removing the event, check to see if it's the next lowest expiry.\n                this.nextStickyEventExpiryTs = Math.min(this.nextStickyEventExpiryTs, event.unstableStickyExpiresAt);\n            }\n        }\n        if (removedEvents.length) {\n            this.emit(RoomStickyEventsEvent.Update, [], [], removedEvents);\n        }\n        // Finally, schedule the next run.\n        this.scheduleStickyTimer();\n    };\n\n    /**\n     * Handles incoming event redactions. Checks the sticky map\n     * for any active sticky events being redacted.\n     * @param redactedEvent The MatrixEvent OR event ID of the event being redacted. MAY not be a sticky event.\n     */\n    public handleRedaction(redactedEvent: MatrixEvent | string): void {\n        // Note, we do not adjust`nextStickyEventExpiryTs` here.\n        // If this event happens to be the most recent expiring event\n        // then we may do one extra iteration of cleanExpiredStickyEvents\n        // but this saves us having to iterate over all events here to calculate\n        // the next expiry time.\n\n        // Note, as soon as we find a positive match on an event in this function\n        // we can return. There is no need to continue iterating on a positive match\n        // as an event can only appear in one map.\n\n        // Handle unkeyedStickyEvents first since it's *quick*.\n        const redactEventId = typeof redactedEvent === \"string\" ? redactedEvent : redactedEvent.getId();\n        for (const event of this.unkeyedStickyEvents) {\n            if (event.getId() === redactEventId) {\n                this.unkeyedStickyEvents.delete(event);\n                this.emit(RoomStickyEventsEvent.Update, [], [], [event]);\n                return;\n            }\n        }\n\n        // Faster method of finding the event since we have the event cached.\n        if (typeof redactedEvent !== \"string\" && !redactedEvent.isRedacted()) {\n            const stickyKey = redactedEvent.getContent().msc4354_sticky_key;\n            if (typeof stickyKey !== \"string\" && stickyKey !== undefined) {\n                return; // Not a sticky event.\n            }\n            const eventType = redactedEvent.getType();\n            const sender = redactedEvent.getSender();\n            assertIsUserId(sender);\n            const innerMap = this.stickyEventsMap.get(eventType);\n            if (!innerMap) {\n                return;\n            }\n            const mapKey = RoomStickyEventsStore.stickyMapKey(stickyKey, sender);\n            const [currentEvent, ...previousEvents] = innerMap.get(mapKey) ?? [];\n            if (!currentEvent) {\n                // No event current in the map so ignore.\n                return;\n            }\n            logger.debug(`Redaction for ${redactEventId} under sticky key ${stickyKey}`);\n            // Revert to previous state, taking care to skip any other redacted events.\n            const newEvents = previousEvents.filter((e) => !e.isRedacted()).sort(RoomStickyEventsStore.sortStickyEvent);\n            this.stickyEventsMap.get(eventType)?.set(mapKey, newEvents);\n            if (newEvents.length) {\n                this.emit(\n                    RoomStickyEventsEvent.Update,\n                    [],\n                    [\n                        {\n                            // This looks confusing. This emits that the newer event\n                            // has been redacted and the previous event has taken it's place.\n                            previous: currentEvent,\n                            current: newEvents[0],\n                        },\n                    ],\n                    [],\n                );\n            } else {\n                // We did not find a previous event, so just expire.\n                innerMap.delete(mapKey);\n                if (innerMap.size === 0) {\n                    this.stickyEventsMap.delete(eventType);\n                }\n                this.emit(RoomStickyEventsEvent.Update, [], [], [currentEvent]);\n            }\n            return;\n        }\n\n        // We only know the event ID of the redacted event, so we need to\n        // traverse the map to find our event.\n        for (const innerMap of this.stickyEventsMap.values()) {\n            for (const [currentEvent] of innerMap.values()) {\n                if (currentEvent.getId() !== redactEventId) {\n                    continue;\n                }\n                // Found the event.\n                return this.handleRedaction(currentEvent);\n            }\n        }\n    }\n\n    /**\n     * Clear all events and stop the timer from firing.\n     */\n    public clear(): void {\n        this.stickyEventsMap.clear();\n        // Unschedule timer.\n        this.nextStickyEventExpiryTs = Number.MAX_SAFE_INTEGER;\n        this.scheduleStickyTimer();\n    }\n}\n","/*\nCopyright 2015 - 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { M_POLL_START } from \"matrix-events-sdk\";\n\nimport {\n    DuplicateStrategy,\n    EventTimelineSet,\n    type EventTimelineSetHandlerMap,\n    type IAddLiveEventOptions,\n} from \"./event-timeline-set.ts\";\nimport { Direction, EventTimeline } from \"./event-timeline.ts\";\nimport { getHttpUriForMxc } from \"../content-repo.ts\";\nimport * as utils from \"../utils.ts\";\nimport { normalize, noUnsafeEventProps, removeElement } from \"../utils.ts\";\nimport {\n    type IEvent,\n    type IThreadBundledRelationship,\n    MatrixEvent,\n    MatrixEventEvent,\n    type MatrixEventHandlerMap,\n} from \"./event.ts\";\nimport { EventStatus } from \"./event-status.ts\";\nimport { RoomMember } from \"./room-member.ts\";\nimport { type Hero, type IRoomSummary, RoomSummary } from \"./room-summary.ts\";\nimport { logger } from \"../logger.ts\";\nimport { TypedReEmitter } from \"../ReEmitter.ts\";\nimport {\n    EVENT_VISIBILITY_CHANGE_TYPE,\n    EventType,\n    RelationType,\n    RoomCreateTypeField,\n    RoomType,\n    UNSIGNED_THREAD_ID_FIELD,\n    UNSTABLE_ELEMENT_FUNCTIONAL_USERS,\n} from \"../@types/event.ts\";\nimport { type MatrixClient, PendingEventOrdering } from \"../client.ts\";\nimport { type GuestAccess, type HistoryVisibility, type JoinRule, type ResizeMethod } from \"../@types/partials.ts\";\nimport { Filter, type IFilterDefinition } from \"../filter.ts\";\nimport { type RoomState, RoomStateEvent, type RoomStateEventHandlerMap } from \"./room-state.ts\";\nimport { BeaconEvent, type BeaconEventHandlerMap } from \"./beacon.ts\";\nimport {\n    FILTER_RELATED_BY_REL_TYPES,\n    FILTER_RELATED_BY_SENDERS,\n    Thread,\n    THREAD_RELATION_TYPE,\n    ThreadEvent,\n    type ThreadEventHandlerMap as ThreadHandlerMap,\n    ThreadFilterType,\n} from \"./thread.ts\";\nimport {\n    type CachedReceiptStructure,\n    MAIN_ROOM_TIMELINE,\n    type Receipt,\n    type ReceiptContent,\n    ReceiptType,\n} from \"../@types/read_receipts.ts\";\nimport { type IStateEventWithRoomId } from \"../@types/search.ts\";\nimport { RelationsContainer } from \"./relations-container.ts\";\nimport { ReadReceipt, synthesizeReceipt } from \"./read-receipt.ts\";\nimport { isPollEvent, Poll, PollEvent } from \"./poll.ts\";\nimport { RoomReceipts } from \"./room-receipts.ts\";\nimport { compareEventOrdering } from \"./compare-event-ordering.ts\";\nimport { KnownMembership, type Membership } from \"../@types/membership.ts\";\nimport { type Capabilities, type IRoomVersionsCapability, RoomVersionStability } from \"../serverCapabilities.ts\";\nimport { type MSC4186Hero } from \"../sliding-sync.ts\";\nimport { RoomStickyEventsStore, RoomStickyEventsEvent, type RoomStickyEventsMap } from \"./room-sticky-events.ts\";\nimport { RoomRetentionPolicy } from \"./room-retention.ts\";\n\n// These constants are used as sane defaults when the homeserver doesn't support\n// the m.room_versions capability. In practice, KNOWN_SAFE_ROOM_VERSION should be\n// the same as the common default room version whereas SAFE_ROOM_VERSIONS are the\n// room versions which are considered okay for people to run without being asked\n// to upgrade (ie: \"stable\"). Eventually, we should remove these when all homeservers\n// return an m.room_versions capability.\nexport const KNOWN_SAFE_ROOM_VERSION = \"10\";\nconst SAFE_ROOM_VERSIONS = [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\"];\n\ninterface IOpts {\n    /**\n     * Controls where pending messages appear in a room's timeline.\n     * If \"<b>chronological</b>\", messages will appear in the timeline when the call to `sendEvent` was made.\n     * If \"<b>detached</b>\", pending messages will appear in a separate list,\n     * accessible via {@link Room#getPendingEvents}.\n     * Default: \"chronological\".\n     */\n    pendingEventOrdering?: PendingEventOrdering;\n    /**\n     * Set to true to enable improved timeline support.\n     */\n    timelineSupport?: boolean;\n    lazyLoadMembers?: boolean;\n}\n\nexport interface IRecommendedVersion {\n    version: string;\n    needsUpgrade: boolean;\n    urgent: boolean;\n}\n\n// When inserting a visibility event affecting event `eventId`, we\n// need to scan through existing visibility events for `eventId`.\n// In theory, this could take an unlimited amount of time if:\n//\n// - the visibility event was sent by a moderator; and\n// - `eventId` already has many visibility changes (usually, it should\n//   be 2 or less); and\n// - for some reason, the visibility changes are received out of order\n//   (usually, this shouldn't happen at all).\n//\n// For this reason, we limit the number of events to scan through,\n// expecting that a broken visibility change for a single event in\n// an extremely uncommon case (possibly a DoS) is a small\n// price to pay to keep matrix-js-sdk responsive.\nconst MAX_NUMBER_OF_VISIBILITY_EVENTS_TO_SCAN_THROUGH = 30;\n\nexport type NotificationCount = Partial<Record<NotificationCountType, number>>;\n\nexport enum NotificationCountType {\n    Highlight = \"highlight\",\n    Total = \"total\",\n}\n\nexport interface ICreateFilterOpts {\n    // Populate the filtered timeline with already loaded events in the room\n    // timeline. Useful to disable for some filters that can't be achieved by the\n    // client in an efficient manner\n    prepopulateTimeline?: boolean;\n    useSyncEvents?: boolean;\n    pendingEvents?: boolean;\n}\n\nexport enum RoomEvent {\n    MyMembership = \"Room.myMembership\",\n    Tags = \"Room.tags\",\n    AccountData = \"Room.accountData\",\n    Receipt = \"Room.receipt\",\n    Name = \"Room.name\",\n    Redaction = \"Room.redaction\",\n    RedactionCancelled = \"Room.redactionCancelled\",\n    LocalEchoUpdated = \"Room.localEchoUpdated\",\n    Timeline = \"Room.timeline\",\n    TimelineReset = \"Room.timelineReset\",\n    TimelineRefresh = \"Room.TimelineRefresh\",\n    OldStateUpdated = \"Room.OldStateUpdated\",\n    CurrentStateUpdated = \"Room.CurrentStateUpdated\",\n    HistoryImportedWithinTimeline = \"Room.historyImportedWithinTimeline\",\n    UnreadNotifications = \"Room.UnreadNotifications\",\n    Summary = \"Room.Summary\",\n}\n\nexport type RoomEmittedEvents =\n    | RoomEvent\n    | RoomStateEvent.Events\n    | RoomStateEvent.Members\n    | RoomStateEvent.NewMember\n    | RoomStateEvent.Update\n    | RoomStateEvent.Marker\n    | RoomStickyEventsEvent.Update\n    | ThreadEvent.New\n    | ThreadEvent.Update\n    | ThreadEvent.NewReply\n    | ThreadEvent.Delete\n    | MatrixEventEvent.BeforeRedaction\n    | BeaconEvent.New\n    | BeaconEvent.Update\n    | BeaconEvent.Destroy\n    | BeaconEvent.LivenessChange\n    | PollEvent.New;\n\nexport type RoomEventHandlerMap = {\n    /**\n     * Fires when the logged in user's membership in the room is updated.\n     *\n     * @param room - The room in which the membership has been updated\n     * @param membership - The new membership value\n     * @param prevMembership - The previous membership value\n     */\n    [RoomEvent.MyMembership]: (room: Room, membership: Membership, prevMembership?: Membership) => void;\n    /**\n     * Fires whenever a room's tags are updated.\n     * @param event - The tags event\n     * @param room - The room whose Room.tags was updated.\n     * @example\n     * ```\n     * matrixClient.on(\"Room.tags\", function(event, room){\n     *   var newTags = event.getContent().tags;\n     *   if (newTags[\"favourite\"]) showStar(room);\n     * });\n     * ```\n     */\n    [RoomEvent.Tags]: (event: MatrixEvent, room: Room) => void;\n    /**\n     * Fires whenever a room's account_data is updated.\n     * @param event - The account_data event\n     * @param room - The room whose account_data was updated.\n     * @param prevEvent - The event being replaced by\n     * the new account data, if known.\n     * @example\n     * ```\n     * matrixClient.on(\"Room.accountData\", function(event, room, oldEvent){\n     *   if (event.getType() === \"m.room.colorscheme\") {\n     *       applyColorScheme(event.getContents());\n     *   }\n     * });\n     * ```\n     */\n    [RoomEvent.AccountData]: (event: MatrixEvent, room: Room, prevEvent?: MatrixEvent) => void;\n    /**\n     * Fires whenever a receipt is received for a room\n     * @param event - The receipt event\n     * @param room - The room whose receipts was updated.\n     * @example\n     * ```\n     * matrixClient.on(\"Room.receipt\", function(event, room){\n     *   var receiptContent = event.getContent();\n     * });\n     * ```\n     */\n    [RoomEvent.Receipt]: (event: MatrixEvent, room: Room) => void;\n    /**\n     * Fires whenever the name of a room is updated.\n     * @param room - The room whose Room.name was updated.\n     * @example\n     * ```\n     * matrixClient.on(\"Room.name\", function(room){\n     *   var newName = room.name;\n     * });\n     * ```\n     */\n    [RoomEvent.Name]: (room: Room) => void;\n    /**\n     * Fires when an event we had previously received is redacted.\n     *\n     * (Note this is *not* fired when the redaction happens before we receive the\n     * event).\n     *\n     * @param event - The matrix redaction event\n     * @param room - The room containing the redacted event\n     * @param threadId - The thread containing the redacted event (before it was redacted)\n     */\n    [RoomEvent.Redaction]: (event: MatrixEvent, room: Room, threadId?: string) => void;\n    /**\n     * Fires when an event that was previously redacted isn't anymore.\n     * This happens when the redaction couldn't be sent and\n     * was subsequently cancelled by the user. Redactions have a local echo\n     * which is undone in this scenario.\n     *\n     * @param event - The matrix redaction event that was cancelled.\n     * @param room - The room containing the unredacted event\n     */\n    [RoomEvent.RedactionCancelled]: (event: MatrixEvent, room: Room) => void;\n    /**\n     * Fires when the status of a transmitted event is updated.\n     *\n     * <p>When an event is first transmitted, a temporary copy of the event is\n     * inserted into the timeline, with a temporary event id, and a status of\n     * 'SENDING'.\n     *\n     * <p>Once the echo comes back from the server, the content of the event\n     * (MatrixEvent.event) is replaced by the complete event from the homeserver,\n     * thus updating its event id, as well as server-generated fields such as the\n     * timestamp. Its status is set to null.\n     *\n     * <p>Once the /send request completes, if the remote echo has not already\n     * arrived, the event is updated with a new event id and the status is set to\n     * 'SENT'. The server-generated fields are of course not updated yet.\n     *\n     * <p>If the /send fails, In this case, the event's status is set to\n     * 'NOT_SENT'. If it is later resent, the process starts again, setting the\n     * status to 'SENDING'. Alternatively, the message may be cancelled, which\n     * removes the event from the room, and sets the status to 'CANCELLED'.\n     *\n     * <p>This event is raised to reflect each of the transitions above.\n     *\n     * @param event - The matrix event which has been updated\n     *\n     * @param room - The room containing the redacted event\n     *\n     * @param oldEventId - The previous event id (the temporary event id,\n     *    except when updating a successfully-sent event when its echo arrives)\n     *\n     * @param oldStatus - The previous event status.\n     */\n    [RoomEvent.LocalEchoUpdated]: (\n        event: MatrixEvent,\n        room: Room,\n        oldEventId?: string,\n        oldStatus?: EventStatus | null,\n    ) => void;\n    [RoomEvent.OldStateUpdated]: (room: Room, previousRoomState: RoomState, roomState: RoomState) => void;\n    [RoomEvent.CurrentStateUpdated]: (room: Room, previousRoomState: RoomState, roomState: RoomState) => void;\n    [RoomEvent.HistoryImportedWithinTimeline]: (markerEvent: MatrixEvent, room: Room) => void;\n    [RoomEvent.UnreadNotifications]: (unreadNotifications?: NotificationCount, threadId?: string) => void;\n    [RoomEvent.TimelineRefresh]: (room: Room, eventTimelineSet: EventTimelineSet) => void;\n    /**\n     * Fires when a new room summary is returned by `/sync`.\n     *\n     * See https://spec.matrix.org/v1.8/client-server-api/#_matrixclientv3sync_roomsummary\n     * for full details\n     * @param summary - the room summary object\n     */\n    [RoomEvent.Summary]: (summary: IRoomSummary) => void;\n    [ThreadEvent.New]: (thread: Thread, toStartOfTimeline: boolean) => void;\n    /**\n     * Fires when a new poll instance is added to the room state\n     * @param poll - the new poll\n     */\n    [PollEvent.New]: (poll: Poll) => void;\n} & Pick<ThreadHandlerMap, ThreadEvent.Update | ThreadEvent.NewReply | ThreadEvent.Delete> &\n    EventTimelineSetHandlerMap &\n    Pick<MatrixEventHandlerMap, MatrixEventEvent.BeforeRedaction> &\n    Pick<RoomStickyEventsMap, RoomStickyEventsEvent.Update> &\n    Pick<\n        RoomStateEventHandlerMap,\n        | RoomStateEvent.Events\n        | RoomStateEvent.Members\n        | RoomStateEvent.NewMember\n        | RoomStateEvent.Update\n        | RoomStateEvent.Marker\n        | BeaconEvent.New\n    > &\n    Pick<BeaconEventHandlerMap, BeaconEvent.Update | BeaconEvent.Destroy | BeaconEvent.LivenessChange>;\n\nexport class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {\n    public readonly reEmitter: TypedReEmitter<RoomEmittedEvents, RoomEventHandlerMap>;\n    private txnToEvent: Map<string, MatrixEvent> = new Map(); // Pending in-flight requests { string: MatrixEvent }\n    private notificationCounts: NotificationCount = {};\n    private bumpStamp: number | undefined = undefined;\n    private readonly threadNotifications = new Map<string, NotificationCount>();\n    public readonly cachedThreadReadReceipts = new Map<string, CachedReceiptStructure[]>();\n    // Useful to know at what point the current user has started using threads in this room\n    private oldestThreadedReceiptTs = Infinity;\n    /**\n     * A record of the latest unthread receipts per user\n     * This is useful in determining whether a user has read a thread or not\n     */\n    private unthreadedReceipts = new Map<string, Receipt>();\n    private readonly timelineSets: EventTimelineSet[];\n    public readonly polls: Map<string, Poll> = new Map<string, Poll>();\n\n    /**\n     * Empty array if the timeline sets have not been initialised. After initialisation:\n     * 0: All threads\n     * 1: Threads the current user has participated in\n     */\n    public readonly threadsTimelineSets: [] | [EventTimelineSet, EventTimelineSet] = [];\n\n    // any filtered timeline sets we're maintaining for this room\n    private readonly filteredTimelineSets: Record<string, EventTimelineSet> = {}; // filter_id: timelineSet\n    private timelineNeedsRefresh = false;\n    private readonly pendingEventList?: MatrixEvent[];\n    // read by megolm via getter; boolean value - null indicates \"use global value\"\n    private blacklistUnverifiedDevices?: boolean;\n    private selfMembership?: Membership;\n    /**\n     * A `Hero` is a stripped `m.room.member` event which contains the important renderable fields from the event.\n     *\n     * It is used in MSC4186 (Simplified Sliding Sync) as a replacement for the old `summary` field.\n     *\n     * When we are doing old-style (`/v3/sync`) sync, we simulate the SSS behaviour by constructing\n     * a `Hero` object based on the user id we get from the summary. Obviously, in that case,\n     * the `Hero` will lack a `displayName` or `avatarUrl`.\n     */\n    private heroes: Hero[] | null = null;\n    // flags to stop logspam about missing m.room.create events\n    private getTypeWarning = false;\n    private membersPromise?: Promise<boolean>;\n\n    // XXX: These should be read-only\n    /**\n     * The human-readable display name for this room.\n     */\n    public name: string;\n    /**\n     * The un-homoglyphed name for this room.\n     */\n    public normalizedName: string;\n    /**\n     * Dict of room tags; the keys are the tag name and the values\n     * are any metadata associated with the tag - e.g. `{ \"fav\" : { order: 1 } }`\n     */\n    public tags: Record<string, Record<string, any>> = {}; // $tagName: { $metadata: $value }\n    /**\n     * accountData Dict of per-room account_data events; the keys are the\n     * event type and the values are the events.\n     */\n    public accountData: Map<string, MatrixEvent> = new Map(); // $eventType: $event\n    /**\n     * The room summary.\n     */\n    public summary: RoomSummary | null = null;\n    /**\n     * oldState The state of the room at the time of the oldest event in the live timeline.\n     *\n     * @deprecated Present for backwards compatibility.\n     *             Use getLiveTimeline().getState(EventTimeline.BACKWARDS) instead\n     */\n    public oldState!: RoomState;\n    /**\n     * currentState The state of the room at the time of the newest event in the timeline.\n     *\n     * @deprecated Present for backwards compatibility.\n     *             Use getLiveTimeline().getState(EventTimeline.FORWARDS) instead.\n     */\n    public currentState!: RoomState;\n\n    public readonly relations;\n\n    /**\n     * A collection of events known by the client\n     * This is not a comprehensive list of the threads that exist in this room\n     */\n    private threads = new Map<string, Thread>();\n\n    /**\n     * A mapping of eventId to all visibility changes to apply\n     * to the event, by chronological order, as per\n     * https://github.com/matrix-org/matrix-doc/pull/3531\n     *\n     * # Invariants\n     *\n     * - within each list, all events are classed by\n     *   chronological order;\n     * - all events are events such that\n     *  `asVisibilityEvent()` returns a non-null `IVisibilityChange`;\n     * - within each list with key `eventId`, all events\n     *   are in relation to `eventId`.\n     *\n     * @experimental\n     */\n    private visibilityEvents = new Map<string, MatrixEvent[]>();\n\n    /**\n     * The latest receipts (synthetic and real) for each user in each thread\n     * (and unthreaded).\n     */\n    private roomReceipts = new RoomReceipts(this);\n\n    /**\n     * Stores and tracks sticky events\n     */\n    private stickyEvents = new RoomStickyEventsStore();\n\n    private readonly retention?: RoomRetentionPolicy;\n\n    /**\n     * Construct a new Room.\n     *\n     * <p>For a room, we store an ordered sequence of timelines, which may or may not\n     * be continuous. Each timeline lists a series of events, as well as tracking\n     * the room state at the start and the end of the timeline. It also tracks\n     * forward and backward pagination tokens, as well as containing links to the\n     * next timeline in the sequence.\n     *\n     * <p>There is one special timeline - the 'live' timeline, which represents the\n     * timeline to which events are being added in real-time as they are received\n     * from the /sync API. Note that you should not retain references to this\n     * timeline - even if it is the current timeline right now, it may not remain\n     * so if the server gives us a timeline gap in /sync.\n     *\n     * <p>In order that we can find events from their ids later, we also maintain a\n     * map from event_id to timeline and index.\n     *\n     * @param roomId - Required. The ID of this room.\n     * @param client - Required. The client, used to lazy load members.\n     * @param myUserId - Required. The ID of the syncing user.\n     * @param opts - Configuration options\n     */\n    public constructor(\n        public readonly roomId: string,\n        public readonly client: MatrixClient,\n        public readonly myUserId: string,\n        private readonly opts: IOpts = {},\n    ) {\n        super();\n\n        // In some cases, we add listeners for every displayed Matrix event, so it's\n        // common to have quite a few more than the default limit.\n        this.setMaxListeners(100);\n        this.reEmitter = new TypedReEmitter(this);\n\n        opts.pendingEventOrdering = opts.pendingEventOrdering || PendingEventOrdering.Chronological;\n\n        this.name = roomId;\n        this.normalizedName = roomId;\n\n        this.relations = new RelationsContainer(this.client, this);\n\n        // Listen to our own receipt event as a more modular way of processing our own\n        // receipts. No need to remove the listener: it's on ourself anyway.\n        this.on(RoomEvent.Receipt, this.onReceipt);\n        this.reEmitter.reEmit(this.stickyEvents, [RoomStickyEventsEvent.Update]);\n\n        // all our per-room timeline sets. the first one is the unfiltered ones;\n        // the subsequent ones are the filtered ones in no particular order.\n        this.timelineSets = [new EventTimelineSet(this, opts)];\n        this.reEmitter.reEmit(this.getUnfilteredTimelineSet(), [RoomEvent.Timeline, RoomEvent.TimelineReset]);\n\n        this.fixUpLegacyTimelineFields();\n\n        if (this.opts.pendingEventOrdering === PendingEventOrdering.Detached) {\n            this.pendingEventList = [];\n            this.client.store.getPendingEvents(this.roomId).then((events) => {\n                const mapper = this.client.getEventMapper({\n                    decrypt: false,\n                });\n                events.forEach(async (serializedEvent: Partial<IEvent>) => {\n                    const event = mapper(serializedEvent);\n                    await client.decryptEventIfNeeded(event);\n                    event.setStatus(EventStatus.NOT_SENT);\n                    this.addPendingEvent(event, event.getTxnId()!);\n                });\n            });\n        }\n\n        // awaited by getEncryptionTargetMembers while room members are loading\n        if (!this.opts.lazyLoadMembers) {\n            this.membersPromise = Promise.resolve(false);\n        } else {\n            this.membersPromise = undefined;\n        }\n        if (this.client?._unstable_shouldApplyMessageRetention) {\n            this.retention = new RoomRetentionPolicy(this, client.retentionPolicyService, client.store);\n        }\n    }\n\n    private threadTimelineSetsPromise: Promise<[EventTimelineSet, EventTimelineSet]> | null = null;\n    public async createThreadsTimelineSets(): Promise<[EventTimelineSet, EventTimelineSet] | null> {\n        if (this.threadTimelineSetsPromise) {\n            return this.threadTimelineSetsPromise;\n        }\n\n        if (this.client?.supportsThreads()) {\n            try {\n                this.threadTimelineSetsPromise = Promise.all([\n                    this.createThreadTimelineSet(),\n                    this.createThreadTimelineSet(ThreadFilterType.My),\n                ]);\n                const timelineSets = await this.threadTimelineSetsPromise;\n                this.threadsTimelineSets[0] = timelineSets[0];\n                this.threadsTimelineSets[1] = timelineSets[1];\n                return timelineSets;\n            } catch {\n                this.threadTimelineSetsPromise = null;\n                return null;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Bulk decrypt critical events in a room\n     *\n     * Critical events represents the minimal set of events to decrypt\n     * for a typical UI to function properly\n     *\n     * - Last event of every room (to generate likely message preview)\n     * - All events up to the read receipt (to calculate an accurate notification count)\n     *\n     * @returns Signals when all events have been decrypted\n     */\n    public async decryptCriticalEvents(): Promise<void> {\n        if (!this.client.getCrypto()) return;\n\n        const readReceiptEventId = this.getEventReadUpTo(this.client.getUserId()!, true);\n        const events = this.getLiveTimeline().getEvents();\n        const readReceiptTimelineIndex = events.findIndex((matrixEvent) => {\n            return matrixEvent.event.event_id === readReceiptEventId;\n        });\n\n        const decryptionPromises = events\n            .slice(readReceiptTimelineIndex)\n            .reverse()\n            .map((event) => this.client.decryptEventIfNeeded(event));\n\n        await Promise.allSettled(decryptionPromises);\n    }\n\n    /**\n     * Bulk decrypt events in a room\n     *\n     * @returns Signals when all events have been decrypted\n     */\n    public async decryptAllEvents(): Promise<void> {\n        if (!this.client.getCrypto()) return;\n\n        const decryptionPromises = this.getUnfilteredTimelineSet()\n            .getLiveTimeline()\n            .getEvents()\n            .slice(0) // copy before reversing\n            .reverse()\n            .map((event) => this.client.decryptEventIfNeeded(event));\n\n        await Promise.allSettled(decryptionPromises);\n    }\n\n    /**\n     * Gets the creator of the room\n     * @returns The creator of the room, or null if it could not be determined\n     */\n    public getCreator(): string | null {\n        const createEvent = this.currentState.getStateEvents(EventType.RoomCreate, \"\");\n        return createEvent?.getSender() ?? null;\n    }\n\n    /**\n     * Gets the version of the room\n     * @returns The version of the room\n     */\n    public getVersion(): string {\n        return this.currentState.getRoomVersion();\n    }\n\n    /**\n     * Determines the recommended room version for the room. This returns an\n     * object with 3 properties: `version` as the new version the\n     * room should be upgraded to (may be the same as the current version);\n     * `needsUpgrade` to indicate if the room actually can be\n     * upgraded (ie: does the current version not match?); and `urgent`\n     * to indicate if the new version patches a vulnerability in a previous\n     * version.\n     * @returns\n     * Resolves to the version the room should be upgraded to.\n     */\n    public async getRecommendedVersion(): Promise<IRecommendedVersion> {\n        let capabilities: Capabilities = {};\n        try {\n            capabilities = await this.client.getCapabilities();\n        } catch {}\n        let versionCap = capabilities[\"m.room_versions\"];\n        if (!versionCap) {\n            versionCap = {\n                default: KNOWN_SAFE_ROOM_VERSION,\n                available: {},\n            };\n            for (const safeVer of SAFE_ROOM_VERSIONS) {\n                versionCap.available[safeVer] = RoomVersionStability.Stable;\n            }\n        }\n\n        let result = this.checkVersionAgainstCapability(versionCap);\n        if (result.urgent && result.needsUpgrade) {\n            // Something doesn't feel right: we shouldn't need to update\n            // because the version we're on should be in the protocol's\n            // namespace. This usually means that the server was updated\n            // before the client was, making us think the newest possible\n            // room version is not stable. As a solution, we'll refresh\n            // the capability we're using to determine this.\n            logger.warn(\n                \"Refreshing room version capability because the server looks \" +\n                    \"to be supporting a newer room version we don't know about.\",\n            );\n\n            try {\n                capabilities = await this.client.fetchCapabilities();\n            } catch (e) {\n                logger.warn(\"Failed to refresh room version capabilities\", e);\n            }\n            versionCap = capabilities[\"m.room_versions\"];\n            if (!versionCap) {\n                logger.warn(\"No room version capability - assuming upgrade required.\");\n                return result;\n            } else {\n                result = this.checkVersionAgainstCapability(versionCap);\n            }\n        }\n\n        return result;\n    }\n\n    private checkVersionAgainstCapability(versionCap: IRoomVersionsCapability): IRecommendedVersion {\n        const currentVersion = this.getVersion();\n        logger.log(`[${this.roomId}] Current version: ${currentVersion}`);\n        logger.log(`[${this.roomId}] Version capability: `, versionCap);\n\n        const result: IRecommendedVersion = {\n            version: currentVersion,\n            needsUpgrade: false,\n            urgent: false,\n        };\n\n        // If the room is on the default version then nothing needs to change\n        if (currentVersion === versionCap.default) return result;\n\n        const stableVersions = Object.keys(versionCap.available).filter((v) => versionCap.available[v] === \"stable\");\n\n        // Check if the room is on an unstable version. We determine urgency based\n        // off the version being in the Matrix spec namespace or not (if the version\n        // is in the current namespace and unstable, the room is probably vulnerable).\n        if (!stableVersions.includes(currentVersion)) {\n            result.version = versionCap.default;\n            result.needsUpgrade = true;\n            result.urgent = !!this.getVersion().match(/^[0-9]+[0-9.]*$/g);\n            if (result.urgent) {\n                logger.warn(`URGENT upgrade required on ${this.roomId}`);\n            } else {\n                logger.warn(`Non-urgent upgrade required on ${this.roomId}`);\n            }\n            return result;\n        }\n\n        // The room is on a stable, but non-default, version by this point.\n        // No upgrade needed.\n        return result;\n    }\n\n    /**\n     * Determines whether the given user is permitted to perform a room upgrade\n     * @param userId - The ID of the user to test against\n     * @returns True if the given user is permitted to upgrade the room\n     */\n    public userMayUpgradeRoom(userId: string): boolean {\n        return this.currentState.maySendStateEvent(EventType.RoomTombstone, userId);\n    }\n\n    /**\n     * Get the list of pending sent events for this room\n     *\n     * @returns A list of the sent events\n     * waiting for remote echo.\n     *\n     * @throws If `opts.pendingEventOrdering` was not 'detached'\n     */\n    public getPendingEvents(): MatrixEvent[] {\n        if (!this.pendingEventList) {\n            throw new Error(\n                \"Cannot call getPendingEvents with pendingEventOrdering == \" + this.opts.pendingEventOrdering,\n            );\n        }\n\n        return this.pendingEventList;\n    }\n\n    /**\n     * Removes a pending event for this room\n     *\n     * @returns True if an element was removed.\n     */\n    public removePendingEvent(eventId: string): boolean {\n        if (!this.pendingEventList) {\n            throw new Error(\n                \"Cannot call removePendingEvent with pendingEventOrdering == \" + this.opts.pendingEventOrdering,\n            );\n        }\n\n        const removed = removeElement(\n            this.pendingEventList,\n            function (ev) {\n                return ev.getId() == eventId;\n            },\n            false,\n        );\n\n        this.savePendingEvents();\n\n        return removed;\n    }\n\n    /**\n     * Check whether the pending event list contains a given event by ID.\n     * If pending event ordering is not \"detached\" then this returns false.\n     *\n     * @param eventId - The event ID to check for.\n     */\n    public hasPendingEvent(eventId: string): boolean {\n        return this.pendingEventList?.some((event) => event.getId() === eventId) ?? false;\n    }\n\n    /**\n     * Get a specific event from the pending event list, if configured, null otherwise.\n     *\n     * @param eventId - The event ID to check for.\n     */\n    public getPendingEvent(eventId: string): MatrixEvent | null {\n        return this.pendingEventList?.find((event) => event.getId() === eventId) ?? null;\n    }\n\n    /**\n     * Get the live unfiltered timeline for this room.\n     *\n     * @returns live timeline\n     */\n    public getLiveTimeline(): EventTimeline {\n        return this.getUnfilteredTimelineSet().getLiveTimeline();\n    }\n\n    /**\n     * The live event timeline for this room, with the oldest event at index 0.\n     *\n     * @deprecated Present for backwards compatibility.\n     *             Use getLiveTimeline().getEvents() instead\n     */\n    public get timeline(): MatrixEvent[] {\n        return this.getLiveTimeline().getEvents();\n    }\n\n    /**\n     * Get the timestamp of the last message in the room\n     *\n     * @returns the timestamp of the last message in the room\n     */\n    public getLastActiveTimestamp(): number {\n        const timeline = this.getLiveTimeline();\n        const events = timeline.getEvents();\n        if (events.length) {\n            const lastEvent = events[events.length - 1];\n            return lastEvent.getTs();\n        } else {\n            return Number.MIN_SAFE_INTEGER;\n        }\n    }\n\n    /**\n     * Returns the last live event of this room.\n     * \"last\" means latest timestamp.\n     * Instead of using timestamps, it would be better to do the comparison based on the order of the homeserver DAG.\n     * Unfortunately, this information is currently not available in the client.\n     * See {@link https://github.com/matrix-org/matrix-js-sdk/issues/3325}.\n     * \"live of this room\" means from all live timelines: the room and the threads.\n     *\n     * @returns MatrixEvent if there is a last event; else undefined.\n     */\n    public getLastLiveEvent(): MatrixEvent | undefined {\n        const roomEvents = this.getLiveTimeline().getEvents();\n        const lastRoomEvent = roomEvents[roomEvents.length - 1] as MatrixEvent | undefined;\n        const lastThread = this.getLastThread();\n\n        if (!lastThread) return lastRoomEvent;\n\n        const lastThreadEvent = lastThread.events[lastThread.events.length - 1];\n\n        return (lastRoomEvent?.getTs() ?? 0) > (lastThreadEvent?.getTs() ?? 0) ? lastRoomEvent : lastThreadEvent;\n    }\n\n    /**\n     * Returns the last thread of this room.\n     * \"last\" means latest timestamp of the last thread event.\n     * Instead of using timestamps, it would be better to do the comparison based on the order of the homeserver DAG.\n     * Unfortunately, this information is currently not available in the client.\n     * See {@link https://github.com/matrix-org/matrix-js-sdk/issues/3325}.\n     *\n     * @returns the thread with the most recent event in its live time line. undefined if there is no thread.\n     */\n    public getLastThread(): Thread | undefined {\n        return this.getThreads().reduce<Thread | undefined>((lastThread: Thread | undefined, thread: Thread) => {\n            if (!lastThread) return thread;\n\n            const threadEvent = thread.events[thread.events.length - 1];\n            const lastThreadEvent = lastThread.events[lastThread.events.length - 1];\n\n            if ((threadEvent?.getTs() ?? 0) >= (lastThreadEvent?.getTs() ?? 0)) {\n                // Last message of current thread is newer → new last thread.\n                // Equal also means newer, because it was added to the thread map later.\n                return thread;\n            }\n\n            return lastThread;\n        }, undefined);\n    }\n\n    /**\n     * @returns the membership type (join | leave | invite | knock) for the logged in user\n     */\n    public getMyMembership(): Membership {\n        return this.selfMembership ?? KnownMembership.Leave;\n    }\n\n    /**\n     * If this room is a DM we're invited to,\n     * try to find out who invited us\n     * @returns user id of the inviter\n     */\n    public getDMInviter(): string | undefined {\n        const me = this.getMember(this.myUserId);\n        if (me) {\n            return me.getDMInviter();\n        }\n\n        if (this.selfMembership === KnownMembership.Invite) {\n            // fall back to summary information\n            const memberCount = this.getInvitedAndJoinedMemberCount();\n            if (memberCount === 2) {\n                return this.heroes?.[0]?.userId;\n            }\n        }\n    }\n\n    /**\n     * Assuming this room is a DM room, tries to guess with which user.\n     * @returns user id of the other member (could be syncing user)\n     */\n    public guessDMUserId(): string {\n        const me = this.getMember(this.myUserId);\n        if (me) {\n            const inviterId = me.getDMInviter();\n            if (inviterId) {\n                return inviterId;\n            }\n        }\n        // Remember, we're assuming this room is a DM, so returning the first member we find should be fine\n        if (Array.isArray(this.heroes) && this.heroes.length) {\n            return this.heroes[0].userId;\n        }\n        const members = this.currentState.getMembers();\n        const anyMember = members.find((m) => m.userId !== this.myUserId);\n        if (anyMember) {\n            return anyMember.userId;\n        }\n        // it really seems like I'm the only user in the room\n        // so I probably created a room with just me in it\n        // and marked it as a DM. Ok then\n        return this.myUserId;\n    }\n\n    /**\n     * Gets the \"functional members\" in this room.\n     *\n     * Returns the list of userIDs from the `io.element.functional_members` event. Does not consider the\n     * current membership states of those users.\n     *\n     * @see https://github.com/element-hq/element-meta/blob/develop/spec/functional_members.md.\n     */\n    private getFunctionalMembers(): string[] {\n        const mFunctionalMembers = this.currentState.getStateEvents(UNSTABLE_ELEMENT_FUNCTIONAL_USERS.name, \"\");\n        if (Array.isArray(mFunctionalMembers?.getContent().service_members)) {\n            return mFunctionalMembers!.getContent().service_members;\n        }\n        return [];\n    }\n\n    public getAvatarFallbackMember(): RoomMember | undefined {\n        const functionalMembers = this.getFunctionalMembers();\n\n        // Only generate a fallback avatar if the conversation is with a single specific other user (a \"DM\").\n        let nonFunctionalMemberCount = 0;\n        this.getMembers()!.forEach((m) => {\n            if (m.membership !== \"join\" && m.membership !== \"invite\") return;\n            if (functionalMembers.includes(m.userId)) return;\n            nonFunctionalMemberCount++;\n        });\n        if (nonFunctionalMemberCount > 2) return;\n\n        // Prefer the list of heroes, if present. It should only include the single other user in the DM.\n        const nonFunctionalHeroes = this.heroes?.filter((h) => !functionalMembers.includes(h.userId));\n        const hasHeroes = Array.isArray(nonFunctionalHeroes) && nonFunctionalHeroes.length;\n        if (hasHeroes) {\n            // use first hero that has a display name or avatar url, or whose user ID\n            // can be looked up as a member of the room\n            for (const hero of nonFunctionalHeroes) {\n                // If the hero was from a legacy sync (`/v3/sync`), we will need to look the user ID up in the room\n                // the display name and avatar URL will not be set.\n                if (!hero.fromMSC4186) {\n                    // attempt to look up renderable fields from the m.room.member event if it exists\n                    const member = this.getMember(hero.userId);\n                    if (member) {\n                        return member;\n                    }\n                } else {\n                    // use the Hero supplied values for the room member.\n                    // TODO: It's unfortunate that this function, which clearly only cares about the\n                    //       avatar url, returns the entire RoomMember event. We need to fake an event\n                    //       to meet this API shape.\n                    const heroMember = new RoomMember(this.roomId, hero.userId);\n                    // set the display name and avatar url\n                    heroMember.setMembershipEvent(\n                        new MatrixEvent({\n                            // ensure it's unique even if we hit the same millisecond\n                            event_id: \"$\" + this.roomId + hero.userId + new Date().getTime(),\n                            type: EventType.RoomMember,\n                            state_key: hero.userId,\n                            content: {\n                                displayname: hero.displayName,\n                                avatar_url: hero.avatarUrl,\n                            },\n                        }),\n                    );\n                    return heroMember;\n                }\n            }\n            const availableMember = nonFunctionalHeroes\n                .map((hero) => {\n                    return this.getMember(hero.userId);\n                })\n                .find((member) => !!member);\n            if (availableMember) {\n                return availableMember;\n            }\n        }\n\n        // Consider *all*, including previous, members, to generate the avatar for DMs where the other user left.\n        // Needed to generate a matching avatar for rooms named \"Empty Room (was Alice)\".\n        const members = this.getMembers();\n        const nonFunctionalMembers = members?.filter((m) => !functionalMembers.includes(m.userId));\n        if (nonFunctionalMembers.length <= 2) {\n            const availableMember = nonFunctionalMembers.find((m) => {\n                return m.userId !== this.myUserId;\n            });\n            if (availableMember) {\n                return availableMember;\n            }\n        }\n\n        // If all else failed, but the homeserver gave us heroes that previously could not be found in the room members,\n        // trust and try falling back to a hero, creating a one-off member for it\n        if (hasHeroes) {\n            const availableUser = nonFunctionalHeroes\n                .map((hero) => {\n                    return this.client.getUser(hero.userId);\n                })\n                .find((user) => !!user);\n            if (availableUser) {\n                const member = new RoomMember(this.roomId, availableUser.userId);\n                member.user = availableUser;\n                return member;\n            }\n        }\n    }\n\n    /**\n     * Sets the membership this room was received as during sync\n     * @param membership - join | leave | invite\n     */\n    public updateMyMembership(membership: Membership): void {\n        const prevMembership = this.selfMembership;\n        this.selfMembership = membership;\n        if (prevMembership !== membership) {\n            if (membership === KnownMembership.Leave) {\n                this.cleanupAfterLeaving();\n            }\n            this.emit(RoomEvent.MyMembership, this, membership, prevMembership);\n        }\n    }\n\n    private async loadMembersFromServer(): Promise<IStateEventWithRoomId[]> {\n        const lastSyncToken = this.client.store.getSyncToken();\n        const response = await this.client.members(\n            this.roomId,\n            undefined,\n            KnownMembership.Leave,\n            lastSyncToken ?? undefined,\n        );\n        return response.chunk;\n    }\n\n    private async loadMembers(): Promise<{ memberEvents: MatrixEvent[]; fromServer: boolean }> {\n        // were the members loaded from the server?\n        let fromServer = false;\n        let rawMembersEvents = await this.client.store.getOutOfBandMembers(this.roomId);\n        // If the room is encrypted, we always fetch members from the server at\n        // least once, in case the latest state wasn't persisted properly. Note\n        // that this function is only called once (unless loading the members\n        // fails), since loadMembersIfNeeded always returns this.membersPromise\n        // if set, which will be the result of the first (successful) call.\n        if (rawMembersEvents === null || this.hasEncryptionStateEvent()) {\n            fromServer = true;\n            rawMembersEvents = await this.loadMembersFromServer();\n            logger.log(`LL: got ${rawMembersEvents.length} ` + `members from server for room ${this.roomId}`);\n        }\n        const memberEvents = rawMembersEvents.filter(noUnsafeEventProps).map(this.client.getEventMapper());\n        return { memberEvents, fromServer };\n    }\n\n    /**\n     * Check if loading of out-of-band-members has completed\n     *\n     * @returns true if the full membership list of this room has been loaded (including if lazy-loading is disabled).\n     *    False if the load is not started or is in progress.\n     */\n    public membersLoaded(): boolean {\n        if (!this.opts.lazyLoadMembers) {\n            return true;\n        }\n\n        return this.currentState.outOfBandMembersReady();\n    }\n\n    /**\n     * Preloads the member list in case lazy loading\n     * of memberships is in use. Can be called multiple times,\n     * it will only preload once.\n     * @returns when preloading is done and\n     * accessing the members on the room will take\n     * all members in the room into account\n     */\n    public loadMembersIfNeeded(): Promise<boolean> {\n        if (this.membersPromise) {\n            return this.membersPromise;\n        }\n\n        // mark the state so that incoming messages while\n        // the request is in flight get marked as superseding\n        // the OOB members\n        this.currentState.markOutOfBandMembersStarted();\n\n        const inMemoryUpdate = this.loadMembers()\n            .then((result) => {\n                this.currentState.setOutOfBandMembers(result.memberEvents);\n                // recalculate the room name: it may have been based on members, so may have changed\n                this.recalculate();\n                return result.fromServer;\n            })\n            .catch((err) => {\n                // allow retries on fail\n                this.membersPromise = undefined;\n                this.currentState.markOutOfBandMembersFailed();\n                throw err;\n            });\n        // update members in storage, but don't wait for it\n        inMemoryUpdate\n            .then((fromServer) => {\n                if (fromServer) {\n                    const oobMembers = this.currentState\n                        .getMembers()\n                        .filter((m) => m.isOutOfBand())\n                        .map((m) => m.events.member?.event as IStateEventWithRoomId);\n                    logger.log(`LL: telling store to write ${oobMembers.length}` + ` members for room ${this.roomId}`);\n                    const store = this.client.store;\n                    return (\n                        store\n                            .setOutOfBandMembers(this.roomId, oobMembers)\n                            // swallow any IDB error as we don't want to fail\n                            // because of this\n                            .catch((err) => {\n                                logger.log(\"LL: storing OOB room members failed, oh well\", err);\n                            })\n                    );\n                }\n            })\n            .catch((err) => {\n                // as this is not awaited anywhere,\n                // at least show the error in the console\n                logger.error(err);\n            });\n\n        this.membersPromise = inMemoryUpdate;\n\n        return this.membersPromise;\n    }\n\n    /**\n     * Removes the lazily loaded members from storage if needed\n     */\n    public async clearLoadedMembersIfNeeded(): Promise<void> {\n        if (this.opts.lazyLoadMembers && this.membersPromise) {\n            await this.loadMembersIfNeeded();\n            await this.client.store.clearOutOfBandMembers(this.roomId);\n            this.currentState.clearOutOfBandMembers();\n            this.membersPromise = undefined;\n        }\n    }\n\n    /**\n     * called when sync receives this room in the leave section\n     * to do cleanup after leaving a room. Possibly called multiple times.\n     */\n    private cleanupAfterLeaving(): void {\n        this.clearLoadedMembersIfNeeded().catch((err) => {\n            logger.error(`error after clearing loaded members from ` + `room ${this.roomId} after leaving`);\n            logger.log(err);\n        });\n    }\n\n    /**\n     * Empty out the current live timeline and re-request it. This is used when\n     * historical messages are imported into the room via MSC2716 `/batch_send`\n     * because the client may already have that section of the timeline loaded.\n     * We need to force the client to throw away their current timeline so that\n     * when they back paginate over the area again with the historical messages\n     * in between, it grabs the newly imported messages. We can listen for\n     * `UNSTABLE_MSC2716_MARKER`, in order to tell when historical messages are ready\n     * to be discovered in the room and the timeline needs a refresh. The SDK\n     * emits a `RoomEvent.HistoryImportedWithinTimeline` event when we detect a\n     * valid marker and can check the needs refresh status via\n     * `room.getTimelineNeedsRefresh()`.\n     */\n    public async refreshLiveTimeline(): Promise<void> {\n        const liveTimelineBefore = this.getLiveTimeline();\n        const forwardPaginationToken = liveTimelineBefore.getPaginationToken(EventTimeline.FORWARDS);\n        const backwardPaginationToken = liveTimelineBefore.getPaginationToken(EventTimeline.BACKWARDS);\n        const eventsBefore = liveTimelineBefore.getEvents();\n        const mostRecentEventInTimeline = eventsBefore[eventsBefore.length - 1];\n        logger.log(\n            `[refreshLiveTimeline for ${this.roomId}] at ` +\n                `mostRecentEventInTimeline=${mostRecentEventInTimeline && mostRecentEventInTimeline.getId()} ` +\n                `liveTimelineBefore=${liveTimelineBefore.toString()} ` +\n                `forwardPaginationToken=${forwardPaginationToken} ` +\n                `backwardPaginationToken=${backwardPaginationToken}`,\n        );\n\n        // Get the main TimelineSet\n        const timelineSet = this.getUnfilteredTimelineSet();\n\n        let newTimeline: EventTimeline | null = null;\n        // If there isn't any event in the timeline, let's go fetch the latest\n        // event and construct a timeline from it.\n        //\n        // This should only really happen if the user ran into an error\n        // with refreshing the timeline before which left them in a blank\n        // timeline from `resetLiveTimeline`.\n        if (!mostRecentEventInTimeline) {\n            newTimeline = await this.client.getLatestTimeline(timelineSet);\n        } else {\n            // Empty out all of `this.timelineSets`. But we also need to keep the\n            // same `timelineSet` references around so the React code updates\n            // properly and doesn't ignore the room events we emit because it checks\n            // that the `timelineSet` references are the same. We need the\n            // `timelineSet` empty so that the `client.getEventTimeline(...)` call\n            // later, will call `/context` and create a new timeline instead of\n            // returning the same one.\n            this.resetLiveTimeline(null, null);\n\n            // Make the UI timeline show the new blank live timeline we just\n            // reset so that if the network fails below it's showing the\n            // accurate state of what we're working with instead of the\n            // disconnected one in the TimelineWindow which is just hanging\n            // around by reference.\n            this.emit(RoomEvent.TimelineRefresh, this, timelineSet);\n\n            // Use `client.getEventTimeline(...)` to construct a new timeline from a\n            // `/context` response state and events for the most recent event before\n            // we reset everything. The `timelineSet` we pass in needs to be empty\n            // in order for this function to call `/context` and generate a new\n            // timeline.\n            newTimeline = await this.client.getEventTimeline(timelineSet, mostRecentEventInTimeline.getId()!);\n        }\n\n        // If a racing `/sync` beat us to creating a new timeline, use that\n        // instead because it's the latest in the room and any new messages in\n        // the scrollback will include the history.\n        const liveTimeline = timelineSet.getLiveTimeline();\n        if (\n            !liveTimeline ||\n            (liveTimeline.getPaginationToken(Direction.Forward) === null &&\n                liveTimeline.getPaginationToken(Direction.Backward) === null &&\n                liveTimeline.getEvents().length === 0)\n        ) {\n            logger.log(`[refreshLiveTimeline for ${this.roomId}] using our new live timeline`);\n            // Set the pagination token back to the live sync token (`null`) instead\n            // of using the `/context` historical token (ex. `t12-13_0_0_0_0_0_0_0_0`)\n            // so that it matches the next response from `/sync` and we can properly\n            // continue the timeline.\n            newTimeline!.setPaginationToken(forwardPaginationToken, EventTimeline.FORWARDS);\n\n            // Set our new fresh timeline as the live timeline to continue syncing\n            // forwards and back paginating from.\n            timelineSet.setLiveTimeline(newTimeline!);\n            // Fixup `this.oldstate` so that `scrollback` has the pagination tokens\n            // available\n            this.fixUpLegacyTimelineFields();\n        } else {\n            logger.log(\n                `[refreshLiveTimeline for ${this.roomId}] \\`/sync\\` or some other request beat us to creating a new ` +\n                    `live timeline after we reset it. We'll use that instead since any events in the scrollback from ` +\n                    `this timeline will include the history.`,\n            );\n        }\n\n        // The timeline has now been refreshed ✅\n        this.setTimelineNeedsRefresh(false);\n\n        // Emit an event which clients can react to and re-load the timeline\n        // from the SDK\n        this.emit(RoomEvent.TimelineRefresh, this, timelineSet);\n    }\n\n    /**\n     * Reset the live timeline of all timelineSets, and start new ones.\n     *\n     * <p>This is used when /sync returns a 'limited' timeline.\n     *\n     * @param backPaginationToken -   token for back-paginating the new timeline\n     * @param forwardPaginationToken - token for forward-paginating the old live timeline,\n     * if absent or null, all timelines are reset, removing old ones (including the previous live\n     * timeline which would otherwise be unable to paginate forwards without this token).\n     * Removing just the old live timeline whilst preserving previous ones is not supported.\n     */\n    public resetLiveTimeline(backPaginationToken?: string | null, forwardPaginationToken?: string | null): void {\n        for (const timelineSet of this.timelineSets) {\n            timelineSet.resetLiveTimeline(backPaginationToken ?? undefined, forwardPaginationToken ?? undefined);\n        }\n        for (const thread of this.threads.values()) {\n            thread.resetLiveTimeline(backPaginationToken, forwardPaginationToken);\n        }\n\n        this.fixUpLegacyTimelineFields();\n    }\n\n    /**\n     * Fix up this.timeline, this.oldState and this.currentState\n     *\n     * @internal\n     */\n    private fixUpLegacyTimelineFields(): void {\n        const previousOldState = this.oldState;\n        const previousCurrentState = this.currentState;\n\n        // maintain this.oldState and this.currentState as references to the\n        // state at the start and end of that timeline. These are more\n        // for backwards-compatibility than anything else.\n        this.oldState = this.getLiveTimeline().getState(EventTimeline.BACKWARDS)!;\n        this.currentState = this.getLiveTimeline().getState(EventTimeline.FORWARDS)!;\n\n        // Let people know to register new listeners for the new state\n        // references. The reference won't necessarily change every time so only\n        // emit when we see a change.\n        if (previousOldState !== this.oldState) {\n            this.emit(RoomEvent.OldStateUpdated, this, previousOldState, this.oldState);\n        }\n\n        if (previousCurrentState !== this.currentState) {\n            this.emit(RoomEvent.CurrentStateUpdated, this, previousCurrentState, this.currentState);\n\n            // Re-emit various events on the current room state\n            // TODO: If currentState really only exists for backwards\n            // compatibility, shouldn't we be doing this some other way?\n            this.reEmitter.stopReEmitting(previousCurrentState, [\n                RoomStateEvent.Events,\n                RoomStateEvent.Members,\n                RoomStateEvent.NewMember,\n                RoomStateEvent.Update,\n                RoomStateEvent.Marker,\n                BeaconEvent.New,\n                BeaconEvent.Update,\n                BeaconEvent.Destroy,\n                BeaconEvent.LivenessChange,\n            ]);\n            this.reEmitter.reEmit(this.currentState, [\n                RoomStateEvent.Events,\n                RoomStateEvent.Members,\n                RoomStateEvent.NewMember,\n                RoomStateEvent.Update,\n                RoomStateEvent.Marker,\n                BeaconEvent.New,\n                BeaconEvent.Update,\n                BeaconEvent.Destroy,\n                BeaconEvent.LivenessChange,\n            ]);\n        }\n    }\n\n    private onReceipt = (event: MatrixEvent): void => {\n        if (this.hasEncryptionStateEvent()) {\n            this.clearNotificationsOnReceipt(event);\n        }\n    };\n\n    private clearNotificationsOnReceipt(event: MatrixEvent): void {\n        // Like above, we have to listen for read receipts from ourselves in order to\n        // correctly handle notification counts on encrypted rooms.\n        // This fixes https://github.com/vector-im/element-web/issues/9421\n\n        // Figure out if we've read something or if it's just informational\n        // We need to work out what threads we've just recieved receipts for, so we\n        // know which ones to update. If we've received an unthreaded receipt, we'll\n        // need to update all threads.\n        let threadIds: string[] = [];\n        let hasUnthreadedReceipt = false;\n\n        const content = event.getContent();\n\n        for (const receiptGroup of Object.values(content)) {\n            for (const [receiptType, userReceipt] of Object.entries(receiptGroup)) {\n                if (!utils.isSupportedReceiptType(receiptType)) continue;\n                if (!userReceipt) continue;\n\n                for (const [userId, singleReceipt] of Object.entries(userReceipt)) {\n                    if (!singleReceipt || typeof singleReceipt !== \"object\") continue;\n                    const typedSingleReceipt = singleReceipt as Record<string, any>;\n                    if (userId !== this.client.getUserId()) continue;\n                    if (typedSingleReceipt.thread_id === undefined) {\n                        hasUnthreadedReceipt = true;\n                    } else if (typeof typedSingleReceipt.thread_id === \"string\") {\n                        threadIds.push(typedSingleReceipt.thread_id);\n                    }\n                }\n            }\n        }\n\n        if (hasUnthreadedReceipt) {\n            // If we have an unthreaded receipt, we need to update any threads that have a notification\n            // in them (because we know the receipt can't go backwards so we don't need to check any with\n            // no notifications: the number can only decrease from a receipt).\n            threadIds = this.getThreads()\n                .filter(\n                    (thread) =>\n                        this.getThreadUnreadNotificationCount(thread.id, NotificationCountType.Total) > 0 ||\n                        this.getThreadUnreadNotificationCount(thread.id, NotificationCountType.Highlight) > 0,\n                )\n                .map((thread) => thread.id);\n            threadIds.push(\"main\");\n        }\n\n        for (const threadId of threadIds) {\n            // Work backwards to determine how many events are unread. We also set\n            // a limit for how back we'll look to avoid spinning CPU for too long.\n            // If we hit the limit, we assume the count is unchanged.\n            const maxHistory = 20;\n            const timeline = threadId === \"main\" ? this.getLiveTimeline() : this.getThread(threadId)?.liveTimeline;\n\n            if (!timeline) {\n                logger.warn(`Couldn't find timeline for thread ID ${threadId} in room ${this.roomId}`);\n                continue;\n            }\n\n            const events = timeline.getEvents();\n\n            let highlightCount = 0;\n\n            for (let i = events.length - 1; i >= 0; i--) {\n                if (i === events.length - maxHistory) return; // limit reached\n\n                const event = events[i];\n\n                if (this.hasUserReadEvent(this.client.getUserId()!, event.getId()!)) {\n                    // If the user has read the event, then the counting is done.\n                    break;\n                }\n\n                const pushActions = this.client.getPushActionsForEvent(event);\n                highlightCount += pushActions?.tweaks?.highlight ? 1 : 0;\n            }\n\n            // Note: we don't need to handle 'total' notifications because the counts\n            // will come from the server.\n            if (threadId === \"main\") {\n                this.setUnreadNotificationCount(NotificationCountType.Highlight, highlightCount);\n            } else {\n                this.setThreadUnreadNotificationCount(threadId, NotificationCountType.Highlight, highlightCount);\n            }\n        }\n    }\n\n    /**\n     * Return the timeline sets for this room.\n     * @returns array of timeline sets for this room\n     */\n    public getTimelineSets(): EventTimelineSet[] {\n        return this.timelineSets;\n    }\n\n    /**\n     * Helper to return the main unfiltered timeline set for this room\n     * @returns room's unfiltered timeline set\n     */\n    public getUnfilteredTimelineSet(): EventTimelineSet {\n        return this.timelineSets[0];\n    }\n\n    /**\n     * Get the timeline which contains the given event from the unfiltered set, if any\n     *\n     * @param eventId -  event ID to look for\n     * @returns timeline containing\n     * the given event, or null if unknown\n     */\n    public getTimelineForEvent(eventId: string): EventTimeline | null {\n        const event = this.findEventById(eventId);\n        const thread = this.findThreadForEvent(event);\n        if (thread) {\n            return thread.timelineSet.getTimelineForEvent(eventId);\n        } else {\n            return this.getUnfilteredTimelineSet().getTimelineForEvent(eventId);\n        }\n    }\n\n    /**\n     * Add a new timeline to this room's unfiltered timeline set\n     *\n     * @returns newly-created timeline\n     */\n    public addTimeline(): EventTimeline {\n        return this.getUnfilteredTimelineSet().addTimeline();\n    }\n\n    /**\n     * Whether the timeline needs to be refreshed in order to pull in new\n     * historical messages that were imported.\n     * @param value - The value to set\n     */\n    public setTimelineNeedsRefresh(value: boolean): void {\n        this.timelineNeedsRefresh = value;\n    }\n\n    /**\n     * Whether the timeline needs to be refreshed in order to pull in new\n     * historical messages that were imported.\n     * @returns .\n     */\n    public getTimelineNeedsRefresh(): boolean {\n        return this.timelineNeedsRefresh;\n    }\n\n    /**\n     * Get an event which is stored in our unfiltered timeline set, or in a thread\n     *\n     * @param eventId - event ID to look for\n     * @returns the given event, or undefined if unknown\n     */\n    public findEventById(eventId: string): MatrixEvent | undefined {\n        let event = this.getUnfilteredTimelineSet().findEventById(eventId);\n\n        if (!event) {\n            const threads = this.getThreads();\n            for (let i = 0; i < threads.length; i++) {\n                const thread = threads[i];\n                event = thread.findEventById(eventId);\n                if (event) {\n                    return event;\n                }\n            }\n        }\n\n        return event;\n    }\n\n    /**\n     * Get one of the notification counts for this room\n     * @param type - The type of notification count to get. default: 'total'\n     * @returns The notification count, or undefined if there is no count\n     *                  for this type.\n     */\n    public getUnreadNotificationCount(type = NotificationCountType.Total): number {\n        let count = this.getRoomUnreadNotificationCount(type);\n        for (const threadNotification of this.threadNotifications.values()) {\n            count += threadNotification[type] ?? 0;\n        }\n        return count;\n    }\n\n    /**\n     * Get the notification for the event context (room or thread timeline)\n     */\n    public getUnreadCountForEventContext(type = NotificationCountType.Total, event: MatrixEvent): number {\n        const isThreadEvent = !!event.threadRootId && !event.isThreadRoot;\n\n        return (\n            (isThreadEvent\n                ? this.getThreadUnreadNotificationCount(event.threadRootId, type)\n                : this.getRoomUnreadNotificationCount(type)) ?? 0\n        );\n    }\n\n    /**\n     * Get one of the notification counts for this room\n     * @param type - The type of notification count to get. default: 'total'\n     * @returns The notification count, or undefined if there is no count\n     *                  for this type.\n     */\n    public getRoomUnreadNotificationCount(type = NotificationCountType.Total): number {\n        return this.notificationCounts[type] ?? 0;\n    }\n\n    /**\n     * Get one of the notification counts for a thread\n     * @param threadId - the root event ID\n     * @param type - The type of notification count to get. default: 'total'\n     * @returns The notification count, or undefined if there is no count\n     *          for this type.\n     */\n    public getThreadUnreadNotificationCount(threadId: string, type = NotificationCountType.Total): number {\n        return this.threadNotifications.get(threadId)?.[type] ?? 0;\n    }\n\n    /**\n     * Checks if the current room has unread thread notifications\n     * @returns\n     */\n    public hasThreadUnreadNotification(): boolean {\n        for (const notification of this.threadNotifications.values()) {\n            if ((notification.highlight ?? 0) > 0 || (notification.total ?? 0) > 0) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Swet one of the notification count for a thread\n     * @param threadId - the root event ID\n     * @param type - The type of notification count to get. default: 'total'\n     * @returns\n     */\n    public setThreadUnreadNotificationCount(threadId: string, type: NotificationCountType, count: number): void {\n        const notification: NotificationCount = {\n            highlight: this.threadNotifications.get(threadId)?.highlight,\n            total: this.threadNotifications.get(threadId)?.total,\n            [type]: count,\n        };\n\n        this.threadNotifications.set(threadId, notification);\n\n        this.emit(RoomEvent.UnreadNotifications, notification, threadId);\n    }\n\n    /**\n     * @returns the notification count type for all the threads in the room\n     */\n    public get threadsAggregateNotificationType(): NotificationCountType | null {\n        let type: NotificationCountType | null = null;\n        for (const threadNotification of this.threadNotifications.values()) {\n            if ((threadNotification.highlight ?? 0) > 0) {\n                return NotificationCountType.Highlight;\n            } else if ((threadNotification.total ?? 0) > 0 && !type) {\n                type = NotificationCountType.Total;\n            }\n        }\n        return type;\n    }\n\n    /**\n     * Resets the total thread notifications for all threads in this room to zero,\n     * excluding any threads whose IDs are given in `exceptThreadIds`.\n     *\n     * If the room is not encrypted, also resets the highlight notification count to zero\n     * for the same set of threads.\n     *\n     * This is intended for use from the sync code since we calculate highlight notification\n     * counts locally from decrypted messages. We want to partially trust the total from the\n     * server such that we clear notifications when read receipts arrive. The weird name is\n     * intended to reflect this. You probably do not want to use this.\n     *\n     * @param exceptThreadIds - The thread IDs to exclude from the reset.\n     */\n    public resetThreadUnreadNotificationCountFromSync(exceptThreadIds: string[] = []): void {\n        const isEncrypted = this.hasEncryptionStateEvent();\n\n        for (const [threadId, notifs] of this.threadNotifications) {\n            if (!exceptThreadIds.includes(threadId)) {\n                notifs.total = 0;\n                if (!isEncrypted) {\n                    notifs.highlight = 0;\n                }\n            }\n        }\n\n        this.emit(RoomEvent.UnreadNotifications);\n    }\n\n    /**\n     * Set the bump stamp for this room. This can be used for sorting rooms when the timeline\n     * entries are unknown. Used in MSC4186: Simplified Sliding Sync.\n     * @param bumpStamp The bump_stamp value from the server\n     */\n    public setBumpStamp(bumpStamp: number): void {\n        this.bumpStamp = bumpStamp;\n    }\n\n    /**\n     * Get the bump stamp for this room. This can be used for sorting rooms when the timeline\n     * entries are unknown. Used in MSC4186: Simplified Sliding Sync.\n     * @returns The bump stamp for the room, if it exists.\n     */\n    public getBumpStamp(): number | undefined {\n        return this.bumpStamp;\n    }\n\n    /**\n     * Set one of the notification counts for this room\n     * @param type - The type of notification count to set.\n     * @param count - The new count\n     */\n    public setUnreadNotificationCount(type: NotificationCountType, count: number): void {\n        this.notificationCounts[type] = count;\n        this.emit(RoomEvent.UnreadNotifications, this.notificationCounts);\n    }\n\n    public setUnread(type: NotificationCountType, count: number): void {\n        return this.setUnreadNotificationCount(type, count);\n    }\n\n    /**\n     * Takes a legacy room summary (/v3/sync as opposed to MSC4186) and updates the room with it.\n     *\n     * @param summary - The room summary to update the room with\n     */\n    public setSummary(summary: IRoomSummary): void {\n        const heroes = summary[\"m.heroes\"]?.map((h) => ({ userId: h, fromMSC4186: false }));\n        const joinedCount = summary[\"m.joined_member_count\"];\n        const invitedCount = summary[\"m.invited_member_count\"];\n        if (Number.isInteger(joinedCount)) {\n            this.currentState.setJoinedMemberCount(joinedCount!);\n        }\n        if (Number.isInteger(invitedCount)) {\n            this.currentState.setInvitedMemberCount(invitedCount!);\n        }\n        if (Array.isArray(heroes)) {\n            // filter out ourselves just in case\n            this.heroes = heroes.filter((h) => {\n                return h.userId != this.myUserId;\n            });\n        }\n\n        this.emit(RoomEvent.Summary, summary);\n    }\n\n    /**\n     * Takes information from the MSC4186 room summary and updates the room with it.\n     *\n     * @param heroes - The room's hero members\n     * @param joinedCount - The number of joined members\n     * @param invitedCount - The number of invited members\n     */\n    public setMSC4186SummaryData(\n        heroes: MSC4186Hero[] | undefined,\n        joinedCount: number | undefined,\n        invitedCount: number | undefined,\n    ): void {\n        if (heroes) {\n            this.heroes = heroes\n                .filter((h) => h.user_id !== this.myUserId)\n                .map((h) => ({\n                    userId: h.user_id,\n                    displayName: h.displayname,\n                    avatarUrl: h.avatar_url,\n                    fromMSC4186: true,\n                }));\n        }\n        if (joinedCount !== undefined && Number.isInteger(joinedCount)) {\n            this.currentState.setJoinedMemberCount(joinedCount);\n        }\n        if (invitedCount !== undefined && Number.isInteger(invitedCount)) {\n            this.currentState.setInvitedMemberCount(invitedCount);\n        }\n\n        // Construct a summary object to emit as the event wants the info in a single object\n        // more like old-style (/v3/sync) summaries.\n        this.emit(RoomEvent.Summary, {\n            \"m.heroes\": this.heroes ? this.heroes.map((h) => h.userId) : [],\n            \"m.joined_member_count\": joinedCount,\n            \"m.invited_member_count\": invitedCount,\n        });\n    }\n\n    /**\n     * Whether to send encrypted messages to devices within this room.\n     * @param value - true to blacklist unverified devices, null\n     * to use the global value for this room.\n     */\n    public setBlacklistUnverifiedDevices(value: boolean): void {\n        this.blacklistUnverifiedDevices = value;\n    }\n\n    /**\n     * Whether to send encrypted messages to devices within this room.\n     * @returns true if blacklisting unverified devices, null\n     * if the global value should be used for this room.\n     */\n    public getBlacklistUnverifiedDevices(): boolean | null {\n        if (this.blacklistUnverifiedDevices === undefined) return null;\n        return this.blacklistUnverifiedDevices;\n    }\n\n    /**\n     * Get the avatar URL for a room if one was set.\n     * @param baseUrl - The homeserver base URL. See\n     * {@link MatrixClient#getHomeserverUrl}.\n     * @param width - The desired width of the thumbnail.\n     * @param height - The desired height of the thumbnail.\n     * @param resizeMethod - The thumbnail resize method to use, either\n     * \"crop\" or \"scale\".\n     * @param allowDefault - True to allow an identicon for this room if an\n     * avatar URL wasn't explicitly set. Default: true. (Deprecated)\n     * @param useAuthentication - (optional) If true, the caller supports authenticated\n     * media and wants an authentication-required URL. Note that server support for\n     * authenticated media will not be checked - it is the caller's responsibility\n     * to do so before calling this function. Note also that useAuthentication\n     * implies allowRedirects. Defaults to false (unauthenticated endpoints).\n     * @returns the avatar URL or null.\n     */\n    public getAvatarUrl(\n        baseUrl: string,\n        width: number,\n        height: number,\n        resizeMethod: ResizeMethod,\n        allowDefault = true,\n        useAuthentication: boolean = false,\n    ): string | null {\n        const mainUrl = this.getMxcAvatarUrl();\n        if (!mainUrl && !allowDefault) {\n            return null;\n        }\n\n        if (mainUrl) {\n            return getHttpUriForMxc(\n                baseUrl,\n                mainUrl,\n                width,\n                height,\n                resizeMethod,\n                undefined,\n                undefined,\n                useAuthentication,\n            );\n        }\n\n        return null;\n    }\n\n    /**\n     * Get the mxc avatar url for the room, if one was set.\n     * @returns the mxc avatar url or falsy\n     */\n    public getMxcAvatarUrl(): string | null {\n        const url = this.currentState.getStateEvents(EventType.RoomAvatar, \"\")?.getContent().url;\n        return url && typeof url === \"string\" ? url : null;\n    }\n\n    /**\n     * Get this room's canonical alias\n     * The alias returned by this function may not necessarily\n     * still point to this room.\n     * @returns The room's canonical alias, or null if there is none\n     */\n    public getCanonicalAlias(): string | null {\n        const canonicalAlias = this.currentState.getStateEvents(EventType.RoomCanonicalAlias, \"\")?.getContent().alias;\n        return canonicalAlias && typeof canonicalAlias === \"string\" ? canonicalAlias : null;\n    }\n\n    /**\n     * Get this room's alternative aliases\n     * @returns The room's alternative aliases, or an empty array\n     */\n    public getAltAliases(): string[] {\n        const altAliases = this.currentState.getStateEvents(EventType.RoomCanonicalAlias, \"\")?.getContent().alt_aliases;\n        if (Array.isArray(altAliases)) {\n            return altAliases.filter((alias) => typeof alias === \"string\");\n        }\n        return [];\n    }\n\n    /**\n     * Add events to a timeline\n     *\n     * <p>Will fire \"Room.timeline\" for each event added.\n     *\n     * @param events - A list of events to add.\n     *\n     * @param toStartOfTimeline -   True to add these events to the start\n     * (oldest) instead of the end (newest) of the timeline. If true, the oldest\n     * event will be the <b>last</b> element of 'events'.\n     *\n     * @param timeline -   timeline to\n     *    add events to.\n     *\n     * @param paginationToken -   token for the next batch of events\n     *\n     * @remarks\n     * Fires {@link RoomEvent.Timeline}\n     */\n    public addEventsToTimeline(\n        events: MatrixEvent[],\n        toStartOfTimeline: boolean,\n        addToState: boolean,\n        timeline: EventTimeline,\n        paginationToken?: string,\n    ): void {\n        // ALWAYS filter out any events that are past retention\n        events = events.filter((e) => this.retention?.shouldEventBeRetained(e) ?? true);\n        timeline.getTimelineSet().addEventsToTimeline(events, toStartOfTimeline, addToState, timeline, paginationToken);\n    }\n\n    /**\n     * Get the instance of the thread associated with the current event\n     * @param eventId - the ID of the current event\n     * @returns a thread instance if known\n     */\n    public getThread(eventId: string): Thread | null {\n        return this.threads.get(eventId) ?? null;\n    }\n\n    /**\n     * Get all the known threads in the room\n     */\n    public getThreads(): Thread[] {\n        return Array.from(this.threads.values());\n    }\n\n    /**\n     * Get a member from the current room state.\n     * @param userId - The user ID of the member.\n     * @returns The member or `null`.\n     */\n    public getMember(userId: string): RoomMember | null {\n        return this.currentState.getMember(userId);\n    }\n\n    /**\n     * Get all currently loaded members from the current\n     * room state.\n     * @returns Room members\n     */\n    public getMembers(): RoomMember[] {\n        return this.currentState.getMembers();\n    }\n\n    /**\n     * Get a list of members whose membership state is \"join\".\n     * @returns A list of currently joined members.\n     */\n    public getJoinedMembers(): RoomMember[] {\n        return this.getMembersWithMembership(KnownMembership.Join);\n    }\n\n    /**\n     * Returns the number of joined members in this room\n     * This method caches the result.\n     * This is a wrapper around the method of the same name in roomState, returning\n     * its result for the room's current state.\n     * @returns The number of members in this room whose membership is 'join'\n     */\n    public getJoinedMemberCount(): number {\n        return this.currentState.getJoinedMemberCount();\n    }\n\n    /**\n     * Returns the number of invited members in this room\n     * @returns The number of members in this room whose membership is 'invite'\n     */\n    public getInvitedMemberCount(): number {\n        return this.currentState.getInvitedMemberCount();\n    }\n\n    /**\n     * Returns the number of invited + joined members in this room\n     * @returns The number of members in this room whose membership is 'invite' or 'join'\n     */\n    public getInvitedAndJoinedMemberCount(): number {\n        return this.getInvitedMemberCount() + this.getJoinedMemberCount();\n    }\n\n    /**\n     * Get a list of members with given membership state.\n     * @param membership - The membership state.\n     * @returns A list of members with the given membership state.\n     */\n    public getMembersWithMembership(membership: Membership): RoomMember[] {\n        return this.currentState.getMembers().filter(function (m) {\n            return m.membership === membership;\n        });\n    }\n\n    /**\n     * Get a list of members we should be encrypting for in this room\n     * @returns A list of members who\n     * we should encrypt messages for in this room.\n     */\n    public async getEncryptionTargetMembers(): Promise<RoomMember[]> {\n        await this.loadMembersIfNeeded();\n        let members = this.getMembersWithMembership(KnownMembership.Join);\n        if (this.shouldEncryptForInvitedMembers()) {\n            members = members.concat(this.getMembersWithMembership(KnownMembership.Invite));\n        }\n        return members;\n    }\n\n    /**\n     * Determine whether we should encrypt messages for invited users in this room\n     * @returns if we should encrypt messages for invited users\n     */\n    public shouldEncryptForInvitedMembers(): boolean {\n        const ev = this.currentState.getStateEvents(EventType.RoomHistoryVisibility, \"\");\n        return ev?.getContent()?.history_visibility !== \"joined\";\n    }\n\n    /**\n     * Get the default room name (i.e. what a given user would see if the\n     * room had no m.room.name)\n     * @param userId - The userId from whose perspective we want\n     * to calculate the default name\n     * @returns The default room name\n     */\n    public getDefaultRoomName(userId: string): string {\n        return this.calculateRoomName(userId, true);\n    }\n\n    /**\n     * Check if the given user_id has the given membership state.\n     * @param userId - The user ID to check.\n     * @param membership - The membership e.g. `'join'`\n     * @returns True if this user_id has the given membership state.\n     */\n    public hasMembershipState(userId: string, membership: Membership): boolean {\n        const member = this.getMember(userId);\n        if (!member) {\n            return false;\n        }\n        return member.membership === membership;\n    }\n\n    /**\n     * Add a timelineSet for this room with the given filter\n     * @param filter - The filter to be applied to this timelineSet\n     * @param opts - Configuration options\n     * @returns The timelineSet\n     */\n    public getOrCreateFilteredTimelineSet(\n        filter: Filter,\n        { prepopulateTimeline = true, useSyncEvents = true, pendingEvents = true }: ICreateFilterOpts = {},\n    ): EventTimelineSet {\n        if (this.filteredTimelineSets[filter.filterId!]) {\n            return this.filteredTimelineSets[filter.filterId!];\n        }\n        const opts = Object.assign({ filter, pendingEvents }, this.opts);\n        const timelineSet = new EventTimelineSet(this, opts);\n        this.reEmitter.reEmit(timelineSet, [RoomEvent.Timeline, RoomEvent.TimelineReset]);\n        if (useSyncEvents) {\n            this.filteredTimelineSets[filter.filterId!] = timelineSet;\n            this.timelineSets.push(timelineSet);\n        }\n\n        const unfilteredLiveTimeline = this.getLiveTimeline();\n        // Not all filter are possible to replicate client-side only\n        // When that's the case we do not want to prepopulate from the live timeline\n        // as we would get incorrect results compared to what the server would send back\n        if (prepopulateTimeline) {\n            // populate up the new timelineSet with filtered events from our live\n            // unfiltered timeline.\n            //\n            // XXX: This is risky as our timeline\n            // may have grown huge and so take a long time to filter.\n            // see https://github.com/vector-im/vector-web/issues/2109\n\n            unfilteredLiveTimeline.getEvents().forEach(function (event) {\n                timelineSet.addLiveEvent(event, { addToState: false }); // Filtered timeline sets should not track state\n            });\n\n            // find the earliest unfiltered timeline\n            let timeline = unfilteredLiveTimeline;\n            while (timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS)) {\n                timeline = timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS)!;\n            }\n\n            timelineSet\n                .getLiveTimeline()\n                .setPaginationToken(timeline.getPaginationToken(EventTimeline.BACKWARDS), EventTimeline.BACKWARDS);\n        } else if (useSyncEvents) {\n            const livePaginationToken = unfilteredLiveTimeline.getPaginationToken(Direction.Forward);\n            timelineSet.getLiveTimeline().setPaginationToken(livePaginationToken, Direction.Backward);\n        }\n\n        // alternatively, we could try to do something like this to try and re-paginate\n        // in the filtered events from nothing, but Mark says it's an abuse of the API\n        // to do so:\n        //\n        // timelineSet.resetLiveTimeline(\n        //      unfilteredLiveTimeline.getPaginationToken(EventTimeline.FORWARDS)\n        // );\n\n        return timelineSet;\n    }\n\n    private async getThreadListFilter(filterType = ThreadFilterType.All): Promise<Filter> {\n        const myUserId = this.client.getUserId()!;\n        const filter = new Filter(myUserId);\n\n        const definition: IFilterDefinition = {\n            room: {\n                timeline: {\n                    [FILTER_RELATED_BY_REL_TYPES.name]: [THREAD_RELATION_TYPE.name],\n                },\n            },\n        };\n\n        if (filterType === ThreadFilterType.My) {\n            definition!.room!.timeline![FILTER_RELATED_BY_SENDERS.name] = [myUserId];\n        }\n\n        filter.setDefinition(definition);\n        const filterId = await this.client.getOrCreateFilter(`THREAD_PANEL_${this.roomId}_${filterType}`, filter);\n\n        filter.filterId = filterId;\n\n        return filter;\n    }\n\n    private async createThreadTimelineSet(filterType?: ThreadFilterType): Promise<EventTimelineSet> {\n        let timelineSet: EventTimelineSet;\n        if (Thread.hasServerSideListSupport) {\n            timelineSet = new EventTimelineSet(\n                this,\n                {\n                    ...this.opts,\n                    pendingEvents: false,\n                },\n                undefined,\n                undefined,\n                filterType ?? ThreadFilterType.All,\n            );\n            this.reEmitter.reEmit(timelineSet, [RoomEvent.Timeline, RoomEvent.TimelineReset]);\n        } else if (Thread.hasServerSideSupport) {\n            const filter = await this.getThreadListFilter(filterType);\n\n            timelineSet = this.getOrCreateFilteredTimelineSet(filter, {\n                prepopulateTimeline: false,\n                useSyncEvents: false,\n                pendingEvents: false,\n            });\n        } else {\n            timelineSet = new EventTimelineSet(this, {\n                pendingEvents: false,\n            });\n\n            Array.from(this.threads).forEach(([, thread]) => {\n                if (thread.length === 0) return;\n                const currentUserParticipated = thread.timeline.some((event) => {\n                    return event.getSender() === this.client.getUserId();\n                });\n                if (filterType !== ThreadFilterType.My || currentUserParticipated) {\n                    timelineSet.getLiveTimeline().addEvent(thread.rootEvent!, {\n                        toStartOfTimeline: false,\n                        addToState: false,\n                    });\n                }\n            });\n        }\n\n        return timelineSet;\n    }\n\n    private threadsReady = false;\n\n    /**\n     * Takes the given thread root events and creates threads for them.\n     */\n    public processThreadRoots(events: MatrixEvent[], toStartOfTimeline: boolean): void {\n        if (!this.client.supportsThreads()) return;\n        for (const rootEvent of events) {\n            EventTimeline.setEventMetadata(rootEvent, this.currentState, toStartOfTimeline);\n            if (!this.getThread(rootEvent.getId()!)) {\n                this.createThread(rootEvent.getId()!, rootEvent, [], toStartOfTimeline);\n            }\n        }\n    }\n\n    /**\n     * Fetch the bare minimum of room threads required for the thread list to work reliably.\n     * With server support that means fetching one page.\n     * Without server support that means fetching as much at once as the server allows us to.\n     */\n    public async fetchRoomThreads(): Promise<void> {\n        if (this.threadsReady || !this.client.supportsThreads()) {\n            return;\n        }\n\n        if (Thread.hasServerSideListSupport) {\n            await Promise.all([\n                this.fetchRoomThreadList(ThreadFilterType.All),\n                this.fetchRoomThreadList(ThreadFilterType.My),\n            ]);\n        } else {\n            const allThreadsFilter = await this.getThreadListFilter();\n\n            const { chunk: events } = await this.client.createMessagesRequest(\n                this.roomId,\n                \"\",\n                Number.MAX_SAFE_INTEGER,\n                Direction.Backward,\n                allThreadsFilter,\n            );\n\n            if (!events.length) return;\n\n            // Sorted by last_reply origin_server_ts\n            const threadRoots = events.map(this.client.getEventMapper()).sort((eventA, eventB) => {\n                /**\n                 * `origin_server_ts` in a decentralised world is far from ideal\n                 * but for lack of any better, we will have to use this\n                 * Long term the sorting should be handled by homeservers and this\n                 * is only meant as a short term patch\n                 */\n                const threadAMetadata = eventA.getServerAggregatedRelation<IThreadBundledRelationship>(\n                    THREAD_RELATION_TYPE.name,\n                )!;\n                const threadBMetadata = eventB.getServerAggregatedRelation<IThreadBundledRelationship>(\n                    THREAD_RELATION_TYPE.name,\n                )!;\n                return threadAMetadata.latest_event.origin_server_ts - threadBMetadata.latest_event.origin_server_ts;\n            });\n\n            let latestMyThreadsRootEvent: MatrixEvent | undefined;\n            const roomState = this.getLiveTimeline().getState(EventTimeline.FORWARDS);\n            for (const rootEvent of threadRoots) {\n                const opts = {\n                    duplicateStrategy: DuplicateStrategy.Ignore,\n                    fromCache: false,\n                    addToState: false,\n                    roomState,\n                };\n                this.threadsTimelineSets[0]?.addLiveEvent(rootEvent, opts);\n\n                const threadRelationship = rootEvent.getServerAggregatedRelation<IThreadBundledRelationship>(\n                    THREAD_RELATION_TYPE.name,\n                );\n                if (threadRelationship?.current_user_participated) {\n                    this.threadsTimelineSets[1]?.addLiveEvent(rootEvent, opts);\n                    latestMyThreadsRootEvent = rootEvent;\n                }\n            }\n\n            this.processThreadRoots(threadRoots, true);\n\n            this.client.decryptEventIfNeeded(threadRoots[threadRoots.length - 1]);\n            if (latestMyThreadsRootEvent) {\n                this.client.decryptEventIfNeeded(latestMyThreadsRootEvent);\n            }\n        }\n\n        this.on(ThreadEvent.NewReply, this.onThreadReply);\n        this.on(ThreadEvent.Update, this.onThreadUpdate);\n        this.on(ThreadEvent.Delete, this.onThreadDelete);\n        this.threadsReady = true;\n    }\n\n    /**\n     * Process a list of poll events.\n     *\n     * @param events - List of events\n     */\n    public async processPollEvents(events: MatrixEvent[]): Promise<void> {\n        for (const event of events) {\n            try {\n                // Continue if the event is a clear text, non-poll event.\n                if (!event.isEncrypted() && !isPollEvent(event)) continue;\n\n                /**\n                 * Try to decrypt the event. Promise resolution does not guarantee a successful decryption.\n                 * Retry is handled in {@link processPollEvent}.\n                 */\n                await this.client.decryptEventIfNeeded(event);\n                this.processPollEvent(event);\n            } catch (err) {\n                logger.warn(\"Error processing poll event\", event.getId(), err);\n            }\n        }\n    }\n\n    /**\n     * Processes poll events:\n     * If the event has a decryption failure, it will listen for decryption and tries again.\n     * If it is a poll start event (`m.poll.start`),\n     * it creates and stores a Poll model and emits a PollEvent.New event.\n     * If the event is related to a poll, it will add it to the poll.\n     * Noop for other cases.\n     *\n     * @param event - Event that could be a poll event\n     */\n    private async processPollEvent(event: MatrixEvent): Promise<void> {\n        if (event.isDecryptionFailure()) {\n            event.once(MatrixEventEvent.Decrypted, (maybeDecryptedEvent: MatrixEvent) => {\n                this.processPollEvent(maybeDecryptedEvent);\n            });\n            return;\n        }\n\n        if (M_POLL_START.matches(event.getType())) {\n            try {\n                const poll = new Poll(event, this.client, this);\n                this.polls.set(event.getId()!, poll);\n                this.emit(PollEvent.New, poll);\n\n                // remove the poll when redacted\n                event.once(MatrixEventEvent.BeforeRedaction, (redactedEvent: MatrixEvent) => {\n                    this.polls.delete(redactedEvent.getId()!);\n                });\n            } catch {}\n            // poll creation can fail for malformed poll start events\n            return;\n        }\n\n        const relationEventId = event.relationEventId;\n\n        if (relationEventId && this.polls.has(relationEventId)) {\n            const poll = this.polls.get(relationEventId);\n            poll?.onNewRelation(event);\n        }\n    }\n\n    /**\n     * Fetch a single page of threadlist messages for the specific thread filter\n     * @internal\n     */\n    private async fetchRoomThreadList(filter?: ThreadFilterType): Promise<void> {\n        if (!this.client.supportsThreads()) return;\n        if (this.threadsTimelineSets.length === 0) return;\n\n        const timelineSet = filter === ThreadFilterType.My ? this.threadsTimelineSets[1] : this.threadsTimelineSets[0];\n\n        const { chunk: events, end } = await this.client.createThreadListMessagesRequest(\n            this.roomId,\n            null,\n            undefined,\n            Direction.Backward,\n            timelineSet.threadListType,\n            timelineSet.getFilter(),\n        );\n\n        timelineSet.getLiveTimeline().setPaginationToken(end ?? null, Direction.Backward);\n\n        if (!events.length) return;\n\n        const matrixEvents = events.map(this.client.getEventMapper());\n        this.processThreadRoots(matrixEvents, true);\n        const roomState = this.getLiveTimeline().getState(EventTimeline.FORWARDS);\n        for (const rootEvent of matrixEvents) {\n            timelineSet.addLiveEvent(rootEvent, {\n                duplicateStrategy: DuplicateStrategy.Replace,\n                fromCache: false,\n                roomState,\n                addToState: false,\n            });\n        }\n    }\n\n    private onThreadUpdate = (thread: Thread): void => {\n        this.updateThreadRootEvents(thread, false, false);\n    };\n\n    private onThreadReply = (thread: Thread): void => {\n        this.updateThreadRootEvents(thread, false, true);\n    };\n\n    private onThreadDelete = (thread: Thread): void => {\n        this.threads.delete(thread.id);\n\n        const timeline = this.getTimelineForEvent(thread.id);\n        const roomEvent = timeline?.getEvents()?.find((it) => it.getId() === thread.id);\n        if (roomEvent) {\n            thread.clearEventMetadata(roomEvent);\n        } else {\n            logger.debug(\"onThreadDelete: Could not find root event in room timeline\");\n        }\n        for (const timelineSet of this.threadsTimelineSets) {\n            timelineSet.removeEvent(thread.id);\n        }\n    };\n\n    /**\n     * Forget the timelineSet for this room with the given filter\n     *\n     * @param filter - the filter whose timelineSet is to be forgotten\n     */\n    public removeFilteredTimelineSet(filter: Filter): void {\n        const timelineSet = this.filteredTimelineSets[filter.filterId!];\n        delete this.filteredTimelineSets[filter.filterId!];\n        const i = this.timelineSets.indexOf(timelineSet);\n        if (i > -1) {\n            this.timelineSets.splice(i, 1);\n        }\n    }\n\n    /**\n     * Determine which timeline(s) a given event should live in\n     * Thread roots live in both the main timeline and their corresponding thread timeline\n     * Relations, redactions, replies to thread relation events live only in the thread timeline\n     * Relations (other than m.thread), redactions, replies to a thread root live only in the main timeline\n     * Relations, redactions, replies where the parent cannot be found live in no timelines but should be aggregated regardless.\n     * Otherwise, the event lives in the main timeline only.\n     *\n     * Note: when a redaction is applied, the redacted event, events relating\n     * to it, and the redaction event itself, will all move to the main thread.\n     * This method classifies them as inside the thread of the redacted event.\n     * They are moved later as part of makeRedacted.\n     * This will change if MSC3389 is merged.\n     */\n    public eventShouldLiveIn(\n        event: MatrixEvent,\n        events?: MatrixEvent[],\n        roots?: Set<string>,\n    ): {\n        shouldLiveInRoom: boolean;\n        shouldLiveInThread: boolean;\n        threadId?: string;\n    } {\n        if (!this.client?.supportsThreads()) {\n            return {\n                shouldLiveInRoom: true,\n                shouldLiveInThread: false,\n            };\n        }\n\n        // A thread root is the only event shown in both timelines\n        if (event.isThreadRoot || roots?.has(event.getId()!)) {\n            return {\n                shouldLiveInRoom: true,\n                shouldLiveInThread: true,\n                threadId: event.getId(),\n            };\n        }\n\n        const isThreadRelation = event.isRelation(THREAD_RELATION_TYPE.name);\n        const parentEventId = event.getAssociatedId();\n        const threadRootId = event.threadRootId;\n\n        // Where the parent is the thread root and this is a non-thread relation this should live only in the main timeline\n        if (!!parentEventId && !isThreadRelation && (threadRootId === parentEventId || roots?.has(parentEventId!))) {\n            return {\n                shouldLiveInRoom: true,\n                shouldLiveInThread: false,\n            };\n        }\n\n        let parentEvent: MatrixEvent | undefined;\n        if (parentEventId) {\n            parentEvent = this.findEventById(parentEventId) ?? events?.find((e) => e.getId() === parentEventId);\n        }\n\n        // Treat non-thread-relations, redactions, and replies as extensions of their parents so evaluate parentEvent instead\n        if (parentEvent && !isThreadRelation) {\n            return this.eventShouldLiveIn(parentEvent, events, roots);\n        }\n\n        // A thread relation (1st and 2nd order) is always only shown in a thread\n        if (threadRootId != undefined) {\n            return {\n                shouldLiveInRoom: false,\n                shouldLiveInThread: true,\n                threadId: threadRootId,\n            };\n        }\n\n        // Due to replies not being typical relations and being used as fallbacks for threads relations\n        // If we bypass the if case above then we know we are not a thread, so if we are still a reply\n        // then we know that we must be in the main timeline. Same goes if we have no associated parent event.\n        if (!parentEventId || !!event.replyEventId) {\n            return {\n                shouldLiveInRoom: true,\n                shouldLiveInThread: false,\n            };\n        }\n\n        // We've exhausted all scenarios,\n        // we cannot assume that it lives in the main timeline as this may be a relation for an unknown thread\n        // adding the event in the wrong timeline causes stuck notifications and can break ability to send read receipts\n        return {\n            shouldLiveInRoom: false,\n            shouldLiveInThread: false,\n        };\n    }\n\n    public findThreadForEvent(event?: MatrixEvent): Thread | null {\n        if (!event) return null;\n\n        const { threadId } = this.eventShouldLiveIn(event);\n        return threadId ? this.getThread(threadId) : null;\n    }\n\n    private addThreadedEvents(threadId: string, events: MatrixEvent[], toStartOfTimeline = false): void {\n        const thread = this.getThread(threadId);\n        if (thread) {\n            thread.addEvents(events, toStartOfTimeline);\n        } else {\n            const rootEvent = this.findEventById(threadId) ?? events.find((e) => e.getId() === threadId);\n            this.createThread(threadId, rootEvent, events, toStartOfTimeline);\n        }\n    }\n\n    /**\n     * Adds events to a thread's timeline. Will fire \"Thread.update\"\n     */\n    public processThreadedEvents(events: MatrixEvent[], toStartOfTimeline: boolean): void {\n        // ALWAYS filter out any events that are past retention\n        events = events.filter((e) => this.retention?.shouldEventBeRetained(e) ?? true);\n        events.forEach(this.tryApplyRedaction);\n\n        const eventsByThread: { [threadId: string]: MatrixEvent[] } = {};\n        for (const event of events) {\n            const { threadId, shouldLiveInThread } = this.eventShouldLiveIn(event);\n            if (shouldLiveInThread && !eventsByThread[threadId!]) {\n                eventsByThread[threadId!] = [];\n            }\n            eventsByThread[threadId!]?.push(event);\n        }\n\n        Object.entries(eventsByThread).map(([threadId, threadEvents]) =>\n            this.addThreadedEvents(threadId, threadEvents, toStartOfTimeline),\n        );\n    }\n\n    private updateThreadRootEvents = (thread: Thread, toStartOfTimeline: boolean, recreateEvent: boolean): void => {\n        if (thread.length) {\n            this.updateThreadRootEvent(this.threadsTimelineSets?.[0], thread, toStartOfTimeline, recreateEvent);\n            if (thread.hasCurrentUserParticipated) {\n                this.updateThreadRootEvent(this.threadsTimelineSets?.[1], thread, toStartOfTimeline, recreateEvent);\n            }\n        }\n    };\n\n    private updateThreadRootEvent = (\n        timelineSet: EventTimelineSet | undefined,\n        thread: Thread,\n        toStartOfTimeline: boolean,\n        recreateEvent: boolean,\n    ): void => {\n        if (timelineSet && thread.rootEvent) {\n            if (recreateEvent) {\n                timelineSet.removeEvent(thread.id);\n            }\n            if (Thread.hasServerSideSupport) {\n                timelineSet.addLiveEvent(thread.rootEvent, {\n                    duplicateStrategy: DuplicateStrategy.Replace,\n                    fromCache: false,\n                    roomState: this.currentState,\n                    addToState: false,\n                });\n            } else {\n                timelineSet.addEventToTimeline(thread.rootEvent, timelineSet.getLiveTimeline(), {\n                    toStartOfTimeline,\n                    addToState: false,\n                });\n            }\n        }\n    };\n\n    public createThread(\n        threadId: string,\n        rootEvent: MatrixEvent | undefined,\n        events: MatrixEvent[] = [],\n        toStartOfTimeline: boolean,\n    ): Thread {\n        if (this.threads.has(threadId)) {\n            return this.threads.get(threadId)!;\n        }\n\n        if (rootEvent) {\n            const relatedEvents = this.relations.getAllChildEventsForEvent(rootEvent.getId()!);\n            if (relatedEvents?.length) {\n                // Include all relations of the root event, given it'll be visible in both timelines,\n                // except `m.replace` as that will already be applied atop the event using `MatrixEvent::makeReplaced`\n                events = events.concat(relatedEvents.filter((e) => !e.isRelation(RelationType.Replace)));\n            }\n        }\n\n        const thread = new Thread(threadId, rootEvent, {\n            room: this,\n            client: this.client,\n            pendingEventOrdering: this.opts.pendingEventOrdering,\n            receipts: this.cachedThreadReadReceipts.get(threadId) ?? [],\n        });\n\n        // Add the re-emitter before we start adding events to the thread so we don't miss events\n        this.reEmitter.reEmit(thread, [\n            ThreadEvent.Delete,\n            ThreadEvent.Update,\n            ThreadEvent.NewReply,\n            RoomEvent.Timeline,\n            RoomEvent.TimelineReset,\n        ]);\n\n        // All read receipts should now come down from sync, we do not need to keep\n        // a reference to the cached receipts anymore.\n        this.cachedThreadReadReceipts.delete(threadId);\n\n        // If we managed to create a thread and figure out its `id` then we can use it\n        // This has to happen before thread.addEvents, because that adds events to the eventtimeline, and the\n        // eventtimeline sometimes looks up thread information via the room.\n        this.threads.set(thread.id, thread);\n\n        // This is necessary to be able to jump to events in threads:\n        // If we jump to an event in a thread where neither the event, nor the root,\n        // nor any thread event are loaded yet, we'll load the event as well as the thread root, create the thread,\n        // and pass the event through this.\n        thread.addEvents(events, false);\n\n        // We need to update the thread root events, but the thread may not be ready yet.\n        // If it isn't, it will fire ThreadEvent.Update when it is and we'll call updateThreadRootEvents then.\n        if (this.threadsReady && thread.initialEventsFetched) {\n            this.updateThreadRootEvents(thread, toStartOfTimeline, false);\n        }\n        this.emit(ThreadEvent.New, thread, toStartOfTimeline);\n\n        return thread;\n    }\n\n    /**\n     * Applies an event as a redaction of another event, regardless of whether the redacting\n     * event is actually a redaction.\n     *\n     * Callers should use tryApplyRedaction instead.\n     *\n     * @param redactionEvent The event which redacts an event.\n     * @param redactedEvent The event being redacted.\n     * @private\n     */\n    private applyEventAsRedaction(redactionEvent: MatrixEvent, redactedEvent: MatrixEvent): void {\n        const threadRootId = redactedEvent.threadRootId;\n        redactedEvent.makeRedacted(redactionEvent, this);\n\n        // If this is in the current state, replace it with the redacted version\n        if (redactedEvent.isState()) {\n            const currentStateEvent = this.currentState.getStateEvents(\n                redactedEvent.getType(),\n                redactedEvent.getStateKey()!,\n            );\n            if (currentStateEvent?.getId() === redactedEvent.getId()) {\n                this.currentState.setStateEvents([redactedEvent]);\n            }\n        }\n\n        this.emit(RoomEvent.Redaction, redactionEvent, this, threadRootId);\n\n        // TODO: we stash user displaynames (among other things) in\n        // RoomMember objects which are then attached to other events\n        // (in the sender and target fields). We should get those\n        // RoomMember objects to update themselves when the events that\n        // they are based on are changed.\n\n        // Remove any visibility change on this event.\n        this.visibilityEvents.delete(redactedEvent.getId()!);\n\n        // If this event is a visibility change event, remove it from the\n        // list of visibility changes and update any event affected by it.\n        if (redactedEvent.isVisibilityEvent()) {\n            this.redactVisibilityChangeEvent(redactionEvent);\n        }\n    }\n\n    public readonly tryApplyRedaction = (event: MatrixEvent): void => {\n        // FIXME: apply redactions to notification list\n\n        // NB: We continue to add the redaction event to the timeline at the\n        // end of this function so clients can say \"so and so redacted an event\"\n        // if they wish to. Also this may be needed to trigger an update.\n\n        if (event.isRedaction()) {\n            const redactId = event.event.redacts;\n\n            // if we know about this event, redact its contents now.\n            const redactedEvent = redactId ? this.findEventById(redactId) : undefined;\n            if (redactId) {\n                try {\n                    this.stickyEvents.handleRedaction(redactedEvent || redactId);\n                } catch (ex) {\n                    // Non-critical failure, but we should warn.\n                    logger.error(\"Failed to handle redaction for sticky event\", ex);\n                }\n            }\n            if (redactedEvent) {\n                this.applyEventAsRedaction(event, redactedEvent);\n            }\n        } else if (event.getType() === EventType.RoomMember) {\n            const membership = event.getContent()[\"membership\"];\n            if (\n                membership !== KnownMembership.Ban &&\n                !(membership === KnownMembership.Leave && event.getStateKey() !== event.getSender())\n            ) {\n                // Not a ban or kick, therefore not a membership event we care about here.\n                return;\n            }\n            const redactEvents = event.getContent()[\"org.matrix.msc4293.redact_events\"];\n            if (redactEvents !== true) {\n                // Invalid or not set - nothing to redact.\n                return;\n            }\n            const state = this.getLiveTimeline().getState(Direction.Forward)!;\n            if (!state.maySendRedactionForEvent(event, event.getSender()!)) {\n                // If the sender can't redact the membership event, then they won't be able to\n                // redact any of the target's events either, so skip.\n                return;\n            }\n\n            // The redaction is possible, so let's find all the events and apply it.\n            const events = this.getTimelineSets()\n                .map((s) => s.getTimelines())\n                .reduce((p, c) => {\n                    p.push(...c);\n                    return p;\n                }, [])\n                .map((t) => t.getEvents().filter((e) => e.getSender() === event.getStateKey()))\n                .reduce((p, c) => {\n                    p.push(...c);\n                    return c;\n                }, []);\n            for (const toRedact of events) {\n                this.applyEventAsRedaction(event, toRedact);\n            }\n        }\n    };\n\n    private processLiveEvent(event: MatrixEvent): void {\n        this.tryApplyRedaction(event);\n\n        // Implement MSC3531: hiding messages.\n        if (event.isVisibilityEvent()) {\n            // This event changes the visibility of another event, record\n            // the visibility change, inform clients if necessary.\n            this.applyNewVisibilityEvent(event);\n        }\n        // If any pending visibility change is waiting for this (older) event,\n        this.applyPendingVisibilityEvents(event);\n\n        // Sliding Sync modifications:\n        // The proxy cannot guarantee every sent event will have a transaction_id field, so we need\n        // to check the event ID against the list of pending events if there is no transaction ID\n        // field. Only do this for events sent by us though as it's potentially expensive to loop\n        // the pending events map.\n        const txnId = event.getUnsigned().transaction_id;\n        if (!txnId && event.getSender() === this.myUserId) {\n            // check the txn map for a matching event ID\n            for (const [tid, localEvent] of this.txnToEvent) {\n                if (localEvent.getId() === event.getId()) {\n                    logger.debug(\"processLiveEvent: found sent event without txn ID: \", tid, event.getId());\n                    // update the unsigned field so we can re-use the same codepaths\n                    const unsigned = event.getUnsigned();\n                    unsigned.transaction_id = tid;\n                    event.setUnsigned(unsigned);\n                    break;\n                }\n            }\n        }\n    }\n\n    /**\n     * Add an event to the end of this room's live timelines. Will fire\n     * \"Room.timeline\".\n     *\n     * @param event - Event to be added\n     * @param addLiveEventOptions - addLiveEvent options\n     * @internal\n     *\n     * @remarks\n     * Fires {@link RoomEvent.Timeline}\n     */\n    private addLiveEvent(event: MatrixEvent, addLiveEventOptions: IAddLiveEventOptions): void {\n        const { duplicateStrategy, timelineWasEmpty, fromCache, addToState } = addLiveEventOptions;\n\n        // add to our timeline sets\n        for (const timelineSet of this.timelineSets) {\n            timelineSet.addLiveEvent(event, {\n                duplicateStrategy,\n                fromCache,\n                timelineWasEmpty,\n                addToState,\n            });\n        }\n\n        // synthesize and inject implicit read receipts\n        // Done after adding the event because otherwise the app would get a read receipt\n        // pointing to an event that wasn't yet in the timeline\n        // Don't synthesize RR for m.room.redaction as this causes the RR to go missing.\n        if (event.sender && event.getType() !== EventType.RoomRedaction) {\n            this.addReceipt(synthesizeReceipt(event.sender.userId, event, ReceiptType.Read), true);\n\n            // Any live events from a user could be taken as implicit\n            // presence information: evidence that they are currently active.\n            // ...except in a world where we use 'user.currentlyActive' to reduce\n            // presence spam, this isn't very useful - we'll get a transition when\n            // they are no longer currently active anyway. So don't bother to\n            // reset the lastActiveAgo and lastPresenceTs from the RoomState's user.\n        }\n    }\n\n    /**\n     * Add a pending outgoing event to this room.\n     *\n     * <p>The event is added to either the pendingEventList, or the live timeline,\n     * depending on the setting of opts.pendingEventOrdering.\n     *\n     * <p>This is an internal method, intended for use by MatrixClient.\n     *\n     * @param event - The event to add.\n     *\n     * @param txnId - Transaction id for this outgoing event\n     *\n     * @throws if the event doesn't have status SENDING, or we aren't given a\n     * unique transaction id.\n     *\n     * @remarks\n     * Fires {@link RoomEvent.LocalEchoUpdated}\n     */\n    public addPendingEvent(event: MatrixEvent, txnId: string): void {\n        if (event.status !== EventStatus.SENDING && event.status !== EventStatus.NOT_SENT) {\n            throw new Error(\"addPendingEvent called on an event with status \" + event.status);\n        }\n\n        if (this.txnToEvent.get(txnId)) {\n            throw new Error(\"addPendingEvent called on an event with known txnId \" + txnId);\n        }\n\n        // call setEventMetadata to set up event.sender etc\n        // as event is shared over all timelineSets, we set up its metadata based\n        // on the unfiltered timelineSet.\n        EventTimeline.setEventMetadata(event, this.getLiveTimeline().getState(EventTimeline.FORWARDS)!, false);\n\n        this.txnToEvent.set(txnId, event);\n        if (this.pendingEventList) {\n            if (this.pendingEventList.some((e) => e.status === EventStatus.NOT_SENT)) {\n                logger.warn(\"Setting event as NOT_SENT due to messages in the same state\");\n                event.setStatus(EventStatus.NOT_SENT);\n            }\n            this.pendingEventList.push(event);\n            this.savePendingEvents();\n            if (event.isRelation()) {\n                // For pending events, add them to the relations collection immediately.\n                // (The alternate case below already covers this as part of adding to\n                // the timeline set.)\n                this.aggregateNonLiveRelation(event);\n            }\n\n            if (event.isRedaction()) {\n                const redactId = event.event.redacts;\n                let redactedEvent = this.pendingEventList.find((e) => e.getId() === redactId);\n                if (!redactedEvent && redactId) {\n                    redactedEvent = this.findEventById(redactId);\n                }\n                if (redactedEvent) {\n                    redactedEvent.markLocallyRedacted(event);\n                    this.emit(RoomEvent.Redaction, event, this, redactedEvent.threadRootId);\n                }\n            }\n        } else {\n            for (const timelineSet of this.timelineSets) {\n                if (timelineSet.getFilter()) {\n                    if (timelineSet.getFilter()!.filterRoomTimeline([event]).length) {\n                        timelineSet.addEventToTimeline(event, timelineSet.getLiveTimeline(), {\n                            toStartOfTimeline: false,\n                            addToState: false, // We don't support localEcho of state events yet\n                        });\n                    }\n                } else {\n                    timelineSet.addEventToTimeline(event, timelineSet.getLiveTimeline(), {\n                        toStartOfTimeline: false,\n                        addToState: false, // We don't support localEcho of state events yet\n                    });\n                }\n            }\n        }\n\n        this.emit(RoomEvent.LocalEchoUpdated, event, this);\n    }\n\n    /**\n     * Persists all pending events to local storage\n     *\n     * If the current room is encrypted only encrypted events will be persisted\n     * all messages that are not yet encrypted will be discarded\n     *\n     * This is because the flow of EVENT_STATUS transition is\n     * `queued => sending => encrypting => sending => sent`\n     *\n     * Steps 3 and 4 are skipped for unencrypted room.\n     * It is better to discard an unencrypted message rather than persisting\n     * it locally for everyone to read\n     */\n    private savePendingEvents(): void {\n        if (this.pendingEventList) {\n            const pendingEvents = this.pendingEventList\n                .map((event) => {\n                    return {\n                        ...event.event,\n                        txn_id: event.getTxnId(),\n                    };\n                })\n                .filter((event) => {\n                    // Filter out the unencrypted messages if the room is encrypted\n                    const isEventEncrypted = event.type === EventType.RoomMessageEncrypted;\n                    const isRoomEncrypted = this.hasEncryptionStateEvent();\n                    return isEventEncrypted || !isRoomEncrypted;\n                });\n\n            this.client.store.setPendingEvents(this.roomId, pendingEvents);\n        }\n    }\n\n    /**\n     * Used to aggregate the local echo for a relation, and also\n     * for re-applying a relation after it's redaction has been cancelled,\n     * as the local echo for the redaction of the relation would have\n     * un-aggregated the relation. Note that this is different from regular messages,\n     * which are just kept detached for their local echo.\n     *\n     * Also note that live events are aggregated in the live EventTimelineSet.\n     * @param event - the relation event that needs to be aggregated.\n     */\n    private aggregateNonLiveRelation(event: MatrixEvent): void {\n        this.relations.aggregateChildEvent(event);\n    }\n\n    public getEventForTxnId(txnId: string): MatrixEvent | undefined {\n        return this.txnToEvent.get(txnId);\n    }\n\n    /**\n     * Deal with the echo of a message we sent.\n     *\n     * <p>We move the event to the live timeline if it isn't there already, and\n     * update it.\n     *\n     * @param remoteEvent -   The event received from\n     *    /sync\n     * @param localEvent -    The local echo, which\n     *    should be either in the pendingEventList or the timeline.\n     *\n     * @internal\n     *\n     * @remarks\n     * Fires {@link RoomEvent.LocalEchoUpdated}\n     */\n    public handleRemoteEcho(remoteEvent: MatrixEvent, localEvent: MatrixEvent): void {\n        const oldEventId = localEvent.getId()!;\n        const newEventId = remoteEvent.getId()!;\n        const oldStatus = localEvent.status;\n\n        logger.debug(`Got remote echo for event ${oldEventId} -> ${newEventId} old status ${oldStatus}`);\n\n        // no longer pending\n        this.txnToEvent.delete(remoteEvent.getUnsigned().transaction_id!);\n\n        // if it's in the pending list, remove it\n        if (this.pendingEventList) {\n            this.removePendingEvent(oldEventId);\n        }\n\n        // replace the event source (this will preserve the plaintext payload if\n        // any, which is good, because we don't want to try decoding it again).\n        localEvent.handleRemoteEcho(remoteEvent.event);\n\n        const { shouldLiveInRoom, threadId } = this.eventShouldLiveIn(remoteEvent);\n        const thread = threadId ? this.getThread(threadId) : null;\n        thread?.setEventMetadata(localEvent);\n        thread?.timelineSet.handleRemoteEcho(localEvent, oldEventId, newEventId);\n\n        if (shouldLiveInRoom) {\n            for (const timelineSet of this.timelineSets) {\n                // if it's already in the timeline, update the timeline map. If it's not, add it.\n                timelineSet.handleRemoteEcho(localEvent, oldEventId, newEventId);\n            }\n        }\n\n        this.emit(RoomEvent.LocalEchoUpdated, localEvent, this, oldEventId, oldStatus);\n    }\n\n    /**\n     * Update the status / event id on a pending event, to reflect its transmission\n     * progress.\n     *\n     * <p>This is an internal method.\n     *\n     * @param event -      local echo event\n     * @param newStatus -  status to assign\n     * @param newEventId -      new event id to assign. Ignored unless newStatus == EventStatus.SENT.\n     *\n     * @remarks\n     * Fires {@link RoomEvent.LocalEchoUpdated}\n     */\n    public updatePendingEvent(event: MatrixEvent, newStatus: EventStatus, newEventId?: string): void {\n        logger.log(\n            `setting pendingEvent status to ${newStatus} in ${event.getRoomId()} ` +\n                `event ID ${event.getId()} -> ${newEventId}`,\n        );\n\n        // if the message was sent, we expect an event id\n        if (newStatus == EventStatus.SENT && !newEventId) {\n            throw new Error(\"updatePendingEvent called with status=SENT, but no new event id\");\n        }\n\n        // SENT races against /sync, so we have to special-case it.\n        if (newStatus == EventStatus.SENT) {\n            const timeline = this.getTimelineForEvent(newEventId!);\n            if (timeline) {\n                // we've already received the event via the event stream.\n                // nothing more to do here, assuming the transaction ID was correctly matched.\n                // Let's check that.\n                const remoteEvent = this.findEventById(newEventId!);\n                const remoteTxnId = remoteEvent?.getUnsigned().transaction_id;\n                if (!remoteTxnId && remoteEvent) {\n                    // This code path is mostly relevant for the Sliding Sync proxy.\n                    // The remote event did not contain a transaction ID, so we did not handle\n                    // the remote echo yet. Handle it now.\n                    const unsigned = remoteEvent.getUnsigned();\n                    unsigned.transaction_id = event.getTxnId();\n                    remoteEvent.setUnsigned(unsigned);\n                    // the remote event is _already_ in the timeline, so we need to remove it so\n                    // we can convert the local event into the final event.\n                    this.removeEvent(remoteEvent.getId()!);\n                    this.handleRemoteEcho(remoteEvent, event);\n                }\n                return;\n            }\n        }\n\n        const oldStatus = event.status;\n        const oldEventId = event.getId()!;\n\n        if (!oldStatus) {\n            throw new Error(\"updatePendingEventStatus called on an event which is not a local echo.\");\n        }\n\n        const allowed = ALLOWED_TRANSITIONS[oldStatus];\n        if (!allowed?.includes(newStatus)) {\n            throw new Error(`Invalid EventStatus transition ${oldStatus}->${newStatus}`);\n        }\n\n        event.setStatus(newStatus);\n\n        if (newStatus == EventStatus.SENT) {\n            // update the event id\n            event.replaceLocalEventId(newEventId!);\n\n            const { shouldLiveInRoom, threadId } = this.eventShouldLiveIn(event);\n            const thread = threadId ? this.getThread(threadId) : undefined;\n            thread?.setEventMetadata(event);\n            thread?.timelineSet.replaceEventId(oldEventId, newEventId!);\n\n            if (shouldLiveInRoom) {\n                // if the event was already in the timeline (which will be the case if\n                // opts.pendingEventOrdering==chronological), we need to update the\n                // timeline map.\n                for (const timelineSet of this.timelineSets) {\n                    timelineSet.replaceEventId(oldEventId, newEventId!);\n                }\n            }\n        } else if (newStatus == EventStatus.CANCELLED) {\n            // remove it from the pending event list, or the timeline.\n            if (this.pendingEventList) {\n                const removedEvent = this.getPendingEvent(oldEventId);\n                this.removePendingEvent(oldEventId);\n                if (removedEvent?.isRedaction()) {\n                    this.revertRedactionLocalEcho(removedEvent);\n                }\n            }\n            this.removeEvent(oldEventId);\n        }\n        this.savePendingEvents();\n\n        this.emit(RoomEvent.LocalEchoUpdated, event, this, oldEventId, oldStatus);\n    }\n\n    private revertRedactionLocalEcho(redactionEvent: MatrixEvent): void {\n        const redactId = redactionEvent.event.redacts;\n        if (!redactId) {\n            return;\n        }\n        const redactedEvent = this.getUnfilteredTimelineSet().findEventById(redactId);\n        if (redactedEvent) {\n            redactedEvent.unmarkLocallyRedacted();\n            // re-render after undoing redaction\n            this.emit(RoomEvent.RedactionCancelled, redactionEvent, this);\n            // reapply relation now redaction failed\n            if (redactedEvent.isRelation()) {\n                this.aggregateNonLiveRelation(redactedEvent);\n            }\n        }\n    }\n\n    private assertTimelineSetsAreLive(): void {\n        for (let i = 0; i < this.timelineSets.length; i++) {\n            const liveTimeline = this.timelineSets[i].getLiveTimeline();\n            if (liveTimeline.getPaginationToken(EventTimeline.FORWARDS)) {\n                throw new Error(\n                    \"live timeline \" +\n                        i +\n                        \" is no longer live - it has a pagination token \" +\n                        \"(\" +\n                        liveTimeline.getPaginationToken(EventTimeline.FORWARDS) +\n                        \")\",\n                );\n            }\n            if (liveTimeline.getNeighbouringTimeline(EventTimeline.FORWARDS)) {\n                throw new Error(`live timeline ${i} is no longer live - it has a neighbouring timeline`);\n            }\n        }\n    }\n\n    /**\n     * Add some events to this room. This can include state events, message\n     * events and typing notifications. These events are treated as \"live\" so\n     * they will go to the end of the timeline.\n     *\n     * @param events - A list of events to add.\n     * @param addLiveEventOptions - addLiveEvent options\n     * @throws If `duplicateStrategy` is not falsey, 'replace' or 'ignore'.\n     */\n    public async addLiveEvents(events: MatrixEvent[], addLiveEventOptions: IAddLiveEventOptions): Promise<void> {\n        const { duplicateStrategy, fromCache, timelineWasEmpty = false, addToState } = addLiveEventOptions;\n        if (duplicateStrategy && [\"replace\", \"ignore\"].indexOf(duplicateStrategy) === -1) {\n            throw new Error(\"duplicateStrategy MUST be either 'replace' or 'ignore'\");\n        }\n\n        // ALWAYS filter out any events that are past retention\n        events = events.filter((e) => this.retention?.shouldEventBeRetained(e) ?? true);\n\n        // sanity check that the live timeline is still live\n        this.assertTimelineSetsAreLive();\n\n        const threadRoots = this.findThreadRoots(events);\n        const eventsByThread: { [threadId: string]: MatrixEvent[] } = {};\n\n        const options: IAddLiveEventOptions = {\n            duplicateStrategy,\n            fromCache,\n            timelineWasEmpty,\n            addToState,\n        };\n\n        // List of extra events to check for being parents of any relations encountered\n        const neighbouringEvents = [...events];\n\n        for (const event of events) {\n            // TODO: We should have a filter to say \"only add state event types X Y Z to the timeline\".\n            this.processLiveEvent(event);\n\n            if (event.getUnsigned().transaction_id) {\n                const existingEvent = this.txnToEvent.get(event.getUnsigned().transaction_id!);\n                if (existingEvent) {\n                    // remote echo of an event we sent earlier\n                    this.handleRemoteEcho(event, existingEvent);\n                    continue; // we can skip adding the event to the timeline sets, it is already there\n                }\n            }\n\n            let {\n                shouldLiveInRoom,\n                shouldLiveInThread,\n                threadId = \"\",\n            } = this.eventShouldLiveIn(event, neighbouringEvents, threadRoots);\n\n            if (!shouldLiveInThread && !shouldLiveInRoom && event.isRelation()) {\n                try {\n                    const parentEvent = new MatrixEvent(\n                        await this.client.fetchRoomEvent(this.roomId, event.relationEventId!),\n                    );\n                    neighbouringEvents.push(parentEvent);\n                    if (parentEvent.threadRootId) {\n                        threadRoots.add(parentEvent.threadRootId);\n                        const unsigned = event.getUnsigned();\n                        unsigned[UNSIGNED_THREAD_ID_FIELD.name] = parentEvent.threadRootId;\n                        event.setUnsigned(unsigned);\n                    }\n\n                    ({\n                        shouldLiveInRoom,\n                        shouldLiveInThread,\n                        threadId = \"\",\n                    } = this.eventShouldLiveIn(event, neighbouringEvents, threadRoots));\n                } catch (e) {\n                    logger.error(\"Failed to load parent event of unhandled relation\", e);\n                }\n            }\n\n            if (shouldLiveInThread && !eventsByThread[threadId]) {\n                eventsByThread[threadId] = [];\n            }\n            eventsByThread[threadId]?.push(event);\n\n            if (shouldLiveInRoom) {\n                this.addLiveEvent(event, options);\n            } else if (!shouldLiveInThread && event.isRelation()) {\n                this.relations.aggregateChildEvent(event);\n            }\n        }\n\n        Object.entries(eventsByThread).forEach(([threadId, threadEvents]) => {\n            this.addThreadedEvents(threadId, threadEvents, false);\n        });\n    }\n\n    public partitionThreadedEvents(\n        events: MatrixEvent[],\n    ): [timelineEvents: MatrixEvent[], threadedEvents: MatrixEvent[], unknownRelations: MatrixEvent[]] {\n        // Indices to the events array, for readability\n        const ROOM = 0;\n        const THREAD = 1;\n        const UNKNOWN_RELATION = 2;\n        if (this.client.supportsThreads()) {\n            const threadRoots = this.findThreadRoots(events);\n            return events.reduce<[MatrixEvent[], MatrixEvent[], MatrixEvent[]]>(\n                (memo, event: MatrixEvent) => {\n                    const { shouldLiveInRoom, shouldLiveInThread, threadId } = this.eventShouldLiveIn(\n                        event,\n                        events,\n                        threadRoots,\n                    );\n\n                    if (shouldLiveInRoom) {\n                        memo[ROOM].push(event);\n                    }\n\n                    if (shouldLiveInThread) {\n                        event.setThreadId(threadId ?? \"\");\n                        memo[THREAD].push(event);\n                    }\n\n                    if (!shouldLiveInThread && !shouldLiveInRoom) {\n                        memo[UNKNOWN_RELATION].push(event);\n                    }\n\n                    return memo;\n                },\n                [[], [], []],\n            );\n        } else {\n            // When `threadSupport` is disabled treat all events as timelineEvents\n            return [events as MatrixEvent[], [] as MatrixEvent[], [] as MatrixEvent[]];\n        }\n    }\n\n    /**\n     * Given some events, find the IDs of all the thread roots that are referred to by them.\n     */\n    private findThreadRoots(events: MatrixEvent[]): Set<string> {\n        const threadRoots = new Set<string>();\n        for (const event of events) {\n            const threadRootId = event.threadRootId;\n            if (threadRootId != undefined) {\n                threadRoots.add(threadRootId);\n            }\n        }\n        return threadRoots;\n    }\n\n    /**\n     * Add a receipt event to the room.\n     * @param event - The m.receipt event.\n     * @param synthetic - True if this event is implicit.\n     */\n    public addReceipt(event: MatrixEvent, synthetic = false): void {\n        const content = event.getContent<ReceiptContent>();\n\n        this.roomReceipts.add(content, synthetic);\n\n        // TODO: delete the following code when it has been replaced by RoomReceipts\n        Object.keys(content).forEach((eventId: string) => {\n            Object.keys(content[eventId]).forEach((receiptType: ReceiptType | string) => {\n                Object.keys(content[eventId][receiptType]).forEach((userId: string) => {\n                    const receipt = content[eventId][receiptType][userId] as Receipt;\n                    const receiptForMainTimeline = !receipt.thread_id || receipt.thread_id === MAIN_ROOM_TIMELINE;\n                    const receiptDestination: Thread | this | undefined = receiptForMainTimeline\n                        ? this\n                        : this.threads.get(receipt.thread_id ?? \"\");\n\n                    if (receiptDestination) {\n                        receiptDestination.addReceiptToStructure(\n                            eventId,\n                            receiptType as ReceiptType,\n                            userId,\n                            receipt,\n                            synthetic,\n                        );\n\n                        // If the read receipt sent for the logged in user matches\n                        // the last event of the live timeline, then we know for a fact\n                        // that the user has read that message, so we can mark the room\n                        // as read and not wait for the remote echo from synapse.\n                        //\n                        // This needs to be done after the initial sync as we do not want this\n                        // logic to run whilst the room is being initialised\n                        //\n                        // We only do this for non-synthetic receipts, because\n                        // our intention is to do this when the user really did\n                        // just read a message, not when we are e.g. receiving\n                        // an event during the sync. More explanation at:\n                        // https://github.com/matrix-org/matrix-js-sdk/issues/3684\n                        if (!synthetic && this.client.isInitialSyncComplete() && userId === this.client.getUserId()) {\n                            const lastEvent = receiptDestination.timeline[receiptDestination.timeline.length - 1];\n                            if (lastEvent && eventId === lastEvent.getId() && userId === lastEvent.getSender()) {\n                                receiptDestination.setUnread(NotificationCountType.Total, 0);\n                                receiptDestination.setUnread(NotificationCountType.Highlight, 0);\n                            }\n                        }\n                    } else {\n                        // The thread does not exist locally, keep the read receipt\n                        // in a cache locally, and re-apply  the `addReceipt` logic\n                        // when the thread is created\n                        this.cachedThreadReadReceipts.set(receipt.thread_id!, [\n                            ...(this.cachedThreadReadReceipts.get(receipt.thread_id!) ?? []),\n                            { eventId, receiptType, userId, receipt, synthetic },\n                        ]);\n                    }\n\n                    const me = this.client.getUserId();\n                    // Track the time of the current user's oldest threaded receipt in the room.\n                    if (userId === me && !receiptForMainTimeline && receipt.ts < this.oldestThreadedReceiptTs) {\n                        this.oldestThreadedReceiptTs = receipt.ts;\n                    }\n\n                    // Track each user's unthreaded read receipt.\n                    if (!receipt.thread_id && receipt.ts > (this.unthreadedReceipts.get(userId)?.ts ?? 0)) {\n                        this.unthreadedReceipts.set(userId, receipt);\n                    }\n                });\n            });\n        });\n        // End of code to delete when replaced by RoomReceipts\n\n        // send events after we've regenerated the structure & cache, otherwise things that\n        // listened for the event would read stale data.\n        this.emit(RoomEvent.Receipt, event, this);\n    }\n\n    /**\n     * Adds/handles ephemeral events such as typing notifications and read receipts.\n     * @param events - A list of events to process\n     */\n    public addEphemeralEvents(events: MatrixEvent[]): void {\n        for (const event of events) {\n            if (event.getType() === EventType.Typing) {\n                this.currentState.setTypingEvent(event);\n            } else if (event.getType() === EventType.Receipt) {\n                this.addReceipt(event);\n            } // else ignore - life is too short for us to care about these events\n        }\n    }\n\n    /**\n     * Removes events from this room.\n     * @param eventIds - A list of eventIds to remove.\n     */\n    public removeEvents(eventIds: string[]): void {\n        for (const eventId of eventIds) {\n            this.removeEvent(eventId);\n        }\n    }\n\n    /**\n     * Removes a single event from this room.\n     *\n     * @param eventId -  The id of the event to remove\n     *\n     * @returns true if the event was removed from any of the room's timeline sets\n     */\n    public removeEvent(eventId: string): boolean {\n        let removedAny = false;\n        for (const timelineSet of this.timelineSets) {\n            const removed = timelineSet.removeEvent(eventId);\n            if (removed) {\n                if (removed.isRedaction()) {\n                    this.revertRedactionLocalEcho(removed);\n                }\n                removedAny = true;\n            }\n        }\n        return removedAny;\n    }\n\n    /**\n     * Recalculate various aspects of the room, including the room name and\n     * room summary. Call this any time the room's current state is modified.\n     * May fire \"Room.name\" if the room name is updated.\n     *\n     * @remarks\n     * Fires {@link RoomEvent.Name}\n     */\n    public recalculate(): void {\n        // set fake stripped state events if this is an invite room so logic remains\n        // consistent elsewhere.\n        const membershipEvent = this.currentState.getStateEvents(EventType.RoomMember, this.myUserId);\n        if (membershipEvent) {\n            const membership = membershipEvent.getContent().membership;\n            this.updateMyMembership(membership!);\n\n            if (membership === KnownMembership.Invite) {\n                const strippedStateEvents = membershipEvent.getUnsigned().invite_room_state || [];\n                strippedStateEvents.forEach((strippedEvent) => {\n                    const existingEvent = this.currentState.getStateEvents(strippedEvent.type, strippedEvent.state_key);\n                    if (!existingEvent) {\n                        // set the fake stripped event instead\n                        this.currentState.setStateEvents([\n                            new MatrixEvent({\n                                type: strippedEvent.type,\n                                state_key: strippedEvent.state_key,\n                                content: strippedEvent.content,\n                                event_id: \"$fake\" + Date.now(),\n                                room_id: this.roomId,\n                                sender: this.myUserId, // technically a lie\n                            }),\n                        ]);\n                    }\n                });\n            }\n        }\n\n        const oldName = this.name;\n        this.name = this.calculateRoomName(this.myUserId);\n        this.normalizedName = normalize(this.name);\n        this.summary = new RoomSummary(this.roomId, {\n            title: this.name,\n        });\n\n        if (oldName !== this.name) {\n            this.emit(RoomEvent.Name, this);\n        }\n    }\n\n    /**\n     * Update the room-tag event for the room.  The previous one is overwritten.\n     * @param event - the m.tag event\n     */\n    public addTags(event: MatrixEvent): void {\n        // event content looks like:\n        // content: {\n        //    tags: {\n        //       $tagName: { $metadata: $value },\n        //       $tagName: { $metadata: $value },\n        //    }\n        // }\n\n        // XXX: do we need to deep copy here?\n        this.tags = event.getContent().tags || {};\n\n        // XXX: we could do a deep-comparison to see if the tags have really\n        // changed - but do we want to bother?\n        this.emit(RoomEvent.Tags, event, this);\n    }\n\n    /**\n     * Update the account_data events for this room, overwriting events of the same type.\n     * @param events - an array of account_data events to add\n     */\n    public addAccountData(events: MatrixEvent[]): void {\n        for (const event of events) {\n            if (event.getType() === \"m.tag\") {\n                this.addTags(event);\n            }\n            const eventType = event.getType();\n            const lastEvent = this.accountData.get(eventType);\n            this.accountData.set(eventType, event);\n            this.emit(RoomEvent.AccountData, event, this, lastEvent);\n        }\n    }\n\n    /**\n     * Access account_data event of given event type for this room\n     * @param type - the type of account_data event to be accessed\n     * @returns the account_data event in question\n     */\n    public getAccountData(type: EventType | string): MatrixEvent | undefined {\n        return this.accountData.get(type);\n    }\n\n    /**\n     * Get an iterator of currently active sticky events.\n     */\n    // eslint-disable-next-line\n    public _unstable_getStickyEvents(): ReturnType<RoomStickyEventsStore[\"getStickyEvents\"]> {\n        return this.stickyEvents.getStickyEvents();\n    }\n\n    /**\n     * Get a sticky event that match the given `type`, `sender`, and `stickyKey`\n     * @param type The event `type`.\n     * @param sender The sender of the sticky event.\n     * @param stickyKey The sticky key used by the event.\n     * @returns A matching active sticky event, or undefined.\n     */\n    // eslint-disable-next-line\n    public _unstable_getKeyedStickyEvent(\n        sender: string,\n        type: string,\n        stickyKey: string,\n    ): ReturnType<RoomStickyEventsStore[\"getKeyedStickyEvent\"]> {\n        return this.stickyEvents.getKeyedStickyEvent(sender, type, stickyKey);\n    }\n\n    /**\n     * Get active sticky events without a sticky key that match the given `type` and `sender`.\n     * @param type The event `type`.\n     * @param sender The sender of the sticky event.\n     * @returns An array of matching sticky events.\n     */\n    // eslint-disable-next-line\n    public _unstable_getUnkeyedStickyEvent(\n        sender: string,\n        type: string,\n    ): ReturnType<RoomStickyEventsStore[\"getUnkeyedStickyEvent\"]> {\n        return this.stickyEvents.getUnkeyedStickyEvent(sender, type);\n    }\n\n    /**\n     * Add a series of sticky events, emitting `RoomEvent.StickyEvents` if any\n     * changes were made.\n     * @param events A set of new sticky events.\n     * @internal\n     */\n    // eslint-disable-next-line\n    public _unstable_addStickyEvents(events: MatrixEvent[]): ReturnType<RoomStickyEventsStore[\"addStickyEvents\"]> {\n        // ALWAYS filter out any events that are past retention\n        events = events.filter((e) => this.retention?.shouldEventBeRetained(e) ?? true);\n\n        return this.stickyEvents.addStickyEvents(events);\n    }\n\n    /**\n     * Returns whether the syncing user has permission to send a message in the room\n     * @returns true if the user should be permitted to send\n     *                   message events into the room.\n     */\n    public maySendMessage(): boolean {\n        return (\n            this.getMyMembership() === KnownMembership.Join &&\n            (this.hasEncryptionStateEvent()\n                ? this.currentState.maySendEvent(EventType.RoomMessageEncrypted, this.myUserId)\n                : this.currentState.maySendEvent(EventType.RoomMessage, this.myUserId))\n        );\n    }\n\n    /**\n     * Returns whether the given user has permissions to issue an invite for this room.\n     * @param userId - the ID of the Matrix user to check permissions for\n     * @returns true if the user should be permitted to issue invites for this room.\n     */\n    public canInvite(userId: string): boolean {\n        let canInvite = this.getMyMembership() === KnownMembership.Join;\n        const powerLevelsEvent = this.currentState.getStateEvents(EventType.RoomPowerLevels, \"\");\n        const powerLevels = powerLevelsEvent && powerLevelsEvent.getContent();\n        const me = this.getMember(userId);\n        if (powerLevels && me && powerLevels.invite > me.powerLevel) {\n            canInvite = false;\n        }\n        return canInvite;\n    }\n\n    /**\n     * Returns the join rule based on the m.room.join_rule state event, defaulting to `invite`.\n     * @returns the join_rule applied to this room\n     */\n    public getJoinRule(): JoinRule {\n        return this.currentState.getJoinRule();\n    }\n\n    /**\n     * Returns the history visibility based on the m.room.history_visibility state event, defaulting to `shared`.\n     * @returns the history_visibility applied to this room\n     */\n    public getHistoryVisibility(): HistoryVisibility {\n        return this.currentState.getHistoryVisibility();\n    }\n\n    /**\n     * Returns the history visibility based on the m.room.history_visibility state event, defaulting to `shared`.\n     * @returns the history_visibility applied to this room\n     */\n    public getGuestAccess(): GuestAccess {\n        return this.currentState.getGuestAccess();\n    }\n\n    /**\n     * Returns the type of the room from the `m.room.create` event content or undefined if none is set\n     * @returns the type of the room.\n     */\n    public getType(): RoomType | string | undefined {\n        const createEvent = this.currentState.getStateEvents(EventType.RoomCreate, \"\");\n        if (!createEvent) {\n            if (!this.getTypeWarning) {\n                logger.warn(\"[getType] Room \" + this.roomId + \" does not have an m.room.create event\");\n                this.getTypeWarning = true;\n            }\n            return undefined;\n        }\n        return createEvent.getContent()[RoomCreateTypeField];\n    }\n\n    /**\n     * Returns whether the room is a space-room as defined by MSC1772.\n     * @returns true if the room's type is RoomType.Space\n     */\n    public isSpaceRoom(): boolean {\n        return this.getType() === RoomType.Space;\n    }\n\n    /**\n     * Returns whether the room is a call-room as defined by MSC3417.\n     * @returns true if the room's type is RoomType.UnstableCall\n     */\n    public isCallRoom(): boolean {\n        return this.getType() === RoomType.UnstableCall;\n    }\n\n    /**\n     * Returns whether the room is a video room.\n     * @returns true if the room's type is RoomType.ElementVideo\n     */\n    public isElementVideoRoom(): boolean {\n        return this.getType() === RoomType.ElementVideo;\n    }\n\n    /**\n     * Find the predecessor of this room.\n     *\n     * @param msc3946ProcessDynamicPredecessor - if true, look for an\n     * m.room.predecessor state event and use it if found (MSC3946).\n     * @returns null if this room has no predecessor. Otherwise, returns\n     * the roomId, last eventId and viaServers of the predecessor room.\n     *\n     * If msc3946ProcessDynamicPredecessor is true, use m.predecessor events\n     * as well as m.room.create events to find predecessors.\n     *\n     * Note: if an m.predecessor event is used, eventId may be undefined\n     * since last_known_event_id is optional.\n     *\n     * Note: viaServers may be undefined, and will definitely be undefined if\n     * this predecessor comes from a RoomCreate event (rather than a\n     * RoomPredecessor, which has the optional via_servers property).\n     */\n    public findPredecessor(\n        msc3946ProcessDynamicPredecessor = false,\n    ): { roomId: string; eventId?: string; viaServers?: string[] } | null {\n        const currentState = this.getLiveTimeline().getState(EventTimeline.FORWARDS);\n        if (!currentState) {\n            return null;\n        }\n        return currentState.findPredecessor(msc3946ProcessDynamicPredecessor);\n    }\n\n    private roomNameGenerator(state: RoomNameState): string {\n        if (this.client.roomNameGenerator) {\n            const name = this.client.roomNameGenerator(this.roomId, state);\n            if (name !== null) {\n                return name;\n            }\n        }\n\n        switch (state.type) {\n            case RoomNameType.Actual:\n                return state.name;\n            case RoomNameType.Generated:\n                switch (state.subtype) {\n                    case \"Inviting\":\n                        return `Inviting ${memberNamesToRoomName(state.names, state.count)}`;\n                    default:\n                        return memberNamesToRoomName(state.names, state.count);\n                }\n            case RoomNameType.EmptyRoom:\n                if (state.oldName) {\n                    return `Empty room (was ${state.oldName})`;\n                } else {\n                    return \"Empty room\";\n                }\n        }\n    }\n\n    /**\n     * This is an internal method. Calculates the name of the room from the current\n     * room state.\n     * @param userId - The client's user ID. Used to filter room members\n     * correctly.\n     * @param ignoreRoomNameEvent - Return the implicit room name that we'd see if there\n     * was no m.room.name event.\n     * @returns The calculated room name.\n     */\n    private calculateRoomName(userId: string, ignoreRoomNameEvent = false): string {\n        if (!ignoreRoomNameEvent) {\n            const name = this.currentState.getStateEvents(EventType.RoomName, \"\")?.getContent().name;\n            if (name && typeof name === \"string\") {\n                return this.roomNameGenerator({\n                    type: RoomNameType.Actual,\n                    name,\n                });\n            }\n        }\n\n        const alias = this.getCanonicalAlias();\n        if (alias) {\n            return this.roomNameGenerator({\n                type: RoomNameType.Actual,\n                name: alias,\n            });\n        }\n\n        const joinedMemberCount = this.currentState.getJoinedMemberCount();\n        const invitedMemberCount = this.currentState.getInvitedMemberCount();\n        // -1 because these numbers include the syncing user\n        let inviteJoinCount = joinedMemberCount + invitedMemberCount - 1;\n\n        // get service members (e.g. helper bots) for exclusion\n        const excludedUserIds = this.getFunctionalMembers();\n\n        // get members from heroes that are NOT ourselves\n        let otherNames: string[] = [];\n        if (this.heroes) {\n            // if we have heroes, use those as the names\n            this.heroes.forEach((hero) => {\n                // filter service members\n                if (excludedUserIds.includes(hero.userId)) {\n                    inviteJoinCount--;\n                    return;\n                }\n                // If the hero has a display name, use that.\n                // Otherwise, look their user ID up in the membership and use\n                // the name from there, or the user ID as a last resort.\n                if (hero.displayName) {\n                    otherNames.push(hero.displayName);\n                } else {\n                    const member = this.getMember(hero.userId);\n                    otherNames.push(member ? member.name : hero.userId);\n                }\n            });\n        } else {\n            let otherMembers = this.currentState.getMembers().filter((m) => {\n                return (\n                    m.userId !== userId &&\n                    (m.membership === KnownMembership.Invite || m.membership === KnownMembership.Join)\n                );\n            });\n            otherMembers = otherMembers.filter(({ userId }) => {\n                // filter service members\n                if (excludedUserIds.includes(userId)) {\n                    inviteJoinCount--;\n                    return false;\n                }\n                return true;\n            });\n            // make sure members have stable order\n            const collator = new Intl.Collator();\n            otherMembers.sort((a, b) => collator.compare(a.userId, b.userId));\n            // only 5 first members, immitate summaryHeroes\n            otherMembers = otherMembers.slice(0, 5);\n            otherNames = otherMembers.map((m) => m.name);\n        }\n\n        if (inviteJoinCount) {\n            return this.roomNameGenerator({\n                type: RoomNameType.Generated,\n                names: otherNames,\n                count: inviteJoinCount,\n            });\n        }\n\n        const myMembership = this.getMyMembership();\n        // if I have created a room and invited people through\n        // 3rd party invites\n        if (myMembership == KnownMembership.Join) {\n            const thirdPartyInvites = this.currentState.getStateEvents(EventType.RoomThirdPartyInvite);\n\n            if (thirdPartyInvites?.length) {\n                const thirdPartyNames = thirdPartyInvites.map((i) => {\n                    return i.getContent().display_name;\n                });\n\n                return this.roomNameGenerator({\n                    type: RoomNameType.Generated,\n                    subtype: \"Inviting\",\n                    names: thirdPartyNames,\n                    count: thirdPartyNames.length + 1,\n                });\n            }\n        }\n\n        // let's try to figure out who was here before\n        let leftNames = otherNames;\n        // if we didn't have heroes, try finding them in the room state\n        if (!leftNames.length) {\n            leftNames = this.currentState\n                .getMembers()\n                .filter((m) => {\n                    return (\n                        m.userId !== userId &&\n                        m.membership !== KnownMembership.Invite &&\n                        m.membership !== KnownMembership.Join\n                    );\n                })\n                .map((m) => m.name);\n        }\n\n        let oldName: string | undefined;\n        if (leftNames.length) {\n            oldName = this.roomNameGenerator({\n                type: RoomNameType.Generated,\n                names: leftNames,\n                count: leftNames.length + 1,\n            });\n        }\n\n        return this.roomNameGenerator({\n            type: RoomNameType.EmptyRoom,\n            oldName,\n        });\n    }\n\n    /**\n     * When we receive a new visibility change event:\n     *\n     * - store this visibility change alongside the timeline, in case we\n     *   later need to apply it to an event that we haven't received yet;\n     * - if we have already received the event whose visibility has changed,\n     *   patch it to reflect the visibility change and inform listeners.\n     */\n    private applyNewVisibilityEvent(event: MatrixEvent): void {\n        const visibilityChange = event.asVisibilityChange();\n        if (!visibilityChange) {\n            // The event is ill-formed.\n            return;\n        }\n\n        // Ignore visibility change events that are not emitted by moderators.\n        const userId = event.getSender();\n        if (!userId) {\n            return;\n        }\n        const isPowerSufficient =\n            (EVENT_VISIBILITY_CHANGE_TYPE.name &&\n                this.currentState.maySendStateEvent(EVENT_VISIBILITY_CHANGE_TYPE.name, userId)) ||\n            (EVENT_VISIBILITY_CHANGE_TYPE.altName &&\n                this.currentState.maySendStateEvent(EVENT_VISIBILITY_CHANGE_TYPE.altName, userId));\n        if (!isPowerSufficient) {\n            // Powerlevel is insufficient.\n            return;\n        }\n\n        // Record this change in visibility.\n        // If the event is not in our timeline and we only receive it later,\n        // we may need to apply the visibility change at a later date.\n\n        const visibilityEventsOnOriginalEvent = this.visibilityEvents.get(visibilityChange.eventId);\n        if (visibilityEventsOnOriginalEvent) {\n            // It would be tempting to simply erase the latest visibility change\n            // but we need to record all of the changes in case the latest change\n            // is ever redacted.\n            //\n            // In practice, linear scans through `visibilityEvents` should be fast.\n            // However, to protect against a potential DoS attack, we limit the\n            // number of iterations in this loop.\n            let index = visibilityEventsOnOriginalEvent.length - 1;\n            const min = Math.max(\n                0,\n                visibilityEventsOnOriginalEvent.length - MAX_NUMBER_OF_VISIBILITY_EVENTS_TO_SCAN_THROUGH,\n            );\n            for (; index >= min; --index) {\n                const target = visibilityEventsOnOriginalEvent[index];\n                if (target.getTs() < event.getTs()) {\n                    break;\n                }\n            }\n            if (index === -1) {\n                visibilityEventsOnOriginalEvent.unshift(event);\n            } else {\n                visibilityEventsOnOriginalEvent.splice(index + 1, 0, event);\n            }\n        } else {\n            this.visibilityEvents.set(visibilityChange.eventId, [event]);\n        }\n\n        // Finally, let's check if the event is already in our timeline.\n        // If so, we need to patch it and inform listeners.\n\n        const originalEvent = this.findEventById(visibilityChange.eventId);\n        if (!originalEvent) {\n            return;\n        }\n        originalEvent.applyVisibilityEvent(visibilityChange);\n    }\n\n    private redactVisibilityChangeEvent(event: MatrixEvent): void {\n        // Sanity checks.\n        if (!event.isVisibilityEvent) {\n            throw new Error(\"expected a visibility change event\");\n        }\n        const relation = event.getRelation();\n        const originalEventId = relation?.event_id;\n        const visibilityEventsOnOriginalEvent = this.visibilityEvents.get(originalEventId!);\n        if (!visibilityEventsOnOriginalEvent) {\n            // No visibility changes on the original event.\n            // In particular, this change event was not recorded,\n            // most likely because it was ill-formed.\n            return;\n        }\n        const index = visibilityEventsOnOriginalEvent.findIndex((change) => change.getId() === event.getId());\n        if (index === -1) {\n            // This change event was not recorded, most likely because\n            // it was ill-formed.\n            return;\n        }\n        // Remove visibility change.\n        visibilityEventsOnOriginalEvent.splice(index, 1);\n\n        // If we removed the latest visibility change event, propagate changes.\n        if (index === visibilityEventsOnOriginalEvent.length) {\n            const originalEvent = this.findEventById(originalEventId!);\n            if (!originalEvent) {\n                return;\n            }\n            if (index === 0) {\n                // We have just removed the only visibility change event.\n                this.visibilityEvents.delete(originalEventId!);\n                originalEvent.applyVisibilityEvent();\n            } else {\n                const newEvent = visibilityEventsOnOriginalEvent[visibilityEventsOnOriginalEvent.length - 1];\n                const newVisibility = newEvent.asVisibilityChange();\n                if (!newVisibility) {\n                    // Event is ill-formed.\n                    // This breaks our invariant.\n                    throw new Error(\"at this stage, visibility changes should be well-formed\");\n                }\n                originalEvent.applyVisibilityEvent(newVisibility);\n            }\n        }\n    }\n\n    /**\n     * When we receive an event whose visibility has been altered by\n     * a (more recent) visibility change event, patch the event in\n     * place so that clients now not to display it.\n     *\n     * @param event - Any matrix event. If this event has at least one a\n     * pending visibility change event, apply the latest visibility\n     * change event.\n     */\n    private applyPendingVisibilityEvents(event: MatrixEvent): void {\n        const visibilityEvents = this.visibilityEvents.get(event.getId()!);\n        if (!visibilityEvents || visibilityEvents.length == 0) {\n            // No pending visibility change in store.\n            return;\n        }\n        const visibilityEvent = visibilityEvents[visibilityEvents.length - 1];\n        const visibilityChange = visibilityEvent.asVisibilityChange();\n        if (!visibilityChange) {\n            return;\n        }\n        if (visibilityChange.visible) {\n            // Events are visible by default, no need to apply a visibility change.\n            // Note that we need to keep the visibility changes in `visibilityEvents`,\n            // in case we later fetch an older visibility change event that is superseded\n            // by `visibilityChange`.\n        }\n        if (visibilityEvent.getTs() < event.getTs()) {\n            // Something is wrong, the visibility change cannot happen before the\n            // event. Presumably an ill-formed event.\n            return;\n        }\n        event.applyVisibilityEvent(visibilityChange);\n    }\n\n    /**\n     * Find when a client has gained thread capabilities by inspecting the oldest\n     * threaded receipt\n     * @returns the timestamp of the oldest threaded receipt\n     */\n    public getOldestThreadedReceiptTs(): number {\n        return this.oldestThreadedReceiptTs;\n    }\n\n    /**\n     * Determines if the given user has read a particular event ID with the known\n     * history of the room. This is not a definitive check as it relies only on\n     * what is available to the room at the time of execution.\n     *\n     * @param userId - The user ID to check the read state of.\n     * @param eventId - The event ID to check if the user read.\n     * @returns true if the user has read the event, false otherwise.\n     */\n    public hasUserReadEvent(userId: string, eventId: string): boolean {\n        return this.roomReceipts.hasUserReadEvent(userId, eventId);\n    }\n\n    /**\n     * Returns the most recent unthreaded receipt for a given user\n     * @param userId - the MxID of the User\n     * @returns an unthreaded Receipt. Can be undefined if receipts have been disabled\n     * or a user chooses to use private read receipts (or we have simply not received\n     * a receipt from this user yet).\n     */\n    public getLastUnthreadedReceiptFor(userId: string): Receipt | undefined {\n        return this.unthreadedReceipts.get(userId);\n    }\n\n    /**\n     * This issue should also be addressed on synapse's side and is tracked as part\n     * of https://github.com/matrix-org/synapse/issues/14837\n     *\n     *\n     * We consider a room  fully read if the current user has sent\n     * the last event in the live timeline of that context and if the read receipt\n     * we have on record matches.\n     * This also detects all unread threads and applies the same logic to those\n     * contexts\n     */\n    public fixupNotifications(userId: string): void {\n        super.fixupNotifications(userId);\n\n        const unreadThreads = this.getThreads().filter(\n            (thread) => this.getThreadUnreadNotificationCount(thread.id, NotificationCountType.Total) > 0,\n        );\n\n        for (const thread of unreadThreads) {\n            thread.fixupNotifications(userId);\n        }\n    }\n\n    /**\n     * Determine the order of two events in this room.\n     *\n     * In principle this should use the same order as the server, but in practice\n     * this is difficult for events that were not received over the Sync API. See\n     * MSC4033 for details.\n     *\n     * This implementation leans on the order of events within their timelines, and\n     * falls back to comparing event timestamps when they are in different\n     * timelines.\n     *\n     * See https://github.com/matrix-org/matrix-js-sdk/issues/3325 for where we are\n     * tracking the work to fix this.\n     *\n     * @param leftEventId - the id of the first event\n     * @param rightEventId - the id of the second event\n\n     * @returns -1 if left \\< right, 1 if left \\> right, 0 if left == right, null if\n     *          we can't tell (because we can't find the events).\n     */\n    public compareEventOrdering(leftEventId: string, rightEventId: string): number | null {\n        return compareEventOrdering(this, leftEventId, rightEventId);\n    }\n\n    /**\n     * Return true if this room has an `m.room.encryption` state event.\n     *\n     * If this returns `true`, events sent to this room should be encrypted (and `MatrixClient.sendEvent` and friends\n     * will encrypt outgoing events).\n     */\n    public hasEncryptionStateEvent(): boolean {\n        return Boolean(\n            this.getLiveTimeline().getState(EventTimeline.FORWARDS)?.getStateEvents(EventType.RoomEncryption, \"\"),\n        );\n    }\n}\n\n// a map from current event status to a list of allowed next statuses\nconst ALLOWED_TRANSITIONS: Record<EventStatus, EventStatus[]> = {\n    [EventStatus.ENCRYPTING]: [EventStatus.SENDING, EventStatus.NOT_SENT, EventStatus.CANCELLED],\n    [EventStatus.SENDING]: [EventStatus.ENCRYPTING, EventStatus.QUEUED, EventStatus.NOT_SENT, EventStatus.SENT],\n    [EventStatus.QUEUED]: [EventStatus.SENDING, EventStatus.NOT_SENT, EventStatus.CANCELLED],\n    [EventStatus.SENT]: [],\n    [EventStatus.NOT_SENT]: [EventStatus.SENDING, EventStatus.QUEUED, EventStatus.CANCELLED],\n    [EventStatus.CANCELLED]: [],\n};\n\nexport enum RoomNameType {\n    EmptyRoom,\n    Generated,\n    Actual,\n}\n\nexport interface EmptyRoomNameState {\n    type: RoomNameType.EmptyRoom;\n    oldName?: string;\n}\n\nexport interface GeneratedRoomNameState {\n    type: RoomNameType.Generated;\n    subtype?: \"Inviting\";\n    names: string[];\n    count: number;\n}\n\nexport interface ActualRoomNameState {\n    type: RoomNameType.Actual;\n    name: string;\n}\n\nexport type RoomNameState = EmptyRoomNameState | GeneratedRoomNameState | ActualRoomNameState;\n\n// Can be overriden by IMatrixClientCreateOpts::memberNamesToRoomNameFn\nfunction memberNamesToRoomName(names: string[], count: number): string {\n    const countWithoutMe = count - 1;\n    if (!names.length) {\n        return \"Empty room\";\n    } else if (names.length === 1 && countWithoutMe <= 1) {\n        return names[0];\n    } else if (names.length === 2 && countWithoutMe <= 2) {\n        return `${names[0]} and ${names[1]}`;\n    } else {\n        const plural = countWithoutMe > 1;\n        if (plural) {\n            return `${names[0]} and ${countWithoutMe} others`;\n        } else {\n            return `${names[0]} and 1 other`;\n        }\n    }\n}\n","/*\nCopyright 2015 - 2021 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { EventContext } from \"./event-context.ts\";\nimport { type EventMapper } from \"../event-mapper.ts\";\nimport { type IResultContext, type ISearchResult } from \"../@types/search.ts\";\n\nexport class SearchResult {\n    /**\n     * Create a SearchResponse from the response to /search\n     */\n\n    public static fromJson(jsonObj: ISearchResult, eventMapper: EventMapper): SearchResult {\n        const jsonContext = jsonObj.context || ({} as IResultContext);\n        let eventsBefore = (jsonContext.events_before || []).map(eventMapper);\n        let eventsAfter = (jsonContext.events_after || []).map(eventMapper);\n\n        const context = new EventContext(eventMapper(jsonObj.result));\n\n        // Filter out any contextual events which do not correspond to the same timeline (thread or room)\n        const threadRootId = context.ourEvent.threadRootId;\n        eventsBefore = eventsBefore.filter((e) => e.threadRootId === threadRootId);\n        eventsAfter = eventsAfter.filter((e) => e.threadRootId === threadRootId);\n\n        context.setPaginateToken(jsonContext.start, true);\n        context.addEvents(eventsBefore, true);\n        context.addEvents(eventsAfter, false);\n        context.setPaginateToken(jsonContext.end, false);\n\n        return new SearchResult(jsonObj.rank, context);\n    }\n\n    /**\n     * Construct a new SearchResult\n     *\n     * @param rank -   where this SearchResult ranks in the results\n     * @param context -  the matching event and its\n     *    context\n     */\n    public constructor(\n        public readonly rank: number,\n        public readonly context: EventContext,\n    ) {}\n}\n","/*\nCopyright 2021 - 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type MatrixClient, PendingEventOrdering } from \"../client.ts\";\nimport { TypedReEmitter } from \"../ReEmitter.ts\";\nimport { RelationType } from \"../@types/event.ts\";\nimport { type IThreadBundledRelationship, MatrixEvent, MatrixEventEvent } from \"./event.ts\";\nimport { Direction, EventTimeline } from \"./event-timeline.ts\";\nimport { EventTimelineSet, type EventTimelineSetHandlerMap } from \"./event-timeline-set.ts\";\nimport { type NotificationCountType, type Room, RoomEvent } from \"./room.ts\";\nimport { type RoomState } from \"./room-state.ts\";\nimport { ServerControlledNamespacedValue } from \"../NamespacedValue.ts\";\nimport { logger } from \"../logger.ts\";\nimport { ReadReceipt } from \"./read-receipt.ts\";\nimport { type CachedReceiptStructure, type Receipt, ReceiptType } from \"../@types/read_receipts.ts\";\nimport { Feature, ServerSupport } from \"../feature.ts\";\n\nexport enum ThreadEvent {\n    New = \"Thread.new\",\n    Update = \"Thread.update\",\n    NewReply = \"Thread.newReply\",\n    ViewThread = \"Thread.viewThread\",\n    Delete = \"Thread.delete\",\n}\n\nexport type ThreadEmittedEvents = Exclude<ThreadEvent, ThreadEvent.New> | RoomEvent.Timeline | RoomEvent.TimelineReset;\n\nexport type ThreadEventHandlerMap = {\n    [ThreadEvent.Update]: (thread: Thread) => void;\n    [ThreadEvent.NewReply]: (thread: Thread, event: MatrixEvent) => void;\n    [ThreadEvent.ViewThread]: () => void;\n    [ThreadEvent.Delete]: (thread: Thread) => void;\n} & EventTimelineSetHandlerMap;\n\ninterface IThreadOpts {\n    room: Room;\n    client: MatrixClient;\n    pendingEventOrdering?: PendingEventOrdering;\n    receipts?: CachedReceiptStructure[];\n}\n\nexport enum FeatureSupport {\n    None = 0,\n    Experimental = 1,\n    Stable = 2,\n}\n\nexport function determineFeatureSupport(stable: boolean, unstable: boolean): FeatureSupport {\n    if (stable) {\n        return FeatureSupport.Stable;\n    } else if (unstable) {\n        return FeatureSupport.Experimental;\n    } else {\n        return FeatureSupport.None;\n    }\n}\n\nexport class Thread extends ReadReceipt<ThreadEmittedEvents, ThreadEventHandlerMap> {\n    public static hasServerSideSupport = FeatureSupport.None;\n    public static hasServerSideListSupport = FeatureSupport.None;\n    public static hasServerSideFwdPaginationSupport = FeatureSupport.None;\n\n    /**\n     * A reference to all the events ID at the bottom of the threads\n     */\n    public readonly timelineSet: EventTimelineSet;\n\n    private _currentUserParticipated = false;\n\n    private reEmitter: TypedReEmitter<ThreadEmittedEvents, ThreadEventHandlerMap>;\n\n    /**\n     * The last event in this thread, if we don't yet have this in the timeline.\n     *\n     * When we run {@link processRootEvent} (which I think happens during the\n     * setting-up of the thread), we set this to the event pointed to by the\n     * server in `latest_event` [1] that came through with the thread root.\n     *\n     * [1]: https://spec.matrix.org/v1.8/client-server-api/#server-side-aggregation-of-mthread-relationships\n     *\n     * Later, when we have populated the timeline, this is set to undefined, so\n     * that methods like {@link replyToEvent} fall through to use lastReply,\n     * which looks in the timeline for the latest event that is a \"thread reply\"\n     * i.e. directly refers to the thread root with an m.thread relation.\n     *\n     * So it looks like this is only really relevant when initialEventsFetched\n     * is false, because as soon as the initial events have been fetched, we\n     * should have a proper chunk of timeline from the pagination fetch.\n     *\n     * If all replies in this thread are redacted, this is set to the root\n     * event. I'm not clear what the meaning of this is, since usually after the\n     * initial events have been fetched, lastEvent should be undefined.\n     * In fact, the whole usage inside onRedaction looks suspect - it may be\n     * that we were thinking lastEvent always refers to the actual last event,\n     * but it only does so before initialEventsFetched becomes true.\n     *\n     * The usage of lastEvent inside {@link onEcho} looks suspicious, since I'd\n     * think we probably mean {@link replyToEvent} there - we are trying not to\n     * echo a duplicate event, and we probably want that behaviour even after\n     * initialEventsFetched has become true.\n     *\n     * -- andyb\n     */\n    private lastEvent: MatrixEvent | undefined;\n\n    private replyCount = 0;\n    private lastPendingEvent: MatrixEvent | undefined;\n    private pendingReplyCount = 0;\n\n    public readonly room: Room;\n    public readonly client: MatrixClient;\n    private readonly pendingEventOrdering: PendingEventOrdering;\n    private processRootEventPromise?: Promise<void>;\n\n    /**\n     * Whether or not we need to fetch the initial set of events for the thread. We can\n     * only do this if the server has support for it, so if it doesn't we just pretend\n     * that we've already fetched them.\n     */\n    public initialEventsFetched = !Thread.hasServerSideSupport;\n    private initalEventFetchProm: Promise<boolean> | undefined;\n\n    /**\n     * An array of events to add to the timeline once the thread has been initialised\n     * with server suppport.\n     */\n    public replayEvents: MatrixEvent[] | null = [];\n\n    public constructor(\n        public readonly id: string,\n        public rootEvent: MatrixEvent | undefined,\n        opts: IThreadOpts,\n    ) {\n        super();\n\n        // each Event in the thread adds a reemitter, so we could hit the listener limit.\n        this.setMaxListeners(1000);\n\n        if (!opts?.room) {\n            // Logging/debugging for https://github.com/vector-im/element-web/issues/22141\n            // Hope is that we end up with a more obvious stack trace.\n            throw new Error(\"element-web#22141: A thread requires a room in order to function\");\n        }\n\n        this.room = opts.room;\n        this.client = opts.client;\n        this.pendingEventOrdering = opts.pendingEventOrdering ?? PendingEventOrdering.Chronological;\n        this.timelineSet = new EventTimelineSet(\n            this.room,\n            {\n                timelineSupport: true,\n                pendingEvents: true,\n            },\n            this.client,\n            this,\n        );\n        this.reEmitter = new TypedReEmitter(this);\n\n        this.reEmitter.reEmit(this.timelineSet, [RoomEvent.Timeline, RoomEvent.TimelineReset]);\n\n        this.room.on(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);\n        this.room.on(RoomEvent.Redaction, this.onRedaction);\n        this.room.on(RoomEvent.LocalEchoUpdated, this.onLocalEcho);\n        this.room.on(RoomEvent.TimelineReset, this.onTimelineReset);\n        this.timelineSet.on(RoomEvent.Timeline, this.onTimelineEvent);\n\n        this.processReceipts(opts.receipts);\n\n        // even if this thread is thought to be originating from this client, we initialise it as we may be in a\n        // gappy sync and a thread around this event may already exist.\n        this.updateThreadMetadata();\n        this.setEventMetadata(this.rootEvent);\n    }\n\n    private onTimelineReset = async (): Promise<void> => {\n        // We hit a gappy sync, ask the server for an update\n        await this.processRootEventPromise;\n        this.processRootEventPromise = undefined;\n    };\n\n    private async fetchRootEvent(): Promise<void> {\n        // Always fetch the root event, even if we already have it, so we can get the latest\n        // state (via unsigned).\n        try {\n            const eventData = await this.client.fetchRoomEvent(this.roomId, this.id);\n            const mapper = this.client.getEventMapper();\n            this.rootEvent = mapper(eventData); // will merge with existing event object if such is known\n        } catch (e) {\n            logger.error(\"Failed to fetch thread root to construct thread with\", e);\n        }\n        await this.processEvent(this.rootEvent);\n    }\n\n    public static setServerSideSupport(status: FeatureSupport): void {\n        Thread.hasServerSideSupport = status;\n        // XXX: This global latching behaviour is really unexpected and means that you can't undo when moving to a server without support\n        if (status !== FeatureSupport.Stable) {\n            FILTER_RELATED_BY_SENDERS.setPreferUnstable(true);\n            FILTER_RELATED_BY_REL_TYPES.setPreferUnstable(true);\n            THREAD_RELATION_TYPE.setPreferUnstable(true);\n        }\n    }\n\n    public static setServerSideListSupport(status: FeatureSupport): void {\n        Thread.hasServerSideListSupport = status;\n    }\n\n    public static setServerSideFwdPaginationSupport(status: FeatureSupport): void {\n        Thread.hasServerSideFwdPaginationSupport = status;\n    }\n\n    private onBeforeRedaction = (event: MatrixEvent, redaction: MatrixEvent): void => {\n        if (\n            event?.isRelation(THREAD_RELATION_TYPE.name) &&\n            this.room.eventShouldLiveIn(event).threadId === this.id &&\n            event.getId() !== this.id && // the root event isn't counted in the length so ignore this redaction\n            !redaction.status // only respect it when it succeeds\n        ) {\n            this.replyCount--;\n            this.updatePendingReplyCount();\n            this.emit(ThreadEvent.Update, this);\n        }\n    };\n\n    private onRedaction = async (event: MatrixEvent, room: Room, threadRootId?: string): Promise<void> => {\n        if (threadRootId !== this.id) return; // ignore redactions for other timelines\n        if (this.replyCount <= 0) {\n            for (const threadEvent of this.timeline) {\n                this.clearEventMetadata(threadEvent);\n            }\n            this.lastEvent = this.rootEvent;\n            this._currentUserParticipated = false;\n            this.emit(ThreadEvent.Delete, this);\n        } else {\n            if (this.lastEvent?.getId() === event.getAssociatedId()) {\n                // XXX: If our last event got redacted we query the server for the last event once again\n                await this.processRootEventPromise;\n                this.processRootEventPromise = undefined;\n            }\n            await this.updateThreadMetadata();\n        }\n    };\n\n    private onTimelineEvent = (\n        event: MatrixEvent,\n        room: Room | undefined,\n        toStartOfTimeline: boolean | undefined,\n    ): void => {\n        // Add a synthesized receipt when paginating forward in the timeline\n        if (!toStartOfTimeline) {\n            const sender = event.getSender();\n            if (sender && room && this.shouldSendLocalEchoReceipt(sender, event)) {\n                room.addLocalEchoReceipt(sender, event, ReceiptType.Read);\n            }\n            if (event.getId() !== this.id && event.isRelation(THREAD_RELATION_TYPE.name)) {\n                this.replyCount++;\n            }\n        }\n        this.onEcho(event, toStartOfTimeline ?? false);\n    };\n\n    private shouldSendLocalEchoReceipt(sender: string, event: MatrixEvent): boolean {\n        const recursionSupport = this.client.canSupport.get(Feature.RelationsRecursion) ?? ServerSupport.Unsupported;\n\n        if (recursionSupport === ServerSupport.Unsupported) {\n            // Normally we add a local receipt, but if we don't have\n            // recursion support, then events may arrive out of order, so we\n            // only create a receipt if it's after our existing receipt.\n            const oldReceiptEventId = this.getReadReceiptForUserId(sender)?.eventId;\n            if (oldReceiptEventId) {\n                const receiptEvent = this.findEventById(oldReceiptEventId);\n                if (receiptEvent && receiptEvent.getTs() > event.getTs()) {\n                    return false;\n                }\n            }\n        }\n\n        return true;\n    }\n\n    private onLocalEcho = (event: MatrixEvent): void => {\n        this.onEcho(event, false);\n    };\n\n    private onEcho = async (event: MatrixEvent, toStartOfTimeline: boolean): Promise<void> => {\n        if (event.threadRootId !== this.id) return; // ignore echoes for other timelines\n        if (this.lastEvent === event) return; // ignore duplicate events\n        await this.updateThreadMetadata();\n        if (!event.isRelation(THREAD_RELATION_TYPE.name)) return; // don't send a new reply event for reactions or edits\n        if (toStartOfTimeline) return; // ignore messages added to the start of the timeline\n        // Clear the lastEvent and instead start tracking locally using lastReply\n        this.lastEvent = undefined;\n        this.emit(ThreadEvent.NewReply, this, event);\n    };\n\n    public get roomState(): RoomState {\n        return this.room.getLiveTimeline().getState(EventTimeline.FORWARDS)!;\n    }\n\n    private addEventToTimeline(event: MatrixEvent, toStartOfTimeline: boolean): void {\n        if (!this.findEventById(event.getId()!)) {\n            this.timelineSet.addEventToTimeline(event, this.liveTimeline, {\n                toStartOfTimeline,\n                fromCache: false,\n                roomState: this.roomState,\n                addToState: false,\n            });\n        }\n    }\n\n    /**\n     * TEMPORARY. Only call this when MSC3981 is not available, and we have some\n     * late-arriving events to insert, because we recursively found them as part\n     * of populating a thread. When we have MSC3981 we won't need it, because\n     * they will all be supplied by the homeserver in one request, and they will\n     * already be in the right order in that response.\n     * This is a copy of addEventToTimeline above, modified to call\n     * insertEventIntoTimeline so this event is inserted into our best guess of\n     * the right place based on timestamp. (We should be using Sync Order but we\n     * don't have it.)\n     *\n     * @internal\n     */\n    public insertEventIntoTimeline(event: MatrixEvent): void {\n        const eventId = event.getId();\n        if (!eventId) {\n            return;\n        }\n        // If the event is already in this thread, bail out\n        if (this.findEventById(eventId)) {\n            return;\n        }\n        this.timelineSet.insertEventIntoTimeline(event, this.liveTimeline, this.roomState, false);\n    }\n\n    public addEvents(events: MatrixEvent[], toStartOfTimeline: boolean): void {\n        events.forEach((ev) => this.addEvent(ev, toStartOfTimeline, false));\n        this.updateThreadMetadata();\n    }\n\n    /**\n     * Add an event to the thread and updates\n     * the tail/root references if needed\n     * Will fire \"Thread.update\"\n     * @param event - The event to add\n     * @param toStartOfTimeline - whether the event is being added\n     * to the start (and not the end) of the timeline.\n     * @param emit - whether to emit the Update event if the thread was updated or not.\n     */\n    public addEvent(event: MatrixEvent, toStartOfTimeline: boolean, emit = true): void {\n        // Modify this event to point at our room's state, and mark its thread\n        // as this.\n        this.setEventMetadata(event);\n\n        // Decide whether this event is going to be added at the end of the timeline.\n        const lastReply = this.lastReply();\n        const isNewestReply = !lastReply || event.localTimestamp >= lastReply!.localTimestamp;\n\n        if (!Thread.hasServerSideSupport) {\n            // When there's no server-side support, just add it to the end of the timeline.\n            this.addEventToTimeline(event, toStartOfTimeline);\n            this.client.decryptEventIfNeeded(event);\n        } else if (event.isRelation(RelationType.Annotation) || event.isRelation(RelationType.Replace)) {\n            this.addRelatedThreadEvent(event, toStartOfTimeline);\n            return;\n        } else if (!toStartOfTimeline && isNewestReply) {\n            // When we've asked for the event to be added to the end,\n            // and this event belongs at the end, add it.\n            this.addEventToTimeline(event, false);\n            this.fetchEditsWhereNeeded(event);\n        } else {\n            // Otherwise, we should add it, but we suspect it is out of order.\n            // This may be because we've just created the thread object and are\n            // still fetching events, in which case add it where we think is sensible\n            // and it will be removed and replaced with the events from the pagination\n            // request once that completes.\n            if (toStartOfTimeline) {\n                // If we're adding at the start of the timeline, it doesn't\n                // matter that it's out of order.\n                this.addEventToTimeline(event, toStartOfTimeline);\n            } else {\n                // We think this event might be out of order, because isNewestReply\n                // is false (otherwise we would have gone into the earlier if\n                // clause), so try to insert it in the right place based on\n                // timestamp.\n                this.insertEventIntoTimeline(event);\n            }\n        }\n\n        if (\n            event.getId() !== this.id &&\n            event.isRelation(THREAD_RELATION_TYPE.name) &&\n            !toStartOfTimeline &&\n            isNewestReply\n        ) {\n            // Clear the last event as we have the latest end of the timeline\n            this.lastEvent = undefined;\n        }\n\n        if (emit) {\n            this.emit(ThreadEvent.NewReply, this, event);\n            this.updateThreadMetadata();\n        }\n    }\n\n    private addRelatedThreadEvent(event: MatrixEvent, toStartOfTimeline: boolean): void {\n        // If this event is not a direct member of the thread, but is a\n        // reference to something that is, then we have two cases:\n\n        if (!this.initialEventsFetched) {\n            // Case 1: we haven't yet fetched events from the server. In\n            // this case, when we do, the events we get back might only be\n            // the first-order ones, so this event (which is second-order -\n            // a reference to something directly in the thread) needs to be\n            // kept so we can replay it when the first-order ones turn up.\n\n            /**\n             * A thread can be fully discovered via a single sync response\n             * And when that's the case we still ask the server to do an initialisation\n             * as it's the safest to ensure we have everything.\n             * However when we are in that scenario we might loose annotation or edits\n             *\n             * This fix keeps a reference to those events and replay them once the thread\n             * has been initialised properly.\n             */\n            this.replayEvents?.push(event);\n\n            // For annotations (reactions), aggregate immediately (pre-init) to keep\n            // reaction counts/summary visible while the thread is still initialising.\n            // Only aggregate as child: parent aggregation is unnecessary here.\n            if (event.isRelation(RelationType.Annotation)) {\n                this.timelineSet.relations?.aggregateChildEvent(event, this.timelineSet);\n            }\n        } else {\n            // Case 2: this is happening later, and we have a timeline. In\n            // this case, these events might be out-of order.\n            //\n            // Specifically, if the server doesn't support recursion, so we\n            // only get these events through sync, they might be coming\n            // later than the first-order ones, so we insert them based on\n            // timestamp (despite the problems with this documented in\n            // #3325).\n            //\n            // If the server does support recursion, we should have got all\n            // the interspersed events from the server when we fetched the\n            // initial events, so if they are coming via sync they should be\n            // the latest ones, so we can add them as normal.\n            //\n            // (Note that both insertEventIntoTimeline and addEventToTimeline\n            // do nothing if we have seen this event before.)\n\n            const recursionSupport =\n                this.client.canSupport.get(Feature.RelationsRecursion) ?? ServerSupport.Unsupported;\n\n            if (recursionSupport === ServerSupport.Unsupported) {\n                this.insertEventIntoTimeline(event);\n            } else {\n                this.addEventToTimeline(event, toStartOfTimeline);\n            }\n            // Aggregation is handled by EventTimelineSet when inserting/adding.\n        }\n    }\n\n    public async processEvent(event: MatrixEvent | null | undefined): Promise<void> {\n        if (event) {\n            this.setEventMetadata(event);\n            await this.fetchEditsWhereNeeded(event);\n        }\n    }\n\n    /**\n     * Processes the receipts that were caught during initial sync\n     * When clients become aware of a thread, they try to retrieve those read receipts\n     * and apply them to the current thread\n     * @param receipts - A collection of the receipts cached from initial sync\n     */\n    private processReceipts(receipts: CachedReceiptStructure[] = []): void {\n        for (const { eventId, receiptType, userId, receipt, synthetic } of receipts) {\n            this.addReceiptToStructure(eventId, receiptType as ReceiptType, userId, receipt, synthetic);\n        }\n    }\n\n    private getRootEventBundledRelationship(rootEvent = this.rootEvent): IThreadBundledRelationship | undefined {\n        return rootEvent?.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);\n    }\n\n    private async processRootEvent(): Promise<void> {\n        const bundledRelationship = this.getRootEventBundledRelationship();\n        if (Thread.hasServerSideSupport && bundledRelationship) {\n            this.replyCount = bundledRelationship.count;\n            this._currentUserParticipated = !!bundledRelationship.current_user_participated;\n\n            const mapper = this.client.getEventMapper();\n            // re-insert roomId\n            this.lastEvent = mapper({\n                ...bundledRelationship.latest_event,\n                room_id: this.roomId,\n            });\n            this.updatePendingReplyCount();\n            await this.processEvent(this.lastEvent);\n        }\n    }\n\n    private updatePendingReplyCount(): void {\n        const unfilteredPendingEvents =\n            this.pendingEventOrdering === PendingEventOrdering.Detached ? this.room.getPendingEvents() : this.events;\n        const pendingEvents = unfilteredPendingEvents.filter(\n            (ev) =>\n                ev.threadRootId === this.id &&\n                ev.isRelation(THREAD_RELATION_TYPE.name) &&\n                ev.status !== null &&\n                ev.getId() !== this.lastEvent?.getId(),\n        );\n        this.lastPendingEvent = pendingEvents.length ? pendingEvents[pendingEvents.length - 1] : undefined;\n        this.pendingReplyCount = pendingEvents.length;\n    }\n\n    /**\n     * Reset the live timeline of all timelineSets, and start new ones.\n     *\n     * <p>This is used when /sync returns a 'limited' timeline. 'Limited' means that there's a gap between the messages\n     * /sync returned, and the last known message in our timeline. In such a case, our live timeline isn't live anymore\n     * and has to be replaced by a new one. To make sure we can continue paginating our timelines correctly, we have to\n     * set new pagination tokens on the old and the new timeline.\n     *\n     * @param backPaginationToken -   token for back-paginating the new timeline\n     * @param forwardPaginationToken - token for forward-paginating the old live timeline,\n     * if absent or null, all timelines are reset, removing old ones (including the previous live\n     * timeline which would otherwise be unable to paginate forwards without this token).\n     * Removing just the old live timeline whilst preserving previous ones is not supported.\n     */\n    public async resetLiveTimeline(\n        backPaginationToken?: string | null,\n        forwardPaginationToken?: string | null,\n    ): Promise<void> {\n        const oldLive = this.liveTimeline;\n        this.timelineSet.resetLiveTimeline(backPaginationToken ?? undefined, forwardPaginationToken ?? undefined);\n        const newLive = this.liveTimeline;\n\n        // FIXME: Remove the following as soon as https://github.com/matrix-org/synapse/issues/14830 is resolved.\n        //\n        // The pagination API for thread timelines currently can't handle the type of pagination tokens returned by sync\n        //\n        // To make this work anyway, we'll have to transform them into one of the types that the API can handle.\n        // One option is passing the tokens to /messages, which can handle sync tokens, and returns the right format.\n        // /messages does not return new tokens on requests with a limit of 0.\n        // This means our timelines might overlap a slight bit, but that's not an issue, as we deduplicate messages\n        // anyway.\n\n        let newBackward: string | undefined;\n        let oldForward: string | undefined;\n        if (backPaginationToken) {\n            const res = await this.client.createMessagesRequest(this.roomId, backPaginationToken, 1, Direction.Forward);\n            newBackward = res.end;\n        }\n        if (forwardPaginationToken) {\n            const res = await this.client.createMessagesRequest(\n                this.roomId,\n                forwardPaginationToken,\n                1,\n                Direction.Backward,\n            );\n            oldForward = res.start;\n        }\n        // Only replace the token if we don't have paginated away from this position already. This situation doesn't\n        // occur today, but if the above issue is resolved, we'd have to go down this path.\n        if (forwardPaginationToken && oldLive.getPaginationToken(Direction.Forward) === forwardPaginationToken) {\n            oldLive.setPaginationToken(oldForward ?? null, Direction.Forward);\n        }\n        if (backPaginationToken && newLive.getPaginationToken(Direction.Backward) === backPaginationToken) {\n            newLive.setPaginationToken(newBackward ?? null, Direction.Backward);\n        }\n    }\n\n    private async updateThreadFromRootEvent(): Promise<void> {\n        if (Thread.hasServerSideSupport) {\n            // Ensure we show *something* as soon as possible, we'll update it as soon as we get better data, but we\n            // don't want the thread preview to be empty if we can avoid it\n            if (!this.initialEventsFetched && !this.lastEvent) {\n                await this.processRootEvent();\n            }\n            await this.fetchRootEvent();\n        }\n        await this.processRootEvent();\n    }\n\n    private async updateThreadMetadata(): Promise<void> {\n        this.updatePendingReplyCount();\n\n        if (!this.processRootEventPromise) {\n            // We only want to do this once otherwise we end up rolling back to the last unsigned summary we have for the thread\n            this.processRootEventPromise = this.updateThreadFromRootEvent();\n        }\n        await this.processRootEventPromise;\n\n        if (!this.initialEventsFetched) {\n            if (this.initalEventFetchProm) {\n                await this.initalEventFetchProm;\n            } else {\n                // fetch initial events to allow proper pagination\n                try {\n                    // clear out any events that were added before the pagination request\n                    // completed (eg. from sync). They'll be replaced by those from the pagination.\n                    // Really, we should do this after the pagination request completes, but\n                    // paginateEventTimeline does the request and adds the events in one go, so\n                    // this would need a refactor in order to do. It's therefore possible there's\n                    // a remaining race where an event comes in while the pagination request is\n                    // happening.\n                    this.timelineSet.resetLiveTimeline();\n                    // if the thread has regular events, this will just load the last reply.\n                    // if the thread is newly created, this will load the root event.\n                    if (this.replyCount === 0 && this.rootEvent) {\n                        this.timelineSet.addEventsToTimeline([this.rootEvent], true, false, this.liveTimeline, null);\n                        this.liveTimeline.setPaginationToken(null, Direction.Backward);\n                    } else {\n                        this.initalEventFetchProm = this.client.paginateEventTimeline(this.liveTimeline, {\n                            backwards: true,\n                        });\n                        await this.initalEventFetchProm;\n                    }\n                    // We have now fetched the initial events, so set the flag. We need to do this before\n                    // we actually add the events, so `this.addEvents` knows that it can now safely add\n                    // them rather than buffer them in the pending event list. The main thing is that this\n                    // must remain false while the async fetch happens, so we don't try to add events before\n                    // the pagination has finished. The important thing is that we're not await-ing anything\n                    // else between setting this and adding events, so no races.\n                    this.initialEventsFetched = true;\n                    for (const event of this.replayEvents!) {\n                        this.addEvent(event, false);\n                    }\n                    this.replayEvents = null;\n                    // just to make sure that, if we've created a timeline window for this thread before the thread itself\n                    // existed (e.g. when creating a new thread), we'll make sure the panel is force refreshed correctly.\n                    this.emit(RoomEvent.TimelineReset, this.room, this.timelineSet, true);\n                } catch (e) {\n                    logger.error(\"Failed to load start of newly created thread: \", e);\n                    this.initialEventsFetched = false;\n                }\n            }\n        }\n\n        this.emit(ThreadEvent.Update, this);\n    }\n\n    // XXX: Workaround for https://github.com/matrix-org/matrix-spec-proposals/pull/2676/files#r827240084\n    private async fetchEditsWhereNeeded(...events: MatrixEvent[]): Promise<unknown> {\n        const recursionSupport = this.client.canSupport.get(Feature.RelationsRecursion) ?? ServerSupport.Unsupported;\n        if (recursionSupport === ServerSupport.Unsupported) {\n            return Promise.all(\n                events.filter(isAnEncryptedThreadMessage).map(async (event: MatrixEvent) => {\n                    try {\n                        const relations = await this.client.relations(\n                            this.roomId,\n                            event.getId()!,\n                            RelationType.Replace,\n                            event.getType(),\n                            {\n                                limit: 1,\n                            },\n                        );\n                        if (relations.events.length) {\n                            const editEvent = relations.events[0];\n                            event.makeReplaced(editEvent);\n                            this.insertEventIntoTimeline(editEvent);\n                        }\n                    } catch (e) {\n                        logger.error(\"Failed to load edits for encrypted thread event\", e);\n                    }\n                }),\n            );\n        }\n    }\n\n    public setEventMetadata(event: MatrixEvent | null | undefined): void {\n        if (event) {\n            EventTimeline.setEventMetadata(event, this.roomState, false);\n            event.setThread(this);\n        }\n    }\n\n    public clearEventMetadata(event: MatrixEvent | null | undefined): void {\n        if (event) {\n            event.setThread(undefined);\n            delete event.event?.unsigned?.[\"m.relations\"]?.[THREAD_RELATION_TYPE.name];\n        }\n    }\n\n    /**\n     * Finds an event by ID in the current thread\n     */\n    public findEventById(eventId: string): MatrixEvent | undefined {\n        return this.timelineSet.findEventById(eventId);\n    }\n\n    /**\n     * Return last reply to the thread, if known.\n     */\n    public lastReply(\n        matches: (ev: MatrixEvent) => boolean = (ev): boolean => ev.isRelation(THREAD_RELATION_TYPE.name),\n    ): MatrixEvent | null {\n        for (let i = this.timeline.length - 1; i >= 0; i--) {\n            const event = this.timeline[i];\n            if (matches(event)) {\n                return event;\n            }\n        }\n        return null;\n    }\n\n    public get roomId(): string {\n        return this.room.roomId;\n    }\n\n    /**\n     * The number of messages in the thread\n     * Only count rel_type=m.thread as we want to\n     * exclude annotations from that number\n     */\n    public get length(): number {\n        return this.replyCount + this.pendingReplyCount;\n    }\n\n    /**\n     * A getter for the last event of the thread.\n     * This might be a synthesized event, if so, it will not emit any events to listeners.\n     */\n    public get replyToEvent(): MatrixEvent | null {\n        return this.lastPendingEvent ?? this.lastEvent ?? this.lastReply();\n    }\n\n    /**\n     * The live event timeline for this thread.\n     * @deprecated Present for backwards compatibility.\n     *             Use this.events instead\n     * @returns The live event timeline for this thread.\n     */\n    public get timeline(): MatrixEvent[] {\n        return this.events;\n    }\n\n    public get events(): MatrixEvent[] {\n        return this.liveTimeline.getEvents();\n    }\n\n    public has(eventId: string): boolean {\n        return this.timelineSet.findEventById(eventId) instanceof MatrixEvent;\n    }\n\n    public get hasCurrentUserParticipated(): boolean {\n        return this._currentUserParticipated;\n    }\n\n    public get liveTimeline(): EventTimeline {\n        return this.timelineSet.getLiveTimeline();\n    }\n\n    public getUnfilteredTimelineSet(): EventTimelineSet {\n        return this.timelineSet;\n    }\n\n    public addReceipt(event: MatrixEvent, synthetic: boolean): void {\n        throw new Error(\"Unsupported function on the thread model\");\n    }\n\n    /**\n     * Get the ID of the event that a given user has read up to within this thread,\n     * or null if we have received no read receipt (at all) from them.\n     * @param userId - The user ID to get read receipt event ID for\n     * @param ignoreSynthesized - If true, return only receipts that have been\n     *                            sent by the server, not implicit ones generated\n     *                            by the JS SDK.\n     * @returns ID of the latest event that the given user has read, or null.\n     */\n    public getEventReadUpTo(userId: string, ignoreSynthesized?: boolean): string | null {\n        // TODO: we think the implementation here is not right. Here is a sketch\n        // of the right answer:\n        //\n        // for event in timeline.events.reversed():\n        //     if room.hasUserReadEvent(event):\n        //         return event\n        // return null\n        //\n        // If this is too slow, we might be able to improve it by trying walking\n        // forward from the threaded receipt in this thread. We could alternate\n        // between backwards-from-front and forwards-from-threaded-receipt to\n        // improve our chances of hitting the right answer sooner.\n        //\n        // Either way, it's still fundamentally slow because we have to walk\n        // events.\n        //\n        // We also might just want to limit the time we spend on this by giving\n        // up after, say, 100 events.\n        //\n        // --- andyb\n\n        const isCurrentUser = userId === this.client.getUserId();\n        const lastReply = this.timeline[this.timeline.length - 1];\n        if (isCurrentUser && lastReply) {\n            // If the last activity in a thread is prior to the first threaded read receipt\n            // sent in the room (suggesting that it was sent before the user started\n            // using a client that supported threaded read receipts), we want to\n            // consider this thread as read.\n            const beforeFirstThreadedReceipt = lastReply.getTs() < this.room.getOldestThreadedReceiptTs();\n            const lastReplyId = lastReply.getId();\n            // Some unsent events do not have an ID, we do not want to consider them read\n            if (beforeFirstThreadedReceipt && lastReplyId) {\n                return lastReplyId;\n            }\n        }\n\n        const readUpToId = super.getEventReadUpTo(userId, ignoreSynthesized);\n\n        // Check whether the unthreaded read receipt for that user is more recent\n        // than the read receipt inside that thread.\n        if (lastReply) {\n            const unthreadedReceipt = this.room.getLastUnthreadedReceiptFor(userId);\n            if (!unthreadedReceipt) {\n                return readUpToId;\n            }\n\n            for (let i = this.timeline?.length - 1; i >= 0; --i) {\n                const ev = this.timeline[i];\n                // If we encounter the `readUpToId` we do not need to look further\n                // there is no \"more recent\" unthreaded read receipt\n                if (ev.getId() === readUpToId) return readUpToId;\n\n                // Inspecting events from most recent to oldest, we're checking\n                // whether an unthreaded read receipt is more recent that the current event.\n                // We usually prefer relying on the order of the DAG but in this scenario\n                // it is not possible and we have to rely on timestamp\n                if (ev.getTs() < unthreadedReceipt.ts) return ev.getId() ?? readUpToId;\n            }\n        }\n\n        return readUpToId;\n    }\n\n    /**\n     * Determine if the given user has read a particular event.\n     *\n     * It is invalid to call this method with an event that is not part of this thread.\n     *\n     * This is not a definitive check as it only checks the events that have been\n     * loaded client-side at the time of execution.\n     * @param userId - The user ID to check the read state of.\n     * @param eventId - The event ID to check if the user read.\n     * @returns True if the user has read the event, false otherwise.\n     */\n    public hasUserReadEvent(userId: string, eventId: string): boolean {\n        if (userId === this.client.getUserId()) {\n            // Consider an event read if it's part of a thread that is before the\n            // first threaded receipt sent in that room. It is likely that it is\n            // part of a thread that was created before MSC3771 was implemented.\n            // Or before the last unthreaded receipt for the logged in user\n            const beforeFirstThreadedReceipt =\n                (this.lastReply()?.getTs() ?? 0) < this.room.getOldestThreadedReceiptTs();\n            const unthreadedReceiptTs = this.room.getLastUnthreadedReceiptFor(userId)?.ts ?? 0;\n            const beforeLastUnthreadedReceipt = (this?.lastReply()?.getTs() ?? 0) < unthreadedReceiptTs;\n            if (beforeFirstThreadedReceipt || beforeLastUnthreadedReceipt) {\n                return true;\n            }\n        }\n\n        return this.room.hasUserReadEvent(userId, eventId);\n    }\n\n    public setUnread(type: NotificationCountType, count: number): void {\n        return this.room.setThreadUnreadNotificationCount(this.id, type, count);\n    }\n\n    /**\n     * Returns the most recent unthreaded receipt for a given user\n     * @param userId - the MxID of the User\n     * @returns an unthreaded Receipt. Can be undefined if receipts have been disabled\n     * or a user chooses to use private read receipts (or we have simply not received\n     * a receipt from this user yet).\n     */\n    public getLastUnthreadedReceiptFor(userId: string): Receipt | undefined {\n        return this.room.getLastUnthreadedReceiptFor(userId);\n    }\n}\n\n/**\n * Decide whether an event deserves to have its potential edits fetched.\n *\n * @returns true if this event is encrypted and is a message that is part of a\n * thread - either inside it, or a root.\n */\nfunction isAnEncryptedThreadMessage(event: MatrixEvent): boolean {\n    return event.isEncrypted() && (event.isRelation(THREAD_RELATION_TYPE.name) || event.isThreadRoot);\n}\n\nexport const FILTER_RELATED_BY_SENDERS = new ServerControlledNamespacedValue(\n    \"related_by_senders\",\n    \"io.element.relation_senders\",\n);\nexport const FILTER_RELATED_BY_REL_TYPES = new ServerControlledNamespacedValue(\n    \"related_by_rel_types\",\n    \"io.element.relation_types\",\n);\nexport const THREAD_RELATION_TYPE = new ServerControlledNamespacedValue(\"m.thread\", \"io.element.thread\");\n\nexport enum ThreadFilterType {\n    \"My\",\n    \"All\",\n}\n\nexport function threadFilterTypeToFilter(type: ThreadFilterType | null): \"all\" | \"participated\" {\n    switch (type) {\n        case ThreadFilterType.My:\n            return \"participated\";\n        default:\n            return \"all\";\n    }\n}\n","/*\nCopyright 2021 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// eslint-disable-next-line no-restricted-imports\nimport { EventEmitter } from \"events\";\n\n/** Events emitted by EventEmitter itself */\nexport enum EventEmitterEvents {\n    NewListener = \"newListener\",\n    RemoveListener = \"removeListener\",\n    Error = \"error\",\n}\n\ntype AnyListener = (...args: any) => any;\n\n/** Base class for types mapping from event name to the type of listeners to that event */\nexport type ListenerMap<E extends string> = { [eventName in E]: AnyListener };\n\ntype EventEmitterEventListener = (eventName: string, listener: AnyListener) => void;\ntype EventEmitterErrorListener = (error: Error) => void;\n\n/**\n * The expected type of a listener function for a particular event.\n *\n * Type parameters:\n *   * `E` - List of all events emitted by the `TypedEventEmitter`. Normally an enum type.\n *   * `A` - A type providing mappings from event names to listener types.\n *   * `T` - The name of the actual event that this listener is for. Normally one of the types in `E` or\n *           {@link EventEmitterEvents}.\n */\nexport type Listener<E extends string, A extends ListenerMap<E>, T extends E | EventEmitterEvents> = T extends E\n    ? A[T]\n    : T extends EventEmitterEvents\n      ? EventEmitterErrorListener\n      : EventEmitterEventListener;\n\n/**\n * Typed Event Emitter class which can act as a Base Model for all our model\n * and communication events.\n * This makes it much easier for us to distinguish between events, as we now need\n * to properly type this, so that our events are not stringly-based and prone\n * to silly typos.\n *\n * Type parameters:\n *  * `Events` - List of all events emitted by this `TypedEventEmitter`. Normally an enum type.\n *  * `Arguments` - A {@link ListenerMap} type providing mappings from event names to listener types.\n *  * `SuperclassArguments` - TODO: not really sure. Alternative listener mappings, I think? But only honoured for `.emit`?\n */\nexport class TypedEventEmitter<\n    Events extends string,\n    Arguments extends ListenerMap<Events>,\n    SuperclassArguments extends ListenerMap<any> = Arguments,\n> extends EventEmitter {\n    /**\n     * Alias for {@link on}.\n     */\n    public addListener<T extends Events | EventEmitterEvents>(\n        event: T,\n        listener: Listener<Events, Arguments, T>,\n    ): this {\n        return super.addListener(event, listener);\n    }\n\n    /**\n     * Synchronously calls each of the listeners registered for the event named\n     * `event`, in the order they were registered, passing the supplied arguments\n     * to each.\n     *\n     * @param event - The name of the event to emit\n     * @param args - Arguments to pass to the listener\n     * @returns `true` if the event had listeners, `false` otherwise.\n     */\n    public emit<T extends Events>(event: T, ...args: Parameters<SuperclassArguments[T]>): boolean;\n    public emit<T extends Events>(event: T, ...args: Parameters<Arguments[T]>): boolean;\n    public emit<T extends Events>(event: T, ...args: any[]): boolean {\n        return super.emit(event, ...args);\n    }\n\n    /**\n     * Similar to `emit` but calls all listeners within a `Promise.all` and returns the promise chain\n     * @param event - The name of the event to emit\n     * @param args - Arguments to pass to the listener\n     * @returns `true` if the event had listeners, `false` otherwise.\n     */\n    public async emitPromised<T extends Events>(\n        event: T,\n        ...args: Parameters<SuperclassArguments[T]>\n    ): Promise<boolean>;\n    public async emitPromised<T extends Events>(event: T, ...args: Parameters<Arguments[T]>): Promise<boolean>;\n    public async emitPromised<T extends Events>(event: T, ...args: any[]): Promise<boolean> {\n        const listeners = this.listeners(event);\n        return Promise.allSettled(listeners.map((l) => l(...args))).then(() => {\n            return listeners.length > 0;\n        });\n    }\n\n    /**\n     * Returns the number of listeners listening to the event named `event`.\n     *\n     * @param event - The name of the event being listened for\n     */\n    public listenerCount(event: Events | EventEmitterEvents): number {\n        return super.listenerCount(event);\n    }\n\n    /**\n     * Returns a copy of the array of listeners for the event named `event`.\n     */\n    public listeners(event: Events | EventEmitterEvents): ReturnType<EventEmitter[\"listeners\"]> {\n        return super.listeners(event);\n    }\n\n    /**\n     * Alias for {@link removeListener}\n     */\n    public off<T extends Events | EventEmitterEvents>(event: T, listener: Listener<Events, Arguments, T>): this {\n        return super.off(event, listener);\n    }\n\n    /**\n     * Adds the `listener` function to the end of the listeners array for the\n     * event named `event`.\n     *\n     * No checks are made to see if the `listener` has already been added. Multiple calls\n     * passing the same combination of `event` and `listener` will result in the `listener`\n     * being added, and called, multiple times.\n     *\n     * By default, event listeners are invoked in the order they are added. The\n     * {@link prependListener} method can be used as an alternative to add the\n     * event listener to the beginning of the listeners array.\n     *\n     * @param event - The name of the event.\n     * @param listener - The callback function\n     *\n     * @returns a reference to the `EventEmitter`, so that calls can be chained.\n     */\n    public on<T extends Events | EventEmitterEvents>(event: T, listener: Listener<Events, Arguments, T>): this {\n        return super.on(event, listener);\n    }\n\n    /**\n     * Adds a **one-time** `listener` function for the event named `event`. The\n     * next time `event` is triggered, this listener is removed and then invoked.\n     *\n     * Returns a reference to the `EventEmitter`, so that calls can be chained.\n     *\n     * By default, event listeners are invoked in the order they are added.\n     * The {@link prependOnceListener} method can be used as an alternative to add the\n     * event listener to the beginning of the listeners array.\n     *\n     * @param event - The name of the event.\n     * @param listener - The callback function\n     *\n     * @returns a reference to the `EventEmitter`, so that calls can be chained.\n     */\n    public once<T extends Events | EventEmitterEvents>(event: T, listener: Listener<Events, Arguments, T>): this {\n        return super.once(event, listener);\n    }\n\n    /**\n     * Adds the `listener` function to the _beginning_ of the listeners array for the\n     * event named `event`.\n     *\n     * No checks are made to see if the `listener` has already been added. Multiple calls\n     * passing the same combination of `event` and `listener` will result in the `listener`\n     * being added, and called, multiple times.\n     *\n     * @param event - The name of the event.\n     * @param listener - The callback function\n     *\n     * @returns a reference to the `EventEmitter`, so that calls can be chained.\n     */\n    public prependListener<T extends Events | EventEmitterEvents>(\n        event: T,\n        listener: Listener<Events, Arguments, T>,\n    ): this {\n        return super.prependListener(event, listener);\n    }\n\n    /**\n     * Adds a **one-time**`listener` function for the event named `event` to the _beginning_ of the listeners array.\n     * The next time `event` is triggered, this listener is removed, and then invoked.\n     *\n     * @param event - The name of the event.\n     * @param listener - The callback function\n     *\n     * @returns a reference to the `EventEmitter`, so that calls can be chained.\n     */\n    public prependOnceListener<T extends Events | EventEmitterEvents>(\n        event: T,\n        listener: Listener<Events, Arguments, T>,\n    ): this {\n        return super.prependOnceListener(event, listener);\n    }\n\n    /**\n     * Removes all listeners, or those of the specified `event`.\n     *\n     * It is bad practice to remove listeners added elsewhere in the code,\n     * particularly when the `EventEmitter` instance was created by some other\n     * component or module (e.g. sockets or file streams).\n     *\n     * @param event - The name of the event. If undefined, all listeners everywhere are removed.\n     * @returns a reference to the `EventEmitter`, so that calls can be chained.\n     */\n    public removeAllListeners(event?: Events | EventEmitterEvents): this {\n        // EventEmitter::removeAllListeners uses `arguments.length` to determine undefined case\n        if (event === undefined) {\n            return super.removeAllListeners();\n        }\n        return super.removeAllListeners(event);\n    }\n\n    /**\n     * Removes the specified `listener` from the listener array for the event named `event`.\n     *\n     * @returns a reference to the `EventEmitter`, so that calls can be chained.\n     */\n    public removeListener<T extends Events | EventEmitterEvents>(\n        event: T,\n        listener: Listener<Events, Arguments, T>,\n    ): this {\n        return super.removeListener(event, listener);\n    }\n\n    /**\n     * Returns a copy of the array of listeners for the event named `eventName`,\n     * including any wrappers (such as those created by `.once()`).\n     */\n    public rawListeners(event: Events | EventEmitterEvents): ReturnType<EventEmitter[\"rawListeners\"]> {\n        return super.rawListeners(event);\n    }\n}\n","/*\nCopyright 2015 - 2021 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type Json, type JsonValue } from \"../@types/json.ts\";\nimport { type MatrixClient } from \"../matrix.ts\";\nimport { type MatrixEvent } from \"./event.ts\";\nimport { TypedEventEmitter } from \"./typed-event-emitter.ts\";\n\nexport enum UserEvent {\n    DisplayName = \"User.displayName\",\n    AvatarUrl = \"User.avatarUrl\",\n    Presence = \"User.presence\",\n    CurrentlyActive = \"User.currentlyActive\",\n    LastPresenceTs = \"User.lastPresenceTs\",\n}\n\n/**\n * An object of extended profile attributes for a user as it arrives down the sync stream.\n */\nexport type SyncUserProfile = Record<string, Json | JsonValue>;\n\nexport type UserEventHandlerMap = {\n    /**\n     * Fires whenever any user's display name changes.\n     * @param event - The matrix event which caused this event to fire.\n     * @param user - The user whose User.displayName changed.\n     * @example\n     * ```\n     * matrixClient.on(\"User.displayName\", function(event, user){\n     *   var newName = user.displayName;\n     * });\n     * ```\n     */\n    [UserEvent.DisplayName]: (event: MatrixEvent | undefined, user: User) => void;\n    /**\n     * Fires whenever any user's avatar URL changes.\n     * @param event - The matrix event which caused this event to fire.\n     * @param user - The user whose User.avatarUrl changed.\n     * @example\n     * ```\n     * matrixClient.on(\"User.avatarUrl\", function(event, user){\n     *   var newUrl = user.avatarUrl;\n     * });\n     * ```\n     */\n    [UserEvent.AvatarUrl]: (event: MatrixEvent | undefined, user: User) => void;\n    /**\n     * Fires whenever any user's presence changes.\n     * @param event - The matrix event which caused this event to fire.\n     * @param user - The user whose User.presence changed.\n     * @example\n     * ```\n     * matrixClient.on(\"User.presence\", function(event, user){\n     *   var newPresence = user.presence;\n     * });\n     * ```\n     */\n    [UserEvent.Presence]: (event: MatrixEvent | undefined, user: User) => void;\n    /**\n     * Fires whenever any user's currentlyActive changes.\n     * @param event - The matrix event which caused this event to fire.\n     * @param user - The user whose User.currentlyActive changed.\n     * @example\n     * ```\n     * matrixClient.on(\"User.currentlyActive\", function(event, user){\n     *   var newCurrentlyActive = user.currentlyActive;\n     * });\n     * ```\n     */\n    [UserEvent.CurrentlyActive]: (event: MatrixEvent | undefined, user: User) => void;\n    /**\n     * Fires whenever any user's lastPresenceTs changes,\n     * ie. whenever any presence event is received for a user.\n     * @param event - The matrix event which caused this event to fire.\n     * @param user - The user whose User.lastPresenceTs changed.\n     * @example\n     * ```\n     * matrixClient.on(\"User.lastPresenceTs\", function(event, user){\n     *   var newlastPresenceTs = user.lastPresenceTs;\n     * });\n     * ```\n     */\n    [UserEvent.LastPresenceTs]: (event: MatrixEvent | undefined, user: User) => void;\n};\n\nexport class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {\n    private modified = -1;\n\n    /**\n     * The 'displayname' of the user if known.\n     * @privateRemarks\n     * Should be read-only\n     */\n    public displayName?: string;\n    public rawDisplayName?: string;\n    /**\n     * The 'avatar_url' of the user if known.\n     * @privateRemarks\n     * Should be read-only\n     */\n    public avatarUrl?: string;\n    /**\n     * The presence status message if known.\n     * @privateRemarks\n     * Should be read-only\n     */\n    public presenceStatusMsg?: string;\n    /**\n     * The presence enum if known.\n     * @privateRemarks\n     * Should be read-only\n     */\n    public presence = \"offline\";\n    /**\n     * Timestamp (ms since the epoch) for when we last received presence data for this user.\n     * We can subtract lastActiveAgo from this to approximate an absolute value for when a user was last active.\n     * @privateRemarks\n     * Should be read-only\n     */\n    public lastActiveAgo = 0;\n    /**\n     * The time elapsed in ms since the user interacted proactively with the server,\n     * or we saw a message from the user\n     * @privateRemarks\n     * Should be read-only\n     */\n    public lastPresenceTs = 0;\n    /**\n     * Whether we should consider lastActiveAgo to be an approximation\n     * and that the user should be seen as active 'now'\n     * @privateRemarks\n     * Should be read-only\n     */\n    public currentlyActive = false;\n    /**\n     * The events describing this user.\n     * @privateRemarks\n     * Should be read-only\n     */\n    public events: {\n        /** The m.presence event for this user. */\n        presence?: MatrixEvent;\n        profile?: MatrixEvent;\n    } = {};\n\n    /**\n     * Construct a new User. A User must have an ID and can optionally have extra information associated with it.\n     * @param userId - Required. The ID of this user.\n     * @deprecated use `User.createUser`\n     */\n    public constructor(public readonly userId: string) {\n        super();\n        this.displayName = userId;\n        this.rawDisplayName = userId;\n        this.updateModifiedTime();\n    }\n\n    /**\n     * Construct a new User whose events will also emit on MatrixClient.\n     * A User must have an ID and can optionally have extra information associated with it.\n     * @param userId - Required. The ID of this user.\n     * @param client - An instance of MatrixClient object\n     * @returns User object with reEmitter setup on client\n     */\n    public static createUser(userId: string, client: MatrixClient): User {\n        const user = new User(userId);\n        client.reEmitter.reEmit(user, [\n            UserEvent.AvatarUrl,\n            UserEvent.DisplayName,\n            UserEvent.Presence,\n            UserEvent.CurrentlyActive,\n            UserEvent.LastPresenceTs,\n        ]);\n        return user;\n    }\n\n    /**\n     * Update this User with the given presence event. May fire \"User.presence\",\n     * \"User.avatarUrl\" and/or \"User.displayName\" if this event updates this user's\n     * properties.\n     * @param event - The `m.presence` event.\n     *\n     * @remarks\n     * Fires {@link UserEvent.Presence}\n     * Fires {@link UserEvent.DisplayName}\n     * Fires {@link UserEvent.AvatarUrl}\n     */\n    public setPresenceEvent(event: MatrixEvent): void {\n        if (event.getType() !== \"m.presence\") {\n            return;\n        }\n        const firstFire = this.events.presence === null;\n        this.events.presence = event;\n\n        const eventsToFire: UserEvent[] = [];\n        if (event.getContent().presence !== this.presence || firstFire) {\n            eventsToFire.push(UserEvent.Presence);\n        }\n        if (event.getContent().avatar_url && event.getContent().avatar_url !== this.avatarUrl) {\n            eventsToFire.push(UserEvent.AvatarUrl);\n        }\n        if (event.getContent().displayname && event.getContent().displayname !== this.displayName) {\n            eventsToFire.push(UserEvent.DisplayName);\n        }\n        if (\n            event.getContent().currently_active !== undefined &&\n            event.getContent().currently_active !== this.currentlyActive\n        ) {\n            eventsToFire.push(UserEvent.CurrentlyActive);\n        }\n\n        this.presence = event.getContent().presence;\n        eventsToFire.push(UserEvent.LastPresenceTs);\n\n        if (event.getContent().status_msg) {\n            this.presenceStatusMsg = event.getContent().status_msg;\n        }\n        if (event.getContent().displayname) {\n            this.displayName = event.getContent().displayname;\n        }\n        if (event.getContent().avatar_url) {\n            this.avatarUrl = event.getContent().avatar_url;\n        }\n        this.lastActiveAgo = event.getContent().last_active_ago;\n        this.lastPresenceTs = Date.now();\n        this.currentlyActive = event.getContent().currently_active;\n\n        this.updateModifiedTime();\n\n        for (const eventToFire of eventsToFire) {\n            this.emit(eventToFire, event, this);\n        }\n    }\n\n    /**\n     * Manually set this user's display name. No event is emitted in response to this\n     * as there is no underlying MatrixEvent to emit with.\n     * @param name - The new display name.\n     */\n    public setDisplayName(name: string): void {\n        const oldName = this.displayName;\n        this.displayName = name;\n        if (name !== oldName) {\n            this.updateModifiedTime();\n        }\n    }\n\n    /**\n     * Manually set this user's non-disambiguated display name. No event is emitted\n     * in response to this as there is no underlying MatrixEvent to emit with.\n     * @param name - The new display name.\n     */\n    public setRawDisplayName(name?: string): void {\n        this.rawDisplayName = name;\n    }\n\n    /**\n     * Manually set this user's avatar URL. No event is emitted in response to this\n     * as there is no underlying MatrixEvent to emit with.\n     * @param url - The new avatar URL.\n     */\n    public setAvatarUrl(url?: string): void {\n        const oldUrl = this.avatarUrl;\n        this.avatarUrl = url;\n        if (url !== oldUrl) {\n            this.updateModifiedTime();\n        }\n    }\n\n    /**\n     * Update the last modified time to the current time.\n     */\n    private updateModifiedTime(): void {\n        this.modified = Date.now();\n    }\n\n    /**\n     * Get the timestamp when this User was last updated. This timestamp is\n     * updated when this User receives a new Presence event which has updated a\n     * property on this object. It is updated <i>before</i> firing events.\n     * @returns The timestamp\n     */\n    public getLastModifiedTime(): number {\n        return this.modified;\n    }\n\n    /**\n     * Get the absolute timestamp when this User was last known active on the server.\n     * It is *NOT* accurate if this.currentlyActive is true.\n     * @returns The timestamp\n     */\n    public getLastActiveTs(): number {\n        return this.lastPresenceTs - this.lastActiveAgo;\n    }\n}\n","/*\nCopyright 2023-2026 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type NonEmptyArray } from \"../@types/common.ts\";\nimport { hasRequiredStringProperty, isRecord } from \"../@types/type-guards.ts\";\n\ntype LocalizableKeys = \"client_name\" | \"client_uri\" | \"policy_uri\" | \"tos_uri\" | \"logo_uri\";\n\n/**\n * Request body for dynamic registration as defined by https://spec.matrix.org/v1.18/client-server-api/#client-registration\n */\nexport type OAuthRegistrationRequest = {\n    /**\n     * Kind of the application.\n     *\n     * The homeserver MUST support the web and native values to be able to perform redirect URI validation.\n     *\n     * Defaults to web if omitted.\n     */\n    application_type?: \"web\" | \"native\";\n    /**\n     * Human-readable name of the client to be presented to the user.\n     *\n     * This field can be localized by specifying `client_name#$lang`.\n     */\n    client_name?: string;\n    /**\n     * A URL to a valid web page that SHOULD give the user more information about the client.\n     *\n     * This URL MUST use the https scheme and SHOULD NOT require authentication to access.\n     * It MUST NOT use a user or password in the authority component of the URI.\n     *\n     * The server MAY reject client registrations if this field is invalid or missing.\n     *\n     * This URI is a common base for all the other URIs in the metadata:\n     * those MUST be either on the same host or on a subdomain of the host of the client_uri.\n     * The port number, path and query components MAY be different.\n     *\n     * For example, if the client_uri is https://example.com/,\n     * then one of the redirect_uris can be https://example.com/callback or https://app.example.com/callback,\n     * but not https://app.com/callback.\n     *\n     * This field can be localized by specifying `client_uri#$lang`.\n     */\n    client_uri: string;\n    /**\n     * Array of the OAuth 2.0 grant types that the client may use.\n     *\n     * This MUST include:\n     *\n     * the authorization_code value to use the authorization code grant,\n     * the refresh_token value to use the refresh token grant.\n     */\n    grant_types?: NonEmptyArray<string>;\n    /**\n     * URL that references a logo for the client.\n     *\n     * This URL MUST use the https scheme.\n     *\n     * This field can be localized by specifying `logo_uri#$lang`.\n     */\n    logo_uri?: string;\n    /**\n     * URL that points to a human-readable policy document for the client.\n     *\n     * This URL MUST use the https scheme and SHOULD NOT require authentication to access.\n     * It MUST NOT use a user or password in the authority component of the URI.\n     *\n     * This field can be localized by specifying `policy_uri#$lang`.\n     */\n    policy_uri?: string;\n    /**\n     * Array of redirection URIs for use in redirect-based flows.\n     *\n     * At least one URI is required to use the authorization code grant.\n     */\n    redirect_uris?: NonEmptyArray<string>;\n    /**\n     * Array of the OAuth 2.0 response types that the client may use.\n     *\n     * This MUST include the code value to use the authorization code grant.\n     */\n    response_types?: NonEmptyArray<string>;\n    /**\n     * String indicator of the requested authentication method for the token endpoint.\n     */\n    token_endpoint_auth_method?: string;\n    /**\n     * URL that points to a human-readable terms of service document for the client.\n     *\n     * This URL MUST use the https scheme and SHOULD NOT require authentication to access.\n     * It MUST NOT use a user or password in the authority component of the URI.\n     *\n     * This field can be localized by specifying `tos_uri#$lang`.\n     */\n    tos_uri?: string;\n} & {\n    // --- Dynamic Localized Fields (e.g., client_name#es-ES) ---\n    [K in `${LocalizableKeys}#${string}`]?: string;\n};\n\n/**\n * The OAuth 2.0 grant types that are defined for Matrix in https://spec.matrix.org/v1.17/client-server-api/#grant-types\n */\nexport enum OAuthGrantType {\n    /**\n     * As per RFC 6749 section 4.1, the authorization code grant lets the client obtain an access token through a browser redirect.\n     *\n     * See https://spec.matrix.org/v1.18/client-server-api/#authorization-code-grant\n     */\n    AuthorizationCode = \"authorization_code\",\n    /**\n     * As per RFC 6749 section 6, the refresh token grant lets the client exchange a refresh token for an access token.\n     *\n     * https://spec.matrix.org/v1.18/client-server-api/#refresh-token-grant\n     */\n    RefreshToken = \"refresh_token\",\n    /**\n     * As per RFC 8628, the device authorization grant lets clients on devices with limited input capabilities obtain\n     * an access token by having the user complete authorization on a separate device with a web browser.\n     *\n     * See https://spec.matrix.org/v1.18/client-server-api/#device-authorization-grant\n     */\n    DeviceAuthorization = \"urn:ietf:params:oauth:grant-type:device_code\",\n}\n\n/**\n * Check that URIs have a common base,\n * as per https://spec.matrix.org/v1.18/client-server-api/#redirect-uri-validation\n */\nexport function urlHasCommonBase(base: URL, urlStr?: string): boolean {\n    if (!urlStr) return false;\n    const url = new URL(urlStr);\n    if (url.protocol !== base.protocol) return false;\n    if (url.hostname !== base.hostname && !url.hostname.endsWith(`.${base.hostname}`)) return false;\n    return true;\n}\n\n/**\n * Response from dynamic registration\n */\ntype RegistrationResponse = {\n    client_id: string;\n};\n\n/**\n * Validate the given response matches the format expected for a {@link RegistrationResponse}\n * @param response - the response to validate\n * @throws if the response does not match the expected format\n */\nexport function validateRegistrationResponse(response: unknown): response is RegistrationResponse {\n    return isRecord(response) && hasRequiredStringProperty(response, \"client_id\");\n}\n","/*\nCopyright 2018 New Vector Ltd\nCopyright 2019 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { encodeUnpaddedBase64Url } from \"./base64.ts\";\n\n/**\n * String representing the lowercase latin alphabet for use in {@link secureRandomStringFrom}\n * (can be combined with other such exports or other characters by appending strings)\n */\nexport const LOWERCASE = \"abcdefghijklmnopqrstuvwxyz\";\n\n/**\n * String representing the uppercase latin alphabet for use in secureRandomStringFrom\n * (can be combined with other such exports or other characters by appending strings)\n */\nexport const UPPERCASE = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n\n/**\n * String representing the arabic numerals for use in secureRandomStringFrom\n * (can be combined with other such exports or other characters by appending strings)\n */\nexport const DIGITS = \"0123456789\";\n\nexport function secureRandomBase64Url(len: number): string {\n    const key = new Uint8Array(len);\n    globalThis.crypto.getRandomValues(key);\n\n    return encodeUnpaddedBase64Url(key);\n}\n\n/**\n * Generates a random string of uppercase and lowercase letters plus digits using a\n * cryptographically secure random number generator.\n * @param len The length of the string to generate\n * @returns Random string of uppercase and lowercase letters plus digits of length `len`\n */\nexport function secureRandomString(len: number): string {\n    return secureRandomStringFrom(len, UPPERCASE + LOWERCASE + DIGITS);\n}\n\n/**\n * Generate a cryptographically secure random string using characters given.\n *\n * @param len - The length of the string to generate (must be positive and less than 32768).\n * @param chars - The characters to use in the random string (between 2 and 256 characters long).\n * @returns Random string of characters of length `len`.\n */\nexport function secureRandomStringFrom(len: number, chars: string): string {\n    // This is intended for latin strings so 256 possibilities should be more than enough and\n    // means we can use random bytes, minimising the amount of entropy we need to ask for.\n    if (chars.length < 2 || chars.length > 256) {\n        throw new Error(\"Character set must be between 2 and 256 characters long\");\n    }\n\n    if (len < 1 || len > 32768) {\n        throw new Error(\"Requested random string length must be between 1 and 32768\");\n    }\n\n    // We'll generate random unsigned bytes, so get the largest number less than 256 that is a multiple\n    // of the length of the character set: We'll need to discard any random values that are larger than\n    // this as we can't possibly map them onto the character set while keeping each character equally\n    // likely to be chosen (minus 1 to convert to indices in a string). (Essentially, we're using a d8\n    // to choose between 7 possibilities and re-rolling on an 8, keeping all 7 outcomes equally likely.)\n    // Our random values must be strictly less than this\n    const randomValueCutoff = 256 - (256 % chars.length);\n\n    // Grab 30% more entropy than we need. This should be enough that we can discard the values that are\n    // too high without having to go back and grab more unless we're super unlucky.\n    const entropyBuffer = new Uint8Array(Math.floor(len * 1.3));\n    // Mark all of this buffer as used to start with (we haven't populated it with entropy yet) so it will\n    // be filled on the first iteration.\n    let entropyBufferPos = entropyBuffer.length;\n\n    const result = [];\n    while (result.length < len) {\n        if (entropyBufferPos === entropyBuffer.length) {\n            globalThis.crypto.getRandomValues(entropyBuffer);\n            entropyBufferPos = 0;\n        }\n\n        const randomByte = entropyBuffer[entropyBufferPos++];\n\n        if (randomByte < randomValueCutoff) {\n            result.push(chars[randomByte % chars.length]);\n        }\n    }\n\n    return result.join(\"\");\n}\n","/*\nCopyright 2015 - 2021 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n/**\n * This is an internal module which manages queuing, scheduling and retrying\n * of requests.\n */\nimport { logger } from \"./logger.ts\";\nimport { type MatrixEvent } from \"./models/event.ts\";\nimport { EventType } from \"./@types/event.ts\";\nimport { removeElement } from \"./utils.ts\";\nimport { calculateRetryBackoff, type MatrixError } from \"./http-api/index.ts\";\nimport { type ISendEventResponse } from \"./@types/requests.ts\";\n\nconst DEBUG = false; // set true to enable console logging.\n\ninterface IQueueEntry<T> {\n    event: MatrixEvent;\n    resolvers: PromiseWithResolvers<T>;\n    attempts: number;\n}\n\n/**\n * The function to invoke to process (send) events in the queue.\n * @param event - The event to send.\n * @returns Resolved/rejected depending on the outcome of the request.\n */\ntype ProcessFunction<T> = (event: MatrixEvent) => Promise<T>;\n\n// eslint-disable-next-line camelcase\nexport class MatrixScheduler<T = ISendEventResponse> {\n    /**\n     * Default retry algorithm for the matrix scheduler. Retries events up to 4 times with exponential backoff.\n     * @param attempts - Number of attempts that have been made, including the one that just failed (ie. starting at 1)\n     * @see retryAlgorithm\n     */\n    // eslint-disable-next-line @typescript-eslint/naming-convention\n    public static RETRY_BACKOFF_RATELIMIT(event: MatrixEvent | null, attempts: number, err: MatrixError): number {\n        return calculateRetryBackoff(err, attempts, false);\n    }\n\n    /**\n     * Queues `m.room.message` events and lets other events continue\n     * concurrently.\n     * @see queueAlgorithm\n     */\n    // eslint-disable-next-line @typescript-eslint/naming-convention\n    public static QUEUE_MESSAGES(event: MatrixEvent): string | null {\n        // enqueue messages or events that associate with another event (redactions and relations)\n        if (event.getType() === EventType.RoomMessage || event.hasAssociation()) {\n            // put these events in the 'message' queue.\n            return \"message\";\n        }\n        // allow all other events continue concurrently.\n        return null;\n    }\n\n    // queueName: [{\n    //  event: MatrixEvent,  // event to send\n    //  defer: PromiseWithResolvers,  // defer to resolve/reject at the END of the retries\n    //  attempts: Number  // number of times we've called processFn\n    // }, ...]\n    private readonly queues: Record<string, IQueueEntry<T>[]> = {};\n    private activeQueues: string[] = [];\n    private procFn: ProcessFunction<T> | null = null;\n\n    /**\n     * Construct a scheduler for Matrix. Requires\n     * {@link MatrixScheduler#setProcessFunction} to be provided\n     * with a way of processing events.\n     * @param retryAlgorithm - Optional. The retry\n     * algorithm to apply when determining when to try to send an event again.\n     * Defaults to {@link MatrixScheduler.RETRY_BACKOFF_RATELIMIT}.\n     * @param queueAlgorithm - Optional. The queuing\n     * algorithm to apply when determining which events should be sent before the\n     * given event. Defaults to {@link MatrixScheduler.QUEUE_MESSAGES}.\n     */\n    public constructor(\n        /**\n         * The retry algorithm to apply when retrying events. To stop retrying, return\n         * `-1`. If this event was part of a queue, it will be removed from\n         * the queue.\n         * @param event - The event being retried.\n         * @param attempts - The number of failed attempts. This will always be \\>= 1.\n         * @param err - The most recent error message received when trying\n         * to send this event.\n         * @returns The number of milliseconds to wait before trying again. If\n         * this is 0, the request will be immediately retried. If this is\n         * `-1`, the event will be marked as\n         * {@link EventStatus.NOT_SENT} and will not be retried.\n         */\n        public readonly retryAlgorithm = MatrixScheduler.RETRY_BACKOFF_RATELIMIT,\n        /**\n         * The queuing algorithm to apply to events. This function must be idempotent as\n         * it may be called multiple times with the same event. All queues created are\n         * serviced in a FIFO manner. To send the event ASAP, return `null`\n         * which will not put this event in a queue. Events that fail to send that form\n         * part of a queue will be removed from the queue and the next event in the\n         * queue will be sent.\n         * @param event - The event to be sent.\n         * @returns The name of the queue to put the event into. If a queue with\n         * this name does not exist, it will be created. If this is `null`,\n         * the event is not put into a queue and will be sent concurrently.\n         */\n        public readonly queueAlgorithm = MatrixScheduler.QUEUE_MESSAGES,\n    ) {}\n\n    /**\n     * Retrieve a queue based on an event. The event provided does not need to be in\n     * the queue.\n     * @param event - An event to get the queue for.\n     * @returns A shallow copy of events in the queue or null.\n     * Modifying this array will not modify the list itself. Modifying events in\n     * this array <i>will</i> modify the underlying event in the queue.\n     * @see MatrixScheduler.removeEventFromQueue To remove an event from the queue.\n     */\n    public getQueueForEvent(event: MatrixEvent): MatrixEvent[] | null {\n        const name = this.queueAlgorithm(event);\n        if (!name || !this.queues[name]) {\n            return null;\n        }\n        return this.queues[name].map(function (obj) {\n            return obj.event;\n        });\n    }\n\n    /**\n     * Remove this event from the queue. The event is equal to another event if they\n     * have the same ID returned from event.getId().\n     * @param event - The event to remove.\n     * @returns True if this event was removed.\n     */\n    public removeEventFromQueue(event: MatrixEvent): boolean {\n        const name = this.queueAlgorithm(event);\n        if (!name || !this.queues[name]) {\n            return false;\n        }\n        let removed = false;\n        removeElement(this.queues[name], (element) => {\n            if (element.event.getId() === event.getId()) {\n                // XXX we should probably reject the promise?\n                // https://github.com/matrix-org/matrix-js-sdk/issues/496\n                removed = true;\n                return true;\n            }\n            return false;\n        });\n        return removed;\n    }\n\n    /**\n     * Set the process function. Required for events in the queue to be processed.\n     * If set after events have been added to the queue, this will immediately start\n     * processing them.\n     * @param fn - The function that can process events\n     * in the queue.\n     */\n    public setProcessFunction(fn: ProcessFunction<T>): void {\n        this.procFn = fn;\n        this.startProcessingQueues();\n    }\n\n    /**\n     * Queue an event if it is required and start processing queues.\n     * @param event - The event that may be queued.\n     * @returns A promise if the event was queued, which will be\n     * resolved or rejected in due time, else null.\n     */\n    public queueEvent(event: MatrixEvent): Promise<T> | null {\n        const queueName = this.queueAlgorithm(event);\n        if (!queueName) {\n            return null;\n        }\n        // add the event to the queue and make a deferred for it.\n        if (!this.queues[queueName]) {\n            this.queues[queueName] = [];\n        }\n        const eventResolvers = Promise.withResolvers<T>();\n        this.queues[queueName].push({\n            event: event,\n            resolvers: eventResolvers,\n            attempts: 0,\n        });\n        debuglog(\"Queue algorithm dumped event %s into queue '%s'\", event.getId(), queueName);\n        this.startProcessingQueues();\n        return eventResolvers.promise;\n    }\n\n    private startProcessingQueues(): void {\n        if (!this.procFn) return;\n        // for each inactive queue with events in them\n        Object.keys(this.queues)\n            .filter((queueName) => {\n                return this.activeQueues.indexOf(queueName) === -1 && this.queues[queueName].length > 0;\n            })\n            .forEach((queueName) => {\n                // mark the queue as active\n                this.activeQueues.push(queueName);\n                // begin processing the head of the queue\n                debuglog(\"Spinning up queue: '%s'\", queueName);\n                this.processQueue(queueName);\n            });\n    }\n\n    private processQueue = (queueName: string): void => {\n        // get head of queue\n        const obj = this.peekNextEvent(queueName);\n        if (!obj) {\n            this.disableQueue(queueName);\n            return;\n        }\n        debuglog(\"Queue '%s' has %s pending events\", queueName, this.queues[queueName].length);\n        // fire the process function and if it resolves, resolve the deferred. Else\n        // invoke the retry algorithm.\n\n        // First wait for a resolved promise, so the resolve handlers for\n        // the deferred of the previously sent event can run.\n        // This way enqueued relations/redactions to enqueued events can receive\n        // the remove id of their target before being sent.\n        Promise.resolve()\n            .then(() => {\n                return this.procFn!(obj.event);\n            })\n            .then(\n                (res) => {\n                    // remove this from the queue\n                    this.removeNextEvent(queueName);\n                    debuglog(\"Queue '%s' sent event %s\", queueName, obj.event.getId());\n                    obj.resolvers.resolve(res);\n                    // keep processing\n                    this.processQueue(queueName);\n                },\n                (err) => {\n                    obj.attempts += 1;\n                    // ask the retry algorithm when/if we should try again\n                    const waitTimeMs = this.retryAlgorithm(obj.event, obj.attempts, err);\n                    debuglog(\n                        \"retry(%s) err=%s event_id=%s waitTime=%s\",\n                        obj.attempts,\n                        err,\n                        obj.event.getId(),\n                        waitTimeMs,\n                    );\n                    if (waitTimeMs === -1) {\n                        // give up (you quitter!)\n                        logger.info(\"Queue '%s' giving up on event %s\", queueName, obj.event.getId());\n                        // remove this from the queue\n                        this.clearQueue(queueName, err);\n                    } else {\n                        setTimeout(this.processQueue, waitTimeMs, queueName);\n                    }\n                },\n            );\n    };\n\n    private disableQueue(queueName: string): void {\n        // queue is empty. Mark as inactive and stop recursing.\n        const index = this.activeQueues.indexOf(queueName);\n        if (index >= 0) {\n            this.activeQueues.splice(index, 1);\n        }\n        logger.info(\"Stopping queue '%s' as it is now empty\", queueName);\n    }\n\n    private clearQueue(queueName: string, err: unknown): void {\n        logger.info(\"clearing queue '%s'\", queueName);\n        let obj: IQueueEntry<T> | undefined;\n        while ((obj = this.removeNextEvent(queueName))) {\n            obj.resolvers.reject(err);\n        }\n        this.disableQueue(queueName);\n    }\n\n    private peekNextEvent(queueName: string): IQueueEntry<T> | undefined {\n        const queue = this.queues[queueName];\n        if (!Array.isArray(queue)) {\n            return undefined;\n        }\n        return queue[0];\n    }\n\n    private removeNextEvent(queueName: string): IQueueEntry<T> | undefined {\n        const queue = this.queues[queueName];\n        if (!Array.isArray(queue)) {\n            return undefined;\n        }\n        return queue.shift();\n    }\n}\n\n/* istanbul ignore next */\nfunction debuglog(...args: any[]): void {\n    if (DEBUG) {\n        logger.log(...args);\n    }\n}\n","/*\nCopyright 2021-2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n/**\n * Implementation of server-side secret storage\n *\n * @see https://spec.matrix.org/v1.6/client-server-api/#storage\n */\n\nimport { type TypedEventEmitter } from \"./models/typed-event-emitter.ts\";\nimport { ClientEvent, type ClientEventHandlerMap } from \"./client.ts\";\nimport { type MatrixEvent } from \"./models/event.ts\";\nimport { secureRandomString } from \"./randomstring.ts\";\nimport { logger } from \"./logger.ts\";\nimport encryptAESSecretStorageItem from \"./utils/encryptAESSecretStorageItem.ts\";\nimport decryptAESSecretStorageItem from \"./utils/decryptAESSecretStorageItem.ts\";\nimport { type AESEncryptedSecretStoragePayload } from \"./@types/AESEncryptedSecretStoragePayload.ts\";\nimport { type AccountDataEvents, type SecretStorageAccountDataEvents } from \"./@types/event.ts\";\nimport { type EmptyObject } from \"./@types/common.ts\";\n\nexport const SECRET_STORAGE_ALGORITHM_V1_AES = \"m.secret_storage.v1.aes-hmac-sha2\";\n\n/**\n * Common base interface for Secret Storage Keys.\n *\n * The common properties for all encryption keys used in server-side secret storage.\n *\n * @see https://spec.matrix.org/v1.6/client-server-api/#key-storage\n */\nexport interface SecretStorageKeyDescriptionCommon {\n    /** A human-readable name for this key. */\n    // XXX: according to the spec, this is optional\n    name: string;\n\n    /** The encryption algorithm used with this key. */\n    algorithm: string;\n\n    /** Information for deriving this key from a passphrase. */\n    // XXX: according to the spec, this is optional\n    passphrase: PassphraseInfo;\n}\n\n/**\n * Properties for a SSSS key using the `m.secret_storage.v1.aes-hmac-sha2` algorithm.\n *\n * Corresponds to `AesHmacSha2KeyDescription` in the specification.\n *\n * @see https://spec.matrix.org/v1.6/client-server-api/#msecret_storagev1aes-hmac-sha2\n */\nexport interface SecretStorageKeyDescriptionAesV1 extends SecretStorageKeyDescriptionCommon {\n    // XXX: strictly speaking, we should be able to enforce the algorithm here. But\n    //   this interface ends up being incorrectly used where other algorithms are in use (notably\n    //   in device-dehydration support), and unpicking that is too much like hard work\n    //   at the moment.\n    // algorithm: \"m.secret_storage.v1.aes-hmac-sha2\";\n\n    /** The 16-byte AES initialization vector, encoded as base64. */\n    iv: string;\n\n    /** The MAC of the result of encrypting 32 bytes of 0, encoded as base64. */\n    mac: string;\n}\n\n/**\n * Union type for secret storage keys.\n *\n * For now, this is only {@link SecretStorageKeyDescriptionAesV1}, but other interfaces may be added in future.\n */\nexport type SecretStorageKeyDescription = SecretStorageKeyDescriptionAesV1;\n\n/**\n * Information on how to generate the key from a passphrase.\n *\n * @see https://spec.matrix.org/v1.6/client-server-api/#deriving-keys-from-passphrases\n */\nexport interface PassphraseInfo {\n    /** The algorithm to be used to derive the key. */\n    algorithm: \"m.pbkdf2\";\n\n    /** The number of PBKDF2 iterations to use. */\n    iterations: number;\n\n    /** The salt to be used for PBKDF2. */\n    salt: string;\n\n    /** The number of bits to generate. Defaults to 256. */\n    bits?: number;\n}\n\n/**\n * Options for {@link ServerSideSecretStorageImpl#addKey}.\n */\nexport interface AddSecretStorageKeyOpts {\n    /** Information for deriving the key from a passphrase if any. */\n    passphrase?: PassphraseInfo;\n    /** Optional name of the key. */\n    name?: string;\n    /** The private key. Will be used to generate the key check values in the key info; it will not be stored on the server */\n    key: Uint8Array<ArrayBuffer>;\n}\n\n/**\n * Return type for {@link ServerSideSecretStorageImpl#getKey}.\n */\nexport type SecretStorageKeyTuple = [keyId: string, keyInfo: SecretStorageKeyDescription];\n\n/**\n * Return type for {@link ServerSideSecretStorageImpl#addKey}.\n */\nexport type SecretStorageKeyObject = {\n    /** The ID of the key */\n    keyId: string;\n    /**  details about the key */\n    keyInfo: SecretStorageKeyDescription;\n};\n\n/** Interface for managing account data on the server.\n *\n * A subset of {@link MatrixClient}.\n */\nexport interface AccountDataClient extends TypedEventEmitter<ClientEvent.AccountData, ClientEventHandlerMap> {\n    /**\n     * Get account data event of given type for the current user. This variant\n     * gets account data directly from the homeserver if the local store is not\n     * ready, which can be useful very early in startup before the initial sync.\n     *\n     * @param eventType - The type of account data\n     * @returns The contents of the given account data event, or `null` if the event is not found\n     */\n    getAccountDataFromServer: <K extends keyof AccountDataEvents>(eventType: K) => Promise<AccountDataEvents[K] | null>;\n\n    /**\n     * Set account data event for the current user, with retries\n     *\n     * @param eventType - The type of account data\n     * @param content - the content object to be set\n     * @returns an empty object\n     */\n    setAccountData: <K extends keyof AccountDataEvents>(\n        eventType: K,\n        content: AccountDataEvents[K] | Record<string, never>,\n    ) => Promise<EmptyObject>;\n}\n\n/**\n *  Application callbacks for use with {@link SecretStorage.ServerSideSecretStorageImpl}\n */\nexport interface SecretStorageCallbacks {\n    /**\n     * Called to retrieve a secret storage encryption key\n     *\n     * Before a secret can be stored in server-side storage, it must be encrypted with one or more\n     * keys. Similarly, after it has been retrieved from storage, it must be decrypted with one of\n     * the keys it was encrypted with. These encryption keys are known as \"secret storage keys\".\n     *\n     * Descriptions of the secret storage keys are also stored in server-side storage, per the\n     * [matrix specification](https://spec.matrix.org/v1.6/client-server-api/#key-storage), so\n     * before a key can be used in this way, it must have been stored on the server. This is\n     * done via {@link ServerSideSecretStorage#addKey}.\n     *\n     * Obviously the keys themselves are not stored server-side, so the js-sdk calls this callback\n     * in order to retrieve a secret storage key from the application.\n     *\n     * @param keys - An options object, containing only the property `keys`.\n     *\n     * @param name - the name of the *secret* (NB: not the encryption key) being stored or retrieved.\n     *    This is the \"event type\" stored in account data.\n     *\n     * @returns a pair [`keyId`, `privateKey`], where `keyId` is one of the keys from the `keys` parameter,\n     *    and `privateKey` is the raw private encryption key, as appropriate for the encryption algorithm.\n     *    (For `m.secret_storage.v1.aes-hmac-sha2`, it is the input to an HKDF as defined in the\n     *    [specification](https://spec.matrix.org/v1.6/client-server-api/#msecret_storagev1aes-hmac-sha2).)\n     *\n     *    Alternatively, if none of the keys are known, may return `null` — in which case the original\n     *    storage/retrieval operation will fail with an exception.\n     */\n    getSecretStorageKey?: (\n        keys: {\n            /**\n             * details of the secret storage keys required: a map from the key ID\n             * (excluding the `m.secret_storage.key.` prefix) to details of the key.\n             *\n             * When storing a secret, `keys` will contain exactly one entry; this method will be called\n             * once for each secret storage key to be used for encryption.\n             *\n             * For secret retrieval, `keys` may contain several entries, and the application can return\n             * any one of the requested keys.\n             */\n            keys: Record<string, SecretStorageKeyDescription>;\n        },\n        name: string,\n    ) => Promise<[string, Uint8Array<ArrayBuffer>] | null>;\n}\n\n/**\n * Account Data event types which can store secret-storage-encrypted information.\n */\nexport type SecretStorageKey = keyof SecretStorageAccountDataEvents;\n\n/**\n * Account Data event content type for storing secret-storage-encrypted information.\n *\n * See https://spec.matrix.org/v1.13/client-server-api/#msecret_storagev1aes-hmac-sha2-1\n */\nexport interface SecretInfo {\n    encrypted: {\n        [keyId: string]: AESEncryptedSecretStoragePayload;\n    };\n}\n\ninterface Decryptors {\n    encrypt: (plaintext: string) => Promise<AESEncryptedSecretStoragePayload>;\n    decrypt: (ciphertext: AESEncryptedSecretStoragePayload) => Promise<string>;\n}\n\n/**\n * Interface provided by SecretStorage implementations\n *\n * Normally this will just be an {@link ServerSideSecretStorageImpl}, but for backwards\n * compatibility some methods allow other implementations.\n */\nexport interface ServerSideSecretStorage {\n    /**\n     * Add a key for encrypting secrets.\n     *\n     * @param algorithm - the algorithm used by the key.\n     * @param opts - the options for the algorithm.  The properties used\n     *     depend on the algorithm given.\n     * @param keyId - the ID of the key.  If not given, a random\n     *     ID will be generated.\n     *\n     * @returns details about the key.\n     */\n    addKey(algorithm: string, opts: AddSecretStorageKeyOpts, keyId?: string): Promise<SecretStorageKeyObject>;\n\n    /**\n     * Get the key information for a given ID.\n     *\n     * @param keyId - The ID of the key to check\n     *     for. Defaults to the default key ID if not provided.\n     * @returns If the key was found, the return value is an array of\n     *     the form [keyId, keyInfo].  Otherwise, null is returned.\n     *     XXX: why is this an array when addKey returns an object?\n     */\n    getKey(keyId?: string | null): Promise<SecretStorageKeyTuple | null>;\n\n    /**\n     * Check whether we have a key with a given ID.\n     *\n     * @param keyId - The ID of the key to check\n     *     for. Defaults to the default key ID if not provided.\n     * @returns Whether we have the key.\n     */\n    hasKey(keyId?: string): Promise<boolean>;\n\n    /**\n     * Check whether a key matches what we expect based on the key info\n     *\n     * @param key - the key to check\n     * @param info - the key info\n     *\n     * @returns whether or not the key matches\n     */\n    checkKey(key: Uint8Array, info: SecretStorageKeyDescriptionAesV1): Promise<boolean>;\n\n    /**\n     * Store an encrypted secret on the server.\n     *\n     * Details of the encryption keys to be used must previously have been stored in account data\n     * (for example, via {@link ServerSideSecretStorageImpl#addKey}. {@link SecretStorageCallbacks#getSecretStorageKey} will be called to obtain a secret storage\n     * key to decrypt the secret.\n     *\n     * If the secret is `null`, the secret value in the account data will be set to an empty object.\n     * This is considered as \"removing\" the secret.\n     *\n     * @param name - The name of the secret - i.e., the \"event type\" to be stored in the account data\n     * @param secret - The secret contents.\n     * @param keys - The IDs of the keys to use to encrypt the secret, or null/undefined to use the default key\n     *     (will throw if no default key is set).\n     */\n    store(name: string, secret: string | null, keys?: string[] | null): Promise<void>;\n\n    /**\n     * Get a secret from storage, and decrypt it.\n     *\n     * @param name - the name of the secret - i.e., the \"event type\" stored in the account data\n     *\n     * @returns the decrypted contents of the secret, or \"undefined\" if `name` is not found in\n     *    the user's account data.\n     */\n    get(name: string): Promise<string | undefined>;\n\n    /**\n     * Check if a secret is stored on the server.\n     *\n     * @param name - the name of the secret\n     *\n     * @returns map of key name to key info the secret is encrypted\n     *     with, or null if it is not present or not encrypted with a trusted\n     *     key\n     */\n    isStored(name: SecretStorageKey): Promise<Record<string, SecretStorageKeyDescriptionAesV1> | null>;\n\n    /**\n     * Get the current default key ID for encrypting secrets.\n     *\n     * @returns The default key ID or null if no default key ID is set\n     */\n    getDefaultKeyId(): Promise<string | null>;\n\n    /**\n     * Set the default key ID for encrypting secrets.\n     *\n     * If keyId is `null`, the default key id value in the account data will be set to an empty object.\n     * This is considered as \"disabling\" the default key.\n     *\n     * @param keyId - The new default key ID\n     */\n    setDefaultKeyId(keyId: string | null): Promise<void>;\n}\n\n/**\n * Implementation of Server-side secret storage.\n *\n * Secret *sharing* is *not* implemented here: this class is strictly about the storage component of\n * SSSS.\n *\n * @see https://spec.matrix.org/v1.6/client-server-api/#storage\n */\nexport class ServerSideSecretStorageImpl implements ServerSideSecretStorage {\n    /**\n     * Construct a new `SecretStorage`.\n     *\n     * Normally, it is unnecessary to call this directly, since MatrixClient automatically constructs one.\n     * However, it may be useful to construct a new `SecretStorage`, if custom `callbacks` are required, for example.\n     *\n     * @param accountDataAdapter - interface for fetching and setting account data on the server. Normally an instance\n     *   of {@link MatrixClient}.\n     * @param callbacks - application level callbacks for retrieving secret keys\n     */\n    public constructor(\n        private readonly accountDataAdapter: AccountDataClient,\n        private readonly callbacks: SecretStorageCallbacks,\n    ) {}\n\n    /**\n     * Get the current default key ID for encrypting secrets.\n     *\n     * @returns The default key ID or null if no default key ID is set\n     */\n    public async getDefaultKeyId(): Promise<string | null> {\n        const defaultKey = await this.accountDataAdapter.getAccountDataFromServer(\"m.secret_storage.default_key\");\n        if (!defaultKey) return null;\n        return defaultKey.key ?? null;\n    }\n\n    /**\n     * Implementation of {@link ServerSideSecretStorage#setDefaultKeyId}.\n     */\n    public setDefaultKeyId(keyId: string | null): Promise<void> {\n        return new Promise<void>((resolve, reject) => {\n            const listener = (ev: MatrixEvent): void => {\n                if (ev.getType() !== \"m.secret_storage.default_key\") {\n                    //  Different account data item\n                    return;\n                }\n\n                // If keyId === null, the content should be an empty object.\n                // Otherwise, the `key` in the content object should match keyId.\n                const content = ev.getContent();\n                const isSameKey = keyId === null ? Object.keys(content).length === 0 : content.key === keyId;\n                if (isSameKey) {\n                    this.accountDataAdapter.removeListener(ClientEvent.AccountData, listener);\n                    resolve();\n                }\n            };\n            this.accountDataAdapter.on(ClientEvent.AccountData, listener);\n\n            // The spec [1] says that the value of the account data entry should be an object with a `key` property.\n            // It doesn't specify how to delete the default key; we do it by setting the account data to an empty object.\n            //\n            // [1]: https://spec.matrix.org/v1.13/client-server-api/#key-storage\n            const newValue: Record<string, never> | { key: string } = keyId === null ? {} : { key: keyId };\n            this.accountDataAdapter.setAccountData(\"m.secret_storage.default_key\", newValue).catch((e) => {\n                this.accountDataAdapter.removeListener(ClientEvent.AccountData, listener);\n                reject(e);\n            });\n        });\n    }\n\n    /**\n     * Add a key for encrypting secrets.\n     *\n     * @param algorithm - the algorithm used by the key.\n     * @param opts - the options for the algorithm.  The properties used\n     *     depend on the algorithm given.\n     * @param keyId - the ID of the key.  If not given, a random\n     *     ID will be generated.\n     *\n     * @returns An object with:\n     *     keyId: the ID of the key\n     *     keyInfo: details about the key (iv, mac, passphrase)\n     */\n    public async addKey(\n        algorithm: string,\n        opts: AddSecretStorageKeyOpts,\n        keyId?: string,\n    ): Promise<SecretStorageKeyObject> {\n        if (algorithm !== SECRET_STORAGE_ALGORITHM_V1_AES) {\n            throw new Error(`Unknown key algorithm ${algorithm}`);\n        }\n\n        const keyInfo = { algorithm } as SecretStorageKeyDescriptionAesV1;\n\n        if (opts.name) {\n            keyInfo.name = opts.name;\n        }\n\n        if (opts.passphrase) {\n            keyInfo.passphrase = opts.passphrase;\n        }\n\n        const { iv, mac } = await calculateKeyCheck(opts.key);\n        keyInfo.iv = iv;\n        keyInfo.mac = mac;\n\n        // Create a unique key id. XXX: this is racey.\n        if (!keyId) {\n            do {\n                keyId = secureRandomString(32);\n            } while (await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`));\n        }\n\n        await this.accountDataAdapter.setAccountData(`m.secret_storage.key.${keyId}`, keyInfo);\n\n        return {\n            keyId,\n            keyInfo,\n        };\n    }\n\n    /**\n     * Get the key information for a given ID.\n     *\n     * @param keyId - The ID of the key to check\n     *     for. Defaults to the default key ID if not provided.\n     * @returns If the key was found, the return value is an array of\n     *     the form [keyId, keyInfo].  Otherwise, null is returned.\n     *     XXX: why is this an array when addKey returns an object?\n     */\n    public async getKey(keyId?: string | null): Promise<SecretStorageKeyTuple | null> {\n        if (!keyId) {\n            keyId = await this.getDefaultKeyId();\n        }\n        if (!keyId) {\n            return null;\n        }\n\n        const keyInfo = await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`);\n        return keyInfo ? [keyId, keyInfo] : null;\n    }\n\n    /**\n     * Check whether we have a key with a given ID.\n     *\n     * @param keyId - The ID of the key to check\n     *     for. Defaults to the default key ID if not provided.\n     * @returns Whether we have the key.\n     */\n    public async hasKey(keyId?: string): Promise<boolean> {\n        const key = await this.getKey(keyId);\n        return Boolean(key);\n    }\n\n    /**\n     * Check whether a key matches what we expect based on the key info\n     *\n     * @param key - the key to check\n     * @param info - the key info\n     *\n     * @returns whether or not the key matches\n     */\n    public async checkKey(key: Uint8Array<ArrayBuffer>, info: SecretStorageKeyDescriptionAesV1): Promise<boolean> {\n        if (info.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {\n            if (info.mac) {\n                const { mac } = await calculateKeyCheck(key, info.iv);\n                return trimTrailingEquals(info.mac) === trimTrailingEquals(mac);\n            } else {\n                // if we have no information, we have to assume the key is right\n                return true;\n            }\n        } else {\n            throw new Error(\"Unknown algorithm\");\n        }\n    }\n\n    /**\n     * Implementation of {@link ServerSideSecretStorage#store}.\n     */\n    public async store(name: SecretStorageKey, secret: string | null, keys?: string[] | null): Promise<void> {\n        if (secret === null) {\n            // remove secret\n            await this.accountDataAdapter.setAccountData(name, {});\n            return;\n        }\n\n        const encrypted: Record<string, AESEncryptedSecretStoragePayload> = {};\n\n        if (!keys) {\n            const defaultKeyId = await this.getDefaultKeyId();\n            if (!defaultKeyId) {\n                throw new Error(\"No keys specified and no default key present\");\n            }\n            keys = [defaultKeyId];\n        }\n\n        if (keys.length === 0) {\n            throw new Error(\"Zero keys given to encrypt with!\");\n        }\n\n        for (const keyId of keys) {\n            // get key information from key storage\n            const keyInfo = await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`);\n            if (!keyInfo) {\n                throw new Error(\"Unknown key: \" + keyId);\n            }\n\n            // encrypt secret, based on the algorithm\n            if (keyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {\n                const keys = { [keyId]: keyInfo };\n                const [, encryption] = await this.getSecretStorageKey(keys, name);\n                encrypted[keyId] = await encryption.encrypt(secret);\n            } else {\n                logger.warn(\"unknown algorithm for secret storage key \" + keyId + \": \" + keyInfo.algorithm);\n                // do nothing if we don't understand the encryption algorithm\n            }\n        }\n\n        // save encrypted secret\n        await this.accountDataAdapter.setAccountData(name, { encrypted });\n    }\n\n    /**\n     * Get a secret from storage, and decrypt it.\n     *\n     * {@link SecretStorageCallbacks#getSecretStorageKey} will be called to obtain a secret storage\n     * key to decrypt the secret.\n     *\n     * @param name - the name of the secret - i.e., the \"event type\" stored in the account data\n     *\n     * @returns the decrypted contents of the secret, or \"undefined\" if `name` is not found in\n     *    the user's account data.\n     */\n    public async get(name: SecretStorageKey): Promise<string | undefined> {\n        const secretInfo = await this.accountDataAdapter.getAccountDataFromServer(name);\n        if (!secretInfo) {\n            return;\n        }\n        if (!secretInfo.encrypted) {\n            throw new Error(\"Content is not encrypted!\");\n        }\n\n        // get possible keys to decrypt\n        const keys: Record<string, SecretStorageKeyDescriptionAesV1> = {};\n        for (const keyId of Object.keys(secretInfo.encrypted)) {\n            // get key information from key storage\n            const keyInfo = await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`);\n            const encInfo = secretInfo.encrypted[keyId];\n            // only use keys we understand the encryption algorithm of\n            if (keyInfo?.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {\n                if (encInfo.iv && encInfo.ciphertext && encInfo.mac) {\n                    keys[keyId] = keyInfo;\n                }\n            }\n        }\n\n        if (Object.keys(keys).length === 0) {\n            throw new Error(\n                `Could not decrypt ${name} because none of ` +\n                    `the keys it is encrypted with are for a supported algorithm`,\n            );\n        }\n\n        // fetch private key from app\n        const [keyId, decryption] = await this.getSecretStorageKey(keys, name);\n        const encInfo = secretInfo.encrypted[keyId];\n\n        return decryption.decrypt(encInfo);\n    }\n\n    /**\n     * Check if a secret is stored on the server.\n     *\n     * @param name - the name of the secret\n     *\n     * @returns map of key name to key info the secret is encrypted\n     *     with, or null if it is not present or not encrypted with a trusted\n     *     key\n     */\n    public async isStored(name: SecretStorageKey): Promise<Record<string, SecretStorageKeyDescriptionAesV1> | null> {\n        // check if secret exists\n        const secretInfo = await this.accountDataAdapter.getAccountDataFromServer(name);\n        if (!secretInfo?.encrypted) return null;\n\n        const ret: Record<string, SecretStorageKeyDescriptionAesV1> = {};\n\n        // filter secret encryption keys with supported algorithm\n        for (const keyId of Object.keys(secretInfo.encrypted)) {\n            // get key information from key storage\n            const keyInfo = await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`);\n            if (!keyInfo) continue;\n            const encInfo = secretInfo.encrypted[keyId];\n\n            // only use keys we understand the encryption algorithm of\n            if (keyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {\n                if (encInfo.iv && encInfo.ciphertext && encInfo.mac) {\n                    ret[keyId] = keyInfo;\n                }\n            }\n        }\n        return Object.keys(ret).length ? ret : null;\n    }\n\n    private async getSecretStorageKey(\n        keys: Record<string, SecretStorageKeyDescriptionAesV1>,\n        name: string,\n    ): Promise<[string, Decryptors]> {\n        if (!this.callbacks.getSecretStorageKey) {\n            throw new Error(\"No getSecretStorageKey callback supplied\");\n        }\n\n        const returned = await this.callbacks.getSecretStorageKey({ keys }, name);\n\n        if (!returned) {\n            throw new Error(\"getSecretStorageKey callback returned falsey\");\n        }\n        if (returned.length < 2) {\n            throw new Error(\"getSecretStorageKey callback returned invalid data\");\n        }\n\n        const [keyId, privateKey] = returned;\n        if (!keys[keyId]) {\n            throw new Error(\"App returned unknown key from getSecretStorageKey!\");\n        }\n\n        if (keys[keyId].algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {\n            const decryption = {\n                encrypt: function (secret: string): Promise<AESEncryptedSecretStoragePayload> {\n                    return encryptAESSecretStorageItem(secret, privateKey, name);\n                },\n                decrypt: function (encInfo: AESEncryptedSecretStoragePayload): Promise<string> {\n                    return decryptAESSecretStorageItem(encInfo, privateKey, name);\n                },\n            };\n            return [keyId, decryption];\n        } else {\n            throw new Error(\"Unknown key type: \" + keys[keyId].algorithm);\n        }\n    }\n}\n\n/** trim trailing instances of '=' from a string\n *\n * @internal\n *\n * @param input - input string\n */\nexport function trimTrailingEquals(input: string): string {\n    // according to Sonar and CodeQL, a regex such as /=+$/ is superlinear.\n    // Not sure I believe it, but it's easy enough to work around.\n\n    // find the number of characters before the trailing =\n    let i = input.length;\n    while (i >= 1 && input.charCodeAt(i - 1) == 0x3d) i--;\n\n    // trim to the calculated length\n    if (i < input.length) {\n        return input.substring(0, i);\n    } else {\n        return input;\n    }\n}\n\n// string of zeroes, for calculating the key check\nconst ZERO_STR = \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\";\n\n/**\n * Calculate the MAC for checking the key.\n * See https://spec.matrix.org/v1.11/client-server-api/#msecret_storagev1aes-hmac-sha2, steps 3 and 4.\n *\n * @param key - the key to use\n * @param iv - The initialization vector as a base64-encoded string.\n *     If omitted, a random initialization vector will be created.\n * @returns An object that contains, `mac` and `iv` properties.\n */\nexport function calculateKeyCheck(\n    key: Uint8Array<ArrayBuffer>,\n    iv?: string,\n): Promise<AESEncryptedSecretStoragePayload> {\n    return encryptAESSecretStorageItem(ZERO_STR, key, \"\", iv);\n}\n","/*\nCopyright 2024 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { CapabilityPoller } from \"./capabilityPoller.ts\";\nimport { type IHttpOpts, type MatrixHttpApi, Method } from \"./http-api/index.ts\";\nimport type { Logger } from \"./logger.ts\";\n\nexport interface ICapability {\n    enabled: boolean;\n}\n\nexport interface IChangePasswordCapability extends ICapability {}\n\nexport interface IThreadsCapability extends ICapability {}\n\nexport interface IGetLoginTokenCapability extends ICapability {}\n\nexport interface ISetDisplayNameCapability extends ICapability {}\n\nexport interface ISetAvatarUrlCapability extends ICapability {}\n\nexport interface IProfileFieldsCapability extends ICapability {}\n\nexport enum RoomVersionStability {\n    Stable = \"stable\",\n    Unstable = \"unstable\",\n}\n\nexport interface IRoomVersionsCapability {\n    default: string;\n    available: Record<string, RoomVersionStability>;\n}\n\n/**\n * A representation of the capabilities advertised by a homeserver as defined by\n * [Capabilities negotiation](https://spec.matrix.org/v1.6/client-server-api/#get_matrixclientv3capabilities).\n */\nexport interface Capabilities {\n    [key: string]: any;\n    \"m.change_password\"?: IChangePasswordCapability;\n    \"m.room_versions\"?: IRoomVersionsCapability;\n    \"io.element.thread\"?: IThreadsCapability;\n    \"m.get_login_token\"?: IGetLoginTokenCapability;\n    \"org.matrix.msc3882.get_login_token\"?: IGetLoginTokenCapability;\n    \"m.set_displayname\"?: ISetDisplayNameCapability;\n    \"m.set_avatar_url\"?: ISetAvatarUrlCapability;\n    \"uk.tcpip.msc4133.profile_fields\"?: IProfileFieldsCapability;\n    /**\n     * Since Matrix v1.16\n     */\n    \"m.profile_fields\"?: IProfileFieldsCapability;\n}\n\ntype CapabilitiesResponse = {\n    capabilities: Capabilities;\n};\n\n/**\n * Manages storing and periodically refreshing the server capabilities.\n */\nexport class ServerCapabilities extends CapabilityPoller<Capabilities> {\n    public constructor(logger: Logger, http: MatrixHttpApi<IHttpOpts & { onlyData: true }>) {\n        super(logger, http, \"server capabilities\");\n    }\n    /**\n     * Fetches the latest server capabilities from the homeserver and returns them, or rejects\n     * on failure.\n     */\n    public fetch = async (): Promise<Capabilities> => {\n        const resp = await this.http.authedRequest<CapabilitiesResponse>(Method.Get, \"/capabilities\");\n        this.cached = resp[\"capabilities\"];\n        return this.cached;\n    };\n}\n","/*\nCopyright 2022-2024 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { logger } from \"./logger.ts\";\nimport { type MatrixClient } from \"./client.ts\";\nimport { type IRoomEvent, type IStateEvent } from \"./sync-accumulator.ts\";\nimport { TypedEventEmitter } from \"./models/typed-event-emitter.ts\";\nimport { sleep } from \"./utils.ts\";\nimport { type HTTPError } from \"./http-api/index.ts\";\n\n// /sync requests allow you to set a timeout= but the request may continue\n// beyond that and wedge forever, so we need to track how long we are willing\n// to keep open the connection. This constant is *ADDED* to the timeout= value\n// to determine the max time we're willing to wait.\nconst BUFFER_PERIOD_MS = 10 * 1000;\n\nexport const MSC3575_WILDCARD = \"*\";\nexport const MSC3575_STATE_KEY_ME = \"$ME\";\nexport const MSC3575_STATE_KEY_LAZY = \"$LAZY\";\n\n/**\n * Represents a subscription to a room or set of rooms. Controls which events are returned.\n */\nexport interface MSC3575RoomSubscription {\n    required_state?: string[][];\n    timeline_limit?: number;\n    include_old_rooms?: MSC3575RoomSubscription;\n}\n\n/**\n * Controls which rooms are returned in a given list.\n */\nexport interface MSC3575Filter {\n    is_dm?: boolean;\n    is_encrypted?: boolean;\n    is_invite?: boolean;\n    room_name_like?: string;\n    room_types?: string[];\n    not_room_types?: string[];\n    spaces?: string[];\n    tags?: string[];\n    not_tags?: string[];\n}\n\n/**\n * Represents a list subscription.\n */\nexport interface MSC3575List extends MSC3575RoomSubscription {\n    ranges: number[][];\n    sort?: string[];\n    filters?: MSC3575Filter;\n    slow_get_all_rooms?: boolean;\n}\n\n/**\n * A complete Sliding Sync request.\n */\nexport interface MSC3575SlidingSyncRequest {\n    // json body params\n    lists?: Record<string, MSC3575List>;\n    unsubscribe_rooms?: string[];\n    room_subscriptions?: Record<string, MSC3575RoomSubscription>;\n    extensions?: object;\n    txn_id?: string;\n\n    // query params\n    pos?: string;\n    timeout?: number;\n    clientTimeout?: number;\n}\n\n/**\n * New format of hero introduced in MSC4186 with display name and avatar URL\n * in addition to just user_id (as it is on the wire, with underscores)\n * as opposed to Hero in room-summary.ts which has fields in camelCase\n * (and also a flag to note what format the hero came from).\n */\nexport interface MSC4186Hero {\n    user_id: string;\n    displayname?: string;\n    avatar_url?: string;\n}\n\nexport interface MSC3575RoomData {\n    name: string;\n    required_state: IStateEvent[];\n    timeline: (IRoomEvent | IStateEvent)[];\n    heroes?: MSC4186Hero[];\n    notification_count?: number;\n    highlight_count?: number;\n    joined_count?: number;\n    invited_count?: number;\n    invite_state?: IStateEvent[];\n    initial?: boolean;\n    limited?: boolean;\n    is_dm?: boolean;\n    prev_batch?: string;\n    num_live?: number;\n    bump_stamp?: number;\n}\n\ninterface ListResponse {\n    count: number;\n}\n\n/**\n * A complete Sliding Sync response\n */\nexport interface MSC3575SlidingSyncResponse {\n    pos: string;\n    txn_id?: string;\n    lists: Record<string, ListResponse>;\n    rooms: Record<string, MSC3575RoomData>;\n    extensions: Record<string, object>;\n}\n\nexport enum SlidingSyncState {\n    /**\n     * Fired by SlidingSyncEvent.Lifecycle event immediately before processing the response.\n     */\n    RequestFinished = \"FINISHED\",\n    /**\n     * Fired by SlidingSyncEvent.Lifecycle event immediately after all room data listeners have been\n     * invoked, but before list listeners.\n     */\n    Complete = \"COMPLETE\",\n}\n\n/**\n * Internal Class. SlidingList represents a single list in sliding sync. The list can have filters,\n * multiple sliding windows, and maintains the index-\\>room_id mapping.\n */\nclass SlidingList {\n    private list!: MSC3575List;\n    private isModified?: boolean;\n\n    // returned data\n    public joinedCount = 0;\n\n    /**\n     * Construct a new sliding list.\n     * @param list - The range, sort and filter values to use for this list.\n     */\n    public constructor(list: MSC3575List) {\n        this.replaceList(list);\n    }\n\n    /**\n     * Mark this list as modified or not. Modified lists will return sticky params with calls to getList.\n     * This is useful for the first time the list is sent, or if the list has changed in some way.\n     * @param modified - True to mark this list as modified so all sticky parameters will be re-sent.\n     */\n    public setModified(modified: boolean): void {\n        this.isModified = modified;\n    }\n\n    /**\n     * Update the list range for this list. Does not affect modified status as list ranges are non-sticky.\n     * @param newRanges - The new ranges for the list\n     */\n    public updateListRange(newRanges: number[][]): void {\n        this.list.ranges = JSON.parse(JSON.stringify(newRanges));\n    }\n\n    /**\n     * Replace list parameters. All fields will be replaced with the new list parameters.\n     * @param list - The new list parameters\n     */\n    public replaceList(list: MSC3575List): void {\n        list.filters = list.filters ?? {};\n        list.ranges = list.ranges ?? [];\n        this.list = JSON.parse(JSON.stringify(list));\n        this.isModified = true;\n\n        // reset values as the join count may be very different (if filters changed) including the rooms\n        // (e.g. sort orders or sliding window ranges changed)\n\n        // the total number of joined rooms according to the server, always >= len(roomIndexToRoomId)\n        this.joinedCount = 0;\n    }\n\n    /**\n     * Return a copy of the list suitable for a request body.\n     * @param forceIncludeAllParams - True to forcibly include all params even if the list\n     * hasn't been modified. Callers may want to do this if they are modifying the list prior to calling\n     * updateList.\n     */\n    public getList(forceIncludeAllParams: boolean): MSC3575List {\n        let list = {\n            ranges: JSON.parse(JSON.stringify(this.list.ranges)),\n        };\n        if (this.isModified || forceIncludeAllParams) {\n            list = JSON.parse(JSON.stringify(this.list));\n        }\n        return list;\n    }\n}\n\n/**\n * When onResponse extensions should be invoked: before or after processing the main response.\n */\nexport enum ExtensionState {\n    // Call onResponse before processing the response body. This is useful when your extension is\n    // preparing the ground for the response body e.g. processing to-device messages before the\n    // encrypted event arrives.\n    PreProcess = \"ExtState.PreProcess\",\n    // Call onResponse after processing the response body. This is useful when your extension is\n    // decorating data from the client, and you rely on MatrixClient.getRoom returning the Room object\n    // e.g. room account data.\n    PostProcess = \"ExtState.PostProcess\",\n}\n\n/**\n * An interface that must be satisfied to register extensions\n */\nexport interface Extension<Req extends object, Res extends object> {\n    /**\n     * The extension name to go under 'extensions' in the request body.\n     * @returns The JSON key.\n     */\n    name(): string;\n    /**\n     * A function which is called when the request JSON is being formed.\n     * Returns the data to insert under this key.\n     * @param isInitial - True when this is part of the initial request.\n     * @returns The request JSON to send.\n     */\n    onRequest(isInitial: boolean): Promise<Req>;\n    /**\n     * A function which is called when there is response JSON under this extension.\n     * @param data - The response JSON under the extension name.\n     */\n    onResponse(data: Res): Promise<void>;\n    /**\n     * Controls when onResponse should be called.\n     * @returns The state when it should be called.\n     */\n    when(): ExtensionState;\n}\n\n/**\n * Events which can be fired by the SlidingSync class. These are designed to provide different levels\n * of information when processing sync responses.\n *  - RoomData: concerns rooms, useful for SlidingSyncSdk to update its knowledge of rooms.\n *  - Lifecycle: concerns callbacks at various well-defined points in the sync process.\n * Specifically, the order of event invocation is:\n *  - Lifecycle (state=RequestFinished)\n *  - RoomData (N times)\n *  - Lifecycle (state=Complete)\n */\nexport enum SlidingSyncEvent {\n    /**\n     * This event fires when there are updates for a room. Fired as and when rooms are encountered\n     * in the response.\n     */\n    RoomData = \"SlidingSync.RoomData\",\n    /**\n     * This event fires at various points in the /sync loop lifecycle.\n     *  - SlidingSyncState.RequestFinished: Fires after we receive a valid response but before the\n     * response has been processed. Perform any pre-process steps here. If there was a problem syncing,\n     * `err` will be set (e.g network errors).\n     *  - SlidingSyncState.Complete: Fires after the response has been processed.\n     */\n    Lifecycle = \"SlidingSync.Lifecycle\",\n}\n\nexport type SlidingSyncEventHandlerMap = {\n    [SlidingSyncEvent.RoomData]: (roomId: string, roomData: MSC3575RoomData) => Promise<void> | void;\n    [SlidingSyncEvent.Lifecycle]: (\n        state: SlidingSyncState,\n        resp: MSC3575SlidingSyncResponse | null,\n        err?: Error,\n    ) => void;\n};\n\n/**\n * SlidingSync is a high-level data structure which controls the majority of sliding sync.\n * It has no hooks into JS SDK except for needing a MatrixClient to perform the HTTP request.\n * This means this class (and everything it uses) can be used in isolation from JS SDK if needed.\n * To hook this up with the JS SDK, you need to use SlidingSyncSdk.\n */\nexport class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSyncEventHandlerMap> {\n    private lists: Map<string, SlidingList>;\n    private listModifiedCount = 0;\n    private terminated = false;\n    // flag set when resend() is called because we cannot rely on detecting AbortError in JS SDK :(\n    private needsResend = false;\n    // map of extension name to req/resp handler\n    private extensions: Record<string, Extension<any, any>> = {};\n\n    private desiredRoomSubscriptions = new Set<string>(); // the *desired* room subscriptions\n    private confirmedRoomSubscriptions = new Set<string>();\n\n    // map of custom subscription name to the subscription\n    private customSubscriptions: Map<string, MSC3575RoomSubscription> = new Map();\n    // map of room ID to custom subscription name\n    private roomIdToCustomSubscription: Map<string, string> = new Map();\n\n    private pendingReq?: Promise<MSC3575SlidingSyncResponse>;\n    private abortController?: AbortController;\n\n    /**\n     * Create a new sliding sync instance\n     * @param proxyBaseUrl - The base URL of the sliding sync proxy\n     * @param lists - The lists to use for sliding sync.\n     * @param roomSubscriptionInfo - The params to use for room subscriptions.\n     * @param client - The client to use for /sync calls.\n     * @param timeoutMS - The number of milliseconds to wait for a response.\n     */\n    public constructor(\n        private readonly proxyBaseUrl: string,\n        lists: Map<string, MSC3575List>,\n        private roomSubscriptionInfo: MSC3575RoomSubscription,\n        private readonly client: MatrixClient,\n        private readonly timeoutMS: number,\n    ) {\n        super();\n        this.lists = new Map<string, SlidingList>();\n        lists.forEach((list, key) => {\n            this.lists.set(key, new SlidingList(list));\n        });\n    }\n\n    /**\n     * Add a custom room subscription, referred to by an arbitrary name. If a subscription with this\n     * name already exists, it is replaced. No requests are sent by calling this method.\n     * @param name - The name of the subscription. Only used to reference this subscription in\n     * useCustomSubscription.\n     * @param sub - The subscription information.\n     */\n    public addCustomSubscription(name: string, sub: MSC3575RoomSubscription): void {\n        if (this.customSubscriptions.has(name)) {\n            logger.warn(`addCustomSubscription: ${name} already exists as a custom subscription, ignoring.`);\n            return;\n        }\n        this.customSubscriptions.set(name, sub);\n    }\n\n    /**\n     * Use a custom subscription previously added via addCustomSubscription. No requests are sent\n     * by calling this method. Use modifyRoomSubscriptions to resend subscription information.\n     * @param roomId - The room to use the subscription in.\n     * @param name - The name of the subscription. If this name is unknown, the default subscription\n     * will be used.\n     */\n    public useCustomSubscription(roomId: string, name: string): void {\n        // We already know about this custom subscription, as it is immutable,\n        // we don't need to unconfirm the subscription.\n        if (this.roomIdToCustomSubscription.get(roomId) === name) {\n            return;\n        }\n        this.roomIdToCustomSubscription.set(roomId, name);\n        // unconfirm this subscription so a resend() will send it up afresh.\n        this.confirmedRoomSubscriptions.delete(roomId);\n    }\n\n    /**\n     * Get the room index data for a list.\n     * @param key - The list key\n     * @returns The list data which contains the rooms in this list\n     */\n    public getListData(key: string): { joinedCount: number } | null {\n        const data = this.lists.get(key);\n        if (!data) {\n            return null;\n        }\n        return {\n            joinedCount: data.joinedCount,\n        };\n    }\n\n    /**\n     * Get the full request list parameters for a list index. This function is provided for callers to use\n     * in conjunction with setList to update fields on an existing list.\n     * @param key - The list key to get the params for.\n     * @returns A copy of the list params or undefined.\n     */\n    public getListParams(key: string): MSC3575List | null {\n        const params = this.lists.get(key);\n        if (!params) {\n            return null;\n        }\n        return params.getList(true);\n    }\n\n    /**\n     * Set new ranges for an existing list. Calling this function when _only_ the ranges have changed\n     * is more efficient than calling setList(index,list) as this function won't resend sticky params,\n     * whereas setList always will.\n     * @param key - The list key to modify\n     * @param ranges - The new ranges to apply.\n     * @returns A promise which resolves to the transaction ID when it has been received down sync\n     * (or rejects with the transaction ID if the action was not applied e.g the request was cancelled\n     * immediately after sending, in which case the action will be applied in the subsequent request)\n     */\n    public setListRanges(key: string, ranges: number[][]): void {\n        const list = this.lists.get(key);\n        if (!list) {\n            throw new Error(\"no list with key \" + key);\n        }\n        list.updateListRange(ranges);\n        this.resend();\n    }\n\n    /**\n     * Add or replace a list. Calling this function will interrupt the /sync request to resend new\n     * lists.\n     * @param key - The key to modify\n     * @param list - The new list parameters.\n     * @returns A promise which resolves to the transaction ID when it has been received down sync\n     * (or rejects with the transaction ID if the action was not applied e.g the request was cancelled\n     * immediately after sending, in which case the action will be applied in the subsequent request)\n     */\n    public setList(key: string, list: MSC3575List): void {\n        const existingList = this.lists.get(key);\n        if (existingList) {\n            existingList.replaceList(list);\n            this.lists.set(key, existingList);\n        } else {\n            this.lists.set(key, new SlidingList(list));\n        }\n        this.listModifiedCount += 1;\n        this.resend();\n    }\n\n    /**\n     * Get the room subscriptions for the sync API.\n     * @returns A copy of the desired room subscriptions.\n     */\n    public getRoomSubscriptions(): Set<string> {\n        return new Set(Array.from(this.desiredRoomSubscriptions));\n    }\n\n    /**\n     * Modify the room subscriptions for the sync API. Calling this function will interrupt the\n     * /sync request to resend new subscriptions. If the /sync stream has not started, this will\n     * prepare the room subscriptions for when start() is called.\n     * @param s - The new desired room subscriptions.\n     */\n    public modifyRoomSubscriptions(s: Set<string>): void {\n        this.desiredRoomSubscriptions = s;\n        this.resend();\n    }\n\n    /**\n     * Modify which events to retrieve for room subscriptions. Invalidates all room subscriptions\n     * such that they will be sent up afresh.\n     * @param rs - The new room subscription fields to fetch.\n     */\n    public modifyRoomSubscriptionInfo(rs: MSC3575RoomSubscription): void {\n        this.roomSubscriptionInfo = rs;\n        this.confirmedRoomSubscriptions = new Set<string>();\n        this.resend();\n    }\n\n    /**\n     * Register an extension to send with the /sync request.\n     * @param ext - The extension to register.\n     */\n    public registerExtension(ext: Extension<any, any>): void {\n        if (this.extensions[ext.name()]) {\n            throw new Error(`registerExtension: ${ext.name()} already exists as an extension`);\n        }\n        this.extensions[ext.name()] = ext;\n    }\n\n    private async getExtensionRequest(isInitial: boolean): Promise<Record<string, object | undefined>> {\n        const ext: Record<string, object | undefined> = {};\n        for (const extName in this.extensions) {\n            ext[extName] = await this.extensions[extName].onRequest(isInitial);\n        }\n        return ext;\n    }\n\n    private async onPreExtensionsResponse(ext: Record<string, object>): Promise<void> {\n        await Promise.all(\n            Object.keys(ext).map(async (extName) => {\n                if (this.extensions[extName].when() == ExtensionState.PreProcess) {\n                    await this.extensions[extName].onResponse(ext[extName]);\n                }\n            }),\n        );\n    }\n\n    private async onPostExtensionsResponse(ext: Record<string, object>): Promise<void> {\n        await Promise.all(\n            Object.keys(ext).map(async (extName) => {\n                if (this.extensions[extName].when() == ExtensionState.PostProcess) {\n                    await this.extensions[extName].onResponse(ext[extName]);\n                }\n            }),\n        );\n    }\n\n    /**\n     * Invoke all attached room data listeners.\n     * @param roomId - The room which received some data.\n     * @param roomData - The raw sliding sync response JSON.\n     */\n    private async invokeRoomDataListeners(roomId: string, roomData: MSC3575RoomData): Promise<void> {\n        if (!roomData.required_state) {\n            roomData.required_state = [];\n        }\n        if (!roomData.timeline) {\n            roomData.timeline = [];\n        }\n        await this.emitPromised(SlidingSyncEvent.RoomData, roomId, roomData);\n    }\n\n    /**\n     * Invoke all attached lifecycle listeners.\n     * @param state - The Lifecycle state\n     * @param resp - The raw sync response JSON\n     * @param err - Any error that occurred when making the request e.g. network errors.\n     */\n    private invokeLifecycleListeners(\n        state: SlidingSyncState,\n        resp: MSC3575SlidingSyncResponse | null,\n        err?: Error,\n    ): void {\n        this.emit(SlidingSyncEvent.Lifecycle, state, resp, err);\n    }\n\n    /**\n     * Resend a Sliding Sync request. Used when something has changed in the request.\n     */\n    public resend(): void {\n        this.needsResend = true;\n        this.abortController?.abort();\n        this.abortController = new AbortController();\n    }\n\n    /**\n     * Stop syncing with the server.\n     */\n    public stop(): void {\n        this.terminated = true;\n        this.abortController?.abort();\n        // remove all listeners so things can be GC'd\n        this.removeAllListeners(SlidingSyncEvent.Lifecycle);\n        this.removeAllListeners(SlidingSyncEvent.RoomData);\n    }\n\n    /**\n     * Re-setup this connection e.g in the event of an expired session.\n     */\n    private resetup(): void {\n        logger.warn(\"SlidingSync: resetting connection info\");\n        // resend sticky params and de-confirm all subscriptions\n        this.lists.forEach((l) => {\n            l.setModified(true);\n        });\n        this.confirmedRoomSubscriptions = new Set<string>(); // leave desired ones alone though!\n        // reset the connection as we might be wedged\n        this.resend();\n    }\n\n    /**\n     * Start syncing with the server. Blocks until stopped.\n     */\n    public async start(): Promise<void> {\n        this.abortController = new AbortController();\n\n        let currentPos: string | undefined;\n        while (!this.terminated) {\n            this.needsResend = false;\n            let resp: MSC3575SlidingSyncResponse | undefined;\n            try {\n                const reqLists: Record<string, MSC3575List> = {};\n                this.lists.forEach((l: SlidingList, key: string) => {\n                    reqLists[key] = l.getList(true);\n                });\n                const reqBody: MSC3575SlidingSyncRequest = {\n                    lists: reqLists,\n                    pos: currentPos,\n                    timeout: this.timeoutMS,\n                    clientTimeout: this.timeoutMS + BUFFER_PERIOD_MS,\n                    extensions: await this.getExtensionRequest(currentPos === undefined),\n                };\n                // check if we are (un)subscribing to a room and modify request this one time for it\n                const newSubscriptions = difference(this.desiredRoomSubscriptions, this.confirmedRoomSubscriptions);\n                const unsubscriptions = difference(this.confirmedRoomSubscriptions, this.desiredRoomSubscriptions);\n                if (unsubscriptions.size > 0) {\n                    reqBody.unsubscribe_rooms = Array.from(unsubscriptions);\n                }\n                if (newSubscriptions.size > 0) {\n                    reqBody.room_subscriptions = {};\n                    for (const roomId of newSubscriptions) {\n                        const customSubName = this.roomIdToCustomSubscription.get(roomId);\n                        let sub = this.roomSubscriptionInfo;\n                        if (customSubName && this.customSubscriptions.has(customSubName)) {\n                            sub = this.customSubscriptions.get(customSubName)!;\n                        }\n                        reqBody.room_subscriptions[roomId] = sub;\n                    }\n                }\n                this.pendingReq = this.client.slidingSync(reqBody, this.proxyBaseUrl, this.abortController.signal);\n                resp = await this.pendingReq;\n                currentPos = resp.pos;\n                // update what we think we're subscribed to.\n                for (const roomId of newSubscriptions) {\n                    this.confirmedRoomSubscriptions.add(roomId);\n                }\n                for (const roomId of unsubscriptions) {\n                    this.confirmedRoomSubscriptions.delete(roomId);\n                }\n                // mark all these lists as having been sent as sticky so we don't keep sending sticky params\n                this.lists.forEach((l) => {\n                    l.setModified(false);\n                });\n                // set default empty values so we don't need to null check\n                resp.lists = resp.lists ?? {};\n                resp.rooms = resp.rooms ?? {};\n                resp.extensions = resp.extensions ?? {};\n                Object.keys(resp.lists).forEach((key: string) => {\n                    const list = this.lists.get(key);\n                    if (!list || !resp) {\n                        return;\n                    }\n                    list.joinedCount = resp.lists[key].count;\n                });\n                this.invokeLifecycleListeners(SlidingSyncState.RequestFinished, resp);\n            } catch (err) {\n                if ((<HTTPError>err).httpStatus) {\n                    this.invokeLifecycleListeners(SlidingSyncState.RequestFinished, null, <Error>err);\n                    if ((<HTTPError>err).httpStatus === 400) {\n                        // session probably expired TODO: assign an errcode\n                        // so drop state and re-request\n                        this.resetup();\n                        currentPos = undefined;\n                        await sleep(50); // in case the 400 was for something else; don't tightloop\n                        continue;\n                    } // else fallthrough to generic error handling\n                } else if (this.needsResend || (<Error>err).name === \"AbortError\") {\n                    continue; // don't sleep as we caused this error by abort()ing the request.\n                }\n                logger.error(err);\n                await sleep(5000);\n            }\n            if (!resp) {\n                continue;\n            }\n            await this.onPreExtensionsResponse(resp.extensions);\n\n            for (const roomId in resp.rooms) {\n                await this.invokeRoomDataListeners(roomId, resp!.rooms[roomId]);\n            }\n\n            this.invokeLifecycleListeners(SlidingSyncState.Complete, resp);\n            await this.onPostExtensionsResponse(resp.extensions);\n        }\n    }\n}\n\nconst difference = (setA: Set<string>, setB: Set<string>): Set<string> => {\n    const diff = new Set(setA);\n    for (const elem of setB) {\n        diff.delete(elem);\n    }\n    return diff;\n};\n","/*\nCopyright 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type IMinimalEvent } from \"./sync-accumulator.ts\";\nimport { EventType } from \"./@types/event.ts\";\nimport { isSupportedReceiptType, MapWithDefault, recursiveMapToObject } from \"./utils.ts\";\nimport { type IContent } from \"./models/event.ts\";\nimport { type ReceiptContent, type ReceiptType } from \"./@types/read_receipts.ts\";\n\ninterface AccumulatedReceipt {\n    data: IMinimalEvent;\n    type: ReceiptType;\n    eventId: string;\n}\n\n/**\n * Summarises the read receipts within a room. Used by the sync accumulator.\n *\n * Given receipts for users, picks the most recently-received one and provides\n * the results in a new fake receipt event returned from\n * buildAccumulatedReceiptEvent().\n *\n * Handles unthreaded receipts and receipts in each thread separately, so the\n * returned event contains the most recently received unthreaded receipt, and\n * the most recently received receipt in each thread.\n */\nexport class ReceiptAccumulator {\n    /** user_id -\\> most-recently-received unthreaded receipt */\n    private unthreadedReadReceipts: Map<string, AccumulatedReceipt> = new Map();\n\n    /** thread_id -\\> user_id -\\> most-recently-received receipt for this thread */\n    private threadedReadReceipts: MapWithDefault<string, Map<string, AccumulatedReceipt>> = new MapWithDefault(\n        () => new Map(),\n    );\n\n    /**\n     * Provide an unthreaded receipt for this user. Overwrites any other\n     * unthreaded receipt we have for this user.\n     */\n    private setUnthreaded(userId: string, receipt: AccumulatedReceipt): void {\n        this.unthreadedReadReceipts.set(userId, receipt);\n    }\n\n    /**\n     * Provide a receipt for this user in this thread. Overwrites any other\n     * receipt we have for this user in this thread.\n     */\n    private setThreaded(threadId: string, userId: string, receipt: AccumulatedReceipt): void {\n        this.threadedReadReceipts.getOrCreate(threadId).set(userId, receipt);\n    }\n\n    /**\n     * @returns an iterator of pairs of [userId, AccumulatedReceipt] - all the\n     *          most recently-received unthreaded receipts for each user.\n     * @yields pairs of [userId, AccumulatedReceipt]\n     */\n    private allUnthreaded(): IterableIterator<[string, AccumulatedReceipt]> {\n        return this.unthreadedReadReceipts.entries();\n    }\n\n    /**\n     * @returns an iterator of pairs of [userId, AccumulatedReceipt] - all the\n     *          most recently-received threaded receipts for each user, in all\n     *          threads.\n     * @yields pairs of [userId, AccumulatedReceipt]\n     */\n    private *allThreaded(): IterableIterator<[string, AccumulatedReceipt]> {\n        for (const receiptsForThread of this.threadedReadReceipts.values()) {\n            for (const e of receiptsForThread.entries()) {\n                yield e;\n            }\n        }\n    }\n\n    /**\n     * Given a list of ephemeral events, find the receipts and store the\n     * relevant ones to be returned later from buildAccumulatedReceiptEvent().\n     */\n    public consumeEphemeralEvents(events: IMinimalEvent[] | undefined): void {\n        events?.forEach((e) => {\n            if (e.type !== EventType.Receipt || !e.content) {\n                // This means we'll drop unknown ephemeral events but that\n                // seems okay.\n                return;\n            }\n\n            // Handle m.receipt events. They clobber based on:\n            //   (user_id, receipt_type)\n            // but they are keyed in the event as:\n            //   content:{ $event_id: { $receipt_type: { $user_id: {json} }}}\n            // so store them in the former so we can accumulate receipt deltas\n            // quickly and efficiently (we expect a lot of them). Fold the\n            // receipt type into the key name since we only have 1 at the\n            // moment (m.read) and nested JSON objects are slower and more\n            // of a hassle to work with. We'll inflate this back out when\n            // getJSON() is called.\n            Object.keys(e.content).forEach((eventId) => {\n                Object.entries<ReceiptContent>(e.content[eventId]).forEach(([key, value]) => {\n                    if (!isSupportedReceiptType(key)) return;\n\n                    for (const userId of Object.keys(value)) {\n                        const data = e.content[eventId][key][userId];\n\n                        const receipt = {\n                            data: e.content[eventId][key][userId],\n                            type: key as ReceiptType,\n                            eventId,\n                        };\n\n                        // In a world that supports threads, read receipts normally have\n                        // a `thread_id` which is either the thread they belong in or\n                        // `MAIN_ROOM_TIMELINE`, so we normally use `setThreaded(...)`\n                        // here. The `MAIN_ROOM_TIMELINE` is just treated as another\n                        // thread.\n                        //\n                        // We still encounter read receipts that are \"unthreaded\"\n                        // (missing the `thread_id` property). These come from clients\n                        // that don't support threads, and from threaded clients that\n                        // are doing a \"Mark room as read\" operation. Unthreaded\n                        // receipts mark everything \"before\" them as read, in all\n                        // threads, where \"before\" means in Sync Order i.e. the order\n                        // the events were received from the homeserver in a sync.\n                        // [Note: we have some bugs where we use timestamp order instead\n                        // of Sync Order, because we don't correctly remember the Sync\n                        // Order. See #3325.]\n                        //\n                        // Calling the wrong method will cause incorrect behavior like\n                        // messages re-appearing as \"new\" when you already read them\n                        // previously.\n                        if (!data.thread_id) {\n                            this.setUnthreaded(userId, receipt);\n                        } else {\n                            this.setThreaded(data.thread_id, userId, receipt);\n                        }\n                    }\n                });\n            });\n        });\n    }\n\n    /**\n     * Build a receipt event that contains all relevant information for this\n     * room, taking the most recently received receipt for each user in an\n     * unthreaded context, and in each thread.\n     */\n    public buildAccumulatedReceiptEvent(roomId: string): IMinimalEvent | null {\n        const receiptEvent: IMinimalEvent = {\n            type: EventType.Receipt,\n            room_id: roomId,\n            content: {\n                // $event_id: { \"m.read\": { $user_id: $json } }\n            } as IContent,\n        };\n\n        const receiptEventContent: MapWithDefault<\n            string,\n            MapWithDefault<ReceiptType, Map<string, object>>\n        > = new MapWithDefault(() => new MapWithDefault(() => new Map()));\n\n        for (const [userId, receiptData] of this.allUnthreaded()) {\n            receiptEventContent\n                .getOrCreate(receiptData.eventId)\n                .getOrCreate(receiptData.type)\n                .set(userId, receiptData.data);\n        }\n\n        for (const [userId, receiptData] of this.allThreaded()) {\n            receiptEventContent\n                .getOrCreate(receiptData.eventId)\n                .getOrCreate(receiptData.type)\n                .set(userId, receiptData.data);\n        }\n\n        receiptEvent.content = recursiveMapToObject(receiptEventContent);\n\n        return receiptEventContent.size > 0 ? receiptEvent : null;\n    }\n}\n","/*\nCopyright 2017 - 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n/**\n * This is an internal module. See {@link SyncAccumulator} for the public class.\n */\n\nimport { logger } from \"./logger.ts\";\nimport { deepCopy } from \"./utils.ts\";\nimport { MAX_STICKY_DURATION_MS, type IContent, type IUnsigned } from \"./models/event.ts\";\nimport { type IRoomSummary } from \"./models/room-summary.ts\";\nimport { type EventType } from \"./@types/event.ts\";\nimport { UNREAD_THREAD_NOTIFICATIONS } from \"./@types/sync.ts\";\nimport { ReceiptAccumulator } from \"./receipt-accumulator.ts\";\nimport { type OlmEncryptionInfo } from \"./crypto-api/index.ts\";\nimport { type SyncUserProfile } from \"./matrix.ts\";\n\ninterface IOpts {\n    /**\n     * The ideal maximum number of timeline entries to keep in the sync response.\n     * This is best-effort, as clients do not always have a back-pagination token for each event,\n     * so it's possible there may be slightly *less* than this value. There will never be more.\n     * This cannot be 0 or else it makes it impossible to scroll back in a room.\n     * Default: 50.\n     */\n    maxTimelineEntries?: number;\n    /**\n     * Whether to use the stable or unstable fields for user profiles.\n     */\n    profileFieldsStable?: boolean;\n}\n\nexport interface IMinimalEvent {\n    content: IContent;\n    type: EventType | string;\n    room_id?: string;\n    unsigned?: IUnsigned;\n}\n\nexport interface IEphemeral {\n    events: IMinimalEvent[];\n}\n\n/* eslint-disable camelcase */\ninterface UnreadNotificationCounts {\n    highlight_count?: number;\n    notification_count?: number;\n}\n\nexport interface IRoomEvent extends IMinimalEvent {\n    event_id: string;\n    sender: string;\n    origin_server_ts: number;\n}\n\nexport interface IStateEvent extends IRoomEvent {\n    prev_content?: IContent;\n    state_key: string;\n}\n\ninterface IState {\n    events: IStateEvent[];\n}\n\nexport interface ITimeline {\n    events: Array<IRoomEvent | IStateEvent>;\n    limited?: boolean;\n    prev_batch: string | null;\n}\n\ntype StickyEventFields = {\n    msc4354_sticky: { duration_ms: number };\n    content: { msc4354_sticky_key?: string };\n};\n\nexport type IStickyEvent = IRoomEvent & StickyEventFields;\n\nexport type IStickyStateEvent = IStateEvent & StickyEventFields;\n\nexport interface ISticky {\n    events: Array<IStickyEvent | IStickyStateEvent>;\n}\n\nexport interface IJoinedRoom {\n    \"summary\": IRoomSummary;\n    // One of `state` or `state_after` is required.\n    \"state\"?: IState;\n    \"org.matrix.msc4222.state_after\"?: IState; // https://github.com/matrix-org/matrix-spec-proposals/pull/4222\n    \"msc4354_sticky\"?: ISticky; // https://github.com/matrix-org/matrix-spec-proposals/pull/4354\n    \"timeline\": ITimeline;\n    \"ephemeral\": IEphemeral;\n    \"account_data\": IAccountData;\n    \"unread_notifications\": UnreadNotificationCounts;\n    \"unread_thread_notifications\"?: Record<string, UnreadNotificationCounts>;\n    \"org.matrix.msc3773.unread_thread_notifications\"?: Record<string, UnreadNotificationCounts>;\n}\n\nexport interface IStrippedState {\n    content: IContent;\n    state_key: string;\n    type: EventType | string;\n    sender: string;\n}\n\nexport interface IInviteState {\n    events: IStrippedState[];\n}\n\nexport interface IKnockState {\n    events: IStrippedState[];\n}\n\nexport interface IInvitedRoom {\n    invite_state: IInviteState;\n}\n\nexport interface ILeftRoom {\n    // One of `state` or `state_after` is required.\n    \"state\"?: IState;\n    \"org.matrix.msc4222.state_after\"?: IState;\n    \"timeline\": ITimeline;\n    \"account_data\": IAccountData;\n}\n\nexport interface IKnockedRoom {\n    knock_state: IKnockState;\n}\n\nexport interface IRooms {\n    [Category.Join]: Record<string, IJoinedRoom>;\n    [Category.Invite]: Record<string, IInvitedRoom>;\n    [Category.Leave]: Record<string, ILeftRoom>;\n    [Category.Knock]: Record<string, IKnockedRoom>;\n}\n\ninterface IPresence {\n    events: IMinimalEvent[];\n}\n\ninterface IAccountData {\n    events: IMinimalEvent[];\n}\n\n/** A to-device message as received from the sync. */\nexport interface IToDeviceEvent {\n    content: IContent;\n    sender: string;\n    type: string;\n}\n\n/**\n * A (possibly decrypted) to-device message after it has been successfully processed by the sdk.\n *\n * If the message was encrypted, the `encryptionInfo` field will contain the encryption information.\n * If the message was sent in clear, this field will be null.\n *\n * The `message` field contains the message `type`, `content`, and `sender` as if the message was sent in clear.\n */\nexport interface ReceivedToDeviceMessage {\n    /** The message type, content, and sender as if the message was sent in clear. */\n    message: IToDeviceEvent;\n    /**\n     * Information about the encryption of the message.\n     * Will be null if the message was sent in clear\n     */\n    encryptionInfo: OlmEncryptionInfo | null;\n}\n\ninterface IToDevice {\n    events: IToDeviceEvent[];\n}\n\nexport interface IDeviceLists {\n    changed?: string[];\n    left?: string[];\n}\n\n/**\n * The \"users\" section of the sync update which contains extended profile updates.\n */\nexport interface UsersUpdate {\n    [userId: string]: {\n        profile_updates?: SyncUserProfile | null;\n    };\n}\n\nexport interface ISyncResponse {\n    \"next_batch\": string;\n    \"rooms\": IRooms;\n    \"presence\"?: IPresence;\n    \"account_data\": IAccountData;\n    \"to_device\"?: IToDevice;\n    \"device_lists\"?: IDeviceLists;\n    \"device_one_time_keys_count\"?: Record<string, number>;\n    \"users\"?: UsersUpdate;\n    \"org.matrix.msc4429.users\"?: UsersUpdate;\n    \"device_unused_fallback_key_types\"?: string[];\n    \"org.matrix.msc2732.device_unused_fallback_key_types\"?: string[];\n}\n/* eslint-enable camelcase */\n\nexport enum Category {\n    Invite = \"invite\",\n    Leave = \"leave\",\n    Join = \"join\",\n    Knock = \"knock\",\n}\n\ninterface IRoom {\n    _currentState: { [eventType: string]: { [stateKey: string]: IStateEvent } };\n    _timeline: {\n        event: IRoomEvent | IStateEvent;\n        token: string | null;\n    }[];\n    _summary: Partial<IRoomSummary>;\n    _accountData: { [eventType: string]: IMinimalEvent };\n    _unreadNotifications: Partial<UnreadNotificationCounts>;\n    _unreadThreadNotifications?: Record<string, Partial<UnreadNotificationCounts>>;\n    _receipts: ReceiptAccumulator;\n    _stickyEvents: {\n        readonly event: IStickyEvent | IStickyStateEvent;\n        /**\n         * This is the timestamp at which point it is safe to remove this event from the store.\n         * This value is immutable\n         */\n        readonly expiresTs: number;\n    }[];\n}\n\nexport interface ISyncData {\n    nextBatch: string;\n    accountData: IMinimalEvent[];\n    roomsData: IRooms;\n}\n\ntype TaggedEvent = IRoomEvent & { _localTs?: number };\n\nfunction isTaggedEvent(event: IRoomEvent): event is TaggedEvent {\n    return \"_localTs\" in event && event[\"_localTs\"] !== undefined;\n}\n\n/**\n * The purpose of this class is to accumulate /sync responses such that a\n * complete \"initial\" JSON response can be returned which accurately represents\n * the sum total of the /sync responses accumulated to date. It only handles\n * room data: that is, everything under the \"rooms\" top-level key.\n *\n * This class is used when persisting room data so a complete /sync response can\n * be loaded from disk and incremental syncs can be performed on the server,\n * rather than asking the server to do an initial sync on startup.\n */\nexport class SyncAccumulator {\n    private accountData: Record<string, IMinimalEvent> = {}; // $event_type: Object\n    private inviteRooms: Record<string, IInvitedRoom> = {}; // $roomId: { ... sync 'invite' json data ... }\n    private knockRooms: Record<string, IKnockedRoom> = {}; // $roomId: { ... sync 'knock' json data ... }\n    private joinRooms: { [roomId: string]: IRoom } = {};\n    // the /sync token which corresponds to the last time rooms were\n    // accumulated. We remember this so that any caller can obtain a\n    // coherent /sync response and know at what point they should be\n    // streaming from without losing events.\n    private nextBatch: string | null = null;\n\n    public constructor(private readonly opts: IOpts = {}) {\n        this.opts.maxTimelineEntries = this.opts.maxTimelineEntries || 50;\n    }\n\n    public accumulate(syncResponse: ISyncResponse, fromDatabase = false): void {\n        this.accumulateRooms(syncResponse, fromDatabase);\n        this.accumulateAccountData(syncResponse);\n        this.nextBatch = syncResponse.next_batch;\n    }\n\n    private accumulateAccountData(syncResponse: ISyncResponse): void {\n        if (!syncResponse.account_data || !syncResponse.account_data.events) {\n            return;\n        }\n        // Clobbers based on event type.\n        syncResponse.account_data.events.forEach((e) => {\n            this.accountData[e.type] = e;\n        });\n    }\n\n    /**\n     * Accumulate incremental /sync room data.\n     * @param syncResponse - the complete /sync JSON\n     * @param fromDatabase - True if the sync response is one saved to the database\n     */\n    private accumulateRooms(syncResponse: ISyncResponse, fromDatabase = false): void {\n        if (!syncResponse.rooms) {\n            return;\n        }\n        if (syncResponse.rooms.invite) {\n            Object.keys(syncResponse.rooms.invite).forEach((roomId) => {\n                this.accumulateRoom(roomId, Category.Invite, syncResponse.rooms.invite[roomId], fromDatabase);\n            });\n        }\n        if (syncResponse.rooms.join) {\n            Object.keys(syncResponse.rooms.join).forEach((roomId) => {\n                this.accumulateRoom(roomId, Category.Join, syncResponse.rooms.join[roomId], fromDatabase);\n            });\n        }\n        if (syncResponse.rooms.leave) {\n            Object.keys(syncResponse.rooms.leave).forEach((roomId) => {\n                this.accumulateRoom(roomId, Category.Leave, syncResponse.rooms.leave[roomId], fromDatabase);\n            });\n        }\n        if (syncResponse.rooms.knock) {\n            Object.keys(syncResponse.rooms.knock).forEach((roomId) => {\n                this.accumulateRoom(roomId, Category.Knock, syncResponse.rooms.knock[roomId], fromDatabase);\n            });\n        }\n    }\n\n    private accumulateRoom(roomId: string, category: Category.Invite, data: IInvitedRoom, fromDatabase: boolean): void;\n    private accumulateRoom(roomId: string, category: Category.Join, data: IJoinedRoom, fromDatabase: boolean): void;\n    private accumulateRoom(roomId: string, category: Category.Leave, data: ILeftRoom, fromDatabase: boolean): void;\n    private accumulateRoom(roomId: string, category: Category.Knock, data: IKnockedRoom, fromDatabase: boolean): void;\n    private accumulateRoom(roomId: string, category: Category, data: any, fromDatabase = false): void {\n        // Valid /sync state transitions\n        //       +--------+ <======+            1: Accept an invite\n        //   +== | INVITE |        | (5)        2: Leave a room\n        //   |   +--------+ =====+ |            3: Join a public room previously\n        //   |(1)            (4) | |               left (handle as if new room)\n        //   V         (2)       V |            4: Reject an invite\n        // +------+ ========> +--------+         5: Invite to a room previously\n        // | JOIN |    (3)    | LEAVE* |            left (handle as if new room)\n        // +------+ <======== +--------+\n        //\n        // * equivalent to \"no state\"\n        switch (category) {\n            case Category.Invite: // (5)\n                if (this.knockRooms[roomId]) {\n                    // was previously knock, now invite, need to delete knock state\n                    delete this.knockRooms[roomId];\n                }\n                this.accumulateInviteState(roomId, data as IInvitedRoom);\n                break;\n\n            case Category.Knock:\n                this.accumulateKnockState(roomId, data as IKnockedRoom);\n                break;\n\n            case Category.Join:\n                if (this.knockRooms[roomId]) {\n                    // delete knock state on join\n                    delete this.knockRooms[roomId];\n                } else if (this.inviteRooms[roomId]) {\n                    // (1)\n                    // was previously invite, now join. We expect /sync to give\n                    // the entire state and timeline on 'join', so delete previous\n                    // invite state\n                    delete this.inviteRooms[roomId];\n                }\n                // (3)\n                this.accumulateJoinState(roomId, data as IJoinedRoom, fromDatabase);\n                break;\n\n            case Category.Leave:\n                if (this.knockRooms[roomId]) {\n                    // delete knock state on leave\n                    delete this.knockRooms[roomId];\n                } else if (this.inviteRooms[roomId]) {\n                    // (4)\n                    delete this.inviteRooms[roomId];\n                } else {\n                    // (2)\n                    delete this.joinRooms[roomId];\n                }\n                break;\n\n            default:\n                logger.error(\"Unknown cateogory: \", category);\n        }\n    }\n\n    private accumulateInviteState(roomId: string, data: IInvitedRoom): void {\n        if (!data.invite_state || !data.invite_state.events) {\n            // no new data\n            return;\n        }\n        if (!this.inviteRooms[roomId]) {\n            this.inviteRooms[roomId] = {\n                invite_state: data.invite_state,\n            };\n            return;\n        }\n        // accumulate extra keys for invite->invite transitions\n        // clobber based on event type / state key\n        // We expect invite_state to be small, so just loop over the events\n        const currentData = this.inviteRooms[roomId];\n        data.invite_state.events.forEach((e) => {\n            let hasAdded = false;\n            for (let i = 0; i < currentData.invite_state.events.length; i++) {\n                const current = currentData.invite_state.events[i];\n                if (current.type === e.type && current.state_key == e.state_key) {\n                    currentData.invite_state.events[i] = e; // update\n                    hasAdded = true;\n                }\n            }\n            if (!hasAdded) {\n                currentData.invite_state.events.push(e);\n            }\n        });\n    }\n\n    private accumulateKnockState(roomId: string, data: IKnockedRoom): void {\n        if (!data.knock_state || !data.knock_state.events) {\n            // no new data\n            return;\n        }\n        if (!this.knockRooms[roomId]) {\n            this.knockRooms[roomId] = {\n                knock_state: data.knock_state,\n            };\n            return;\n        }\n        // accumulate extra keys\n        // clobber based on event type / state key\n        // We expect knock_state to be small, so just loop over the events\n        const currentData = this.knockRooms[roomId];\n        data.knock_state.events.forEach((e) => {\n            let hasAdded = false;\n            for (let i = 0; i < currentData.knock_state.events.length; i++) {\n                const current = currentData.knock_state.events[i];\n                if (current.type === e.type && current.state_key == e.state_key) {\n                    currentData.knock_state.events[i] = e; // update\n                    hasAdded = true;\n                }\n            }\n            if (!hasAdded) {\n                currentData.knock_state.events.push(e);\n            }\n        });\n    }\n\n    // Accumulate timeline and state events in a room.\n    private accumulateJoinState(roomId: string, data: IJoinedRoom, fromDatabase = false): void {\n        const now = Date.now();\n        // We expect this function to be called a lot (every /sync) so we want\n        // this to be fast. /sync stores events in an array but we often want\n        // to clobber based on type/state_key. Rather than convert arrays to\n        // maps all the time, just keep private maps which contain\n        // the actual current accumulated sync state, and array-ify it when\n        // getJSON() is called.\n\n        // State resolution:\n        // The 'state' key is the delta from the previous sync (or start of time\n        // if no token was supplied), to the START of the timeline. To obtain\n        // the current state, we need to \"roll forward\" state by reading the\n        // timeline. We want to store the current state so we can drop events\n        // out the end of the timeline based on opts.maxTimelineEntries.\n        //\n        //      'state'     'timeline'     current state\n        // |-------x<======================>x\n        //          T   I   M   E\n        //\n        // When getJSON() is called, we 'roll back' the current state by the\n        // number of entries in the timeline to work out what 'state' should be.\n\n        // Back-pagination:\n        // On an initial /sync, the server provides a back-pagination token for\n        // the start of the timeline. When /sync deltas come down, they also\n        // include back-pagination tokens for the start of the timeline. This\n        // means not all events in the timeline have back-pagination tokens, as\n        // it is only the ones at the START of the timeline which have them.\n        // In order for us to have a valid timeline (and back-pagination token\n        // to match), we need to make sure that when we remove old timeline\n        // events, that we roll forward to an event which has a back-pagination\n        // token. This means we can't keep a strict sliding-window based on\n        // opts.maxTimelineEntries, and we may have a few less. We should never\n        // have more though, provided that the /sync limit is less than or equal\n        // to opts.maxTimelineEntries.\n\n        if (!this.joinRooms[roomId]) {\n            // Create truly empty objects so event types of 'hasOwnProperty' and co\n            // don't cause this code to break.\n            this.joinRooms[roomId] = {\n                _currentState: Object.create(null),\n                _timeline: [],\n                _accountData: Object.create(null),\n                _unreadNotifications: {},\n                _unreadThreadNotifications: {},\n                _summary: {},\n                _receipts: new ReceiptAccumulator(),\n                _stickyEvents: [],\n            };\n        }\n        const currentData = this.joinRooms[roomId];\n\n        if (data.account_data && data.account_data.events) {\n            // clobber based on type\n            data.account_data.events.forEach((e) => {\n                currentData._accountData[e.type] = e;\n            });\n        }\n\n        // these probably clobber, spec is unclear.\n        if (data.unread_notifications) {\n            currentData._unreadNotifications = data.unread_notifications;\n        }\n        currentData._unreadThreadNotifications =\n            data[UNREAD_THREAD_NOTIFICATIONS.stable!] ?? data[UNREAD_THREAD_NOTIFICATIONS.unstable!] ?? undefined;\n\n        if (data.summary) {\n            const HEROES_KEY = \"m.heroes\";\n            const INVITED_COUNT_KEY = \"m.invited_member_count\";\n            const JOINED_COUNT_KEY = \"m.joined_member_count\";\n\n            const acc = currentData._summary;\n            const sum = data.summary;\n            acc[HEROES_KEY] = sum[HEROES_KEY] ?? acc[HEROES_KEY];\n            acc[JOINED_COUNT_KEY] = sum[JOINED_COUNT_KEY] ?? acc[JOINED_COUNT_KEY];\n            acc[INVITED_COUNT_KEY] = sum[INVITED_COUNT_KEY] ?? acc[INVITED_COUNT_KEY];\n        }\n\n        // We purposefully do not persist m.typing events.\n        // Technically you could refresh a browser before the timer on a\n        // typing event is up, so it'll look like you aren't typing when\n        // you really still are. However, the alternative is worse. If\n        // we do persist typing events, it will look like people are\n        // typing forever until someone really does start typing (which\n        // will prompt Synapse to send down an actual m.typing event to\n        // clobber the one we persisted).\n\n        // Persist the receipts\n        currentData._receipts.consumeEphemeralEvents(data.ephemeral?.events);\n\n        // if we got a limited sync, we need to remove all timeline entries or else\n        // we will have gaps in the timeline.\n        if (data.timeline && data.timeline.limited) {\n            currentData._timeline = [];\n        }\n\n        // Work out the current state. The deltas need to be applied in the order:\n        // - existing state which didn't come down /sync.\n        // - State events under the 'state' key.\n        // - State events under the 'state_after' key OR state events in the 'timeline' if 'state_after' is not present.\n        data.state?.events?.forEach((e) => {\n            setState(currentData._currentState, e);\n        });\n        data[\"org.matrix.msc4222.state_after\"]?.events?.forEach((e) => {\n            setState(currentData._currentState, e);\n        });\n        data.timeline?.events?.forEach((e, index) => {\n            if (!data[\"org.matrix.msc4222.state_after\"]) {\n                // this nops if 'e' isn't a state event\n                setState(currentData._currentState, e);\n            }\n            // append the event to the timeline. The back-pagination token\n            // corresponds to the first event in the timeline\n            let transformedEvent: TaggedEvent;\n            if (!fromDatabase) {\n                transformedEvent = Object.assign({}, e);\n                if (transformedEvent.unsigned !== undefined) {\n                    transformedEvent.unsigned = Object.assign({}, transformedEvent.unsigned);\n                }\n                const age = e.unsigned?.age;\n                if (age !== undefined) transformedEvent._localTs = Date.now() - age;\n            } else {\n                transformedEvent = e;\n            }\n\n            currentData._timeline.push({\n                event: transformedEvent,\n                token: index === 0 ? (data.timeline.prev_batch ?? null) : null,\n            });\n        });\n\n        // Prune out any events in our stores that have since expired, do this before we\n        // insert new events.\n        currentData._stickyEvents = currentData._stickyEvents.filter(({ expiresTs }) => expiresTs > now);\n\n        // We want this to be fast, so don't worry about duplicate events here. The RoomStickyEventsStore will\n        // process these events into the correct mapped order.\n        if (data.msc4354_sticky?.events) {\n            currentData._stickyEvents = currentData._stickyEvents.concat(\n                data.msc4354_sticky.events.map((event) => {\n                    // If `duration_ms` exceeds the spec limit of a hour, we cap it.\n                    const cappedDuration = Math.min(event.msc4354_sticky.duration_ms, MAX_STICKY_DURATION_MS);\n                    // If `origin_server_ts` claims to have been from the future, we still bound it to now.\n                    const createdTs = Math.min(event.origin_server_ts, now);\n                    return {\n                        event,\n                        expiresTs: cappedDuration + createdTs,\n                    };\n                }),\n            );\n        }\n\n        // attempt to prune the timeline by jumping between events which have\n        // pagination tokens.\n        if (currentData._timeline.length > this.opts.maxTimelineEntries!) {\n            const startIndex = currentData._timeline.length - this.opts.maxTimelineEntries!;\n            for (let i = startIndex; i < currentData._timeline.length; i++) {\n                if (currentData._timeline[i].token) {\n                    // keep all events after this, including this one\n                    currentData._timeline = currentData._timeline.slice(i, currentData._timeline.length);\n                    break;\n                }\n            }\n        }\n    }\n\n    /**\n     * Return everything under the 'rooms' key from a /sync response which\n     * represents all room data that should be stored. This should be paired\n     * with the sync token which represents the most recent /sync response\n     * provided to accumulate().\n     * @param forDatabase - True to generate a sync to be saved to storage\n     * @returns An object with a \"nextBatch\", \"roomsData\" and \"accountData\"\n     * keys.\n     * The \"nextBatch\" key is a string which represents at what point in the\n     * /sync stream the accumulator reached. This token should be used when\n     * restarting a /sync stream at startup. Failure to do so can lead to missing\n     * events. The \"roomsData\" key is an Object which represents the entire\n     * /sync response from the 'rooms' key onwards. The \"accountData\" key is\n     * a list of raw events which represent global account data.\n     */\n    public getJSON(forDatabase = false): ISyncData {\n        const data: IRooms = {\n            join: {},\n            invite: {},\n            knock: {},\n            // always empty. This is set by /sync when a room was previously\n            // in 'invite' or 'join'. On fresh startup, the client won't know\n            // about any previous room being in 'invite' or 'join' so we can\n            // just omit mentioning it at all, even if it has previously come\n            // down /sync.\n            // The notable exception is when a client is kicked or banned:\n            // we may want to hold onto that room so the client can clearly see\n            // why their room has disappeared. We don't persist it though because\n            // it is unclear *when* we can safely remove the room from the DB.\n            // Instead, we assume that if you're loading from the DB, you've\n            // refreshed the page, which means you've seen the kick/ban already.\n            leave: {},\n        };\n        Object.keys(this.inviteRooms).forEach((roomId) => {\n            data.invite[roomId] = this.inviteRooms[roomId];\n        });\n        Object.keys(this.knockRooms).forEach((roomId) => {\n            data.knock[roomId] = this.knockRooms[roomId];\n        });\n        Object.keys(this.joinRooms).forEach((roomId) => {\n            const roomData = this.joinRooms[roomId];\n            const roomJson: IJoinedRoom & {\n                // We track both `state` and `state_after` for downgrade compatibility\n                \"state\": IState;\n                \"org.matrix.msc4222.state_after\": IState;\n            } = {\n                \"ephemeral\": { events: [] },\n                \"account_data\": { events: [] },\n                \"state\": { events: [] },\n                \"org.matrix.msc4222.state_after\": { events: [] },\n                \"timeline\": {\n                    events: [],\n                    prev_batch: null,\n                },\n                \"unread_notifications\": roomData._unreadNotifications,\n                \"unread_thread_notifications\": roomData._unreadThreadNotifications,\n                \"summary\": roomData._summary as IRoomSummary,\n                \"msc4354_sticky\": roomData._stickyEvents?.length\n                    ? {\n                          events: roomData._stickyEvents.map((e) => e.event),\n                      }\n                    : undefined,\n            };\n            // Add account data\n            Object.keys(roomData._accountData).forEach((evType) => {\n                roomJson.account_data.events.push(roomData._accountData[evType]);\n            });\n\n            const receiptEvent = roomData._receipts.buildAccumulatedReceiptEvent(roomId);\n\n            // add only if we have some receipt data\n            if (receiptEvent) {\n                roomJson.ephemeral.events.push(receiptEvent);\n            }\n\n            // Add timeline data\n            roomData._timeline.forEach((msgData) => {\n                if (!roomJson.timeline.prev_batch) {\n                    // the first event we add to the timeline MUST match up to\n                    // the prev_batch token.\n                    if (!msgData.token) {\n                        return; // this shouldn't happen as we prune constantly.\n                    }\n                    roomJson.timeline.prev_batch = msgData.token;\n                }\n\n                let transformedEvent: (IRoomEvent | IStateEvent) & { _localTs?: number };\n                if (!forDatabase && isTaggedEvent(msgData.event)) {\n                    // This means we have to copy each event, so we can fix it up to\n                    // set a correct 'age' parameter whilst keeping the local timestamp\n                    // on our stored event. If this turns out to be a bottleneck, it could\n                    // be optimised either by doing this in the main process after the data\n                    // has been structured-cloned to go between the worker & main process,\n                    // or special-casing data from saved syncs to read the local timestamp\n                    // directly rather than turning it into age to then immediately be\n                    // transformed back again into a local timestamp.\n                    transformedEvent = Object.assign({}, msgData.event);\n                    if (transformedEvent.unsigned !== undefined) {\n                        transformedEvent.unsigned = Object.assign({}, transformedEvent.unsigned);\n                    }\n                    delete transformedEvent._localTs;\n                    transformedEvent.unsigned = transformedEvent.unsigned || {};\n                    transformedEvent.unsigned.age = Date.now() - msgData.event._localTs!;\n                } else {\n                    transformedEvent = msgData.event;\n                }\n                roomJson.timeline.events.push(transformedEvent);\n            });\n\n            // Add state data: roll back current state to the start of timeline,\n            // by \"reverse clobbering\" from the end of the timeline to the start.\n            // Convert maps back into arrays.\n            const rollBackState = Object.create(null);\n            for (let i = roomJson.timeline.events.length - 1; i >= 0; i--) {\n                const timelineEvent = roomJson.timeline.events[i];\n                if (\n                    (timelineEvent as IStateEvent).state_key === null ||\n                    (timelineEvent as IStateEvent).state_key === undefined\n                ) {\n                    continue; // not a state event\n                }\n                // since we're going back in time, we need to use the previous\n                // state value else we'll break causality. We don't have the\n                // complete previous state event, so we need to create one.\n                const prevStateEvent = deepCopy(timelineEvent);\n                if (prevStateEvent.unsigned) {\n                    if (prevStateEvent.unsigned.prev_content) {\n                        prevStateEvent.content = prevStateEvent.unsigned.prev_content;\n                    }\n                    if (prevStateEvent.unsigned.prev_sender) {\n                        prevStateEvent.sender = prevStateEvent.unsigned.prev_sender;\n                    }\n                }\n                setState(rollBackState, prevStateEvent);\n            }\n            Object.keys(roomData._currentState).forEach((evType) => {\n                Object.keys(roomData._currentState[evType]).forEach((stateKey) => {\n                    let ev = roomData._currentState[evType][stateKey];\n                    // Push to both fields to provide downgrade compatibility in the sync accumulator db\n                    // the code will prefer `state_after` if it is present\n                    roomJson[\"org.matrix.msc4222.state_after\"].events.push(ev);\n                    // Roll the state back to the value at the start of the timeline if it was changed\n                    if (rollBackState[evType] && rollBackState[evType][stateKey]) {\n                        ev = rollBackState[evType][stateKey];\n                    }\n                    roomJson.state.events.push(ev);\n                });\n            });\n            data.join[roomId] = roomJson;\n        });\n\n        // Add account data\n        const accData: IMinimalEvent[] = [];\n        Object.keys(this.accountData).forEach((evType) => {\n            accData.push(this.accountData[evType]);\n        });\n\n        return {\n            nextBatch: this.nextBatch!,\n            roomsData: data,\n            accountData: accData,\n        };\n    }\n\n    public getNextBatchToken(): string {\n        return this.nextBatch!;\n    }\n\n    public removeEventsFromRoom(roomId: string, eventIds: string[]): void {\n        this.joinRooms[roomId]._timeline = this.joinRooms[roomId]._timeline.filter(\n            (ev) => !eventIds.includes(ev.event.event_id),\n        );\n        this.joinRooms[roomId]._stickyEvents = this.joinRooms[roomId]._stickyEvents.filter(\n            (ev) => !eventIds.includes(ev.event.event_id),\n        );\n    }\n}\n\nfunction setState(eventMap: Record<string, Record<string, IStateEvent>>, event: IRoomEvent | IStateEvent): void {\n    if ((event as IStateEvent).state_key === null || (event as IStateEvent).state_key === undefined || !event.type) {\n        return;\n    }\n    if (!eventMap[event.type]) {\n        eventMap[event.type] = Object.create(null);\n    }\n    eventMap[event.type][(event as IStateEvent).state_key] = event as IStateEvent;\n}\n","/*\nCopyright 2015 - 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n/*\n * TODO:\n * This class mainly serves to take all the syncing logic out of client.js and\n * into a separate file. It's all very fluid, and this class gut wrenches a lot\n * of MatrixClient props (e.g. http). Given we want to support WebSockets as\n * an alternative syncing API, we may want to have a proper syncing interface\n * for HTTP and WS at some point.\n */\n\nimport type { SyncCryptoCallbacks } from \"./common-crypto/CryptoBackend.ts\";\nimport { type SyncUserProfile, User } from \"./models/user.ts\";\nimport { NotificationCountType, Room, RoomEvent } from \"./models/room.ts\";\nimport { deepCopy, noUnsafeEventProps, unsafeProp } from \"./utils.ts\";\nimport { Filter } from \"./filter.ts\";\nimport { EventTimeline } from \"./models/event-timeline.ts\";\nimport { logger, type Logger } from \"./logger.ts\";\nimport {\n    ClientEvent,\n    type IStoredClientOpts,\n    type MatrixClient,\n    PendingEventOrdering,\n    type ResetTimelineCallback,\n} from \"./client.ts\";\nimport {\n    type IEphemeral,\n    type IInvitedRoom,\n    type IInviteState,\n    type IJoinedRoom,\n    type IKnockedRoom,\n    type ILeftRoom,\n    type IMinimalEvent,\n    type IRoomEvent,\n    type IStateEvent,\n    type IStrippedState,\n    type ISyncResponse,\n    type ITimeline,\n    type IToDeviceEvent,\n    type ReceivedToDeviceMessage,\n} from \"./sync-accumulator.ts\";\nimport { MatrixEvent } from \"./models/event.ts\";\nimport { type MatrixError, Method } from \"./http-api/index.ts\";\nimport { type ISavedSync } from \"./store/index.ts\";\nimport { EventType } from \"./@types/event.ts\";\nimport { type IPushRules } from \"./@types/PushRules.ts\";\nimport { type IMarkerFoundOptions, RoomStateEvent } from \"./models/room-state.ts\";\nimport { RoomMemberEvent } from \"./models/room-member.ts\";\nimport { BeaconEvent } from \"./models/beacon.ts\";\nimport { type IEventsResponse } from \"./@types/requests.ts\";\nimport { UNREAD_THREAD_NOTIFICATIONS } from \"./@types/sync.ts\";\nimport { Feature, ServerSupport } from \"./feature.ts\";\nimport { KnownMembership } from \"./@types/membership.ts\";\n\n// /sync requests allow you to set a timeout= but the request may continue\n// beyond that and wedge forever, so we need to track how long we are willing\n// to keep open the connection. This constant is *ADDED* to the timeout= value\n// to determine the max time we're willing to wait.\nconst BUFFER_PERIOD_MS = 80 * 1000;\n\n// Number of consecutive failed syncs that will lead to a syncState of ERROR as opposed\n// to RECONNECTING. This is needed to inform the client of server issues when the\n// keepAlive is successful but the server /sync fails.\nconst FAILED_SYNC_ERROR_THRESHOLD = 3;\n\nexport enum SyncState {\n    /** Emitted after we try to sync more than `FAILED_SYNC_ERROR_THRESHOLD`\n     * times and are still failing. Or when we enounter a hard error like the\n     * token being invalid. */\n    Error = \"ERROR\",\n    /** Emitted after the first sync events are ready (this could even be sync\n     * events from the cache) */\n    Prepared = \"PREPARED\",\n    /** Emitted when the sync loop is no longer running */\n    Stopped = \"STOPPED\",\n    /** Emitted after each sync request happens */\n    Syncing = \"SYNCING\",\n    /** Emitted after a connectivity error and we're ready to start syncing again */\n    Catchup = \"CATCHUP\",\n    /** Emitted for each time we try reconnecting. Will switch to `Error` after\n     * we reach the `FAILED_SYNC_ERROR_THRESHOLD`\n     */\n    Reconnecting = \"RECONNECTING\",\n}\n\n// Room versions where \"insertion\", \"batch\", and \"marker\" events are controlled\n// by power-levels. MSC2716 is supported in existing room versions but they\n// should only have special meaning when the room creator sends them.\nconst MSC2716_ROOM_VERSIONS = [\"org.matrix.msc2716v3\"];\n\nfunction getFilterName(userId: string, suffix?: string): string {\n    // scope this on the user ID because people may login on many accounts\n    // and they all need to be stored!\n    return `FILTER_SYNC_${userId}` + (suffix ? \"_\" + suffix : \"\");\n}\n\n/**\n * Options passed into the constructor of SyncApi by MatrixClient\n */\nexport interface SyncApiOptions {\n    /**\n     * If crypto is enabled on our client, callbacks into the crypto module\n     */\n    cryptoCallbacks?: SyncCryptoCallbacks;\n\n    /**\n     * A function which is called\n     * with a room ID and returns a boolean. It should return 'true' if the SDK can\n     * SAFELY remove events from this room. It may not be safe to remove events if\n     * there are other references to the timelines for this room.\n     */\n    canResetEntireTimeline?: ResetTimelineCallback;\n\n    /** Logger instance to use for writing debug logs. */\n    logger: Logger;\n}\n\ninterface ISyncOptions {\n    filter?: string;\n    hasSyncedBefore?: boolean;\n}\n\nexport interface ISyncStateData {\n    /**\n     * The matrix error if `state=ERROR`.\n     */\n    error?: Error;\n    /**\n     * The 'since' token passed to /sync.\n     *    `null` for the first successful sync since this client was\n     *    started. Only present if `state=PREPARED` or\n     *    `state=SYNCING`.\n     */\n    oldSyncToken?: string;\n    /**\n     * The 'next_batch' result from /sync, which\n     *    will become the 'since' token for the next call to /sync. Only present if\n     *    `state=PREPARED</code> or <code>state=SYNCING`.\n     */\n    nextSyncToken?: string;\n    /**\n     * True if we are working our way through a\n     *    backlog of events after connecting. Only present if `state=SYNCING`.\n     */\n    catchingUp?: boolean;\n    fromCache?: boolean;\n}\n\nexport enum SetPresence {\n    Offline = \"offline\",\n    Online = \"online\",\n    Unavailable = \"unavailable\",\n}\n\ninterface ISyncParams {\n    \"filter\"?: string;\n    \"timeout\": number;\n    \"since\"?: string;\n    // eslint-disable-next-line camelcase\n    \"full_state\"?: boolean;\n    // eslint-disable-next-line camelcase\n    \"set_presence\"?: SetPresence;\n    \"_cacheBuster\"?: string | number; // not part of the API itself\n    \"org.matrix.msc4222.use_state_after\"?: boolean; // https://github.com/matrix-org/matrix-spec-proposals/pull/4222\n}\n\ntype WrappedRoom<T> = T & {\n    room: Room;\n    isBrandNewRoom: boolean;\n};\n\n/** add default settings to an IStoredClientOpts */\nexport function defaultClientOpts(opts?: IStoredClientOpts): IStoredClientOpts {\n    return {\n        initialSyncLimit: 8,\n        resolveInvitesToProfiles: false,\n        pollTimeout: 30 * 1000,\n        pendingEventOrdering: PendingEventOrdering.Chronological,\n        threadSupport: false,\n        ...opts,\n    };\n}\n\nexport function defaultSyncApiOpts(syncOpts: SyncApiOptions): SyncApiOptions {\n    return {\n        canResetEntireTimeline: (_roomId): boolean => false,\n        ...syncOpts,\n    };\n}\n\nexport class SyncApi {\n    private readonly opts: IStoredClientOpts;\n    private readonly syncOpts: SyncApiOptions;\n\n    private _peekRoom: Room | null = null;\n    private currentSyncRequest?: Promise<ISyncResponse>;\n    private abortController?: AbortController;\n    private syncState: SyncState | null = null;\n    private syncStateData?: ISyncStateData; // additional data (eg. error object for failed sync)\n    private catchingUp = false;\n    private running = false;\n    private keepAliveTimer?: ReturnType<typeof setTimeout>;\n    private connectionReturnedResolvers?: PromiseWithResolvers<boolean>;\n    private notifEvents: MatrixEvent[] = []; // accumulator of sync events in the current sync response\n    private failedSyncCount = 0; // Number of consecutive failed /sync requests\n    private storeIsInvalid = false; // flag set if the store needs to be cleared before we can start\n    private presence?: SetPresence;\n\n    /**\n     * Construct an entity which is able to sync with a homeserver.\n     * @param client - The matrix client instance to use.\n     * @param opts - client config options\n     * @param syncOpts - sync-specific options passed by the client\n     * @internal\n     */\n    public constructor(\n        private readonly client: MatrixClient,\n        opts: IStoredClientOpts | undefined,\n        syncOpts: SyncApiOptions,\n    ) {\n        this.opts = defaultClientOpts(opts);\n        this.syncOpts = defaultSyncApiOpts(syncOpts);\n\n        if (client.getNotifTimelineSet()) {\n            client.reEmitter.reEmit(client.getNotifTimelineSet()!, [RoomEvent.Timeline, RoomEvent.TimelineReset]);\n        }\n    }\n\n    public createRoom(roomId: string): Room {\n        const room = _createAndReEmitRoom(this.client, roomId, this.opts);\n\n        room.on(RoomStateEvent.Marker, (markerEvent, markerFoundOptions) => {\n            this.onMarkerStateEvent(room, markerEvent, markerFoundOptions);\n        });\n\n        return room;\n    }\n\n    /** When we see the marker state change in the room, we know there is some\n     * new historical messages imported by MSC2716 `/batch_send` somewhere in\n     * the room and we need to throw away the timeline to make sure the\n     * historical messages are shown when we paginate `/messages` again.\n     * @param room - The room where the marker event was sent\n     * @param markerEvent - The new marker event\n     * @param setStateOptions - When `timelineWasEmpty` is set\n     * as `true`, the given marker event will be ignored\n     */\n    private onMarkerStateEvent(\n        room: Room,\n        markerEvent: MatrixEvent,\n        { timelineWasEmpty }: IMarkerFoundOptions = {},\n    ): void {\n        // We don't need to refresh the timeline if it was empty before the\n        // marker arrived. This could be happen in a variety of cases:\n        //  1. From the initial sync\n        //  2. If it's from the first state we're seeing after joining the room\n        //  3. Or whether it's coming from `syncFromCache`\n        if (timelineWasEmpty) {\n            this.syncOpts.logger.debug(\n                `MarkerState: Ignoring markerEventId=${markerEvent.getId()} in roomId=${room.roomId} ` +\n                    `because the timeline was empty before the marker arrived which means there is nothing to refresh.`,\n            );\n            return;\n        }\n\n        const isValidMsc2716Event =\n            // Check whether the room version directly supports MSC2716, in\n            // which case, \"marker\" events are already auth'ed by\n            // power_levels\n            MSC2716_ROOM_VERSIONS.includes(room.getVersion()) ||\n            // MSC2716 is also supported in all existing room versions but\n            // special meaning should only be given to \"insertion\", \"batch\",\n            // and \"marker\" events when they come from the room creator\n            markerEvent.getSender() === room.getCreator();\n\n        // It would be nice if we could also specifically tell whether the\n        // historical messages actually affected the locally cached client\n        // timeline or not. The problem is we can't see the prev_events of\n        // the base insertion event that the marker was pointing to because\n        // prev_events aren't available in the client API's. In most cases,\n        // the history won't be in people's locally cached timelines in the\n        // client, so we don't need to bother everyone about refreshing\n        // their timeline. This works for a v1 though and there are use\n        // cases like initially bootstrapping your bridged room where people\n        // are likely to encounter the historical messages affecting their\n        // current timeline (think someone signing up for Beeper and\n        // importing their Whatsapp history).\n        if (isValidMsc2716Event) {\n            // Saw new marker event, let's let the clients know they should\n            // refresh the timeline.\n            this.syncOpts.logger.debug(\n                `MarkerState: Timeline needs to be refreshed because ` +\n                    `a new markerEventId=${markerEvent.getId()} was sent in roomId=${room.roomId}`,\n            );\n            room.setTimelineNeedsRefresh(true);\n            room.emit(RoomEvent.HistoryImportedWithinTimeline, markerEvent, room);\n        } else {\n            this.syncOpts.logger.debug(\n                `MarkerState: Ignoring markerEventId=${markerEvent.getId()} in roomId=${room.roomId} because ` +\n                    `MSC2716 is not supported in the room version or for any room version, the marker wasn't sent ` +\n                    `by the room creator.`,\n            );\n        }\n    }\n\n    /**\n     * Sync rooms the user has left.\n     * @returns Resolved when they've been added to the store.\n     */\n    public async syncLeftRooms(): Promise<Room[]> {\n        const client = this.client;\n\n        // grab a filter with limit=1 and include_leave=true\n        const filter = new Filter(this.client.credentials.userId);\n        filter.setTimelineLimit(1);\n        filter.setIncludeLeaveRooms(true);\n\n        const localTimeoutMs = this.opts.pollTimeout! + BUFFER_PERIOD_MS;\n\n        const filterId = await client.getOrCreateFilter(\n            getFilterName(client.credentials.userId!, \"LEFT_ROOMS\"),\n            filter,\n        );\n\n        const qps: ISyncParams = {\n            \"timeout\": 0, // don't want to block since this is a single isolated req\n            \"filter\": filterId,\n            \"org.matrix.msc4222.use_state_after\": true,\n        };\n\n        const data = await client.http.authedRequest<ISyncResponse>(Method.Get, \"/sync\", qps as any, undefined, {\n            localTimeoutMs,\n        });\n\n        let leaveRooms: WrappedRoom<ILeftRoom>[] = [];\n        if (data.rooms?.leave) {\n            leaveRooms = this.mapSyncResponseToRoomArray(data.rooms.leave);\n        }\n\n        const rooms = await Promise.all(\n            leaveRooms.map(async (leaveObj) => {\n                const room = leaveObj.room;\n                if (!leaveObj.isBrandNewRoom) {\n                    // the intention behind syncLeftRooms is to add in rooms which were\n                    // *omitted* from the initial /sync. Rooms the user were joined to\n                    // but then left whilst the app is running will appear in this list\n                    // and we do not want to bother with them since they will have the\n                    // current state already (and may get dupe messages if we add\n                    // yet more timeline events!), so skip them.\n                    // NB: When we persist rooms to localStorage this will be more\n                    //     complicated...\n                    return;\n                }\n                leaveObj.timeline = leaveObj.timeline || {\n                    prev_batch: null,\n                    events: [],\n                };\n\n                // set the back-pagination token. Do this *before* adding any\n                // events so that clients can start back-paginating.\n                room.getLiveTimeline().setPaginationToken(leaveObj.timeline.prev_batch, EventTimeline.BACKWARDS);\n\n                const { timelineEvents } = await this.mapAndInjectRoomEvents(leaveObj);\n\n                room.recalculate();\n                client.store.storeRoom(room);\n                client.emit(ClientEvent.Room, room);\n\n                this.processEventsForNotifs(room, timelineEvents);\n                return room;\n            }),\n        );\n\n        return rooms.filter(Boolean) as Room[];\n    }\n\n    /**\n     * Peek into a room. This will result in the room in question being synced so it\n     * is accessible via getRooms(). Live updates for the room will be provided.\n     * @param roomId - The room ID to peek into.\n     * @param limit - The number of timeline events to initially retrieve.\n     * @returns A promise which resolves once the room has been added to the\n     * store.\n     */\n    public peek(roomId: string, limit: number = 20): Promise<Room> {\n        if (this._peekRoom?.roomId === roomId) {\n            return Promise.resolve(this._peekRoom);\n        }\n\n        const client = this.client;\n        this._peekRoom = this.createRoom(roomId);\n        return this.client.roomInitialSync(roomId, limit).then((response) => {\n            if (this._peekRoom?.roomId !== roomId) {\n                throw new Error(\"Peeking aborted\");\n            }\n\n            // make sure things are init'd\n            response.messages = response.messages || { chunk: [] };\n            response.messages.chunk = response.messages.chunk || [];\n            response.state = response.state || [];\n\n            // FIXME: Mostly duplicated from injectRoomEvents but not entirely\n            // because \"state\" in this API is at the BEGINNING of the chunk\n            const oldStateEvents = deepCopy(response.state).map(client.getEventMapper());\n            const stateEvents = response.state.map(client.getEventMapper());\n            const messages = response.messages.chunk.map(client.getEventMapper());\n\n            // XXX: copypasted from /sync until we kill off this minging v1 API stuff)\n            // handle presence events (User objects)\n            if (Array.isArray(response.presence)) {\n                response.presence.map(client.getEventMapper()).forEach(function (presenceEvent) {\n                    let user = client.store.getUser(presenceEvent.getContent().user_id);\n                    if (user) {\n                        user.setPresenceEvent(presenceEvent);\n                    } else {\n                        user = User.createUser(presenceEvent.getContent().user_id, client);\n                        user.setPresenceEvent(presenceEvent);\n                        client.store.storeUser(user);\n                    }\n                    client.emit(ClientEvent.Event, presenceEvent);\n                });\n            }\n\n            // set the pagination token before adding the events in case people\n            // fire off pagination requests in response to the Room.timeline\n            // events.\n            if (response.messages.start) {\n                this._peekRoom.oldState.paginationToken = response.messages.start;\n            }\n\n            // set the state of the room to as it was after the timeline executes\n            this._peekRoom.oldState.setStateEvents(oldStateEvents);\n            this._peekRoom.currentState.setStateEvents(stateEvents);\n\n            this.resolveInvites(this._peekRoom);\n            this._peekRoom.recalculate();\n\n            // roll backwards to diverge old state. addEventsToTimeline\n            // will overwrite the pagination token, so make sure it overwrites\n            // it with the right thing.\n            this._peekRoom.addEventsToTimeline(\n                messages.reverse(),\n                true,\n                true,\n                this._peekRoom.getLiveTimeline(),\n                response.messages.start,\n            );\n\n            client.store.storeRoom(this._peekRoom);\n            client.emit(ClientEvent.Room, this._peekRoom);\n\n            this.peekPoll(this._peekRoom);\n            return this._peekRoom;\n        });\n    }\n\n    /**\n     * Stop polling for updates in the peeked room. NOPs if there is no room being\n     * peeked.\n     */\n    public stopPeeking(): void {\n        this._peekRoom = null;\n    }\n\n    /**\n     * Do a peek room poll.\n     * @param token - from= token\n     */\n    private peekPoll(peekRoom: Room, token?: string): void {\n        if (this._peekRoom !== peekRoom) {\n            this.syncOpts.logger.debug(\"Stopped peeking in room %s\", peekRoom.roomId);\n            return;\n        }\n\n        // FIXME: gut wrenching; hard-coded timeout values\n        this.client.http\n            .authedRequest<IEventsResponse>(\n                Method.Get,\n                \"/events\",\n                {\n                    room_id: peekRoom.roomId,\n                    timeout: String(30 * 1000),\n                    from: token,\n                },\n                undefined,\n                {\n                    localTimeoutMs: 50 * 1000,\n                    abortSignal: this.abortController?.signal,\n                },\n            )\n            .then(\n                async (res) => {\n                    if (this._peekRoom !== peekRoom) {\n                        this.syncOpts.logger.debug(\"Stopped peeking in room %s\", peekRoom.roomId);\n                        return;\n                    }\n                    // We have a problem that we get presence both from /events and /sync\n                    // however, /sync only returns presence for users in rooms\n                    // you're actually joined to.\n                    // in order to be sure to get presence for all of the users in the\n                    // peeked room, we handle presence explicitly here. This may result\n                    // in duplicate presence events firing for some users, which is a\n                    // performance drain, but such is life.\n                    // XXX: copypasted from /sync until we can kill this minging v1 stuff.\n\n                    res.chunk\n                        .filter(function (e) {\n                            return e.type === \"m.presence\";\n                        })\n                        .map(this.client.getEventMapper())\n                        .forEach((presenceEvent) => {\n                            let user = this.client.store.getUser(presenceEvent.getContent().user_id);\n                            if (user) {\n                                user.setPresenceEvent(presenceEvent);\n                            } else {\n                                user = User.createUser(presenceEvent.getContent().user_id, this.client);\n                                user.setPresenceEvent(presenceEvent);\n                                this.client.store.storeUser(user);\n                            }\n                            this.client.emit(ClientEvent.Event, presenceEvent);\n                        });\n\n                    // strip out events which aren't for the given room_id (e.g presence)\n                    // and also ephemeral events (which we're assuming is anything without\n                    // and event ID because the /events API doesn't separate them).\n                    const events = res.chunk\n                        .filter(function (e) {\n                            return e.room_id === peekRoom.roomId && e.event_id;\n                        })\n                        .map(this.client.getEventMapper());\n\n                    await peekRoom.addLiveEvents(events, { addToState: true });\n                    this.peekPoll(peekRoom, res.end);\n                },\n                (err) => {\n                    this.syncOpts.logger.error(\"[%s] Peek poll failed: %s\", peekRoom.roomId, err);\n                    setTimeout(() => {\n                        this.peekPoll(peekRoom, token);\n                    }, 30 * 1000);\n                },\n            );\n    }\n\n    /**\n     * Returns the current state of this sync object\n     * @see MatrixClient#event:\"sync\"\n     */\n    public getSyncState(): SyncState | null {\n        return this.syncState;\n    }\n\n    /**\n     * Returns the additional data object associated with\n     * the current sync state, or null if there is no\n     * such data.\n     * Sync errors, if available, are put in the 'error' key of\n     * this object.\n     */\n    public getSyncStateData(): ISyncStateData | null {\n        return this.syncStateData ?? null;\n    }\n\n    public async recoverFromSyncStartupError(savedSyncPromise: Promise<void> | undefined, error: Error): Promise<void> {\n        // Wait for the saved sync to complete - we send the pushrules and filter requests\n        // before the saved sync has finished so they can run in parallel, but only process\n        // the results after the saved sync is done. Equivalently, we wait for it to finish\n        // before reporting failures from these functions.\n        await savedSyncPromise;\n        const keepaliveProm = this.startKeepAlives();\n        this.updateSyncState(SyncState.Error, { error });\n        await keepaliveProm;\n    }\n\n    private shouldAbortSync(error: MatrixError): boolean {\n        if (error.errcode === \"M_UNKNOWN_TOKEN\") {\n            // The logout already happened, we just need to stop.\n            this.syncOpts.logger.warn(\"Token no longer valid - assuming logout\");\n            this.stop();\n            this.updateSyncState(SyncState.Error, { error });\n            return true;\n        }\n        return false;\n    }\n\n    private getPushRules = async (): Promise<void> => {\n        try {\n            this.syncOpts.logger.debug(\"Getting push rules...\");\n            const result = await this.client.getPushRules();\n            this.syncOpts.logger.debug(\"Got push rules\");\n\n            this.client.pushRules = result;\n        } catch (err) {\n            this.syncOpts.logger.error(\"Getting push rules failed\", err);\n            if (this.shouldAbortSync(<MatrixError>err)) return;\n            // wait for saved sync to complete before doing anything else,\n            // otherwise the sync state will end up being incorrect\n            this.syncOpts.logger.debug(\"Waiting for saved sync before retrying push rules...\");\n            await this.recoverFromSyncStartupError(this.savedSyncPromise, <Error>err);\n            return this.getPushRules(); // try again\n        }\n    };\n\n    private buildDefaultFilter = (): Filter => {\n        const filter = new Filter(this.client.credentials.userId);\n        if (this.client.canSupport.get(Feature.ThreadUnreadNotifications) !== ServerSupport.Unsupported) {\n            filter.setUnreadThreadNotifications(true);\n        }\n        return filter;\n    };\n\n    /**\n     * Sets up the sync filter options for lazy loading if enabled,\n     * (or force-disables lazy loading entirely if we're a guest).\n     */\n    private prepareSyncFilterLazyLoading = (): void => {\n        this.syncOpts.logger.debug(\"Prepare lazy loading for sync...\");\n        if (this.client.isGuest()) {\n            this.opts.lazyLoadMembers = false;\n        }\n        if (this.opts.lazyLoadMembers) {\n            this.syncOpts.logger.debug(\"Enabling lazy load on sync filter...\");\n            if (!this.opts.filter) {\n                this.opts.filter = this.buildDefaultFilter();\n            }\n            this.opts.filter.setLazyLoadMembers(true);\n        }\n    };\n\n    /**\n     * Preare sync filter options for the unstable MSC4429 user profile fields if enabled.\n     */\n    private prepareSyncFilterUserProfiles = async (): Promise<void> => {\n        if (this.opts.unstableMSC4429SyncUserProfileFields?.length) {\n            this.syncOpts.logger.debug(\"Enabling EXPERIMENTAL user profiles on sync filter...\");\n            if (!this.opts.filter) {\n                this.opts.filter = this.buildDefaultFilter();\n            }\n            this.opts.filter.setUnstableMSC4429SyncUserProfiles(\n                this.opts.unstableMSC4429SyncUserProfileFields,\n                await this.client.doesServerSupportUnstableFeature(\"org.matrix.msc4429.stable\"),\n            );\n        }\n    };\n\n    private storeClientOptions = async (): Promise<void> => {\n        try {\n            this.syncOpts.logger.debug(\"Storing client options...\");\n            await this.client.storeClientOptions();\n            this.syncOpts.logger.debug(\"Stored client options\");\n        } catch (err) {\n            this.syncOpts.logger.error(\"Storing client options failed\", err);\n            throw err;\n        }\n    };\n\n    private getFilter = async (): Promise<{\n        filterId?: string;\n        filter?: Filter;\n    }> => {\n        this.syncOpts.logger.debug(\"Getting filter...\");\n        let filter: Filter;\n        if (this.opts.filter) {\n            filter = this.opts.filter;\n        } else {\n            filter = this.buildDefaultFilter();\n        }\n\n        let filterId: string;\n        try {\n            filterId = await this.client.getOrCreateFilter(getFilterName(this.client.credentials.userId!), filter);\n        } catch (err) {\n            this.syncOpts.logger.error(\"Getting filter failed\", err);\n            if (this.shouldAbortSync(<MatrixError>err)) return {};\n            // wait for saved sync to complete before doing anything else,\n            // otherwise the sync state will end up being incorrect\n            this.syncOpts.logger.debug(\"Waiting for saved sync before retrying filter...\");\n            await this.recoverFromSyncStartupError(this.savedSyncPromise, <Error>err);\n            return this.getFilter(); // try again\n        }\n        return { filter, filterId };\n    };\n\n    private savedSyncPromise?: Promise<void>;\n\n    /**\n     * Main entry point\n     */\n    public async sync(): Promise<void> {\n        this.running = true;\n        this.abortController = new AbortController();\n\n        globalThis.window?.addEventListener?.(\"online\", this.onOnline, false);\n\n        if (this.client.isGuest()) {\n            // no push rules for guests, no access to POST filter for guests.\n            return this.doSync({});\n        }\n\n        // Pull the saved sync token out first, before the worker starts sending\n        // all the sync data which could take a while. This will let us send our\n        // first incremental sync request before we've processed our saved data.\n        this.syncOpts.logger.debug(\"Getting saved sync token...\");\n        const savedSyncTokenPromise = this.client.store.getSavedSyncToken().then((tok) => {\n            this.syncOpts.logger.debug(\"Got saved sync token\");\n            return tok;\n        });\n\n        this.savedSyncPromise = this.client.store\n            .getSavedSync()\n            .then((savedSync) => {\n                this.syncOpts.logger.debug(`Got reply from saved sync, exists? ${!!savedSync}`);\n                if (savedSync) {\n                    return this.syncFromCache(savedSync);\n                }\n            })\n            .catch((err) => {\n                this.syncOpts.logger.error(\"Getting saved sync failed\", err);\n            });\n\n        // We need to do one-off checks before we can begin the /sync loop.\n        // These are:\n        //   1) We need to get push rules so we can check if events should bing as we get\n        //      them from /sync.\n        //   2) We need to get/create a filter which we can use for /sync.\n        //   3) We need to prepare lazy loading for sync\n        //   4) We need to store the client options\n\n        // Now start the first incremental sync request: this can also\n        // take a while so if we set it going now, we can wait for it\n        // to finish while we process our saved sync data.\n        await this.getPushRules();\n        this.prepareSyncFilterLazyLoading();\n        await this.prepareSyncFilterUserProfiles();\n        await this.storeClientOptions();\n\n        const { filterId, filter } = await this.getFilter();\n        if (!filter) return; // bail, getFilter failed\n\n        // reset the notifications timeline to prepare it to paginate from\n        // the current point in time.\n        // The right solution would be to tie /sync pagination tokens into\n        // /notifications API somehow.\n        this.client.resetNotifTimelineSet();\n\n        if (!this.currentSyncRequest) {\n            let firstSyncFilter = filterId;\n            const savedSyncToken = await savedSyncTokenPromise;\n\n            if (savedSyncToken) {\n                this.syncOpts.logger.debug(\"Sending first sync request...\");\n            } else {\n                this.syncOpts.logger.debug(\"Sending initial sync request...\");\n                const initialFilter = this.buildDefaultFilter();\n                initialFilter.setDefinition(filter.getDefinition());\n                initialFilter.setTimelineLimit(this.opts.initialSyncLimit!);\n                // Use an inline filter, no point uploading it for a single usage\n                firstSyncFilter = JSON.stringify(initialFilter.getDefinition());\n            }\n\n            // Send this first sync request here so we can then wait for the saved\n            // sync data to finish processing before we process the results of this one.\n            this.currentSyncRequest = this.doSyncRequest({ filter: firstSyncFilter }, savedSyncToken);\n        }\n\n        // Now wait for the saved sync to finish...\n        this.syncOpts.logger.debug(\"Waiting for saved sync before starting sync processing...\");\n        await this.savedSyncPromise;\n        // process the first sync request and continue syncing with the normal filterId\n        return this.doSync({ filter: filterId });\n    }\n\n    /**\n     * Stops the sync object from syncing.\n     */\n    public stop(): void {\n        this.syncOpts.logger.debug(\"SyncApi.stop\");\n        // It is necessary to check for the existance of\n        // globalThis.window AND globalThis.window.removeEventListener.\n        // Some platforms (e.g. React Native) register globalThis.window,\n        // but do not have globalThis.window.removeEventListener.\n        globalThis.window?.removeEventListener?.(\"online\", this.onOnline, false);\n        this.running = false;\n        this.abortController?.abort();\n        if (this.keepAliveTimer) {\n            clearTimeout(this.keepAliveTimer);\n            this.keepAliveTimer = undefined;\n        }\n    }\n\n    /**\n     * Retry a backed off syncing request immediately. This should only be used when\n     * the user <b>explicitly</b> attempts to retry their lost connection.\n     * @returns True if this resulted in a request being retried.\n     */\n    public retryImmediately(): boolean {\n        if (!this.connectionReturnedResolvers) {\n            return false;\n        }\n        this.startKeepAlives(0);\n        return true;\n    }\n    /**\n     * Process a single set of cached sync data.\n     * @param savedSync - a saved sync that was persisted by a store. This\n     * should have been acquired via client.store.getSavedSync().\n     */\n    private async syncFromCache(savedSync: ISavedSync): Promise<void> {\n        this.syncOpts.logger.debug(\"sync(): not doing HTTP hit, instead returning stored /sync data\");\n\n        const nextSyncToken = savedSync.nextBatch;\n\n        // Set sync token for future incremental syncing\n        this.client.store.setSyncToken(nextSyncToken);\n\n        // No previous sync, set old token to null\n        const syncEventData: ISyncStateData = {\n            nextSyncToken,\n            catchingUp: false,\n            fromCache: true,\n        };\n\n        const data: ISyncResponse = {\n            next_batch: nextSyncToken,\n            rooms: savedSync.roomsData,\n            account_data: {\n                events: savedSync.accountData,\n            },\n        };\n\n        try {\n            await this.processSyncResponse(syncEventData, data);\n        } catch (e) {\n            this.syncOpts.logger.error(\"Error processing cached sync\", e);\n        }\n\n        // Don't emit a prepared if we've bailed because the store is invalid:\n        // in this case the client will not be usable until stopped & restarted\n        // so this would be useless and misleading.\n        if (!this.storeIsInvalid) {\n            this.updateSyncState(SyncState.Prepared, syncEventData);\n        }\n    }\n\n    /**\n     * Invoke me to do /sync calls\n     */\n    private async doSync(syncOptions: ISyncOptions): Promise<void> {\n        while (this.running) {\n            const syncToken = this.client.store.getSyncToken();\n\n            let data: ISyncResponse;\n            try {\n                if (!this.currentSyncRequest) {\n                    this.currentSyncRequest = this.doSyncRequest(syncOptions, syncToken);\n                }\n                data = await this.currentSyncRequest;\n            } catch (e) {\n                const abort = await this.onSyncError(<MatrixError>e);\n                if (abort) return;\n                continue;\n            } finally {\n                this.currentSyncRequest = undefined;\n            }\n\n            // set the sync token NOW *before* processing the events. We do this so\n            // if something barfs on an event we can skip it rather than constantly\n            // polling with the same token.\n            this.client.store.setSyncToken(data.next_batch);\n\n            // Reset after a successful sync\n            this.failedSyncCount = 0;\n\n            const syncEventData = {\n                oldSyncToken: syncToken ?? undefined,\n                nextSyncToken: data.next_batch,\n                catchingUp: this.catchingUp,\n            };\n\n            try {\n                await this.processSyncResponse(syncEventData, data);\n            } catch (e) {\n                // log the exception with stack if we have it, else fall back\n                // to the plain description\n                this.syncOpts.logger.error(\"Caught /sync error\", e);\n\n                // Emit the exception for client handling\n                this.client.emit(ClientEvent.SyncUnexpectedError, <Error>e);\n            }\n\n            // Persist after processing as `unsigned` may get mutated\n            // with an `org.matrix.msc4023.thread_id`\n            await this.client.store.setSyncData(data);\n\n            // update this as it may have changed\n            syncEventData.catchingUp = this.catchingUp;\n\n            // emit synced events\n            if (!syncOptions.hasSyncedBefore) {\n                this.updateSyncState(SyncState.Prepared, syncEventData);\n                syncOptions.hasSyncedBefore = true;\n            }\n\n            // tell the crypto module to do its processing. It may block (to do a\n            // /keys/changes request).\n            if (this.syncOpts.cryptoCallbacks) {\n                this.syncOpts.cryptoCallbacks.onSyncCompleted(syncEventData);\n            }\n\n            // keep emitting SYNCING -> SYNCING for clients who want to do bulk updates\n            this.updateSyncState(SyncState.Syncing, syncEventData);\n\n            if (this.client.store.wantsSave()) {\n                // tell databases that everything is now in a consistent state and can be saved.\n                await this.client.store.save();\n            }\n        }\n\n        if (!this.running) {\n            this.syncOpts.logger.debug(\"Sync no longer running: exiting.\");\n            if (this.connectionReturnedResolvers) {\n                this.connectionReturnedResolvers.reject();\n                this.connectionReturnedResolvers = undefined;\n            }\n            this.updateSyncState(SyncState.Stopped);\n        }\n    }\n\n    private doSyncRequest(syncOptions: ISyncOptions, syncToken: string | null): Promise<ISyncResponse> {\n        const qps = this.getSyncParams(syncOptions, syncToken);\n        return this.client.http.authedRequest<ISyncResponse>(Method.Get, \"/sync\", qps as any, undefined, {\n            localTimeoutMs: qps.timeout + BUFFER_PERIOD_MS,\n            abortSignal: this.abortController?.signal,\n        });\n    }\n\n    private getSyncParams(syncOptions: ISyncOptions, syncToken: string | null): ISyncParams {\n        let timeout = this.opts.pollTimeout!;\n\n        if (this.getSyncState() !== SyncState.Syncing || this.catchingUp) {\n            // unless we are happily syncing already, we want the server to return\n            // as quickly as possible, even if there are no events queued. This\n            // serves two purposes:\n            //\n            // * When the connection dies, we want to know asap when it comes back,\n            //   so that we can hide the error from the user. (We don't want to\n            //   have to wait for an event or a timeout).\n            //\n            // * We want to know if the server has any to_device messages queued up\n            //   for us. We do that by calling it with a zero timeout until it\n            //   doesn't give us any more to_device messages.\n            this.catchingUp = true;\n            timeout = 0;\n        }\n\n        let filter = syncOptions.filter;\n        if (this.client.isGuest() && !filter) {\n            filter = this.getGuestFilter();\n        }\n\n        const qps: ISyncParams = {\n            filter,\n            timeout,\n            \"org.matrix.msc4222.use_state_after\": true,\n        };\n\n        if (this.opts.disablePresence) {\n            qps.set_presence = SetPresence.Offline;\n        } else if (this.presence !== undefined) {\n            qps.set_presence = this.presence;\n        }\n\n        if (syncToken) {\n            qps.since = syncToken;\n        } else {\n            // use a cachebuster for initialsyncs, to make sure that\n            // we don't get a stale sync\n            // (https://github.com/vector-im/vector-web/issues/1354)\n            qps._cacheBuster = Date.now();\n        }\n\n        if ([SyncState.Reconnecting, SyncState.Error].includes(this.getSyncState()!)) {\n            // we think the connection is dead. If it comes back up, we won't know\n            // about it till /sync returns. If the timeout= is high, this could\n            // be a long time. Set it to 0 when doing retries so we don't have to wait\n            // for an event or a timeout before emiting the SYNCING event.\n            qps.timeout = 0;\n        }\n\n        return qps;\n    }\n\n    /**\n     * Specify the set_presence value to be used for subsequent calls to the Sync API.\n     * @param presence - the presence to specify to set_presence of sync calls\n     */\n    public setPresence(presence?: SetPresence): void {\n        this.presence = presence;\n    }\n\n    private async onSyncError(err: MatrixError): Promise<boolean> {\n        if (!this.running) {\n            this.syncOpts.logger.debug(\"Sync no longer running: exiting\");\n            if (this.connectionReturnedResolvers) {\n                this.connectionReturnedResolvers.reject();\n                this.connectionReturnedResolvers = undefined;\n            }\n            this.updateSyncState(SyncState.Stopped);\n            return true; // abort\n        }\n\n        this.syncOpts.logger.error(\"/sync error %s\", err);\n\n        if (this.shouldAbortSync(err)) {\n            return true; // abort\n        }\n\n        this.failedSyncCount++;\n        this.syncOpts.logger.debug(\"Number of consecutive failed sync requests:\", this.failedSyncCount);\n\n        this.syncOpts.logger.debug(\"Starting keep-alive\");\n        // Note that we do *not* mark the sync connection as\n        // lost yet: we only do this if a keepalive poke\n        // fails, since long lived HTTP connections will\n        // go away sometimes and we shouldn't treat this as\n        // erroneous. We set the state to 'reconnecting'\n        // instead, so that clients can observe this state\n        // if they wish.\n        const keepAlivePromise = this.startKeepAlives();\n\n        this.currentSyncRequest = undefined;\n        // Transition from RECONNECTING to ERROR after a given number of failed syncs\n        this.updateSyncState(\n            this.failedSyncCount >= FAILED_SYNC_ERROR_THRESHOLD ? SyncState.Error : SyncState.Reconnecting,\n            { error: err },\n        );\n\n        const connDidFail = await keepAlivePromise;\n\n        // Only emit CATCHUP if we detected a connectivity error: if we didn't,\n        // it's quite likely the sync will fail again for the same reason and we\n        // want to stay in ERROR rather than keep flip-flopping between ERROR\n        // and CATCHUP.\n        if (connDidFail && this.getSyncState() === SyncState.Error) {\n            this.updateSyncState(SyncState.Catchup, {\n                catchingUp: true,\n            });\n        }\n        return false;\n    }\n\n    /**\n     * Process data returned from a sync response and propagate it\n     * into the model objects\n     *\n     * @param syncEventData - Object containing sync tokens associated with this sync\n     * @param data - The response from /sync\n     */\n    private async processSyncResponse(syncEventData: ISyncStateData, data: ISyncResponse): Promise<void> {\n        const client = this.client;\n\n        // data looks like:\n        // {\n        //    next_batch: $token,\n        //    presence: { events: [] },\n        //    account_data: { events: [] },\n        //    device_lists: { changed: [\"@user:server\", ... ]},\n        //    to_device: { events: [] },\n        //    device_one_time_keys_count: { signed_curve25519: 42 },\n        //    rooms: {\n        //      invite: {\n        //        $roomid: {\n        //          invite_state: { events: [] }\n        //        }\n        //      },\n        //      join: {\n        //        $roomid: {\n        //          state: { events: [] },\n        //          timeline: { events: [], prev_batch: $token, limited: true },\n        //          ephemeral: { events: [] },\n        //          summary: {\n        //             m.heroes: [ $user_id ],\n        //             m.joined_member_count: $count,\n        //             m.invited_member_count: $count\n        //          },\n        //          account_data: { events: [] },\n        //          unread_notifications: {\n        //              highlight_count: 0,\n        //              notification_count: 0,\n        //          }\n        //          \"org.matrix.msc4222.state_after\": { events: [] },  // only if \"org.matrix.msc4222.use_state_after\" is true\n        //          msc4354_sticky: { events: [] }, // only if \"org.matrix.msc4354.sticky\" is true\n        //        }\n        //      },\n        //      leave: {\n        //        $roomid: {\n        //          state: { events: [] },\n        //          timeline: { events: [], prev_batch: $token }\n        //        }\n        //      }\n        //    }\n        // }\n\n        // TODO-arch:\n        // - Each event we pass through needs to be emitted via 'event', can we\n        //   do this in one place?\n        // - The isBrandNewRoom boilerplate is boilerplatey.\n\n        // handle presence events (User objects)\n        if (Array.isArray(data.presence?.events)) {\n            data.presence!.events.filter(noUnsafeEventProps)\n                .map(client.getEventMapper())\n                .forEach(function (presenceEvent) {\n                    let user = client.store.getUser(presenceEvent.getSender()!);\n                    if (user) {\n                        user.setPresenceEvent(presenceEvent);\n                    } else {\n                        user = User.createUser(presenceEvent.getSender()!, client);\n                        user.setPresenceEvent(presenceEvent);\n                        client.store.storeUser(user);\n                    }\n                    client.emit(ClientEvent.Event, presenceEvent);\n                });\n        }\n\n        // handle non-room account_data\n        if (Array.isArray(data.account_data?.events)) {\n            const events = data.account_data.events.filter(noUnsafeEventProps).map(client.getEventMapper());\n            const prevEventsMap = events.reduce<Record<string, MatrixEvent | undefined>>((m, c) => {\n                m[c.getType()!] = client.store.getAccountData(c.getType());\n                return m;\n            }, {});\n            client.store.storeAccountDataEvents(events);\n            events.forEach(function (accountDataEvent) {\n                // Honour push rules that come down the sync stream but also\n                // honour push rules that were previously cached. Base rules\n                // will be updated when we receive push rules via getPushRules\n                // (see sync) before syncing over the network.\n                if (accountDataEvent.getType() === EventType.PushRules) {\n                    const rules = accountDataEvent.getContent<IPushRules>();\n                    client.setPushRules(rules);\n                }\n                const prevEvent = prevEventsMap[accountDataEvent.getType()!];\n                client.emit(ClientEvent.AccountData, accountDataEvent, prevEvent);\n                return accountDataEvent;\n            });\n        }\n\n        // handle user profile updates (MSC4429)\n        const userUpdate = data[\"users\"] ?? data[\"org.matrix.msc4429.users\"];\n        if (typeof userUpdate === \"object\" && userUpdate !== null) {\n            const usersToRemove: string[] = [];\n            const profilesToAmend: Map<string, SyncUserProfile> = new Map();\n            for (const [userId, userData] of Object.entries(userUpdate)) {\n                logger.info(`Storing user profile ${userId}`, userData);\n                if (userData.profile_updates) {\n                    const existingProfile = await client.store.getUserProfile(userId);\n                    profilesToAmend.set(userId, { ...existingProfile, ...userData.profile_updates });\n                } else if (userData.profile_updates === null) {\n                    usersToRemove.push(userId);\n                }\n            }\n            if (usersToRemove.length) {\n                await client.store.removeUserProfiles(usersToRemove);\n            }\n            if (profilesToAmend.size) {\n                await client.store.storeUserProfiles(profilesToAmend);\n            }\n\n            // emit after we've update the store so that clients can get the updated profile if they want to\n            for (const [userId, userData] of Object.entries(userUpdate)) {\n                client.emit(ClientEvent.UserProfileUpdate, userId, userData.profile_updates ?? null);\n            }\n        }\n\n        // handle to-device events\n        if (data.to_device && Array.isArray(data.to_device.events) && data.to_device.events.length > 0) {\n            const toDeviceMessages: IToDeviceEvent[] = data.to_device.events.filter(noUnsafeEventProps);\n\n            let receivedToDeviceMessages: ReceivedToDeviceMessage[];\n            if (this.syncOpts.cryptoCallbacks) {\n                receivedToDeviceMessages =\n                    await this.syncOpts.cryptoCallbacks.preprocessToDeviceMessages(toDeviceMessages);\n            } else {\n                // Crypto is not enabled, so we just return the events.\n                receivedToDeviceMessages = toDeviceMessages.map((rawEvent) => ({\n                    message: rawEvent,\n                    encryptionInfo: null,\n                }));\n            }\n\n            processToDeviceMessages(receivedToDeviceMessages, client);\n        } else {\n            // no more to-device events: we can stop polling with a short timeout.\n            this.catchingUp = false;\n        }\n\n        // the returned json structure is a bit crap, so make it into a\n        // nicer form (array) after applying sanity to make sure we don't fail\n        // on missing keys (on the off chance)\n        let inviteRooms: WrappedRoom<IInvitedRoom>[] = [];\n        let joinRooms: WrappedRoom<IJoinedRoom>[] = [];\n        let leaveRooms: WrappedRoom<ILeftRoom>[] = [];\n        let knockRooms: WrappedRoom<IKnockedRoom>[] = [];\n\n        if (data.rooms) {\n            if (data.rooms.invite) {\n                inviteRooms = this.mapSyncResponseToRoomArray(data.rooms.invite);\n            }\n            if (data.rooms.join) {\n                joinRooms = this.mapSyncResponseToRoomArray(data.rooms.join);\n            }\n            if (data.rooms.leave) {\n                leaveRooms = this.mapSyncResponseToRoomArray(data.rooms.leave);\n            }\n            if (data.rooms.knock) {\n                knockRooms = this.mapSyncResponseToRoomArray(data.rooms.knock);\n            }\n        }\n\n        this.notifEvents = [];\n\n        // Handle invites\n        await Promise.all(\n            inviteRooms.map(async (inviteObj) => {\n                const room = inviteObj.room;\n                const stateEvents = this.mapSyncEventsFormat(inviteObj.invite_state, room);\n\n                await this.injectRoomEvents(room, stateEvents, undefined);\n\n                if (inviteObj.isBrandNewRoom) {\n                    room.recalculate();\n                    client.store.storeRoom(room);\n                    client.emit(ClientEvent.Room, room);\n                } else {\n                    // Update room state for invite->reject->invite cycles\n                    room.recalculate();\n                }\n                stateEvents.forEach(function (e) {\n                    client.emit(ClientEvent.Event, e);\n                });\n            }),\n        );\n\n        // Handle joins\n        await Promise.all(\n            joinRooms.map(async (joinObj) => {\n                const room = joinObj.room;\n                const stateEvents = this.mapSyncEventsFormat(joinObj.state, room);\n                const stateAfterEvents = this.mapSyncEventsFormat(joinObj[\"org.matrix.msc4222.state_after\"], room);\n                // Prevent events from being decrypted ahead of time\n                // this helps large account to speed up faster\n                // room::decryptCriticalEvent is in charge of decrypting all the events\n                // required for a client to function properly\n                const timelineEvents = this.mapSyncEventsFormat(joinObj.timeline, room, false);\n                const ephemeralEvents = this.mapSyncEventsFormat(joinObj.ephemeral);\n                const accountDataEvents = this.mapSyncEventsFormat(joinObj.account_data);\n                const stickyEvents = this.mapSyncEventsFormat(joinObj.msc4354_sticky);\n\n                // If state_after is present, this is the events that form the state at the end of the timeline block and\n                // regular timeline events do *not* count towards state. If it's not present, then the state is formed by\n                // the state events plus the timeline events. Note mapSyncEventsFormat returns an empty array if the field\n                // is absent so we explicitly check the field on the original object.\n                const eventsFormingFinalState = joinObj[\"org.matrix.msc4222.state_after\"]\n                    ? stateAfterEvents\n                    : stateEvents.concat(timelineEvents);\n\n                const encrypted = this.isRoomEncrypted(room, eventsFormingFinalState);\n                // We store the server-provided value first so it's correct when any of the events fire.\n                if (joinObj.unread_notifications) {\n                    /**\n                     * We track unread notifications ourselves in encrypted rooms, so don't\n                     * bother setting it here. We trust our calculations better than the\n                     * server's for this case, and therefore will assume that our non-zero\n                     * count is accurate.\n                     * XXX: this is known faulty as the push rule for `.m.room.encrypted` may be disabled so server\n                     * may issue notification counts of 0 which we wrongly trust.\n                     * https://github.com/matrix-org/matrix-spec-proposals/pull/2654 would fix this\n                     *\n                     * @see import(\"./client\").fixNotificationCountOnDecryption\n                     */\n                    if (!encrypted || joinObj.unread_notifications.notification_count === 0) {\n                        // In an encrypted room, if the room has notifications enabled then it's typical for\n                        // the server to flag all new messages as notifying. However, some push rules calculate\n                        // events as ignored based on their event contents (e.g. ignoring msgtype=m.notice messages)\n                        // so we want to calculate this figure on the client in all cases.\n                        room.setUnreadNotificationCount(\n                            NotificationCountType.Total,\n                            joinObj.unread_notifications.notification_count ?? 0,\n                        );\n                    }\n\n                    if (!encrypted || room.getUnreadNotificationCount(NotificationCountType.Highlight) <= 0) {\n                        // If the locally stored highlight count is zero, use the server provided value.\n                        room.setUnreadNotificationCount(\n                            NotificationCountType.Highlight,\n                            joinObj.unread_notifications.highlight_count ?? 0,\n                        );\n                    }\n                }\n\n                const unreadThreadNotifications =\n                    joinObj[UNREAD_THREAD_NOTIFICATIONS.name] ?? joinObj[UNREAD_THREAD_NOTIFICATIONS.altName!];\n                if (unreadThreadNotifications) {\n                    // This mirrors the logic above for rooms: take the *total* notification count from\n                    // the server for unencrypted rooms or is it's zero. Any threads not present in this\n                    // object implicitly have zero notifications, so start by clearing the total counts\n                    // for all such threads.\n                    room.resetThreadUnreadNotificationCountFromSync(Object.keys(unreadThreadNotifications));\n                    for (const [threadId, unreadNotification] of Object.entries(unreadThreadNotifications)) {\n                        if (!encrypted || unreadNotification.notification_count === 0) {\n                            room.setThreadUnreadNotificationCount(\n                                threadId,\n                                NotificationCountType.Total,\n                                unreadNotification.notification_count ?? 0,\n                            );\n                        }\n\n                        const hasNoNotifications =\n                            room.getThreadUnreadNotificationCount(threadId, NotificationCountType.Highlight) <= 0;\n                        if (!encrypted || (encrypted && hasNoNotifications)) {\n                            room.setThreadUnreadNotificationCount(\n                                threadId,\n                                NotificationCountType.Highlight,\n                                unreadNotification.highlight_count ?? 0,\n                            );\n                        }\n                    }\n                } else {\n                    room.resetThreadUnreadNotificationCountFromSync();\n                }\n\n                joinObj.timeline = joinObj.timeline || ({} as ITimeline);\n\n                if (joinObj.isBrandNewRoom) {\n                    // set the back-pagination token. Do this *before* adding any\n                    // events so that clients can start back-paginating.\n                    if (joinObj.timeline.prev_batch !== null) {\n                        room.getLiveTimeline().setPaginationToken(joinObj.timeline.prev_batch, EventTimeline.BACKWARDS);\n                    }\n                } else if (joinObj.timeline.limited) {\n                    let limited = true;\n\n                    // we've got a limited sync, so we *probably* have a gap in the\n                    // timeline, so should reset. But we might have been peeking or\n                    // paginating and already have some of the events, in which\n                    // case we just want to append any subsequent events to the end\n                    // of the existing timeline.\n                    //\n                    // This is particularly important in the case that we already have\n                    // *all* of the events in the timeline - in that case, if we reset\n                    // the timeline, we'll end up with an entirely empty timeline,\n                    // which we'll try to paginate but not get any new events (which\n                    // will stop us linking the empty timeline into the chain).\n                    //\n                    for (let i = timelineEvents.length - 1; i >= 0; i--) {\n                        const eventId = timelineEvents[i].getId()!;\n                        if (room.getTimelineForEvent(eventId)) {\n                            this.syncOpts.logger.debug(`Already have event ${eventId} in limited sync - not resetting`);\n                            limited = false;\n\n                            // we might still be missing some of the events before i;\n                            // we don't want to be adding them to the end of the\n                            // timeline because that would put them out of order.\n                            timelineEvents.splice(0, i);\n\n                            // XXX: there's a problem here if the skipped part of the\n                            // timeline modifies the state set in stateEvents, because\n                            // we'll end up using the state from stateEvents rather\n                            // than the later state from timelineEvents. We probably\n                            // need to wind stateEvents forward over the events we're\n                            // skipping.\n\n                            break;\n                        }\n                    }\n\n                    if (limited) {\n                        room.resetLiveTimeline(\n                            joinObj.timeline.prev_batch,\n                            this.syncOpts.canResetEntireTimeline!(room.roomId)\n                                ? null\n                                : (syncEventData.oldSyncToken ?? null),\n                        );\n\n                        // We have to assume any gap in any timeline is\n                        // reason to stop incrementally tracking notifications and\n                        // reset the timeline.\n                        client.resetNotifTimelineSet();\n                    }\n                }\n\n                // process any crypto events *before* emitting the RoomStateEvent events. This\n                // avoids a race condition if the application tries to send a message after the\n                // state event is processed, but before crypto is enabled, which then causes the\n                // crypto layer to complain.\n\n                if (this.syncOpts.cryptoCallbacks) {\n                    for (const e of eventsFormingFinalState) {\n                        if (e.isState() && e.getType() === EventType.RoomEncryption && e.getStateKey() === \"\") {\n                            await this.syncOpts.cryptoCallbacks.onCryptoEvent(room, e);\n                        }\n                    }\n                }\n\n                // Proactively decrypt state events: normally we decrypt on demand, but for state\n                // events we need them immediately, so we handle them here. Specifically, consumers\n                // (e.g. Element Web) expect state events to be unencrypted upon receipt.\n                for (const ev of timelineEvents.filter((ev) => ev.isState())) {\n                    await this.client.decryptEventIfNeeded(ev);\n                }\n\n                try {\n                    if (\"org.matrix.msc4222.state_after\" in joinObj) {\n                        await this.injectRoomEvents(\n                            room,\n                            undefined,\n                            stateAfterEvents,\n                            timelineEvents,\n                            syncEventData.fromCache,\n                        );\n                    } else {\n                        await this.injectRoomEvents(\n                            room,\n                            stateEvents,\n                            undefined,\n                            timelineEvents,\n                            syncEventData.fromCache,\n                        );\n                    }\n                } catch (e) {\n                    this.syncOpts.logger.error(`Failed to process events on room ${room.roomId}:`, e);\n                }\n\n                // set summary after processing events,\n                // because it will trigger a name calculation\n                // which needs the room state to be up to date\n                if (joinObj.summary) {\n                    room.setSummary(joinObj.summary);\n                }\n\n                // we deliberately don't add ephemeral events to the timeline\n                room.addEphemeralEvents(ephemeralEvents);\n\n                // we deliberately don't add accountData to the timeline\n                room.addAccountData(accountDataEvents);\n\n                // Sticky events primarily come via the `timeline` field, with the\n                // sticky info field marking them as sticky.\n                // If the sync is \"gappy\" (meaning it is skipping events to catch up) then\n                // sticky events will instead come down the sticky section.\n                // This ensures we collect sticky events from both places.\n                const stickyEventsAndStickyEventsFromTheTimeline = stickyEvents.concat(\n                    timelineEvents.filter((e) => e.unstableStickyInfo !== undefined),\n                );\n                // Note: We calculate sticky events before emitting `.Room` as it's nice to have\n                // sticky events calculated and ready to go.\n                room._unstable_addStickyEvents(stickyEventsAndStickyEventsFromTheTimeline);\n\n                room.recalculate();\n                if (joinObj.isBrandNewRoom) {\n                    client.store.storeRoom(room);\n                    client.emit(ClientEvent.Room, room);\n                }\n\n                this.processEventsForNotifs(room, timelineEvents);\n\n                const emitEvent = (e: MatrixEvent): boolean => client.emit(ClientEvent.Event, e);\n                // this fires a couple of times for some events. (eg state events are in the timeline and the state)\n                // should this get a sync section as an additional event emission param (e, syncSection))?\n                stateEvents.forEach(emitEvent);\n                timelineEvents.forEach(emitEvent);\n                ephemeralEvents.forEach(emitEvent);\n                accountDataEvents.forEach(emitEvent);\n                stickyEvents\n                    .filter(\n                        (stickyEvent) =>\n                            // This is highly unlikey, but in the case where a sticky event\n                            // has appeared in the timeline AND the sticky section, we only\n                            // want to emit the event once.\n                            !timelineEvents.some((timelineEvent) => timelineEvent.getId() === stickyEvent.getId()),\n                    )\n                    .forEach(emitEvent);\n                // Decrypt only the last message in all rooms to make sure we can generate a preview\n                // And decrypt all events after the recorded read receipt to ensure an accurate\n                // notification count\n                room.decryptCriticalEvents();\n            }),\n        );\n\n        // Handle leaves (e.g. kicked rooms)\n        await Promise.all(\n            leaveRooms.map(async (leaveObj) => {\n                const room = leaveObj.room;\n                const { timelineEvents, stateEvents, stateAfterEvents } = await this.mapAndInjectRoomEvents(leaveObj);\n                const accountDataEvents = this.mapSyncEventsFormat(leaveObj.account_data);\n\n                room.addAccountData(accountDataEvents);\n\n                room.recalculate();\n                if (leaveObj.isBrandNewRoom) {\n                    client.store.storeRoom(room);\n                    client.emit(ClientEvent.Room, room);\n                }\n\n                this.processEventsForNotifs(room, timelineEvents);\n\n                stateEvents?.forEach(function (e) {\n                    client.emit(ClientEvent.Event, e);\n                });\n                stateAfterEvents?.forEach(function (e) {\n                    client.emit(ClientEvent.Event, e);\n                });\n                timelineEvents.forEach(function (e) {\n                    client.emit(ClientEvent.Event, e);\n                });\n                accountDataEvents.forEach(function (e) {\n                    client.emit(ClientEvent.Event, e);\n                });\n            }),\n        );\n\n        // Handle knocks\n        await Promise.all(\n            knockRooms.map(async (knockObj) => {\n                const room = knockObj.room;\n                const stateEvents = this.mapSyncEventsFormat(knockObj.knock_state, room);\n\n                await this.injectRoomEvents(room, stateEvents, undefined);\n\n                if (knockObj.isBrandNewRoom) {\n                    room.recalculate();\n                    client.store.storeRoom(room);\n                    client.emit(ClientEvent.Room, room);\n                } else {\n                    // Update room state for knock->leave->knock cycles\n                    room.recalculate();\n                }\n                stateEvents.forEach(function (e) {\n                    client.emit(ClientEvent.Event, e);\n                });\n            }),\n        );\n\n        // update the notification timeline, if appropriate.\n        // we only do this for live events, as otherwise we can't order them sanely\n        // in the timeline relative to ones paginated in by /notifications.\n        // XXX: we could fix this by making EventTimeline support chronological\n        // ordering... but it doesn't, right now.\n        if (syncEventData.oldSyncToken && this.notifEvents.length) {\n            this.notifEvents.sort(function (a, b) {\n                return a.getTs() - b.getTs();\n            });\n            this.notifEvents.forEach(function (event) {\n                client.getNotifTimelineSet()?.addLiveEvent(event, { addToState: true });\n            });\n        }\n\n        // Handle device list updates\n        if (data.device_lists) {\n            if (this.syncOpts.cryptoCallbacks) {\n                await this.syncOpts.cryptoCallbacks.processDeviceLists(data.device_lists);\n            } else {\n                // FIXME if we *don't* have a crypto module, we still need to\n                // invalidate the device lists. But that would require a\n                // substantial bit of rework :/.\n            }\n        }\n\n        // Handle one_time_keys_count and unused fallback keys\n        await this.syncOpts.cryptoCallbacks?.processKeyCounts(\n            data.device_one_time_keys_count,\n            data.device_unused_fallback_key_types ?? data[\"org.matrix.msc2732.device_unused_fallback_key_types\"],\n        );\n    }\n\n    /**\n     * Starts polling the connectivity check endpoint\n     * @param delay - How long to delay until the first poll.\n     *        defaults to a short, randomised interval (to prevent\n     *        tight-looping if /versions succeeds but /sync etc. fail).\n     * @returns which resolves once the connection returns\n     */\n    private startKeepAlives(delay?: number): Promise<boolean> {\n        if (delay === undefined) {\n            delay = 2000 + Math.floor(Math.random() * 5000);\n        }\n\n        if (this.keepAliveTimer !== null) {\n            clearTimeout(this.keepAliveTimer);\n        }\n        if (delay > 0) {\n            this.keepAliveTimer = setTimeout(this.pokeKeepAlive.bind(this), delay);\n        } else {\n            this.pokeKeepAlive();\n        }\n        if (!this.connectionReturnedResolvers) {\n            this.connectionReturnedResolvers = Promise.withResolvers();\n        }\n        return this.connectionReturnedResolvers.promise;\n    }\n\n    /**\n     * Make a dummy call to /_matrix/client/versions, to see if the HS is\n     * reachable.\n     *\n     * On failure, schedules a call back to itself. On success, resolves\n     * this.connectionReturnedResolvers.\n     *\n     * @param connDidFail - True if a connectivity failure has been detected. Optional.\n     */\n    private pokeKeepAlive(connDidFail = false): void {\n        if (!this.running) {\n            // we are in a keepAlive, retrying to connect, but the syncronization\n            // was stopped, so we are stopping the retry.\n            clearTimeout(this.keepAliveTimer);\n            if (this.connectionReturnedResolvers) {\n                this.connectionReturnedResolvers.reject(\"SyncApi.stop() was called\");\n                this.connectionReturnedResolvers = undefined;\n            }\n            return;\n        }\n\n        const success = (): void => {\n            clearTimeout(this.keepAliveTimer);\n            if (this.connectionReturnedResolvers) {\n                this.connectionReturnedResolvers.resolve(connDidFail);\n                this.connectionReturnedResolvers = undefined;\n            }\n        };\n\n        this.client.http\n            .request(\n                Method.Get,\n                \"/_matrix/client/versions\",\n                undefined, // queryParams\n                undefined, // data\n                {\n                    prefix: \"\",\n                    localTimeoutMs: 15 * 1000,\n                    abortSignal: this.abortController?.signal,\n                },\n            )\n            .then(\n                () => {\n                    success();\n                },\n                (err) => {\n                    if (err.httpStatus == 400 || err.httpStatus == 404) {\n                        // treat this as a success because the server probably just doesn't\n                        // support /versions: point is, we're getting a response.\n                        // We wait a short time though, just in case somehow the server\n                        // is in a mode where it 400s /versions responses and sync etc.\n                        // responses fail, this will mean we don't hammer in a loop.\n                        this.keepAliveTimer = setTimeout(success, 2000);\n                    } else {\n                        connDidFail = true;\n                        this.keepAliveTimer = setTimeout(\n                            this.pokeKeepAlive.bind(this, connDidFail),\n                            5000 + Math.floor(Math.random() * 5000),\n                        );\n                        // A keepalive has failed, so we emit the\n                        // error state (whether or not this is the\n                        // first failure).\n                        // Note we do this after setting the timer:\n                        // this lets the unit tests advance the mock\n                        // clock when they get the error.\n                        this.updateSyncState(SyncState.Error, { error: err });\n                    }\n                },\n            );\n    }\n\n    private mapSyncResponseToRoomArray<T extends ILeftRoom | IJoinedRoom | IInvitedRoom | IKnockedRoom>(\n        obj: Record<string, T>,\n    ): Array<WrappedRoom<T>> {\n        // Maps { roomid: {stuff}, roomid: {stuff} }\n        // to\n        // [{stuff+Room+isBrandNewRoom}, {stuff+Room+isBrandNewRoom}]\n        const client = this.client;\n        return Object.keys(obj)\n            .filter((k) => !unsafeProp(k))\n            .map((roomId) => {\n                let room = client.store.getRoom(roomId);\n                let isBrandNewRoom = false;\n                if (!room) {\n                    room = this.createRoom(roomId);\n                    isBrandNewRoom = true;\n                }\n                return {\n                    ...obj[roomId],\n                    room,\n                    isBrandNewRoom,\n                };\n            });\n    }\n\n    private mapSyncEventsFormat(\n        obj: IInviteState | ITimeline | IEphemeral | undefined,\n        room?: Room,\n        decrypt = true,\n    ): MatrixEvent[] {\n        if (!obj || !Array.isArray(obj.events)) {\n            return [];\n        }\n        const mapper = this.client.getEventMapper({ decrypt });\n        type TaggedEvent = (IStrippedState | IRoomEvent | IStateEvent | IMinimalEvent) & { room_id?: string };\n        return (obj.events as TaggedEvent[]).filter(noUnsafeEventProps).map(function (e) {\n            if (room) {\n                e.room_id = room.roomId;\n            }\n            return mapper(e);\n        });\n    }\n\n    /**\n     */\n    private resolveInvites(room: Room): void {\n        if (!room || !this.opts.resolveInvitesToProfiles) {\n            return;\n        }\n        const client = this.client;\n        // For each invited room member we want to give them a displayname/avatar url\n        // if they have one (the m.room.member invites don't contain this).\n        room.getMembersWithMembership(KnownMembership.Invite).forEach(function (member) {\n            if (member.requestedProfileInfo) return;\n            member.requestedProfileInfo = true;\n            // try to get a cached copy first.\n            const user = client.getUser(member.userId);\n            let promise;\n            if (user) {\n                promise = Promise.resolve({\n                    avatar_url: user.avatarUrl,\n                    displayname: user.displayName,\n                });\n            } else {\n                promise = client.getProfileInfo(member.userId);\n            }\n            promise.then(\n                function (info) {\n                    // slightly naughty by doctoring the invite event but this means all\n                    // the code paths remain the same between invite/join display name stuff\n                    // which is a worthy trade-off for some minor pollution.\n                    const inviteEvent = member.events.member;\n                    if (inviteEvent?.getContent().membership !== KnownMembership.Invite) {\n                        // between resolving and now they have since joined, so don't clobber\n                        return;\n                    }\n                    inviteEvent.getContent().avatar_url = info.avatar_url;\n                    inviteEvent.getContent().displayname = info.displayname;\n                    // fire listeners\n                    member.setMembershipEvent(inviteEvent, room.currentState);\n                },\n                function (err) {\n                    // OH WELL.\n                },\n            );\n        });\n    }\n\n    private findEncryptionEvent(events?: MatrixEvent[]): MatrixEvent | undefined {\n        return events?.find((e) => e.getType() === EventType.RoomEncryption && e.getStateKey() === \"\");\n    }\n\n    // When processing the sync response we cannot rely on Room.hasEncryptionStateEvent we actually\n    // inject the events into the room object, so we have to inspect the events themselves.\n    private isRoomEncrypted(room: Room, eventsFormingFinalState: MatrixEvent[]): boolean {\n        return room.hasEncryptionStateEvent() || !!this.findEncryptionEvent(eventsFormingFinalState);\n    }\n\n    private async mapAndInjectRoomEvents(wrappedRoom: WrappedRoom<ILeftRoom>): Promise<{\n        timelineEvents: MatrixEvent[];\n        stateEvents?: MatrixEvent[];\n        stateAfterEvents?: MatrixEvent[];\n    }> {\n        const stateEvents = this.mapSyncEventsFormat(wrappedRoom.state, wrappedRoom.room);\n        const stateAfterEvents = this.mapSyncEventsFormat(\n            wrappedRoom[\"org.matrix.msc4222.state_after\"],\n            wrappedRoom.room,\n        );\n        const timelineEvents = this.mapSyncEventsFormat(wrappedRoom.timeline, wrappedRoom.room);\n\n        if (\"org.matrix.msc4222.state_after\" in wrappedRoom) {\n            await this.injectRoomEvents(wrappedRoom.room, undefined, stateAfterEvents, timelineEvents);\n        } else {\n            await this.injectRoomEvents(wrappedRoom.room, stateEvents, undefined, timelineEvents);\n        }\n\n        return { timelineEvents, stateEvents, stateAfterEvents };\n    }\n\n    /**\n     * Injects events into a room's model.\n     * @param stateEventList - A list of state events. This is the state\n     * at the *START* of the timeline list if it is supplied.\n     * @param stateAfterEventList - A list of state events. This is the state\n     * at the *END* of the timeline list if it is supplied.\n     * @param timelineEventList - A list of timeline events, including threaded. Lower index\n     * is earlier in time. Higher index is later.\n     * @param fromCache - whether the sync response came from cache\n     *\n     * No more than one of stateEventList and stateAfterEventList must be supplied. If\n     * stateEventList is supplied, the events in timelineEventList are added to the state\n     * after stateEventList. If stateAfterEventList is supplied, the events in timelineEventList\n     * are not added to the state.\n     */\n    public async injectRoomEvents(\n        room: Room,\n        stateEventList: MatrixEvent[],\n        stateAfterEventList: undefined,\n        timelineEventList?: MatrixEvent[],\n        fromCache?: boolean,\n    ): Promise<void>;\n    public async injectRoomEvents(\n        room: Room,\n        stateEventList: undefined,\n        stateAfterEventList: MatrixEvent[],\n        timelineEventList?: MatrixEvent[],\n        fromCache?: boolean,\n    ): Promise<void>;\n    public async injectRoomEvents(\n        room: Room,\n        stateEventList: MatrixEvent[] | undefined,\n        stateAfterEventList: MatrixEvent[] | undefined,\n        timelineEventList?: MatrixEvent[],\n        fromCache = false,\n    ): Promise<void> {\n        const eitherStateEventList = stateAfterEventList ?? stateEventList!;\n\n        // If there are no events in the timeline yet, initialise it with\n        // the given state events\n        const liveTimeline = room.getLiveTimeline();\n        const timelineWasEmpty = liveTimeline.getEvents().length == 0;\n        if (timelineWasEmpty) {\n            // Passing these events into initialiseState will freeze them, so we need\n            // to compute and cache the push actions for them now, otherwise sync dies\n            // with an attempt to assign to read only property.\n            // XXX: This is pretty horrible and is assuming all sorts of behaviour from\n            // these functions that it shouldn't be. We should probably either store the\n            // push actions cache elsewhere so we can freeze MatrixEvents, or otherwise\n            // find some solution where MatrixEvents are immutable but allow for a cache\n            // field.\n\n            for (const ev of eitherStateEventList) {\n                this.client.getPushActionsForEvent(ev);\n            }\n            liveTimeline.initialiseState(eitherStateEventList, {\n                timelineWasEmpty,\n            });\n        }\n\n        this.resolveInvites(room);\n\n        // recalculate the room name at this point as adding events to the timeline\n        // may make notifications appear which should have the right name.\n        // XXX: This looks suspect: we'll end up recalculating the room once here\n        // and then again after adding events (processSyncResponse calls it after\n        // calling us) even if no state events were added. It also means that if\n        // one of the room events in timelineEventList is something that needs\n        // a recalculation (like m.room.name) we won't recalculate until we've\n        // finished adding all the events, which will cause the notification to have\n        // the old room name rather than the new one.\n        room.recalculate();\n\n        // If the timeline wasn't empty, we process the state events here: they're\n        // defined as updates to the state before the start of the timeline, so this\n        // starts to roll the state forward.\n        // XXX: That's what we *should* do, but this can happen if we were previously\n        // peeking in a room, in which case we obviously do *not* want to add the\n        // state events here onto the end of the timeline. Historically, the js-sdk\n        // has just set these new state events on the old and new state. This seems\n        // very wrong because there could be events in the timeline that diverge the\n        // state, in which case this is going to leave things out of sync. However,\n        // for now I think it;s best to behave the same as the code has done previously.\n        if (!timelineWasEmpty) {\n            // XXX: As above, don't do this...\n            //room.addLiveEvents(stateEventList || []);\n            // Do this instead...\n            room.oldState.setStateEvents(eitherStateEventList);\n            room.currentState.setStateEvents(eitherStateEventList);\n        }\n\n        // Execute the timeline events. If addToState is true the timeline has any state\n        // events in it, this will continue to diverge the current state.\n        // This also needs to be done before running push rules on the events as they need\n        // to be decorated with sender etc.\n        await room.addLiveEvents(timelineEventList || [], {\n            fromCache,\n            timelineWasEmpty,\n            addToState: stateAfterEventList === undefined,\n        });\n        this.client.processBeaconEvents(room, timelineEventList);\n    }\n\n    /**\n     * Takes a list of timelineEvents and adds and adds to notifEvents\n     * as appropriate.\n     * This must be called after the room the events belong to has been stored.\n     *\n     * @param timelineEventList - A list of timeline events. Lower index\n     * is earlier in time. Higher index is later.\n     */\n    private processEventsForNotifs(room: Room, timelineEventList: MatrixEvent[]): void {\n        // gather our notifications into this.notifEvents\n        if (this.client.getNotifTimelineSet()) {\n            for (const event of timelineEventList) {\n                const pushActions = this.client.getPushActionsForEvent(event);\n                if (pushActions?.notify && pushActions.tweaks?.highlight) {\n                    this.notifEvents.push(event);\n                }\n            }\n        }\n    }\n\n    private getGuestFilter(): string {\n        // Dev note: This used to be conditional to return a filter of 20 events maximum, but\n        // the condition never went to the other branch. This is now hardcoded.\n        return \"{}\";\n    }\n\n    /**\n     * Sets the sync state and emits an event to say so\n     * @param newState - The new state string\n     * @param data - Object of additional data to emit in the event\n     */\n    private updateSyncState(newState: SyncState, data?: ISyncStateData): void {\n        const old = this.syncState;\n        this.syncState = newState;\n        this.syncStateData = data;\n        this.client.emit(ClientEvent.Sync, this.syncState, old, data);\n    }\n\n    /**\n     * Event handler for the 'online' event\n     * This event is generally unreliable and precise behaviour\n     * varies between browsers, so we poll for connectivity too,\n     * but this might help us reconnect a little faster.\n     */\n    private onOnline = (): void => {\n        this.syncOpts.logger.debug(\"Browser thinks we are back online\");\n        this.startKeepAlives(0);\n    };\n}\n\n// /!\\ This function is not intended for public use! It's only exported from\n// here in order to share some common logic with sliding-sync-sdk.ts.\nexport function _createAndReEmitRoom(client: MatrixClient, roomId: string, opts: Partial<IStoredClientOpts>): Room {\n    const { timelineSupport } = client;\n\n    const room = new Room(roomId, client, client.getUserId()!, {\n        lazyLoadMembers: opts.lazyLoadMembers,\n        pendingEventOrdering: opts.pendingEventOrdering,\n        timelineSupport,\n    });\n\n    client.reEmitter.reEmit(room, [\n        RoomEvent.Name,\n        RoomEvent.Redaction,\n        RoomEvent.RedactionCancelled,\n        RoomEvent.Receipt,\n        RoomEvent.Tags,\n        RoomEvent.LocalEchoUpdated,\n        RoomEvent.AccountData,\n        RoomEvent.MyMembership,\n        RoomEvent.Timeline,\n        RoomEvent.TimelineReset,\n        RoomStateEvent.Events,\n        RoomStateEvent.Members,\n        RoomStateEvent.NewMember,\n        RoomStateEvent.Update,\n        BeaconEvent.New,\n        BeaconEvent.Update,\n        BeaconEvent.Destroy,\n        BeaconEvent.LivenessChange,\n    ]);\n\n    // We need to add a listener for RoomState.members in order to hook them\n    // correctly.\n    room.on(RoomStateEvent.NewMember, (event, state, member) => {\n        member.user = client.getUser(member.userId) ?? undefined;\n        client.reEmitter.reEmit(member, [\n            RoomMemberEvent.Name,\n            RoomMemberEvent.Typing,\n            RoomMemberEvent.PowerLevel,\n            RoomMemberEvent.Membership,\n        ]);\n    });\n\n    return room;\n}\n\n/**\n * Process a list of (decrypted, where possible) received to-device events.\n *\n * Emits the appropriate {@link ClientEvent.ReceivedToDeviceMessage} event.\n * Also converts the events into `MatrixEvent`s, and emits the now deprecated {@link ClientEvent.ToDeviceEvent} events for compatibility.\n * */\nexport function processToDeviceMessages(toDeviceMessages: ReceivedToDeviceMessage[], client: MatrixClient): void {\n    const cancelledKeyVerificationTxns: string[] = [];\n    toDeviceMessages\n        .map((processedMessage) => {\n            // map is a cheap inline forEach\n            // We want to flag m.key.verification.start events as cancelled\n            // if there's an accompanying m.key.verification.cancel event, so\n            // we pull out the transaction IDs from the cancellation events\n            // so we can flag the verification events as cancelled in the loop\n            // below.\n            if (processedMessage.message.type === \"m.key.verification.cancel\") {\n                const txnId: string = processedMessage.message.content[\"transaction_id\"];\n                if (txnId) {\n                    cancelledKeyVerificationTxns.push(txnId);\n                }\n            }\n\n            // as mentioned above, .map is a cheap inline forEach, so return\n            // the unmodified event.\n            return processedMessage;\n        })\n        .forEach(function (processedEvent) {\n            // For backwards compatibility, we also emit the event as a `MatrixEvent` using `ClientEvent.ToDeviceEvent`.\n            {\n                const toDeviceEvent = processedEvent.message;\n                const content = toDeviceEvent.content;\n                // The message is cloned before being passed to the MatrixEvent constructor, because\n                // the `makeEncrypted` method will mutate the type and content properties of the original message and will interfere\n                // with the emitted event for `ReceivedToDeviceMessage`.\n                const deprecatedCompatibilityEvent = new MatrixEvent(Object.assign({}, toDeviceEvent));\n                if (\n                    toDeviceEvent.type === \"m.key.verification.start\" ||\n                    toDeviceEvent.type === \"m.key.verification.request\"\n                ) {\n                    const txnId = content[\"transaction_id\"];\n                    if (cancelledKeyVerificationTxns.includes(txnId)) {\n                        deprecatedCompatibilityEvent.flagCancelled();\n                    }\n                }\n                if (processedEvent.encryptionInfo) {\n                    // Restore partially the legacy behavior to detect encrypted messages.\n                    // Now `event.isEncrypted()` will return true.\n                    deprecatedCompatibilityEvent.makeEncrypted(\n                        EventType.RoomMessageEncrypted,\n                        { ciphertext: \"\" },\n                        processedEvent.encryptionInfo.senderCurve25519KeyBase64,\n                        \"\",\n                    );\n                }\n\n                client.emit(ClientEvent.ToDeviceEvent, deprecatedCompatibilityEvent);\n            }\n\n            client.emit(ClientEvent.ReceivedToDeviceMessage, processedEvent);\n        });\n}\n","/*\nCopyright 2015, 2016, 2019, 2023 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n/**\n * This is an internal module.\n */\n\nimport unhomoglyph from \"unhomoglyph\";\nimport promiseRetry from \"p-retry\";\n\nimport { type IEvent, type MatrixEvent } from \"./models/event.ts\";\nimport { M_TIMESTAMP } from \"./@types/location.ts\";\nimport { ReceiptType } from \"./@types/read_receipts.ts\";\nimport { type BaseLogger } from \"./logger.ts\";\n\nconst interns = new Map<string, string>();\n\n/**\n * Internalises a string, reusing a known pointer or storing the pointer\n * if needed for future strings.\n * @param str - The string to internalise.\n * @returns The internalised string.\n */\nexport function internaliseString(str: string): string {\n    // Unwrap strings before entering the map, if we somehow got a wrapped\n    // string as our input. This should only happen from tests.\n    if ((str as unknown) instanceof String) {\n        str = str.toString();\n    }\n\n    // Check the map to see if we can store the value\n    if (!interns.has(str)) {\n        interns.set(str, str);\n    }\n\n    // Return any cached string reference\n    return interns.get(str)!;\n}\n\n/**\n * Encode a dictionary of query parameters.\n * Omits any undefined/null values.\n * @param params - A dict of key/values to encode e.g.\n * `{\"foo\": \"bar\", \"baz\": \"taz\"}`\n * @returns The encoded string e.g. foo=bar&baz=taz\n */\nexport function encodeParams(params: QueryDict, urlSearchParams?: URLSearchParams): URLSearchParams {\n    const searchParams = urlSearchParams ?? new URLSearchParams();\n    for (const [key, val] of Object.entries(params)) {\n        if (val !== undefined && val !== null) {\n            if (Array.isArray(val)) {\n                val.forEach((v) => {\n                    searchParams.append(key, String(v));\n                });\n            } else {\n                searchParams.append(key, String(val));\n            }\n        }\n    }\n    return searchParams;\n}\n\nexport type QueryDict = Record<string, string[] | string | number | boolean | undefined>;\n\n/**\n * Replace a stable parameter with the unstable naming for params\n */\nexport function replaceParam(stable: string, unstable: string, dict: QueryDict): QueryDict {\n    const result = {\n        ...dict,\n        [unstable]: dict[stable],\n    };\n    delete result[stable];\n    return result;\n}\n\n/**\n * Decode a query string in `application/x-www-form-urlencoded` format.\n * @param query - A query string to decode e.g.\n * foo=bar&via=server1&server2\n * @returns The decoded object, if any keys occurred multiple times\n * then the value will be an array of strings, else it will be an array.\n * This behaviour matches Node's qs.parse but is built on URLSearchParams\n * for native web compatibility\n */\nexport function decodeParams(query: string): Record<string, string | string[]> {\n    const o: Record<string, string | string[]> = {};\n    const params = new URLSearchParams(query);\n    for (const key of params.keys()) {\n        const val = params.getAll(key);\n        o[key] = val.length === 1 ? val[0] : val;\n    }\n    return o;\n}\n\n/**\n * Encodes a URI according to a set of template variables. Variables will be\n * passed through encodeURIComponent.\n * @param pathTemplate - The path with template variables e.g. '/foo/$bar'.\n * @param variables - The key/value pairs to replace the template\n * variables with. E.g. `{ \"$bar\": \"baz\" }`.\n * @returns The result of replacing all template variables e.g. '/foo/baz'.\n */\nexport function encodeUri(pathTemplate: string, variables: Record<string, string | null | undefined>): string {\n    for (const key in variables) {\n        if (!variables.hasOwnProperty(key)) {\n            continue;\n        }\n        const value = variables[key];\n        if (value === undefined || value === null) {\n            continue;\n        }\n        pathTemplate = pathTemplate.replace(key, encodeURIComponent(value));\n    }\n    return pathTemplate;\n}\n\n/**\n * The removeElement() method removes the first element in the array that\n * satisfies (returns true) the provided testing function.\n * @param array - The array.\n * @param fn - Function to execute on each value in the array, with the\n * function signature `fn(element, index, array)`. Return true to\n * remove this element and break.\n * @param reverse - True to search in reverse order.\n * @returns True if an element was removed.\n */\nexport function removeElement<T>(array: T[], fn: (t: T, i?: number, a?: T[]) => boolean, reverse?: boolean): boolean {\n    let i: number;\n    if (reverse) {\n        for (i = array.length - 1; i >= 0; i--) {\n            if (fn(array[i], i, array)) {\n                array.splice(i, 1);\n                return true;\n            }\n        }\n    } else {\n        for (i = 0; i < array.length; i++) {\n            if (fn(array[i], i, array)) {\n                array.splice(i, 1);\n                return true;\n            }\n        }\n    }\n    return false;\n}\n\n/**\n * Checks if the given thing is a function.\n * @param value - The thing to check.\n * @returns True if it is a function.\n */\nexport function isFunction(value: any): boolean {\n    return Object.prototype.toString.call(value) === \"[object Function]\";\n}\n\n/**\n * Checks that the given object has the specified keys.\n * @param obj - The object to check.\n * @param keys - The list of keys that 'obj' must have.\n * @throws If the object is missing keys.\n */\n// note using 'keys' here would shadow the 'keys' function defined above\nexport function checkObjectHasKeys(obj: object, keys: string[]): void {\n    for (const key of keys) {\n        if (!obj.hasOwnProperty(key)) {\n            throw new Error(\"Missing required key: \" + key);\n        }\n    }\n}\n\n/**\n * Deep copy the given object. The object MUST NOT have circular references and\n * MUST NOT have functions.\n * @param obj - The object to deep copy.\n * @returns A copy of the object without any references to the original.\n */\nexport function deepCopy<T>(obj: T): T {\n    return JSON.parse(JSON.stringify(obj));\n}\n\n/**\n * Compare two objects for equality. The objects MUST NOT have circular references.\n *\n * @param x - The first object to compare.\n * @param y - The second object to compare.\n *\n * @returns true if the two objects are equal\n */\nexport function deepCompare(x: any, y: any): boolean {\n    // Inspired by\n    // http://stackoverflow.com/questions/1068834/object-comparison-in-javascript#1144249\n\n    // Compare primitives and functions.\n    // Also check if both arguments link to the same object.\n    if (x === y) {\n        return true;\n    }\n\n    if (typeof x !== typeof y) {\n        return false;\n    }\n\n    // special-case NaN (since NaN !== NaN)\n    if (typeof x === \"number\" && isNaN(x) && isNaN(y)) {\n        return true;\n    }\n\n    // special-case null (since typeof null == 'object', but null.constructor\n    // throws)\n    if (x === null || y === null) {\n        return x === y;\n    }\n\n    // everything else is either an unequal primitive, or an object\n    // XXX: this check has been temporarily tweaked due to issues in the jest test environment,\n    // this will be reverted as part of the migration to vitest\n    if (\n        x.constructor.name !== \"Object\" &&\n        x.constructor.name !== \"RegExp\" &&\n        x.constructor.name !== \"Date\" &&\n        x.constructor.name !== \"Array\"\n    ) {\n        return false;\n    }\n\n    // check they are the same type of object\n    // XXX: this check has been temporarily tweaked due to issues in the jest test environment,\n    // this will be reverted as part of the migration to vitest\n    if (x.prototype !== y.prototype) {\n        return false;\n    }\n\n    // special-casing for some special types of object\n    if (x instanceof RegExp || x instanceof Date) {\n        return x.toString() === y.toString();\n    }\n\n    // the object algorithm works for Array, but it's sub-optimal.\n    if (Array.isArray(x)) {\n        if (x.length !== y.length) {\n            return false;\n        }\n\n        for (let i = 0; i < x.length; i++) {\n            if (!deepCompare(x[i], y[i])) {\n                return false;\n            }\n        }\n    } else {\n        // check that all of y's direct keys are in x\n        for (const p in y) {\n            if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {\n                return false;\n            }\n        }\n\n        // finally, compare each of x's keys with y\n        for (const p in x) {\n            if (y.hasOwnProperty(p) !== x.hasOwnProperty(p) || !deepCompare(x[p], y[p])) {\n                return false;\n            }\n        }\n    }\n    return true;\n}\n\n// Dev note: This returns an array of tuples, but jsdoc doesn't like that. https://github.com/jsdoc/jsdoc/issues/1703\n/**\n * Creates an array of object properties/values (entries) then\n * sorts the result by key, recursively. The input object must\n * ensure it does not have loops. If the input is not an object\n * then it will be returned as-is.\n * @param obj - The object to get entries of\n * @returns The entries, sorted by key.\n */\nexport function deepSortedObjectEntries(obj: any): [string, any][] {\n    if (typeof obj !== \"object\") return obj;\n\n    // Apparently these are object types...\n    if (obj === null || obj === undefined || Array.isArray(obj)) return obj;\n\n    const pairs: [string, any][] = [];\n    for (const [k, v] of Object.entries(obj)) {\n        pairs.push([k, deepSortedObjectEntries(v)]);\n    }\n\n    // lexicographicCompare is faster than localeCompare, so let's use that.\n    pairs.sort((a, b) => lexicographicCompare(a[0], b[0]));\n\n    return pairs;\n}\n\n/**\n * Returns whether the given value is a finite number without type-coercion\n *\n * @param value - the value to test\n * @returns whether or not value is a finite number without type-coercion\n */\nexport function isNumber(value: any): value is number {\n    return typeof value === \"number\" && isFinite(value);\n}\n\n/**\n * Removes zero width chars, diacritics and whitespace from the string\n * Also applies an unhomoglyph on the string, to prevent similar looking chars\n * @param str - the string to remove hidden characters from\n * @returns a string with the hidden characters removed\n */\nexport function removeHiddenChars(str: string): string {\n    if (typeof str === \"string\") {\n        return unhomoglyph(str.normalize(\"NFD\").replace(removeHiddenCharsRegex, \"\"));\n    }\n    return \"\";\n}\n\n/**\n * Removes the direction override characters from a string\n * @returns string with chars removed\n */\nexport function removeDirectionOverrideChars(str: string): string {\n    if (typeof str === \"string\") {\n        return str.replace(/[\\u202d-\\u202e]/g, \"\");\n    }\n    return \"\";\n}\n\nexport function normalize(str: string): string {\n    // Note: we have to match the filter with the removeHiddenChars() because the\n    // function strips spaces and other characters (M becomes RN for example, in lowercase).\n    return (\n        removeHiddenChars(str.toLowerCase())\n            // Strip all punctuation\n            .replace(/[\\\\'!\"#$%&()*+,\\-./:;<=>?@[\\]^_`{|}~\\u2000-\\u206f\\u2e00-\\u2e7f]/g, \"\")\n            // We also doubly convert to lowercase to work around oddities of the library.\n            .toLowerCase()\n    );\n}\n\n// Regex matching bunch of unicode control characters and otherwise misleading/invisible characters.\n// Includes:\n// various width spaces U+2000 - U+200D\n// LTR and RTL marks U+200E and U+200F\n// LTR/RTL and other directional formatting marks U+202A - U+202F\n// Arabic Letter RTL mark U+061C\n// Combining characters U+0300 - U+036F\n// Zero width no-break space (BOM) U+FEFF\n// Blank/invisible characters (U2800, U2062-U2063)\n// eslint-disable-next-line no-misleading-character-class\nconst removeHiddenCharsRegex = /[\\u2000-\\u200F\\u202A-\\u202F\\u0300-\\u036F\\uFEFF\\u061C\\u2800\\u2062-\\u2063\\s]/g;\n\nexport function escapeRegExp(string: string): string {\n    return string.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Converts Matrix glob-style string to a regular expression\n * https://spec.matrix.org/v1.7/appendices/#glob-style-matching\n * @param glob - Matrix glob-style string\n * @returns regular expression\n */\nexport function globToRegexp(glob: string): string {\n    return escapeRegExp(glob).replace(/\\\\\\*/g, \".*\").replace(/\\?/g, \".\");\n}\n\nexport function ensureNoTrailingSlash(url: string): string;\nexport function ensureNoTrailingSlash(url: undefined): undefined;\nexport function ensureNoTrailingSlash(url?: string): string | undefined;\nexport function ensureNoTrailingSlash(url?: string): string | undefined {\n    if (url?.endsWith(\"/\")) {\n        return url.slice(0, -1);\n    } else {\n        return url;\n    }\n}\n\n/**\n * Returns a promise which resolves with a given value after the given number of ms\n */\nexport function sleep<T>(ms: number, value?: T): Promise<T> {\n    return new Promise((resolve) => {\n        setTimeout(resolve, ms, value);\n    });\n}\n\n/**\n * Utility to log the duration of a promise.\n *\n * @param logger - The logger to log to.\n * @param name - The name of the operation.\n * @param block - The block to execute.\n */\nexport async function logDuration<T>(logger: BaseLogger, name: string, block: () => Promise<T>): Promise<T> {\n    const start = Date.now();\n    try {\n        return await block();\n    } finally {\n        const end = Date.now();\n        logger.debug(`[Perf]: ${name} took ${end - start}ms`);\n    }\n}\n\n/**\n * Utility to log the duration of a synchronous block.\n *\n * @param logger - The logger to log to.\n * @param name - The name of the operation.\n * @param block - The block to execute.\n */\nexport function logDurationSync<T>(logger: BaseLogger, name: string, block: () => T): T {\n    const start = Date.now();\n    try {\n        return block();\n    } finally {\n        const end = Date.now();\n        logger.debug(`[Perf]: ${name} took ${end - start}ms`);\n    }\n}\n\n/**\n * Promise/async version of {@link setImmediate}.\n *\n * Implementation is based on `setTimeout` for wider compatibility.\n * @deprecated Use {@link sleep} instead.\n */\nexport function immediate(): Promise<void> {\n    return new Promise((resolve) => setTimeout(resolve));\n}\n\nexport function isNullOrUndefined(val: any): boolean {\n    return val === null || val === undefined;\n}\n\nexport async function promiseMapSeries<T>(\n    promises: Array<T | Promise<T>>,\n    fn: (t: T) => Promise<unknown> | undefined, // if async we don't care about the type as we only await resolution\n): Promise<void> {\n    for (const o of promises) {\n        await fn(await o);\n    }\n}\n\nexport function promiseTry<T>(fn: () => T | Promise<T>): Promise<T> {\n    return Promise.resolve(fn());\n}\n\n// Creates and awaits all promises, running no more than `chunkSize` at the same time\nexport async function chunkPromises<T>(fns: (() => Promise<T>)[], chunkSize: number): Promise<T[]> {\n    const results: T[] = [];\n    for (let i = 0; i < fns.length; i += chunkSize) {\n        results.push(...(await Promise.all(fns.slice(i, i + chunkSize).map((fn) => fn()))));\n    }\n    return results;\n}\n\n/**\n * Retries the function until it succeeds or is interrupted. The given function must return\n * a promise which throws/rejects on error, otherwise the retry will assume the request\n * succeeded. The promise chain returned will contain the successful promise. The given function\n * should always return a new promise.\n * @param promiseFn - The function to call to get a fresh promise instance. Takes an\n * attempt count as an argument, for logging/debugging purposes.\n * @param shouldRetry - Optional function which is called with the error the latest rejection from promiseFn,\n * retrying will ba aborted if this return false.\n * @returns The promise for the retried operation.\n */\nexport function simpleRetryOperation<T>(\n    promiseFn: (attempt: number) => Promise<T>,\n    shouldRetry?: (e: unknown) => boolean,\n): Promise<T> {\n    return promiseRetry(\n        (attempt: number) => {\n            return promiseFn(attempt);\n        },\n        {\n            retries: Infinity,\n            shouldRetry: shouldRetry ? ({ error }): boolean => shouldRetry(error) : undefined,\n            factor: 2,\n            minTimeout: 3000, // ms\n            maxTimeout: 15000, // ms\n        },\n    );\n}\n\n// String averaging inspired by https://stackoverflow.com/a/2510816\n// Dev note: We make the alphabet a string because it's easier to write syntactically\n// than arrays. Thankfully, strings implement the useful parts of the Array interface\n// anyhow.\n\n/**\n * The default alphabet used by string averaging in this SDK. This matches\n * all usefully printable ASCII characters (0x20-0x7E, inclusive).\n */\nexport const DEFAULT_ALPHABET = ((): string => {\n    let str = \"\";\n    for (let c = 0x20; c <= 0x7e; c++) {\n        str += String.fromCharCode(c);\n    }\n    return str;\n})();\n\n/**\n * Pads a string using the given alphabet as a base. The returned string will be\n * padded at the end with the first character in the alphabet.\n *\n * This is intended for use with string averaging.\n * @param s - The string to pad.\n * @param n - The length to pad to.\n * @param alphabet - The alphabet to use as a single string.\n * @returns The padded string.\n */\nexport function alphabetPad(s: string, n: number, alphabet = DEFAULT_ALPHABET): string {\n    return s.padEnd(n, alphabet[0]);\n}\n\n/**\n * Converts a baseN number to a string, where N is the alphabet's length.\n *\n * This is intended for use with string averaging.\n * @param n - The baseN number.\n * @param alphabet - The alphabet to use as a single string.\n * @returns The baseN number encoded as a string from the alphabet.\n */\nexport function baseToString(n: bigint, alphabet = DEFAULT_ALPHABET): string {\n    // Developer note: the stringToBase() function offsets the character set by 1 so that repeated\n    // characters (ie: \"aaaaaa\" in a..z) don't come out as zero. We have to reverse this here as\n    // otherwise we'll be wrong in our conversion. Undoing a +1 before an exponent isn't very fun\n    // though, so we rely on a lengthy amount of `x - 1` and integer division rules to reach a\n    // sane state. This also means we have to do rollover detection: see below.\n\n    const len = BigInt(alphabet.length);\n    if (n <= len) {\n        return alphabet[Number(n) - 1] ?? \"\";\n    }\n\n    let d = n / len;\n    let r = Number(n % len) - 1;\n\n    // Rollover detection: if the remainder is negative, it means that the string needs\n    // to roll over by 1 character downwards (ie: in a..z, the previous to \"aaa\" would be\n    // \"zz\").\n    if (r < 0) {\n        d -= BigInt(Math.abs(r)); // abs() is just to be clear what we're doing. Could also `+= r`.\n        r = Number(len) - 1;\n    }\n\n    return baseToString(d, alphabet) + alphabet[r];\n}\n\n/**\n * Converts a string to a baseN number, where N is the alphabet's length.\n *\n * This is intended for use with string averaging.\n * @param s - The string to convert to a number.\n * @param alphabet - The alphabet to use as a single string.\n * @returns The baseN number.\n */\nexport function stringToBase(s: string, alphabet = DEFAULT_ALPHABET): bigint {\n    const len = BigInt(alphabet.length);\n\n    // In our conversion to baseN we do a couple performance optimizations to avoid using\n    // excess CPU and such. To create baseN numbers, the input string needs to be reversed\n    // so the exponents stack up appropriately, as the last character in the unreversed\n    // string has less impact than the first character (in \"abc\" the A is a lot more important\n    // for lexicographic sorts). We also do a trick with the character codes to optimize the\n    // alphabet lookup, avoiding an index scan of `alphabet.indexOf(reversedStr[i])` - we know\n    // that the alphabet and (theoretically) the input string are constrained on character sets\n    // and thus can do simple subtraction to end up with the same result.\n\n    // Developer caution: we carefully cast to BigInt here to avoid losing precision. We cannot\n    // rely on Math.pow() (for example) to be capable of handling our insane numbers.\n\n    let result = BigInt(0);\n    for (let i = s.length - 1, j = BigInt(0); i >= 0; i--, j++) {\n        const charIndex = s.charCodeAt(i) - alphabet.charCodeAt(0);\n\n        // We add 1 to the char index to offset the whole numbering scheme. We unpack this in\n        // the baseToString() function.\n        result += BigInt(1 + charIndex) * len ** j;\n    }\n    return result;\n}\n\n/**\n * Averages two strings, returning the midpoint between them. This is accomplished by\n * converting both to baseN numbers (where N is the alphabet's length) then averaging\n * those before re-encoding as a string.\n * @param a - The first string.\n * @param b - The second string.\n * @param alphabet - The alphabet to use as a single string.\n * @returns The midpoint between the strings, as a string.\n */\nexport function averageBetweenStrings(a: string, b: string, alphabet = DEFAULT_ALPHABET): string {\n    const padN = Math.max(a.length, b.length);\n    const baseA = stringToBase(alphabetPad(a, padN, alphabet), alphabet);\n    const baseB = stringToBase(alphabetPad(b, padN, alphabet), alphabet);\n    const avg = (baseA + baseB) / BigInt(2);\n\n    // Detect integer division conflicts. This happens when two numbers are divided too close so\n    // we lose a .5 precision. We need to add a padding character in these cases.\n    if (avg === baseA || avg == baseB) {\n        return baseToString(avg, alphabet) + alphabet[0];\n    }\n\n    return baseToString(avg, alphabet);\n}\n\n/**\n * Finds the next string using the alphabet provided. This is done by converting the\n * string to a baseN number, where N is the alphabet's length, then adding 1 before\n * converting back to a string.\n * @param s - The string to start at.\n * @param alphabet - The alphabet to use as a single string.\n * @returns The string which follows the input string.\n */\nexport function nextString(s: string, alphabet = DEFAULT_ALPHABET): string {\n    return baseToString(stringToBase(s, alphabet) + BigInt(1), alphabet);\n}\n\n/**\n * Finds the previous string using the alphabet provided. This is done by converting the\n * string to a baseN number, where N is the alphabet's length, then subtracting 1 before\n * converting back to a string.\n * @param s - The string to start at.\n * @param alphabet - The alphabet to use as a single string.\n * @returns The string which precedes the input string.\n */\nexport function prevString(s: string, alphabet = DEFAULT_ALPHABET): string {\n    return baseToString(stringToBase(s, alphabet) - BigInt(1), alphabet);\n}\n\n/**\n * Compares strings lexicographically as a sort-safe function.\n * @param a - The first (reference) string.\n * @param b - The second (compare) string.\n * @returns Negative if the reference string is before the compare string;\n * positive if the reference string is after; and zero if equal.\n */\nexport function lexicographicCompare(a: string, b: string): number {\n    // Dev note: this exists because I'm sad that you can use math operators on strings, so I've\n    // hidden the operation in this function.\n    if (a < b) {\n        return -1;\n    } else if (a > b) {\n        return 1;\n    } else {\n        return 0;\n    }\n}\n\n/**\n * This function is similar to Object.assign() but it assigns recursively and\n * allows you to ignore nullish values from the source\n *\n * @returns the target object\n */\nexport function recursivelyAssign<T1 extends T2, T2 extends Record<string, any>>(\n    target: T1,\n    source: T2,\n    ignoreNullish = false,\n): T1 & T2 {\n    for (const [sourceKey, sourceValue] of Object.entries(source)) {\n        if (target[sourceKey] instanceof Object && sourceValue) {\n            recursivelyAssign(target[sourceKey], sourceValue);\n            continue;\n        }\n        if ((sourceValue !== null && sourceValue !== undefined) || !ignoreNullish) {\n            safeSet(target, sourceKey, sourceValue);\n            continue;\n        }\n    }\n    return target as T1 & T2;\n}\n\nfunction getContentTimestampWithFallback(event: MatrixEvent): number {\n    return M_TIMESTAMP.findIn<number>(event.getContent()) ?? -1;\n}\n\n/**\n * Sort events by their content m.ts property\n * Latest timestamp first\n */\nexport function sortEventsByLatestContentTimestamp(left: MatrixEvent, right: MatrixEvent): number {\n    return getContentTimestampWithFallback(right) - getContentTimestampWithFallback(left);\n}\n\nexport function isSupportedReceiptType(receiptType: string): boolean {\n    return [ReceiptType.Read, ReceiptType.ReadPrivate].includes(receiptType as ReceiptType);\n}\n\n/**\n * Determines whether two maps are equal.\n * @param eq - The equivalence relation to compare values by. Defaults to strict equality.\n */\nexport function mapsEqual<K, V>(x: Map<K, V>, y: Map<K, V>, eq = (v1: V, v2: V): boolean => v1 === v2): boolean {\n    if (x.size !== y.size) return false;\n    for (const [k, v1] of x) {\n        const v2 = y.get(k);\n        if (v2 === undefined || !eq(v1, v2)) return false;\n    }\n    return true;\n}\n\nfunction processMapToObjectValue(value: any): any {\n    if (value instanceof Map) {\n        // Value is a Map. Recursively map it to an object.\n        return recursiveMapToObject(value);\n    } else if (Array.isArray(value)) {\n        // Value is an Array. Recursively map the value (e.g. to cover Array of Arrays).\n        return value.map((v) => processMapToObjectValue(v));\n    } else {\n        return value;\n    }\n}\n\n/**\n * Recursively converts Maps to plain objects.\n * Also supports sub-lists of Maps.\n */\nexport function recursiveMapToObject(map: Map<any, any>): Record<any, any> {\n    const targetMap = new Map();\n\n    for (const [key, value] of map) {\n        targetMap.set(key, processMapToObjectValue(value));\n    }\n\n    return Object.fromEntries(targetMap.entries());\n}\n\nexport function unsafeProp<K extends keyof any | undefined>(prop: K): boolean {\n    return prop === \"__proto__\" || prop === \"prototype\" || prop === \"constructor\";\n}\n\nexport function safeSet<O extends Record<any, any>, K extends keyof O>(obj: O, prop: K, value: O[K]): void {\n    if (unsafeProp(prop)) {\n        throw new Error(\"Trying to modify prototype or constructor\");\n    }\n\n    obj[prop] = value;\n}\n\nexport function noUnsafeEventProps(event: Partial<IEvent>): boolean {\n    return !(unsafeProp(event.room_id) || unsafeProp(event.sender) || unsafeProp(event.event_id));\n}\n\nexport class MapWithDefault<K, V> extends Map<K, V> {\n    public constructor(private createDefault: () => V) {\n        super();\n    }\n\n    /**\n     * Returns the value if the key already exists.\n     * If not, it creates a new value under that key using the ctor callback and returns it.\n     */\n    public getOrCreate(key: K): V {\n        if (!this.has(key)) {\n            this.set(key, this.createDefault());\n        }\n\n        return this.get(key)!;\n    }\n}\n","/*\n * Copyright 2024 The Matrix.org Foundation C.I.C.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { decodeBase64 } from \"../base64.ts\";\nimport { deriveKeys } from \"./internal/deriveKeys.ts\";\nimport { type AESEncryptedSecretStoragePayload } from \"../@types/AESEncryptedSecretStoragePayload.ts\";\n\n/**\n * Decrypt an AES-encrypted Secret Storage item.\n *\n * @param data - the encrypted data, returned by {@link utils/encryptAESSecretStorageItem.default | encryptAESSecretStorageItem}.\n * @param key - the encryption key to use as an input to the HKDF function which is used to derive the AES key. Must\n *    be the same as provided to {@link utils/encryptAESSecretStorageItem.default | encryptAESSecretStorageItem}.\n * @param name - the name of the secret. Also used as an input to the HKDF operation which is used to derive the AES\n *    key, so again must be the same as provided to {@link utils/encryptAESSecretStorageItem.default | encryptAESSecretStorageItem}.\n */\nexport default async function decryptAESSecretStorageItem(\n    data: AESEncryptedSecretStoragePayload,\n    key: Uint8Array<ArrayBuffer>,\n    name: string,\n): Promise<string> {\n    const [aesKey, hmacKey] = await deriveKeys(key, name);\n\n    const ciphertext = decodeBase64(data.ciphertext);\n\n    if (!(await globalThis.crypto.subtle.verify({ name: \"HMAC\" }, hmacKey, decodeBase64(data.mac), ciphertext))) {\n        throw new Error(`Error decrypting secret ${name}: bad MAC`);\n    }\n\n    const plaintext = await globalThis.crypto.subtle.decrypt(\n        {\n            name: \"AES-CTR\",\n            counter: decodeBase64(data.iv),\n            length: 64,\n        },\n        aesKey,\n        ciphertext,\n    );\n\n    return new TextDecoder().decode(new Uint8Array(plaintext));\n}\n","/*\nCopyright 2022 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n/**\n * A list of the spec versions which the js-sdk is compatible with.\n *\n * In practice, this means: when we connect to a server, it must declare support for one of the versions in this list.\n *\n * Note that it does not *necessarily* mean that the js-sdk has good support for all the features in the listed spec\n * versions; only that we should be able to provide a base level of functionality with a server that offers support for\n * any of the listed versions.\n */\nexport const SUPPORTED_MATRIX_VERSIONS = [\n    \"v1.1\",\n    \"v1.2\",\n    \"v1.3\",\n    \"v1.4\",\n    \"v1.5\",\n    \"v1.6\",\n    \"v1.7\",\n    \"v1.8\",\n    \"v1.9\",\n    \"v1.10\",\n    \"v1.11\",\n    \"v1.12\",\n    \"v1.13\",\n];\n\n/**\n * The oldest Matrix specification version the js-sdk supports.\n */\nexport const MINIMUM_MATRIX_VERSION = SUPPORTED_MATRIX_VERSIONS[0];\n\n/**\n * The most recent Matrix specification version the js-sdk supports.\n */\nexport const MAXIMUM_MATRIX_VERSION = SUPPORTED_MATRIX_VERSIONS[SUPPORTED_MATRIX_VERSIONS.length - 1];\n","/*\nCopyright 2015, 2016 OpenMarket Ltd\nCopyright 2017 New Vector Ltd\nCopyright 2019, 2020 The Matrix.org Foundation C.I.C.\nCopyright 2021 - 2022 Šimon Brandner <simon.bra.ag@gmail.com>\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n/**\n * This is an internal module. See {@link createNewMatrixCall} for the public API.\n */\n\nimport { parse as parseSdp, write as writeSdp } from \"sdp-transform\";\n\nimport { logger } from \"../logger.ts\";\nimport { checkObjectHasKeys, isNullOrUndefined, recursivelyAssign } from \"../utils.ts\";\nimport { type MatrixEvent } from \"../models/event.ts\";\nimport { EventType, type TimelineEvents, ToDeviceMessageId } from \"../@types/event.ts\";\nimport { type RoomMember } from \"../models/room-member.ts\";\nimport { secureRandomString } from \"../randomstring.ts\";\nimport {\n    type MCallReplacesEvent,\n    type MCallAnswer,\n    type MCallInviteNegotiate,\n    type CallCapabilities,\n    SDPStreamMetadataPurpose,\n    type SDPStreamMetadata,\n    SDPStreamMetadataKey,\n    type MCallSDPStreamMetadataChanged,\n    type MCallSelectAnswer,\n    type MCAllAssertedIdentity,\n    type MCallCandidates,\n    type MCallBase,\n    type MCallHangupReject,\n} from \"./callEventTypes.ts\";\nimport { CallFeed } from \"./callFeed.ts\";\nimport { type MatrixClient } from \"../client.ts\";\nimport { EventEmitterEvents, TypedEventEmitter } from \"../models/typed-event-emitter.ts\";\nimport { GroupCallUnknownDeviceError } from \"./groupCall.ts\";\nimport { type IScreensharingOpts } from \"./mediaHandler.ts\";\nimport { MatrixError } from \"../http-api/index.ts\";\nimport { type GroupCallStats } from \"./stats/groupCallStats.ts\";\n\ninterface CallOpts {\n    // The room ID for this call.\n    roomId: string;\n    invitee?: string;\n    // The Matrix Client instance to send events to.\n    client: MatrixClient;\n    /**\n     * Whether relay through TURN should be forced.\n     * @deprecated use opts.forceTURN when creating the matrix client\n     * since it's only possible to set this option on outbound calls.\n     */\n    forceTURN?: boolean;\n    // A list of TURN servers.\n    turnServers?: Array<TurnServer>;\n    opponentDeviceId?: string;\n    opponentSessionId?: string;\n    groupCallId?: string;\n}\n\ninterface TurnServer {\n    urls: Array<string>;\n    username?: string;\n    password?: string;\n    ttl?: number;\n}\n\ninterface AssertedIdentity {\n    id: string;\n    displayName: string;\n}\n\nenum MediaType {\n    AUDIO = \"audio\",\n    VIDEO = \"video\",\n}\n\nenum CodecName {\n    OPUS = \"opus\",\n    // add more as needed\n}\n\n// Used internally to specify modifications to codec parameters in SDP\ninterface CodecParamsMod {\n    mediaType: MediaType;\n    codec: CodecName;\n    enableDtx?: boolean; // true to enable discontinuous transmission, false to disable, undefined to leave as-is\n    maxAverageBitrate?: number; // sets the max average bitrate, or undefined to leave as-is\n}\n\nexport enum CallState {\n    Fledgling = \"fledgling\",\n    InviteSent = \"invite_sent\",\n    WaitLocalMedia = \"wait_local_media\",\n    CreateOffer = \"create_offer\",\n    CreateAnswer = \"create_answer\",\n    Connecting = \"connecting\",\n    Connected = \"connected\",\n    Ringing = \"ringing\",\n    Ended = \"ended\",\n}\n\nexport enum CallType {\n    Voice = \"voice\",\n    Video = \"video\",\n}\n\nexport enum CallDirection {\n    Inbound = \"inbound\",\n    Outbound = \"outbound\",\n}\n\nexport enum CallParty {\n    Local = \"local\",\n    Remote = \"remote\",\n}\n\nexport enum CallEvent {\n    Hangup = \"hangup\",\n    State = \"state\",\n    Error = \"error\",\n    Replaced = \"replaced\",\n\n    // The value of isLocalOnHold() has changed\n    LocalHoldUnhold = \"local_hold_unhold\",\n    // The value of isRemoteOnHold() has changed\n    RemoteHoldUnhold = \"remote_hold_unhold\",\n    // backwards compat alias for LocalHoldUnhold: remove in a major version bump\n    HoldUnhold = \"hold_unhold\",\n    // Feeds have changed\n    FeedsChanged = \"feeds_changed\",\n\n    AssertedIdentityChanged = \"asserted_identity_changed\",\n\n    LengthChanged = \"length_changed\",\n\n    DataChannel = \"datachannel\",\n\n    SendVoipEvent = \"send_voip_event\",\n\n    // When the call instantiates its peer connection\n    // For apps that want to access the underlying peer connection, eg for debugging\n    PeerConnectionCreated = \"peer_connection_created\",\n}\n\nexport enum CallErrorCode {\n    /** The user chose to end the call */\n    UserHangup = \"user_hangup\",\n\n    /** An error code when the local client failed to create an offer. */\n    LocalOfferFailed = \"local_offer_failed\",\n    /**\n     * An error code when there is no local mic/camera to use. This may be because\n     * the hardware isn't plugged in, or the user has explicitly denied access.\n     */\n    NoUserMedia = \"no_user_media\",\n\n    /**\n     * Error code used when a call event failed to send\n     * because unknown devices were present in the room\n     */\n    UnknownDevices = \"unknown_devices\",\n\n    /**\n     * Error code used when we fail to send the invite\n     * for some reason other than there being unknown devices\n     */\n    SendInvite = \"send_invite\",\n\n    /**\n     * An answer could not be created\n     */\n    CreateAnswer = \"create_answer\",\n\n    /**\n     * An offer could not be created\n     */\n    CreateOffer = \"create_offer\",\n\n    /**\n     * Error code used when we fail to send the answer\n     * for some reason other than there being unknown devices\n     */\n    SendAnswer = \"send_answer\",\n\n    /**\n     * The session description from the other side could not be set\n     */\n    SetRemoteDescription = \"set_remote_description\",\n\n    /**\n     * The session description from this side could not be set\n     */\n    SetLocalDescription = \"set_local_description\",\n\n    /**\n     * A different device answered the call\n     */\n    AnsweredElsewhere = \"answered_elsewhere\",\n\n    /**\n     * No media connection could be established to the other party\n     */\n    IceFailed = \"ice_failed\",\n\n    /**\n     * The invite timed out whilst waiting for an answer\n     */\n    InviteTimeout = \"invite_timeout\",\n\n    /**\n     * The call was replaced by another call\n     */\n    Replaced = \"replaced\",\n\n    /**\n     * Signalling for the call could not be sent (other than the initial invite)\n     */\n    SignallingFailed = \"signalling_timeout\",\n\n    /**\n     * The remote party is busy\n     */\n    UserBusy = \"user_busy\",\n\n    /**\n     * We transferred the call off to somewhere else\n     */\n    Transferred = \"transferred\",\n\n    /**\n     * A call from the same user was found with a new session id\n     */\n    NewSession = \"new_session\",\n}\n\n/**\n * The version field that we set in m.call.* events\n */\nconst VOIP_PROTO_VERSION = \"1\";\n\n/** The fallback ICE server to use for STUN or TURN protocols. */\nexport const FALLBACK_ICE_SERVER = \"stun:turn.matrix.org\";\n\n/** The length of time a call can be ringing for. */\nconst CALL_TIMEOUT_MS = 60 * 1000; // ms\n/** The time after which we increment callLength */\nconst CALL_LENGTH_INTERVAL = 1000; // ms\n/** The time after which we end the call, if ICE got disconnected */\nconst ICE_DISCONNECTED_TIMEOUT = 30 * 1000; // ms\n/** The time after which we try a ICE restart, if ICE got disconnected */\nconst ICE_RECONNECTING_TIMEOUT = 2 * 1000; // ms\nexport class CallError extends Error {\n    public readonly code: string;\n\n    public constructor(code: CallErrorCode, msg: string, err: Error) {\n        // Still don't think there's any way to have proper nested errors\n        super(msg + \": \" + err);\n\n        this.code = code;\n    }\n}\n\nexport function genCallID(): string {\n    return Date.now().toString() + secureRandomString(16);\n}\n\nfunction getCodecParamMods(isPtt: boolean): CodecParamsMod[] {\n    const mods = [\n        {\n            mediaType: \"audio\",\n            codec: \"opus\",\n            enableDtx: true,\n            maxAverageBitrate: isPtt ? 12000 : undefined,\n        },\n    ] as CodecParamsMod[];\n\n    return mods;\n}\n\ntype CallEventType =\n    | EventType.CallReplaces\n    | EventType.CallAnswer\n    | EventType.CallSelectAnswer\n    | EventType.CallNegotiate\n    | EventType.CallInvite\n    | EventType.CallCandidates\n    | EventType.CallHangup\n    | EventType.CallReject\n    | EventType.CallSDPStreamMetadataChanged;\n\nexport interface VoipEvent {\n    type: \"toDevice\" | \"sendEvent\";\n    eventType: string;\n    userId?: string;\n    opponentDeviceId?: string;\n    roomId?: string;\n    content: TimelineEvents[CallEventType];\n}\n\n/**\n * These now all have the call object as an argument. Why? Well, to know which call a given event is\n * about you have three options:\n *  1. Use a closure as the callback that remembers what call it's listening to. This can be\n *     a pain because you need to pass the listener function again when you remove the listener,\n *     which might be somewhere else.\n *  2. Use not-very-well-known fact that EventEmitter sets 'this' to the emitter object in the\n *     callback. This doesn't really play well with modern Typescript and eslint and doesn't work\n *     with our pattern of re-emitting events.\n *  3. Pass the object in question as an argument to the callback.\n *\n * Now that we have group calls which have to deal with multiple call objects, this will\n * become more important, and I think methods 1 and 2 are just going to cause issues.\n */\nexport type CallEventHandlerMap = {\n    [CallEvent.DataChannel]: (channel: RTCDataChannel, call: MatrixCall) => void;\n    [CallEvent.FeedsChanged]: (feeds: CallFeed[], call: MatrixCall) => void;\n    [CallEvent.Replaced]: (newCall: MatrixCall, oldCall: MatrixCall) => void;\n    [CallEvent.Error]: (error: CallError, call: MatrixCall) => void;\n    [CallEvent.RemoteHoldUnhold]: (onHold: boolean, call: MatrixCall) => void;\n    [CallEvent.LocalHoldUnhold]: (onHold: boolean, call: MatrixCall) => void;\n    [CallEvent.LengthChanged]: (length: number, call: MatrixCall) => void;\n    [CallEvent.State]: (state: CallState, oldState: CallState, call: MatrixCall) => void;\n    [CallEvent.Hangup]: (call: MatrixCall) => void;\n    [CallEvent.AssertedIdentityChanged]: (call: MatrixCall) => void;\n    /* @deprecated */\n    [CallEvent.HoldUnhold]: (onHold: boolean) => void;\n    [CallEvent.SendVoipEvent]: (event: VoipEvent, call: MatrixCall) => void;\n    [CallEvent.PeerConnectionCreated]: (peerConn: RTCPeerConnection, call: MatrixCall) => void;\n};\n\n// The key of the transceiver map (purpose + media type, separated by ':')\ntype TransceiverKey = string;\n\n// generates keys for the map of transceivers\n// kind is unfortunately a string rather than MediaType as this is the type of\n// track.kind\nfunction getTransceiverKey(purpose: SDPStreamMetadataPurpose, kind: TransceiverKey): string {\n    return purpose + \":\" + kind;\n}\n\nexport class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap> {\n    public roomId: string;\n    public callId: string;\n    public invitee?: string;\n    public hangupParty?: CallParty;\n    public hangupReason?: string;\n    public direction?: CallDirection;\n    public ourPartyId: string;\n    public peerConn?: RTCPeerConnection;\n    public toDeviceSeq = 0;\n\n    // whether this call should have push-to-talk semantics\n    // This should be set by the consumer on incoming & outgoing calls.\n    public isPtt = false;\n\n    private _state = CallState.Fledgling;\n    private readonly client: MatrixClient;\n    private readonly forceTURN?: boolean;\n    private readonly turnServers: Array<TurnServer>;\n    // A queue for candidates waiting to go out.\n    // We try to amalgamate candidates into a single candidate message where\n    // possible\n    private candidateSendQueue: Array<RTCIceCandidate> = [];\n    private candidateSendTries = 0;\n    private candidatesEnded = false;\n    private feeds: Array<CallFeed> = [];\n\n    // our transceivers for each purpose and type of media\n    private transceivers = new Map<TransceiverKey, RTCRtpTransceiver>();\n\n    private inviteOrAnswerSent = false;\n    private waitForLocalAVStream = false;\n    private successor?: MatrixCall;\n    private opponentMember?: RoomMember;\n    private opponentVersion?: number | string;\n    // The party ID of the other side: undefined if we haven't chosen a partner\n    // yet, null if we have but they didn't send a party ID.\n    private opponentPartyId: string | null | undefined;\n    private opponentCaps?: CallCapabilities;\n    private iceDisconnectedTimeout?: ReturnType<typeof setTimeout>;\n    private iceReconnectionTimeOut?: ReturnType<typeof setTimeout> | undefined;\n    private inviteTimeout?: ReturnType<typeof setTimeout>;\n    private readonly removeTrackListeners = new Map<MediaStream, () => void>();\n\n    // The logic of when & if a call is on hold is nontrivial and explained in is*OnHold\n    // This flag represents whether we want the other party to be on hold\n    private remoteOnHold = false;\n\n    // the stats for the call at the point it ended. We can't get these after we\n    // tear the call down, so we just grab a snapshot before we stop the call.\n    // The typescript definitions have this type as 'any' :(\n    private callStatsAtEnd?: any[];\n\n    // Perfect negotiation state: https://www.w3.org/TR/webrtc/#perfect-negotiation-example\n    private makingOffer = false;\n    private ignoreOffer = false;\n    private isSettingRemoteAnswerPending = false;\n\n    private responsePromiseChain?: Promise<void>;\n\n    // If candidates arrive before we've picked an opponent (which, in particular,\n    // will happen if the opponent sends candidates eagerly before the user answers\n    // the call) we buffer them up here so we can then add the ones from the party we pick\n    private remoteCandidateBuffer = new Map<string, MCallCandidates[\"candidates\"]>();\n\n    private remoteAssertedIdentity?: AssertedIdentity;\n    private remoteSDPStreamMetadata?: SDPStreamMetadata;\n\n    private callLengthInterval?: ReturnType<typeof setInterval>;\n    private callStartTime?: number;\n\n    private opponentDeviceId?: string;\n    private hasOpponentDeviceInfo?: boolean;\n    private opponentSessionId?: string;\n    public groupCallId?: string;\n\n    // Used to keep the timer for the delay before actually stopping our\n    // video track after muting (see setLocalVideoMuted)\n    private stopVideoTrackTimer?: ReturnType<typeof setTimeout>;\n    // Used to allow connection without Video and Audio. To establish a webrtc connection without media a Data channel is\n    // needed At the moment this property is true if we allow MatrixClient with isVoipWithNoMediaAllowed = true\n    private readonly isOnlyDataChannelAllowed: boolean;\n    private stats: GroupCallStats | undefined;\n\n    /**\n     * Construct a new Matrix Call.\n     * @param opts - Config options.\n     */\n    public constructor(opts: CallOpts) {\n        super();\n\n        this.roomId = opts.roomId;\n        this.invitee = opts.invitee;\n        this.client = opts.client;\n\n        if (!this.client.deviceId) throw new Error(\"Client must have a device ID to start calls\");\n\n        this.forceTURN = opts.forceTURN ?? false;\n        this.ourPartyId = this.client.deviceId;\n        this.opponentDeviceId = opts.opponentDeviceId;\n        this.opponentSessionId = opts.opponentSessionId;\n        this.groupCallId = opts.groupCallId;\n        // Array of Objects with urls, username, credential keys\n        this.turnServers = opts.turnServers || [];\n        if (this.turnServers.length === 0 && this.client.isFallbackICEServerAllowed()) {\n            this.turnServers.push({\n                urls: [FALLBACK_ICE_SERVER],\n            });\n        }\n        for (const server of this.turnServers) {\n            checkObjectHasKeys(server, [\"urls\"]);\n        }\n        this.callId = genCallID();\n        // If the Client provides calls without audio and video we need a datachannel for a webrtc connection\n        this.isOnlyDataChannelAllowed = this.client.isVoipWithNoMediaAllowed;\n    }\n\n    /**\n     * Place a voice call to this room.\n     * @throws If you have not specified a listener for 'error' events.\n     */\n    public async placeVoiceCall(): Promise<void> {\n        await this.placeCall(true, false);\n    }\n\n    /**\n     * Place a video call to this room.\n     * @throws If you have not specified a listener for 'error' events.\n     */\n    public async placeVideoCall(): Promise<void> {\n        await this.placeCall(true, true);\n    }\n\n    /**\n     * Create a datachannel using this call's peer connection.\n     * @param label - A human readable label for this datachannel\n     * @param options - An object providing configuration options for the data channel.\n     */\n    public createDataChannel(label: string, options: RTCDataChannelInit | undefined): RTCDataChannel {\n        const dataChannel = this.peerConn!.createDataChannel(label, options);\n        this.emit(CallEvent.DataChannel, dataChannel, this);\n        return dataChannel;\n    }\n\n    public getOpponentMember(): RoomMember | undefined {\n        return this.opponentMember;\n    }\n\n    public getOpponentDeviceId(): string | undefined {\n        return this.opponentDeviceId;\n    }\n\n    public getOpponentSessionId(): string | undefined {\n        return this.opponentSessionId;\n    }\n\n    public opponentCanBeTransferred(): boolean {\n        return Boolean(this.opponentCaps && this.opponentCaps[\"m.call.transferee\"]);\n    }\n\n    public opponentSupportsDTMF(): boolean {\n        return Boolean(this.opponentCaps && this.opponentCaps[\"m.call.dtmf\"]);\n    }\n\n    public getRemoteAssertedIdentity(): AssertedIdentity | undefined {\n        return this.remoteAssertedIdentity;\n    }\n\n    public get state(): CallState {\n        return this._state;\n    }\n\n    private set state(state: CallState) {\n        const oldState = this._state;\n        this._state = state;\n        this.emit(CallEvent.State, state, oldState, this);\n    }\n\n    public get type(): CallType {\n        // we may want to look for a video receiver here rather than a track to match the\n        // sender behaviour, although in practice they should be the same thing\n        return this.hasUserMediaVideoSender || this.hasRemoteUserMediaVideoTrack ? CallType.Video : CallType.Voice;\n    }\n\n    public get hasLocalUserMediaVideoTrack(): boolean {\n        return !!this.localUsermediaStream?.getVideoTracks().length;\n    }\n\n    public get hasRemoteUserMediaVideoTrack(): boolean {\n        return this.getRemoteFeeds().some((feed) => {\n            return feed.purpose === SDPStreamMetadataPurpose.Usermedia && feed.stream?.getVideoTracks().length;\n        });\n    }\n\n    public get hasLocalUserMediaAudioTrack(): boolean {\n        return !!this.localUsermediaStream?.getAudioTracks().length;\n    }\n\n    public get hasRemoteUserMediaAudioTrack(): boolean {\n        return this.getRemoteFeeds().some((feed) => {\n            return feed.purpose === SDPStreamMetadataPurpose.Usermedia && !!feed.stream?.getAudioTracks().length;\n        });\n    }\n\n    private get hasUserMediaAudioSender(): boolean {\n        return Boolean(this.transceivers.get(getTransceiverKey(SDPStreamMetadataPurpose.Usermedia, \"audio\"))?.sender);\n    }\n\n    private get hasUserMediaVideoSender(): boolean {\n        return Boolean(this.transceivers.get(getTransceiverKey(SDPStreamMetadataPurpose.Usermedia, \"video\"))?.sender);\n    }\n\n    public get localUsermediaFeed(): CallFeed | undefined {\n        return this.getLocalFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Usermedia);\n    }\n\n    public get localScreensharingFeed(): CallFeed | undefined {\n        return this.getLocalFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Screenshare);\n    }\n\n    public get localUsermediaStream(): MediaStream | undefined {\n        return this.localUsermediaFeed?.stream;\n    }\n\n    public get localScreensharingStream(): MediaStream | undefined {\n        return this.localScreensharingFeed?.stream;\n    }\n\n    public get remoteUsermediaFeed(): CallFeed | undefined {\n        return this.getRemoteFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Usermedia);\n    }\n\n    public get remoteScreensharingFeed(): CallFeed | undefined {\n        return this.getRemoteFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Screenshare);\n    }\n\n    public get remoteUsermediaStream(): MediaStream | undefined {\n        return this.remoteUsermediaFeed?.stream;\n    }\n\n    public get remoteScreensharingStream(): MediaStream | undefined {\n        return this.remoteScreensharingFeed?.stream;\n    }\n\n    private getFeedByStreamId(streamId: string): CallFeed | undefined {\n        return this.getFeeds().find((feed) => feed.stream.id === streamId);\n    }\n\n    /**\n     * Returns an array of all CallFeeds\n     * @returns CallFeeds\n     */\n    public getFeeds(): Array<CallFeed> {\n        return this.feeds;\n    }\n\n    /**\n     * Returns an array of all local CallFeeds\n     * @returns local CallFeeds\n     */\n    public getLocalFeeds(): Array<CallFeed> {\n        return this.feeds.filter((feed) => feed.isLocal());\n    }\n\n    /**\n     * Returns an array of all remote CallFeeds\n     * @returns remote CallFeeds\n     */\n    public getRemoteFeeds(): Array<CallFeed> {\n        return this.feeds.filter((feed) => !feed.isLocal());\n    }\n\n    private async initOpponentCrypto(): Promise<void> {\n        if (!this.opponentDeviceId) return;\n        if (!this.client.getUseE2eForGroupCall()) return;\n        // It's possible to want E2EE and yet not have the means to manage E2EE\n        // ourselves (for example if the client is a RoomWidgetClient)\n        if (!this.client.getCrypto()) {\n            // All we know is the device ID\n            this.hasOpponentDeviceInfo = true;\n            return;\n        }\n        const userId = this.invitee || this.getOpponentMember()?.userId;\n\n        if (!userId) throw new Error(\"Couldn't find opponent user ID to init crypto\");\n\n        // Here we were calling `MatrixClient.crypto.deviceList.downloadKeys` which is not supported by the rust cryptography.\n        this.hasOpponentDeviceInfo = false;\n        throw new GroupCallUnknownDeviceError(userId);\n    }\n\n    /**\n     * Generates and returns localSDPStreamMetadata\n     * @returns localSDPStreamMetadata\n     */\n    private getLocalSDPStreamMetadata(updateStreamIds = false): SDPStreamMetadata {\n        const metadata: SDPStreamMetadata = {};\n        for (const localFeed of this.getLocalFeeds()) {\n            if (updateStreamIds) {\n                localFeed.sdpMetadataStreamId = localFeed.stream.id;\n            }\n\n            metadata[localFeed.sdpMetadataStreamId] = {\n                purpose: localFeed.purpose,\n                audio_muted: localFeed.isAudioMuted(),\n                video_muted: localFeed.isVideoMuted(),\n            };\n        }\n        return metadata;\n    }\n\n    /**\n     * Returns true if there are no incoming feeds,\n     * otherwise returns false\n     * @returns no incoming feeds\n     */\n    public noIncomingFeeds(): boolean {\n        return !this.feeds.some((feed) => !feed.isLocal());\n    }\n\n    private pushRemoteFeed(stream: MediaStream): void {\n        // Fallback to old behavior if the other side doesn't support SDPStreamMetadata\n        if (!this.opponentSupportsSDPStreamMetadata()) {\n            this.pushRemoteFeedWithoutMetadata(stream);\n            return;\n        }\n\n        const userId = this.getOpponentMember()!.userId;\n        const purpose = this.remoteSDPStreamMetadata![stream.id].purpose;\n        const audioMuted = this.remoteSDPStreamMetadata![stream.id].audio_muted;\n        const videoMuted = this.remoteSDPStreamMetadata![stream.id].video_muted;\n\n        if (!purpose) {\n            logger.warn(\n                `Call ${this.callId} pushRemoteFeed() ignoring stream because we didn't get any metadata about it (streamId=${stream.id})`,\n            );\n            return;\n        }\n\n        if (this.getFeedByStreamId(stream.id)) {\n            logger.warn(\n                `Call ${this.callId} pushRemoteFeed() ignoring stream because we already have a feed for it (streamId=${stream.id})`,\n            );\n            return;\n        }\n\n        this.feeds.push(\n            new CallFeed({\n                client: this.client,\n                call: this,\n                roomId: this.roomId,\n                userId,\n                deviceId: this.getOpponentDeviceId(),\n                stream,\n                purpose,\n                audioMuted,\n                videoMuted,\n            }),\n        );\n\n        this.emit(CallEvent.FeedsChanged, this.feeds, this);\n\n        logger.info(\n            `Call ${this.callId} pushRemoteFeed() pushed stream (streamId=${stream.id}, active=${stream.active}, purpose=${purpose})`,\n        );\n    }\n\n    /**\n     * This method is used ONLY if the other client doesn't support sending SDPStreamMetadata\n     */\n    private pushRemoteFeedWithoutMetadata(stream: MediaStream): void {\n        const userId = this.getOpponentMember()!.userId;\n        // We can guess the purpose here since the other client can only send one stream\n        const purpose = SDPStreamMetadataPurpose.Usermedia;\n        const oldRemoteStream = this.feeds.find((feed) => !feed.isLocal())?.stream;\n\n        // Note that we check by ID and always set the remote stream: Chrome appears\n        // to make new stream objects when transceiver directionality is changed and the 'active'\n        // status of streams change - Dave\n        // If we already have a stream, check this stream has the same id\n        if (oldRemoteStream && stream.id !== oldRemoteStream.id) {\n            logger.warn(\n                `Call ${this.callId} pushRemoteFeedWithoutMetadata() ignoring new stream because we already have stream (streamId=${stream.id})`,\n            );\n            return;\n        }\n\n        if (this.getFeedByStreamId(stream.id)) {\n            logger.warn(\n                `Call ${this.callId} pushRemoteFeedWithoutMetadata() ignoring stream because we already have a feed for it (streamId=${stream.id})`,\n            );\n            return;\n        }\n\n        this.feeds.push(\n            new CallFeed({\n                client: this.client,\n                call: this,\n                roomId: this.roomId,\n                audioMuted: false,\n                videoMuted: false,\n                userId,\n                deviceId: this.getOpponentDeviceId(),\n                stream,\n                purpose,\n            }),\n        );\n\n        this.emit(CallEvent.FeedsChanged, this.feeds, this);\n\n        logger.info(\n            `Call ${this.callId} pushRemoteFeedWithoutMetadata() pushed stream (streamId=${stream.id}, active=${stream.active})`,\n        );\n    }\n\n    private pushNewLocalFeed(stream: MediaStream, purpose: SDPStreamMetadataPurpose, addToPeerConnection = true): void {\n        const userId = this.client.getUserId()!;\n\n        // Tracks don't always start off enabled, eg. chrome will give a disabled\n        // audio track if you ask for user media audio and already had one that\n        // you'd set to disabled (presumably because it clones them internally).\n        setTracksEnabled(stream.getAudioTracks(), true);\n        setTracksEnabled(stream.getVideoTracks(), true);\n\n        if (this.getFeedByStreamId(stream.id)) {\n            logger.warn(\n                `Call ${this.callId} pushNewLocalFeed() ignoring stream because we already have a feed for it (streamId=${stream.id})`,\n            );\n            return;\n        }\n\n        this.pushLocalFeed(\n            new CallFeed({\n                client: this.client,\n                roomId: this.roomId,\n                audioMuted: false,\n                videoMuted: false,\n                userId,\n                deviceId: this.getOpponentDeviceId(),\n                stream,\n                purpose,\n            }),\n            addToPeerConnection,\n        );\n    }\n\n    /**\n     * Pushes supplied feed to the call\n     * @param callFeed - to push\n     * @param addToPeerConnection - whether to add the tracks to the peer connection\n     */\n    public pushLocalFeed(callFeed: CallFeed, addToPeerConnection = true): void {\n        if (this.feeds.some((feed) => callFeed.stream.id === feed.stream.id)) {\n            logger.info(\n                `Call ${this.callId} pushLocalFeed() ignoring duplicate local stream (streamId=${callFeed.stream.id})`,\n            );\n            return;\n        }\n\n        this.feeds.push(callFeed);\n\n        if (addToPeerConnection) {\n            for (const track of callFeed.stream.getTracks()) {\n                logger.info(\n                    `Call ${this.callId} pushLocalFeed() adding track to peer connection (id=${track.id}, kind=${track.kind}, streamId=${callFeed.stream.id}, streamPurpose=${callFeed.purpose}, enabled=${track.enabled})`,\n                );\n\n                const tKey = getTransceiverKey(callFeed.purpose, track.kind);\n                if (this.transceivers.has(tKey)) {\n                    // we already have a sender, so we re-use it. We try to re-use transceivers as much\n                    // as possible because they can't be removed once added, so otherwise they just\n                    // accumulate which makes the SDP very large very quickly: in fact it only takes\n                    // about 6 video tracks to exceed the maximum size of an Olm-encrypted\n                    // Matrix event.\n                    const transceiver = this.transceivers.get(tKey)!;\n\n                    transceiver.sender.replaceTrack(track);\n                    // set the direction to indicate we're going to start sending again\n                    // (this will trigger the re-negotiation)\n                    transceiver.direction = transceiver.direction === \"inactive\" ? \"sendonly\" : \"sendrecv\";\n                } else {\n                    // create a new one. We need to use addTrack rather addTransceiver for this because firefox\n                    // doesn't yet implement RTCRTPSender.setStreams()\n                    // (https://bugzilla.mozilla.org/show_bug.cgi?id=1510802) so we'd have no way to group the\n                    // two tracks together into a stream.\n                    const newSender = this.peerConn!.addTrack(track, callFeed.stream);\n\n                    // now go & fish for the new transceiver\n                    const newTransceiver = this.peerConn!.getTransceivers().find((t) => t.sender === newSender);\n                    if (newTransceiver) {\n                        this.transceivers.set(tKey, newTransceiver);\n                    } else {\n                        logger.warn(\n                            `Call ${this.callId} pushLocalFeed() didn't find a matching transceiver after adding track!`,\n                        );\n                    }\n                }\n            }\n        }\n\n        logger.info(\n            `Call ${this.callId} pushLocalFeed() pushed stream (id=${callFeed.stream.id}, active=${callFeed.stream.active}, purpose=${callFeed.purpose})`,\n        );\n\n        this.emit(CallEvent.FeedsChanged, this.feeds, this);\n    }\n\n    /**\n     * Removes local call feed from the call and its tracks from the peer\n     * connection\n     * @param callFeed - to remove\n     */\n    public removeLocalFeed(callFeed: CallFeed): void {\n        const audioTransceiverKey = getTransceiverKey(callFeed.purpose, \"audio\");\n        const videoTransceiverKey = getTransceiverKey(callFeed.purpose, \"video\");\n\n        for (const transceiverKey of [audioTransceiverKey, videoTransceiverKey]) {\n            // this is slightly mixing the track and transceiver API but is basically just shorthand.\n            // There is no way to actually remove a transceiver, so this just sets it to inactive\n            // (or recvonly) and replaces the source with nothing.\n            if (this.transceivers.has(transceiverKey)) {\n                const transceiver = this.transceivers.get(transceiverKey)!;\n                if (transceiver.sender) this.peerConn!.removeTrack(transceiver.sender);\n            }\n        }\n\n        if (callFeed.purpose === SDPStreamMetadataPurpose.Screenshare) {\n            this.client.getMediaHandler().stopScreensharingStream(callFeed.stream);\n        }\n\n        this.deleteFeed(callFeed);\n    }\n\n    private deleteAllFeeds(): void {\n        for (const feed of this.feeds) {\n            if (!feed.isLocal() || !this.groupCallId) {\n                feed.dispose();\n            }\n        }\n\n        this.feeds = [];\n        this.emit(CallEvent.FeedsChanged, this.feeds, this);\n    }\n\n    private deleteFeedByStream(stream: MediaStream): void {\n        const feed = this.getFeedByStreamId(stream.id);\n        if (!feed) {\n            logger.warn(\n                `Call ${this.callId} deleteFeedByStream() didn't find the feed to delete (streamId=${stream.id})`,\n            );\n            return;\n        }\n        this.deleteFeed(feed);\n    }\n\n    private deleteFeed(feed: CallFeed): void {\n        feed.dispose();\n        this.feeds.splice(this.feeds.indexOf(feed), 1);\n        this.emit(CallEvent.FeedsChanged, this.feeds, this);\n    }\n\n    // The typescript definitions have this type as 'any' :(\n    public async getCurrentCallStats(): Promise<any[] | undefined> {\n        if (this.callHasEnded()) {\n            return this.callStatsAtEnd;\n        }\n\n        return this.collectCallStats();\n    }\n\n    private async collectCallStats(): Promise<any[] | undefined> {\n        // This happens when the call fails before it starts.\n        // For example when we fail to get capture sources\n        if (!this.peerConn) return;\n\n        const statsReport = await this.peerConn.getStats();\n        const stats: any[] = [];\n        statsReport.forEach((item) => {\n            stats.push(item);\n        });\n\n        return stats;\n    }\n\n    /**\n     * Configure this call from an invite event. Used by MatrixClient.\n     * @param event - The m.call.invite event\n     */\n    public async initWithInvite(event: MatrixEvent): Promise<void> {\n        const invite = event.getContent<MCallInviteNegotiate>();\n        this.direction = CallDirection.Inbound;\n\n        // make sure we have valid turn creds. Unless something's gone wrong, it should\n        // poll and keep the credentials valid so this should be instant.\n        const haveTurnCreds = await this.client.checkTurnServers();\n        if (!haveTurnCreds) {\n            logger.warn(\n                `Call ${this.callId} initWithInvite() failed to get TURN credentials! Proceeding with call anyway...`,\n            );\n        }\n\n        const sdpStreamMetadata = SDPStreamMetadataKey.findIn(invite);\n        if (sdpStreamMetadata) {\n            this.updateRemoteSDPStreamMetadata(sdpStreamMetadata);\n        } else {\n            logger.debug(\n                `Call ${this.callId} initWithInvite() did not get any SDPStreamMetadata! Can not send/receive multiple streams`,\n            );\n        }\n\n        this.peerConn = this.createPeerConnection();\n        this.emit(CallEvent.PeerConnectionCreated, this.peerConn, this);\n        // we must set the party ID before await-ing on anything: the call event\n        // handler will start giving us more call events (eg. candidates) so if\n        // we haven't set the party ID, we'll ignore them.\n        this.chooseOpponent(event);\n        await this.initOpponentCrypto();\n        try {\n            await this.peerConn.setRemoteDescription(invite.offer);\n            logger.debug(`Call ${this.callId} initWithInvite() set remote description: ${invite.offer.type}`);\n            await this.addBufferedIceCandidates();\n        } catch (e) {\n            logger.debug(`Call ${this.callId} initWithInvite() failed to set remote description`, e);\n            this.terminate(CallParty.Local, CallErrorCode.SetRemoteDescription, false);\n            return;\n        }\n\n        const remoteStream = this.feeds.find((feed) => !feed.isLocal())?.stream;\n\n        // According to previous comments in this file, firefox at some point did not\n        // add streams until media started arriving on them. Testing latest firefox\n        // (81 at time of writing), this is no longer a problem, so let's do it the correct way.\n        //\n        // For example in case of no media webrtc connections like screen share only call we have to allow webrtc\n        // connections without remote media. In this case we always use a data channel. At the moment we allow as well\n        // only data channel as media in the WebRTC connection with this setup here.\n        if (!this.isOnlyDataChannelAllowed && (!remoteStream || remoteStream.getTracks().length === 0)) {\n            logger.error(\n                `Call ${this.callId} initWithInvite() no remote stream or no tracks after setting remote description!`,\n            );\n            this.terminate(CallParty.Local, CallErrorCode.SetRemoteDescription, false);\n            return;\n        }\n\n        this.state = CallState.Ringing;\n\n        if (event.getLocalAge()) {\n            // Time out the call if it's ringing for too long\n            const ringingTimer = setTimeout(() => {\n                if (this.state == CallState.Ringing) {\n                    logger.debug(`Call ${this.callId} initWithInvite() invite has expired. Hanging up.`);\n                    this.hangupParty = CallParty.Remote; // effectively\n                    this.state = CallState.Ended;\n                    this.stopAllMedia();\n                    if (this.peerConn!.signalingState != \"closed\") {\n                        this.peerConn!.close();\n                    }\n                    this.stats?.removeStatsReportGatherer(this.callId);\n                    this.emit(CallEvent.Hangup, this);\n                }\n            }, invite.lifetime - event.getLocalAge());\n\n            const onState = (state: CallState): void => {\n                if (state !== CallState.Ringing) {\n                    clearTimeout(ringingTimer);\n                    this.off(CallEvent.State, onState);\n                }\n            };\n            this.on(CallEvent.State, onState);\n        }\n    }\n\n    /**\n     * Configure this call from a hangup or reject event. Used by MatrixClient.\n     * @param event - The m.call.hangup event\n     */\n    public initWithHangup(event: MatrixEvent): void {\n        // perverse as it may seem, sometimes we want to instantiate a call with a\n        // hangup message (because when getting the state of the room on load, events\n        // come in reverse order and we want to remember that a call has been hung up)\n        this.state = CallState.Ended;\n    }\n\n    private shouldAnswerWithMediaType(\n        wantedValue: boolean | undefined,\n        valueOfTheOtherSide: boolean,\n        type: \"audio\" | \"video\",\n    ): boolean {\n        if (wantedValue && !valueOfTheOtherSide) {\n            // TODO: Figure out how to do this\n            logger.warn(\n                `Call ${this.callId} shouldAnswerWithMediaType() unable to answer with ${type} because the other side isn't sending it either.`,\n            );\n            return false;\n        } else if (\n            !isNullOrUndefined(wantedValue) &&\n            wantedValue !== valueOfTheOtherSide &&\n            !this.opponentSupportsSDPStreamMetadata()\n        ) {\n            logger.warn(\n                `Call ${this.callId} shouldAnswerWithMediaType() unable to answer with ${type}=${wantedValue} because the other side doesn't support it. Answering with ${type}=${valueOfTheOtherSide}.`,\n            );\n            return valueOfTheOtherSide!;\n        }\n        return wantedValue ?? valueOfTheOtherSide!;\n    }\n\n    /**\n     * Answer a call.\n     */\n    public async answer(audio?: boolean, video?: boolean): Promise<void> {\n        if (this.inviteOrAnswerSent) return;\n        // TODO: Figure out how to do this\n        if (audio === false && video === false) throw new Error(\"You CANNOT answer a call without media\");\n\n        if (!this.localUsermediaStream && !this.waitForLocalAVStream) {\n            const prevState = this.state;\n            const answerWithAudio = this.shouldAnswerWithMediaType(audio, this.hasRemoteUserMediaAudioTrack, \"audio\");\n            const answerWithVideo = this.shouldAnswerWithMediaType(video, this.hasRemoteUserMediaVideoTrack, \"video\");\n\n            this.state = CallState.WaitLocalMedia;\n            this.waitForLocalAVStream = true;\n\n            try {\n                const stream = await this.client.getMediaHandler().getUserMediaStream(answerWithAudio, answerWithVideo);\n                this.waitForLocalAVStream = false;\n                const usermediaFeed = new CallFeed({\n                    client: this.client,\n                    roomId: this.roomId,\n                    userId: this.client.getUserId()!,\n                    deviceId: this.client.getDeviceId() ?? undefined,\n                    stream,\n                    purpose: SDPStreamMetadataPurpose.Usermedia,\n                    audioMuted: false,\n                    videoMuted: false,\n                });\n\n                const feeds = [usermediaFeed];\n\n                if (this.localScreensharingFeed) {\n                    feeds.push(this.localScreensharingFeed);\n                }\n\n                this.answerWithCallFeeds(feeds);\n            } catch (e) {\n                if (answerWithVideo) {\n                    // Try to answer without video\n                    logger.warn(\n                        `Call ${this.callId} answer() failed to getUserMedia(), trying to getUserMedia() without video`,\n                    );\n                    this.state = prevState;\n                    this.waitForLocalAVStream = false;\n                    await this.answer(answerWithAudio, false);\n                } else {\n                    this.getUserMediaFailed(<Error>e);\n                    return;\n                }\n            }\n        } else if (this.waitForLocalAVStream) {\n            this.state = CallState.WaitLocalMedia;\n        }\n    }\n\n    public answerWithCallFeeds(callFeeds: CallFeed[]): void {\n        if (this.inviteOrAnswerSent) return;\n\n        this.queueGotCallFeedsForAnswer(callFeeds);\n    }\n\n    /**\n     * Replace this call with a new call, e.g. for glare resolution. Used by\n     * MatrixClient.\n     * @param newCall - The new call.\n     */\n    public replacedBy(newCall: MatrixCall): void {\n        logger.debug(`Call ${this.callId} replacedBy() running (newCallId=${newCall.callId})`);\n        if (this.state === CallState.WaitLocalMedia) {\n            logger.debug(\n                `Call ${this.callId} replacedBy() telling new call to wait for local media (newCallId=${newCall.callId})`,\n            );\n            newCall.waitForLocalAVStream = true;\n        } else if ([CallState.CreateOffer, CallState.InviteSent].includes(this.state)) {\n            if (newCall.direction === CallDirection.Outbound) {\n                newCall.queueGotCallFeedsForAnswer([]);\n            } else {\n                logger.debug(\n                    `Call ${this.callId} replacedBy() handing local stream to new call(newCallId=${newCall.callId})`,\n                );\n                newCall.queueGotCallFeedsForAnswer(this.getLocalFeeds().map((feed) => feed.clone()));\n            }\n        }\n        this.successor = newCall;\n        this.emit(CallEvent.Replaced, newCall, this);\n        this.hangup(CallErrorCode.Replaced, true);\n    }\n\n    /**\n     * Hangup a call.\n     * @param reason - The reason why the call is being hung up.\n     * @param suppressEvent - True to suppress emitting an event.\n     */\n    public hangup(reason: CallErrorCode, suppressEvent: boolean): void {\n        if (this.callHasEnded()) return;\n\n        logger.debug(`Call ${this.callId} hangup() ending call (reason=${reason})`);\n        this.terminate(CallParty.Local, reason, !suppressEvent);\n        // We don't want to send hangup here if we didn't even get to sending an invite\n        if ([CallState.Fledgling, CallState.WaitLocalMedia].includes(this.state)) return;\n        const content: Omit<MCallHangupReject, \"version\" | \"call_id\" | \"party_id\" | \"conf_id\"> = {};\n        // Don't send UserHangup reason to older clients\n        if ((this.opponentVersion && this.opponentVersion !== 0) || reason !== CallErrorCode.UserHangup) {\n            content[\"reason\"] = reason;\n        }\n        this.sendVoipEvent(EventType.CallHangup, content);\n    }\n\n    /**\n     * Reject a call\n     * This used to be done by calling hangup, but is a separate method and protocol\n     * event as of MSC2746.\n     */\n    public reject(): void {\n        if (this.state !== CallState.Ringing) {\n            throw Error(\"Call must be in 'ringing' state to reject!\");\n        }\n\n        if (this.opponentVersion === 0) {\n            logger.info(\n                `Call ${this.callId} reject() opponent version is less than 1: sending hangup instead of reject (opponentVersion=${this.opponentVersion})`,\n            );\n            this.hangup(CallErrorCode.UserHangup, true);\n            return;\n        }\n\n        logger.debug(\"Rejecting call: \" + this.callId);\n        this.terminate(CallParty.Local, CallErrorCode.UserHangup, true);\n        this.sendVoipEvent(EventType.CallReject, {});\n    }\n\n    /**\n     * Adds an audio and/or video track - upgrades the call\n     * @param audio - should add an audio track\n     * @param video - should add an video track\n     */\n    private async upgradeCall(audio: boolean, video: boolean): Promise<void> {\n        // We don't do call downgrades\n        if (!audio && !video) return;\n        if (!this.opponentSupportsSDPStreamMetadata()) return;\n\n        try {\n            logger.debug(`Call ${this.callId} upgradeCall() upgrading call (audio=${audio}, video=${video})`);\n            const getAudio = audio || this.hasLocalUserMediaAudioTrack;\n            const getVideo = video || this.hasLocalUserMediaVideoTrack;\n\n            // updateLocalUsermediaStream() will take the tracks, use them as\n            // replacement and throw the stream away, so it isn't reusable\n            const stream = await this.client.getMediaHandler().getUserMediaStream(getAudio, getVideo, false);\n            await this.updateLocalUsermediaStream(stream, audio, video);\n        } catch (error) {\n            logger.error(`Call ${this.callId} upgradeCall() failed to upgrade the call`, error);\n            this.emit(\n                CallEvent.Error,\n                new CallError(CallErrorCode.NoUserMedia, \"Failed to get camera access: \", <Error>error),\n                this,\n            );\n        }\n    }\n\n    /**\n     * Returns true if this.remoteSDPStreamMetadata is defined, otherwise returns false\n     * @returns can screenshare\n     */\n    public opponentSupportsSDPStreamMetadata(): boolean {\n        return Boolean(this.remoteSDPStreamMetadata);\n    }\n\n    /**\n     * If there is a screensharing stream returns true, otherwise returns false\n     * @returns is screensharing\n     */\n    public isScreensharing(): boolean {\n        return Boolean(this.localScreensharingStream);\n    }\n\n    /**\n     * Starts/stops screensharing\n     * @param enabled - the desired screensharing state\n     * @param opts - screen sharing options\n     * @returns new screensharing state\n     */\n    public async setScreensharingEnabled(enabled: boolean, opts?: IScreensharingOpts): Promise<boolean> {\n        // Skip if there is nothing to do\n        if (enabled && this.isScreensharing()) {\n            logger.warn(\n                `Call ${this.callId} setScreensharingEnabled() there is already a screensharing stream - there is nothing to do!`,\n            );\n            return true;\n        } else if (!enabled && !this.isScreensharing()) {\n            logger.warn(\n                `Call ${this.callId} setScreensharingEnabled() there already isn't a screensharing stream - there is nothing to do!`,\n            );\n            return false;\n        }\n\n        // Fallback to replaceTrack()\n        if (!this.opponentSupportsSDPStreamMetadata()) {\n            return this.setScreensharingEnabledWithoutMetadataSupport(enabled, opts);\n        }\n\n        logger.debug(`Call ${this.callId} setScreensharingEnabled() running (enabled=${enabled})`);\n        if (enabled) {\n            try {\n                const stream = await this.client.getMediaHandler().getScreensharingStream(opts);\n                if (!stream) return false;\n                this.pushNewLocalFeed(stream, SDPStreamMetadataPurpose.Screenshare);\n                return true;\n            } catch (err) {\n                logger.error(`Call ${this.callId} setScreensharingEnabled() failed to get screen-sharing stream:`, err);\n                return false;\n            }\n        } else {\n            const audioTransceiver = this.transceivers.get(\n                getTransceiverKey(SDPStreamMetadataPurpose.Screenshare, \"audio\"),\n            );\n            const videoTransceiver = this.transceivers.get(\n                getTransceiverKey(SDPStreamMetadataPurpose.Screenshare, \"video\"),\n            );\n\n            for (const transceiver of [audioTransceiver, videoTransceiver]) {\n                // this is slightly mixing the track and transceiver API but is basically just shorthand\n                // for removing the sender.\n                if (transceiver && transceiver.sender) this.peerConn!.removeTrack(transceiver.sender);\n            }\n\n            this.client.getMediaHandler().stopScreensharingStream(this.localScreensharingStream!);\n            this.deleteFeedByStream(this.localScreensharingStream!);\n            return false;\n        }\n    }\n\n    /**\n     * Starts/stops screensharing\n     * Should be used ONLY if the opponent doesn't support SDPStreamMetadata\n     * @param enabled - the desired screensharing state\n     * @param opts - screen sharing options\n     * @returns new screensharing state\n     */\n    private async setScreensharingEnabledWithoutMetadataSupport(\n        enabled: boolean,\n        opts?: IScreensharingOpts,\n    ): Promise<boolean> {\n        logger.debug(\n            `Call ${this.callId} setScreensharingEnabledWithoutMetadataSupport() running (enabled=${enabled})`,\n        );\n        if (enabled) {\n            try {\n                const stream = await this.client.getMediaHandler().getScreensharingStream(opts);\n                if (!stream) return false;\n\n                const track = stream.getTracks().find((track) => track.kind === \"video\");\n\n                const sender = this.transceivers.get(\n                    getTransceiverKey(SDPStreamMetadataPurpose.Usermedia, \"video\"),\n                )?.sender;\n\n                sender?.replaceTrack(track ?? null);\n\n                this.pushNewLocalFeed(stream, SDPStreamMetadataPurpose.Screenshare, false);\n\n                return true;\n            } catch (err) {\n                logger.error(\n                    `Call ${this.callId} setScreensharingEnabledWithoutMetadataSupport() failed to get screen-sharing stream:`,\n                    err,\n                );\n                return false;\n            }\n        } else {\n            const track = this.localUsermediaStream?.getTracks().find((track) => track.kind === \"video\");\n            const sender = this.transceivers.get(\n                getTransceiverKey(SDPStreamMetadataPurpose.Usermedia, \"video\"),\n            )?.sender;\n            sender?.replaceTrack(track ?? null);\n\n            this.client.getMediaHandler().stopScreensharingStream(this.localScreensharingStream!);\n            this.deleteFeedByStream(this.localScreensharingStream!);\n\n            return false;\n        }\n    }\n\n    /**\n     * Replaces/adds the tracks from the passed stream to the localUsermediaStream\n     * @param stream - to use a replacement for the local usermedia stream\n     */\n    public async updateLocalUsermediaStream(\n        stream: MediaStream,\n        forceAudio = false,\n        forceVideo = false,\n    ): Promise<void> {\n        const callFeed = this.localUsermediaFeed!;\n        const audioEnabled = forceAudio || (!callFeed.isAudioMuted() && !this.remoteOnHold);\n        const videoEnabled = forceVideo || (!callFeed.isVideoMuted() && !this.remoteOnHold);\n        logger.log(\n            `Call ${this.callId} updateLocalUsermediaStream() running (streamId=${stream.id}, audio=${audioEnabled}, video=${videoEnabled})`,\n        );\n        setTracksEnabled(stream.getAudioTracks(), audioEnabled);\n        setTracksEnabled(stream.getVideoTracks(), videoEnabled);\n\n        // We want to keep the same stream id, so we replace the tracks rather\n        // than the whole stream.\n\n        // Firstly, we replace the tracks in our localUsermediaStream.\n        for (const track of this.localUsermediaStream!.getTracks()) {\n            this.localUsermediaStream!.removeTrack(track);\n            track.stop();\n        }\n        for (const track of stream.getTracks()) {\n            this.localUsermediaStream!.addTrack(track);\n        }\n\n        // Then replace the old tracks, if possible.\n        for (const track of stream.getTracks()) {\n            const tKey = getTransceiverKey(SDPStreamMetadataPurpose.Usermedia, track.kind);\n\n            const transceiver = this.transceivers.get(tKey);\n            const oldSender = transceiver?.sender;\n            let added = false;\n            if (oldSender) {\n                try {\n                    logger.info(\n                        `Call ${this.callId} updateLocalUsermediaStream() replacing track (id=${track.id}, kind=${track.kind}, streamId=${stream.id}, streamPurpose=${callFeed.purpose})`,\n                    );\n                    await oldSender.replaceTrack(track);\n                    // Set the direction to indicate we're going to be sending.\n                    // This is only necessary in the cases where we're upgrading\n                    // the call to video after downgrading it.\n                    transceiver.direction = transceiver.direction === \"inactive\" ? \"sendonly\" : \"sendrecv\";\n                    added = true;\n                } catch (error) {\n                    logger.warn(\n                        `Call ${this.callId} updateLocalUsermediaStream() replaceTrack failed: adding new transceiver instead`,\n                        error,\n                    );\n                }\n            }\n\n            if (!added) {\n                logger.info(\n                    `Call ${this.callId} updateLocalUsermediaStream() adding track to peer connection (id=${track.id}, kind=${track.kind}, streamId=${stream.id}, streamPurpose=${callFeed.purpose})`,\n                );\n\n                const newSender = this.peerConn!.addTrack(track, this.localUsermediaStream!);\n                const newTransceiver = this.peerConn!.getTransceivers().find((t) => t.sender === newSender);\n                if (newTransceiver) {\n                    this.transceivers.set(tKey, newTransceiver);\n                } else {\n                    logger.warn(\n                        `Call ${this.callId} updateLocalUsermediaStream() couldn't find matching transceiver for newly added track!`,\n                    );\n                }\n            }\n        }\n    }\n\n    /**\n     * Set whether our outbound video should be muted or not.\n     * @param muted - True to mute the outbound video.\n     * @returns the new mute state\n     */\n    public async setLocalVideoMuted(muted: boolean): Promise<boolean> {\n        logger.log(`Call ${this.callId} setLocalVideoMuted() running ${muted}`);\n\n        // if we were still thinking about stopping and removing the video\n        // track: don't, because we want it back.\n        if (!muted && this.stopVideoTrackTimer !== undefined) {\n            clearTimeout(this.stopVideoTrackTimer);\n            this.stopVideoTrackTimer = undefined;\n        }\n\n        if (!(await this.client.getMediaHandler().hasVideoDevice())) {\n            return this.isLocalVideoMuted();\n        }\n\n        if (!this.hasUserMediaVideoSender && !muted) {\n            this.localUsermediaFeed?.setAudioVideoMuted(null, muted);\n            await this.upgradeCall(false, true);\n            return this.isLocalVideoMuted();\n        }\n\n        // we may not have a video track - if not, re-request usermedia\n        if (!muted && this.localUsermediaStream!.getVideoTracks().length === 0) {\n            const stream = await this.client.getMediaHandler().getUserMediaStream(true, true);\n            await this.updateLocalUsermediaStream(stream);\n        }\n\n        this.localUsermediaFeed?.setAudioVideoMuted(null, muted);\n\n        this.updateMuteStatus();\n        await this.sendMetadataUpdate();\n\n        // if we're muting video, set a timeout to stop & remove the video track so we release\n        // the camera. We wait a short time to do this because when we disable a track, WebRTC\n        // will send black video for it. If we just stop and remove it straight away, the video\n        // will just freeze which means that when we unmute video, the other side will briefly\n        // get a static frame of us from before we muted. This way, the still frame is just black.\n        // A very small delay is not always enough so the theory here is that it needs to be long\n        // enough for WebRTC to encode a frame: 120ms should be long enough even if we're only\n        // doing 10fps.\n        if (muted) {\n            this.stopVideoTrackTimer = setTimeout(() => {\n                for (const t of this.localUsermediaStream!.getVideoTracks()) {\n                    t.stop();\n                    this.localUsermediaStream!.removeTrack(t);\n                }\n            }, 120);\n        }\n\n        return this.isLocalVideoMuted();\n    }\n\n    /**\n     * Check if local video is muted.\n     *\n     * If there are multiple video tracks, <i>all</i> of the tracks need to be muted\n     * for this to return true. This means if there are no video tracks, this will\n     * return true.\n     * @returns True if the local preview video is muted, else false\n     * (including if the call is not set up yet).\n     */\n    public isLocalVideoMuted(): boolean {\n        return this.localUsermediaFeed?.isVideoMuted() ?? false;\n    }\n\n    /**\n     * Set whether the microphone should be muted or not.\n     * @param muted - True to mute the mic.\n     * @returns the new mute state\n     */\n    public async setMicrophoneMuted(muted: boolean): Promise<boolean> {\n        logger.log(`Call ${this.callId} setMicrophoneMuted() running ${muted}`);\n        if (!(await this.client.getMediaHandler().hasAudioDevice())) {\n            return this.isMicrophoneMuted();\n        }\n\n        if (!muted && (!this.hasUserMediaAudioSender || !this.hasLocalUserMediaAudioTrack)) {\n            await this.upgradeCall(true, false);\n            return this.isMicrophoneMuted();\n        }\n        this.localUsermediaFeed?.setAudioVideoMuted(muted, null);\n        this.updateMuteStatus();\n        await this.sendMetadataUpdate();\n        return this.isMicrophoneMuted();\n    }\n\n    /**\n     * Check if the microphone is muted.\n     *\n     * If there are multiple audio tracks, <i>all</i> of the tracks need to be muted\n     * for this to return true. This means if there are no audio tracks, this will\n     * return true.\n     * @returns True if the mic is muted, else false (including if the call\n     * is not set up yet).\n     */\n    public isMicrophoneMuted(): boolean {\n        return this.localUsermediaFeed?.isAudioMuted() ?? false;\n    }\n\n    /**\n     * @returns true if we have put the party on the other side of the call on hold\n     * (that is, we are signalling to them that we are not listening)\n     */\n    public isRemoteOnHold(): boolean {\n        return this.remoteOnHold;\n    }\n\n    public setRemoteOnHold(onHold: boolean): void {\n        if (this.isRemoteOnHold() === onHold) return;\n        this.remoteOnHold = onHold;\n\n        for (const transceiver of this.peerConn!.getTransceivers()) {\n            // We don't send hold music or anything so we're not actually\n            // sending anything, but sendrecv is fairly standard for hold and\n            // it makes it a lot easier to figure out who's put who on hold.\n            transceiver.direction = onHold ? \"sendonly\" : \"sendrecv\";\n        }\n        this.updateMuteStatus();\n        this.sendMetadataUpdate();\n\n        this.emit(CallEvent.RemoteHoldUnhold, this.remoteOnHold, this);\n    }\n\n    /**\n     * Indicates whether we are 'on hold' to the remote party (ie. if true,\n     * they cannot hear us).\n     * @returns true if the other party has put us on hold\n     */\n    public isLocalOnHold(): boolean {\n        if (this.state !== CallState.Connected) return false;\n\n        let callOnHold = true;\n\n        // We consider a call to be on hold only if *all* the tracks are on hold\n        // (is this the right thing to do?)\n        for (const transceiver of this.peerConn!.getTransceivers()) {\n            const trackOnHold = [\"inactive\", \"recvonly\"].includes(transceiver.currentDirection!);\n\n            if (!trackOnHold) callOnHold = false;\n        }\n\n        return callOnHold;\n    }\n\n    /**\n     * Sends a DTMF digit to the other party\n     * @param digit - The digit (nb. string - '#' and '*' are dtmf too)\n     */\n    public sendDtmfDigit(digit: string): void {\n        for (const sender of this.peerConn!.getSenders()) {\n            if (sender.track?.kind === \"audio\" && sender.dtmf) {\n                sender.dtmf.insertDTMF(digit);\n                return;\n            }\n        }\n\n        throw new Error(\"Unable to find a track to send DTMF on\");\n    }\n\n    private updateMuteStatus(): void {\n        const micShouldBeMuted = this.isMicrophoneMuted() || this.remoteOnHold;\n        const vidShouldBeMuted = this.isLocalVideoMuted() || this.remoteOnHold;\n\n        logger.log(\n            `Call ${this.callId} updateMuteStatus stream ${\n                this.localUsermediaStream!.id\n            } micShouldBeMuted ${micShouldBeMuted} vidShouldBeMuted ${vidShouldBeMuted}`,\n        );\n\n        setTracksEnabled(this.localUsermediaStream!.getAudioTracks(), !micShouldBeMuted);\n        setTracksEnabled(this.localUsermediaStream!.getVideoTracks(), !vidShouldBeMuted);\n    }\n\n    public async sendMetadataUpdate(): Promise<void> {\n        await this.sendVoipEvent(EventType.CallSDPStreamMetadataChanged, {\n            [SDPStreamMetadataKey.name]: this.getLocalSDPStreamMetadata(),\n        });\n    }\n\n    private gotCallFeedsForInvite(callFeeds: CallFeed[], requestScreenshareFeed = false): void {\n        if (this.successor) {\n            this.successor.queueGotCallFeedsForAnswer(callFeeds);\n            return;\n        }\n        if (this.callHasEnded()) {\n            this.stopAllMedia();\n            return;\n        }\n\n        for (const feed of callFeeds) {\n            this.pushLocalFeed(feed);\n        }\n\n        if (requestScreenshareFeed) {\n            this.peerConn!.addTransceiver(\"video\", {\n                direction: \"recvonly\",\n            });\n        }\n\n        this.state = CallState.CreateOffer;\n\n        logger.debug(`Call ${this.callId} gotUserMediaForInvite() run`);\n        // Now we wait for the negotiationneeded event\n    }\n\n    private async sendAnswer(): Promise<void> {\n        const answerContent: Omit<MCallAnswer, \"version\" | \"call_id\" | \"party_id\" | \"conf_id\"> = {\n            answer: {\n                sdp: this.peerConn!.localDescription!.sdp,\n                // type is now deprecated as of Matrix VoIP v1, but\n                // required to still be sent for backwards compat\n                type: this.peerConn!.localDescription!.type,\n            },\n            [SDPStreamMetadataKey.name]: this.getLocalSDPStreamMetadata(true),\n        };\n\n        answerContent.capabilities = {\n            \"m.call.transferee\": this.client.supportsCallTransfer,\n            \"m.call.dtmf\": false,\n        };\n\n        // We have just taken the local description from the peerConn which will\n        // contain all the local candidates added so far, so we can discard any candidates\n        // we had queued up because they'll be in the answer.\n        const discardCount = this.discardDuplicateCandidates();\n        logger.info(\n            `Call ${this.callId} sendAnswer() discarding ${discardCount} candidates that will be sent in answer`,\n        );\n\n        try {\n            await this.sendVoipEvent(EventType.CallAnswer, answerContent);\n            // If this isn't the first time we've tried to send the answer,\n            // we may have candidates queued up, so send them now.\n            this.inviteOrAnswerSent = true;\n        } catch (error) {\n            // We've failed to answer: back to the ringing state\n            this.state = CallState.Ringing;\n            if (error instanceof MatrixError && error.event) this.client.cancelPendingEvent(error.event);\n\n            let code = CallErrorCode.SendAnswer;\n            let message = \"Failed to send answer\";\n            if ((<Error>error).name == \"UnknownDeviceError\") {\n                code = CallErrorCode.UnknownDevices;\n                message = \"Unknown devices present in the room\";\n            }\n            this.emit(CallEvent.Error, new CallError(code, message, <Error>error), this);\n            throw error;\n        }\n\n        // error handler re-throws so this won't happen on error, but\n        // we don't want the same error handling on the candidate queue\n        this.sendCandidateQueue();\n    }\n\n    private queueGotCallFeedsForAnswer(callFeeds: CallFeed[]): void {\n        // Ensure only one negotiate/answer event is being processed at a time.\n        if (this.responsePromiseChain) {\n            this.responsePromiseChain = this.responsePromiseChain.then(() => this.gotCallFeedsForAnswer(callFeeds));\n        } else {\n            this.responsePromiseChain = this.gotCallFeedsForAnswer(callFeeds);\n        }\n    }\n\n    // Enables DTX (discontinuous transmission) on the given session to reduce\n    // bandwidth when transmitting silence\n    private mungeSdp(description: RTCSessionDescriptionInit, mods: CodecParamsMod[]): void {\n        // The only way to enable DTX at this time is through SDP munging\n        const sdp = parseSdp(description.sdp!);\n\n        sdp.media.forEach((media) => {\n            const payloadTypeToCodecMap = new Map<number, string>();\n            const codecToPayloadTypeMap = new Map<string, number>();\n            for (const rtp of media.rtp) {\n                payloadTypeToCodecMap.set(rtp.payload, rtp.codec);\n                codecToPayloadTypeMap.set(rtp.codec, rtp.payload);\n            }\n\n            for (const mod of mods) {\n                if (mod.mediaType !== media.type) continue;\n\n                if (!codecToPayloadTypeMap.has(mod.codec)) {\n                    logger.info(\n                        `Call ${this.callId} mungeSdp() ignoring SDP modifications for ${mod.codec} as it's not present.`,\n                    );\n                    continue;\n                }\n\n                const extraConfig: string[] = [];\n                if (mod.enableDtx !== undefined) {\n                    extraConfig.push(`usedtx=${mod.enableDtx ? \"1\" : \"0\"}`);\n                }\n                if (mod.maxAverageBitrate !== undefined) {\n                    extraConfig.push(`maxaveragebitrate=${mod.maxAverageBitrate}`);\n                }\n\n                let found = false;\n                for (const fmtp of media.fmtp) {\n                    if (payloadTypeToCodecMap.get(fmtp.payload) === mod.codec) {\n                        found = true;\n                        fmtp.config += \";\" + extraConfig.join(\";\");\n                    }\n                }\n                if (!found) {\n                    media.fmtp.push({\n                        payload: codecToPayloadTypeMap.get(mod.codec)!,\n                        config: extraConfig.join(\";\"),\n                    });\n                }\n            }\n        });\n        description.sdp = writeSdp(sdp);\n    }\n\n    private async createOffer(): Promise<RTCSessionDescriptionInit> {\n        const offer = await this.peerConn!.createOffer();\n        this.mungeSdp(offer, getCodecParamMods(this.isPtt));\n        return offer;\n    }\n\n    private async createAnswer(): Promise<RTCSessionDescriptionInit> {\n        const answer = await this.peerConn!.createAnswer();\n        this.mungeSdp(answer, getCodecParamMods(this.isPtt));\n        return answer;\n    }\n\n    private async gotCallFeedsForAnswer(callFeeds: CallFeed[]): Promise<void> {\n        if (this.callHasEnded()) return;\n\n        this.waitForLocalAVStream = false;\n\n        for (const feed of callFeeds) {\n            this.pushLocalFeed(feed);\n        }\n\n        this.state = CallState.CreateAnswer;\n\n        let answer: RTCSessionDescriptionInit;\n        try {\n            this.getRidOfRTXCodecs();\n            answer = await this.createAnswer();\n        } catch (err) {\n            logger.debug(`Call ${this.callId} gotCallFeedsForAnswer() failed to create answer: `, err);\n            this.terminate(CallParty.Local, CallErrorCode.CreateAnswer, true);\n            return;\n        }\n\n        try {\n            await this.peerConn!.setLocalDescription(answer);\n\n            // make sure we're still going\n            if (this.callHasEnded()) return;\n\n            this.state = CallState.Connecting;\n\n            // Allow a short time for initial candidates to be gathered\n            await new Promise((resolve) => {\n                setTimeout(resolve, 200);\n            });\n\n            // make sure the call hasn't ended before we continue\n            if (this.callHasEnded()) return;\n\n            this.sendAnswer();\n        } catch (err) {\n            logger.debug(`Call ${this.callId} gotCallFeedsForAnswer() error setting local description!`, err);\n            this.terminate(CallParty.Local, CallErrorCode.SetLocalDescription, true);\n            return;\n        }\n    }\n\n    /**\n     * Internal\n     */\n    private gotLocalIceCandidate = (event: RTCPeerConnectionIceEvent): void => {\n        if (event.candidate) {\n            if (this.candidatesEnded) {\n                logger.warn(`Call ${this.callId} gotLocalIceCandidate() got candidate after candidates have ended!`);\n            }\n\n            logger.debug(`Call ${this.callId} got local ICE ${event.candidate.sdpMid} ${event.candidate.candidate}`);\n\n            if (this.callHasEnded()) return;\n\n            // As with the offer, note we need to make a copy of this object, not\n            // pass the original: that broke in Chrome ~m43.\n            if (event.candidate.candidate === \"\") {\n                this.queueCandidate(null);\n            } else {\n                this.queueCandidate(event.candidate);\n            }\n        }\n    };\n\n    private onIceGatheringStateChange = (event: Event): void => {\n        logger.debug(\n            `Call ${this.callId} onIceGatheringStateChange() ice gathering state changed to ${\n                this.peerConn!.iceGatheringState\n            }`,\n        );\n        if (this.peerConn?.iceGatheringState === \"complete\") {\n            this.queueCandidate(null); // We should leave it to WebRTC to announce the end\n            logger.debug(\n                `Call ${this.callId} onIceGatheringStateChange() ice gathering state complete, set candidates have ended`,\n            );\n        }\n    };\n\n    public async onRemoteIceCandidatesReceived(ev: MatrixEvent): Promise<void> {\n        if (this.callHasEnded()) {\n            //debuglog(\"Ignoring remote ICE candidate because call has ended\");\n            return;\n        }\n\n        const content = ev.getContent<MCallCandidates>();\n        const candidates = content.candidates;\n        if (!candidates) {\n            logger.info(\n                `Call ${this.callId} onRemoteIceCandidatesReceived() ignoring candidates event with no candidates!`,\n            );\n            return;\n        }\n\n        const fromPartyId = content.version === 0 ? null : content.party_id || null;\n\n        if (this.opponentPartyId === undefined) {\n            // we haven't picked an opponent yet so save the candidates\n            if (fromPartyId) {\n                logger.info(\n                    `Call ${this.callId} onRemoteIceCandidatesReceived() buffering ${candidates.length} candidates until we pick an opponent`,\n                );\n                const bufferedCandidates = this.remoteCandidateBuffer.get(fromPartyId) || [];\n                bufferedCandidates.push(...candidates);\n                this.remoteCandidateBuffer.set(fromPartyId, bufferedCandidates);\n            }\n            return;\n        }\n\n        if (!this.partyIdMatches(content)) {\n            logger.info(\n                `Call ${this.callId} onRemoteIceCandidatesReceived() ignoring candidates from party ID ${content.party_id}: we have chosen party ID ${this.opponentPartyId}`,\n            );\n\n            return;\n        }\n\n        await this.addIceCandidates(candidates);\n    }\n\n    /**\n     * Used by MatrixClient.\n     */\n    public async onAnswerReceived(event: MatrixEvent): Promise<void> {\n        const content = event.getContent<MCallAnswer>();\n        logger.debug(`Call ${this.callId} onAnswerReceived() running (hangupParty=${content.party_id})`);\n\n        if (this.callHasEnded()) {\n            logger.debug(`Call ${this.callId} onAnswerReceived() ignoring answer because call has ended`);\n            return;\n        }\n\n        if (this.opponentPartyId !== undefined) {\n            logger.info(\n                `Call ${this.callId} onAnswerReceived() ignoring answer from party ID ${content.party_id}: we already have an answer/reject from ${this.opponentPartyId}`,\n            );\n            return;\n        }\n\n        this.chooseOpponent(event);\n        await this.addBufferedIceCandidates();\n\n        this.state = CallState.Connecting;\n\n        const sdpStreamMetadata = SDPStreamMetadataKey.findIn(content);\n        if (sdpStreamMetadata) {\n            this.updateRemoteSDPStreamMetadata(sdpStreamMetadata);\n        } else {\n            logger.warn(\n                `Call ${this.callId} onAnswerReceived() did not get any SDPStreamMetadata! Can not send/receive multiple streams`,\n            );\n        }\n\n        try {\n            this.isSettingRemoteAnswerPending = true;\n            await this.peerConn!.setRemoteDescription(content.answer);\n            this.isSettingRemoteAnswerPending = false;\n            logger.debug(`Call ${this.callId} onAnswerReceived() set remote description: ${content.answer.type}`);\n        } catch (e) {\n            this.isSettingRemoteAnswerPending = false;\n            logger.debug(`Call ${this.callId} onAnswerReceived() failed to set remote description`, e);\n            this.terminate(CallParty.Local, CallErrorCode.SetRemoteDescription, false);\n            return;\n        }\n\n        // If the answer we selected has a party_id, send a select_answer event\n        // We do this after setting the remote description since otherwise we'd block\n        // call setup on it\n        if (this.opponentPartyId !== null) {\n            try {\n                await this.sendVoipEvent(EventType.CallSelectAnswer, {\n                    selected_party_id: this.opponentPartyId!,\n                });\n            } catch (err) {\n                // This isn't fatal, and will just mean that if another party has raced to answer\n                // the call, they won't know they got rejected, so we carry on & don't retry.\n                logger.warn(`Call ${this.callId} onAnswerReceived() failed to send select_answer event`, err);\n            }\n        }\n    }\n\n    public async onSelectAnswerReceived(event: MatrixEvent): Promise<void> {\n        if (this.direction !== CallDirection.Inbound) {\n            logger.warn(\n                `Call ${this.callId} onSelectAnswerReceived() got select_answer for an outbound call: ignoring`,\n            );\n            return;\n        }\n\n        const selectedPartyId = event.getContent<MCallSelectAnswer>().selected_party_id;\n\n        if (selectedPartyId === undefined || selectedPartyId === null) {\n            logger.warn(\n                `Call ${this.callId} onSelectAnswerReceived() got nonsensical select_answer with null/undefined selected_party_id: ignoring`,\n            );\n            return;\n        }\n\n        if (selectedPartyId !== this.ourPartyId) {\n            logger.info(\n                `Call ${this.callId} onSelectAnswerReceived() got select_answer for party ID ${selectedPartyId}: we are party ID ${this.ourPartyId}.`,\n            );\n            // The other party has picked somebody else's answer\n            await this.terminate(CallParty.Remote, CallErrorCode.AnsweredElsewhere, true);\n        }\n    }\n\n    public async onNegotiateReceived(event: MatrixEvent): Promise<void> {\n        const content = event.getContent<MCallInviteNegotiate>();\n        const description = content.description;\n        if (!description || !description.sdp || !description.type) {\n            logger.info(`Call ${this.callId} onNegotiateReceived() ignoring invalid m.call.negotiate event`);\n            return;\n        }\n        // Politeness always follows the direction of the call: in a glare situation,\n        // we pick either the inbound or outbound call, so one side will always be\n        // inbound and one outbound\n        const polite = this.direction === CallDirection.Inbound;\n\n        // Here we follow the perfect negotiation logic from\n        // https://w3c.github.io/webrtc-pc/#perfect-negotiation-example\n        const readyForOffer =\n            !this.makingOffer && (this.peerConn!.signalingState === \"stable\" || this.isSettingRemoteAnswerPending);\n\n        const offerCollision = description.type === \"offer\" && !readyForOffer;\n\n        this.ignoreOffer = !polite && offerCollision;\n        if (this.ignoreOffer) {\n            logger.info(\n                `Call ${this.callId} onNegotiateReceived() ignoring colliding negotiate event because we're impolite`,\n            );\n            return;\n        }\n\n        const prevLocalOnHold = this.isLocalOnHold();\n\n        const sdpStreamMetadata = SDPStreamMetadataKey.findIn<SDPStreamMetadata>(content);\n        if (sdpStreamMetadata) {\n            this.updateRemoteSDPStreamMetadata(sdpStreamMetadata);\n        } else {\n            logger.warn(\n                `Call ${this.callId} onNegotiateReceived() received negotiation event without SDPStreamMetadata!`,\n            );\n        }\n\n        try {\n            this.isSettingRemoteAnswerPending = description.type == \"answer\";\n            await this.peerConn!.setRemoteDescription(description); // SRD rolls back as needed\n            this.isSettingRemoteAnswerPending = false;\n\n            logger.debug(`Call ${this.callId} onNegotiateReceived() set remote description: ${description.type}`);\n\n            if (description.type === \"offer\") {\n                let answer: RTCSessionDescriptionInit;\n                try {\n                    this.getRidOfRTXCodecs();\n                    answer = await this.createAnswer();\n                } catch (err) {\n                    logger.debug(`Call ${this.callId} onNegotiateReceived() failed to create answer: `, err);\n                    this.terminate(CallParty.Local, CallErrorCode.CreateAnswer, true);\n                    return;\n                }\n\n                await this.peerConn!.setLocalDescription(answer);\n                logger.debug(`Call ${this.callId} onNegotiateReceived() create an answer`);\n\n                this.sendVoipEvent(EventType.CallNegotiate, {\n                    lifetime: CALL_TIMEOUT_MS,\n                    description: this.peerConn!.localDescription?.toJSON() as RTCSessionDescription,\n                    [SDPStreamMetadataKey.name]: this.getLocalSDPStreamMetadata(true),\n                });\n            }\n        } catch (err) {\n            this.isSettingRemoteAnswerPending = false;\n            logger.warn(`Call ${this.callId} onNegotiateReceived() failed to complete negotiation`, err);\n        }\n\n        const newLocalOnHold = this.isLocalOnHold();\n        if (prevLocalOnHold !== newLocalOnHold) {\n            this.emit(CallEvent.LocalHoldUnhold, newLocalOnHold, this);\n            // also this one for backwards compat\n            this.emit(CallEvent.HoldUnhold, newLocalOnHold);\n        }\n    }\n\n    private updateRemoteSDPStreamMetadata(metadata: SDPStreamMetadata): void {\n        this.remoteSDPStreamMetadata = recursivelyAssign(this.remoteSDPStreamMetadata || {}, metadata, true);\n        for (const feed of this.getRemoteFeeds()) {\n            const streamId = feed.stream.id;\n            const metadata = this.remoteSDPStreamMetadata![streamId];\n\n            feed.setAudioVideoMuted(metadata?.audio_muted, metadata?.video_muted);\n            feed.purpose = this.remoteSDPStreamMetadata![streamId]?.purpose;\n        }\n    }\n\n    public onSDPStreamMetadataChangedReceived(event: MatrixEvent): void {\n        const content = event.getContent<MCallSDPStreamMetadataChanged>();\n        const metadata = SDPStreamMetadataKey.findIn<SDPStreamMetadata>(content);\n        if (metadata) {\n            this.updateRemoteSDPStreamMetadata(metadata);\n        }\n    }\n\n    public async onAssertedIdentityReceived(event: MatrixEvent): Promise<void> {\n        const content = event.getContent<MCAllAssertedIdentity>();\n        if (!content.asserted_identity) return;\n\n        this.remoteAssertedIdentity = {\n            id: content.asserted_identity.id,\n            displayName: content.asserted_identity.display_name,\n        };\n        this.emit(CallEvent.AssertedIdentityChanged, this);\n    }\n\n    public callHasEnded(): boolean {\n        // This exists as workaround to typescript trying to be clever and erroring\n        // when putting if (this.state === CallState.Ended) return; twice in the same\n        // function, even though that function is async.\n        return this.state === CallState.Ended;\n    }\n\n    private queueGotLocalOffer(): void {\n        // Ensure only one negotiate/answer event is being processed at a time.\n        if (this.responsePromiseChain) {\n            this.responsePromiseChain = this.responsePromiseChain.then(() => this.wrappedGotLocalOffer());\n        } else {\n            this.responsePromiseChain = this.wrappedGotLocalOffer();\n        }\n    }\n\n    private async wrappedGotLocalOffer(): Promise<void> {\n        this.makingOffer = true;\n        try {\n            // XXX: in what situations do we believe gotLocalOffer actually throws? It appears\n            // to handle most of its exceptions itself and terminate the call. I'm not entirely\n            // sure it would ever throw, so I can't add a test for these lines.\n            // Also the tense is different between \"gotLocalOffer\" and \"getLocalOfferFailed\" so\n            // it's not entirely clear whether getLocalOfferFailed is just misnamed or whether\n            // they've been cross-polinated somehow at some point.\n            await this.gotLocalOffer();\n        } catch (e) {\n            this.getLocalOfferFailed(e as Error);\n            return;\n        } finally {\n            this.makingOffer = false;\n        }\n    }\n\n    private async gotLocalOffer(): Promise<void> {\n        logger.debug(`Call ${this.callId} gotLocalOffer() running`);\n\n        if (this.callHasEnded()) {\n            logger.debug(\n                `Call ${this.callId} gotLocalOffer() ignoring newly created offer because the call has ended\"`,\n            );\n            return;\n        }\n\n        let offer: RTCSessionDescriptionInit;\n        try {\n            this.getRidOfRTXCodecs();\n            offer = await this.createOffer();\n        } catch (err) {\n            logger.debug(`Call ${this.callId} gotLocalOffer() failed to create offer: `, err);\n            this.terminate(CallParty.Local, CallErrorCode.CreateOffer, true);\n            return;\n        }\n\n        try {\n            await this.peerConn!.setLocalDescription(offer);\n        } catch (err) {\n            logger.debug(`Call ${this.callId} gotLocalOffer() error setting local description!`, err);\n            this.terminate(CallParty.Local, CallErrorCode.SetLocalDescription, true);\n            return;\n        }\n\n        if (this.peerConn!.iceGatheringState === \"gathering\") {\n            // Allow a short time for initial candidates to be gathered\n            await new Promise((resolve) => {\n                setTimeout(resolve, 200);\n            });\n        }\n\n        if (this.callHasEnded()) return;\n\n        const eventType = this.state === CallState.CreateOffer ? EventType.CallInvite : EventType.CallNegotiate;\n\n        const content = {\n            lifetime: CALL_TIMEOUT_MS,\n        } as MCallInviteNegotiate;\n\n        if (eventType === EventType.CallInvite && this.invitee) {\n            content.invitee = this.invitee;\n        }\n\n        // clunky because TypeScript can't follow the types through if we use an expression as the key\n        if (this.state === CallState.CreateOffer) {\n            content.offer = this.peerConn!.localDescription?.toJSON() as RTCSessionDescription;\n        } else {\n            content.description = this.peerConn!.localDescription?.toJSON() as RTCSessionDescription;\n        }\n\n        content.capabilities = {\n            \"m.call.transferee\": this.client.supportsCallTransfer,\n            \"m.call.dtmf\": false,\n        };\n\n        content[SDPStreamMetadataKey.name] = this.getLocalSDPStreamMetadata(true);\n\n        // Get rid of any candidates waiting to be sent: they'll be included in the local\n        // description we just got and will send in the offer.\n        const discardCount = this.discardDuplicateCandidates();\n        logger.info(\n            `Call ${this.callId} gotLocalOffer() discarding ${discardCount} candidates that will be sent in offer`,\n        );\n\n        try {\n            await this.sendVoipEvent(eventType, content);\n        } catch (error) {\n            logger.error(`Call ${this.callId} gotLocalOffer() failed to send invite`, error);\n            if (error instanceof MatrixError && error.event) this.client.cancelPendingEvent(error.event);\n\n            let code = CallErrorCode.SignallingFailed;\n            let message = \"Signalling failed\";\n            if (this.state === CallState.CreateOffer) {\n                code = CallErrorCode.SendInvite;\n                message = \"Failed to send invite\";\n            }\n            if ((<Error>error).name == \"UnknownDeviceError\") {\n                code = CallErrorCode.UnknownDevices;\n                message = \"Unknown devices present in the room\";\n            }\n\n            this.emit(CallEvent.Error, new CallError(code, message, <Error>error), this);\n            this.terminate(CallParty.Local, code, false);\n\n            // no need to carry on & send the candidate queue, but we also\n            // don't want to rethrow the error\n            return;\n        }\n\n        this.sendCandidateQueue();\n        if (this.state === CallState.CreateOffer) {\n            this.inviteOrAnswerSent = true;\n            this.state = CallState.InviteSent;\n            this.inviteTimeout = setTimeout(() => {\n                this.inviteTimeout = undefined;\n                if (this.state === CallState.InviteSent) {\n                    this.hangup(CallErrorCode.InviteTimeout, false);\n                }\n            }, CALL_TIMEOUT_MS);\n        }\n    }\n\n    private getLocalOfferFailed = (err: Error): void => {\n        logger.error(`Call ${this.callId} getLocalOfferFailed() running`, err);\n\n        this.emit(\n            CallEvent.Error,\n            new CallError(CallErrorCode.LocalOfferFailed, \"Failed to get local offer!\", err),\n            this,\n        );\n        this.terminate(CallParty.Local, CallErrorCode.LocalOfferFailed, false);\n    };\n\n    private getUserMediaFailed = (err: Error): void => {\n        if (this.successor) {\n            this.successor.getUserMediaFailed(err);\n            return;\n        }\n\n        logger.warn(`Call ${this.callId} getUserMediaFailed() failed to get user media - ending call`, err);\n\n        this.emit(\n            CallEvent.Error,\n            new CallError(\n                CallErrorCode.NoUserMedia,\n                \"Couldn't start capturing media! Is your microphone set up and does this app have permission?\",\n                err,\n            ),\n            this,\n        );\n        this.terminate(CallParty.Local, CallErrorCode.NoUserMedia, false);\n    };\n\n    private placeCallFailed = (err: Error): void => {\n        if (this.successor) {\n            this.successor.placeCallFailed(err);\n            return;\n        }\n\n        logger.warn(`Call ${this.callId} placeCallWithCallFeeds() failed - ending call`, err);\n\n        this.emit(\n            CallEvent.Error,\n            new CallError(CallErrorCode.IceFailed, \"Couldn't start call! Invalid ICE server configuration.\", err),\n            this,\n        );\n        this.terminate(CallParty.Local, CallErrorCode.IceFailed, false);\n    };\n\n    private onIceConnectionStateChanged = (): void => {\n        if (this.callHasEnded()) {\n            return; // because ICE can still complete as we're ending the call\n        }\n        logger.debug(\n            `Call ${this.callId} onIceConnectionStateChanged() running (state=${this.peerConn?.iceConnectionState}, conn=${this.peerConn?.connectionState})`,\n        );\n\n        // ideally we'd consider the call to be connected when we get media but\n        // chrome doesn't implement any of the 'onstarted' events yet\n        if ([\"connected\", \"completed\"].includes(this.peerConn?.iceConnectionState ?? \"\")) {\n            clearTimeout(this.iceDisconnectedTimeout);\n            this.iceDisconnectedTimeout = undefined;\n            if (this.iceReconnectionTimeOut) {\n                clearTimeout(this.iceReconnectionTimeOut);\n            }\n            this.state = CallState.Connected;\n\n            if (!this.callLengthInterval && !this.callStartTime) {\n                this.callStartTime = Date.now();\n\n                this.callLengthInterval = setInterval(() => {\n                    this.emit(CallEvent.LengthChanged, Math.round((Date.now() - this.callStartTime!) / 1000), this);\n                }, CALL_LENGTH_INTERVAL);\n            }\n        } else if (this.peerConn?.iceConnectionState == \"failed\") {\n            this.candidatesEnded = false;\n            // Firefox for Android does not yet have support for restartIce()\n            // (the types say it's always defined though, so we have to cast\n            // to prevent typescript from warning).\n            if (this.peerConn?.restartIce as (() => void) | null) {\n                this.candidatesEnded = false;\n                logger.debug(\n                    `Call ${this.callId} onIceConnectionStateChanged() ice restart (state=${this.peerConn?.iceConnectionState})`,\n                );\n                this.peerConn!.restartIce();\n            } else {\n                logger.info(\n                    `Call ${this.callId} onIceConnectionStateChanged() hanging up call (ICE failed and no ICE restart method)`,\n                );\n                this.hangup(CallErrorCode.IceFailed, false);\n            }\n        } else if (this.peerConn?.iceConnectionState == \"disconnected\") {\n            this.candidatesEnded = false;\n            this.iceReconnectionTimeOut = setTimeout((): void => {\n                logger.info(\n                    `Call ${this.callId} onIceConnectionStateChanged() ICE restarting because of ICE disconnected, (state=${this.peerConn?.iceConnectionState}, conn=${this.peerConn?.connectionState})`,\n                );\n                if (this.peerConn?.restartIce as (() => void) | null) {\n                    this.candidatesEnded = false;\n                    this.peerConn!.restartIce();\n                }\n                this.iceReconnectionTimeOut = undefined;\n            }, ICE_RECONNECTING_TIMEOUT);\n\n            this.iceDisconnectedTimeout = setTimeout((): void => {\n                logger.info(\n                    `Call ${this.callId} onIceConnectionStateChanged() hanging up call (ICE disconnected for too long)`,\n                );\n                this.hangup(CallErrorCode.IceFailed, false);\n            }, ICE_DISCONNECTED_TIMEOUT);\n            this.state = CallState.Connecting;\n        }\n\n        // In PTT mode, override feed status to muted when we lose connection to\n        // the peer, since we don't want to block the line if they're not saying anything.\n        // Experimenting in Chrome, this happens after 5 or 6 seconds, which is probably\n        // fast enough.\n        if (this.isPtt && [\"failed\", \"disconnected\"].includes(this.peerConn!.iceConnectionState)) {\n            for (const feed of this.getRemoteFeeds()) {\n                feed.setAudioVideoMuted(true, true);\n            }\n        }\n    };\n\n    private onSignallingStateChanged = (): void => {\n        logger.debug(`Call ${this.callId} onSignallingStateChanged() running (state=${this.peerConn?.signalingState})`);\n    };\n\n    private onTrack = (ev: RTCTrackEvent): void => {\n        if (ev.streams.length === 0) {\n            logger.warn(\n                `Call ${this.callId} onTrack() called with streamless track streamless (kind=${ev.track.kind})`,\n            );\n            return;\n        }\n\n        const stream = ev.streams[0];\n        this.pushRemoteFeed(stream);\n\n        if (!this.removeTrackListeners.has(stream)) {\n            const onRemoveTrack = (): void => {\n                if (stream.getTracks().length === 0) {\n                    logger.info(`Call ${this.callId} onTrack() removing track (streamId=${stream.id})`);\n                    this.deleteFeedByStream(stream);\n                    stream.removeEventListener(\"removetrack\", onRemoveTrack);\n                    this.removeTrackListeners.delete(stream);\n                }\n            };\n            stream.addEventListener(\"removetrack\", onRemoveTrack);\n            this.removeTrackListeners.set(stream, onRemoveTrack);\n        }\n    };\n\n    private onDataChannel = (ev: RTCDataChannelEvent): void => {\n        this.emit(CallEvent.DataChannel, ev.channel, this);\n    };\n\n    /**\n     * This method removes all video/rtx codecs from screensharing video\n     * transceivers. This is necessary since they can cause problems. Without\n     * this the following steps should produce an error:\n     *   Chromium calls Firefox\n     *   Firefox answers\n     *   Firefox starts screen-sharing\n     *   Chromium starts screen-sharing\n     *   Call crashes for Chromium with:\n     *       [96685:23:0518/162603.933321:ERROR:webrtc_video_engine.cc(3296)] RTX codec (PT=97) mapped to PT=96 which is not in the codec list.\n     *       [96685:23:0518/162603.933377:ERROR:webrtc_video_engine.cc(1171)] GetChangedRecvParameters called without any video codecs.\n     *       [96685:23:0518/162603.933430:ERROR:sdp_offer_answer.cc(4302)] Failed to set local video description recv parameters for m-section with mid='2'. (INVALID_PARAMETER)\n     */\n    private getRidOfRTXCodecs(): void {\n        // RTCRtpReceiver.getCapabilities and RTCRtpSender.getCapabilities don't seem to be supported on FF before v113\n        if (!RTCRtpReceiver.getCapabilities || !RTCRtpSender.getCapabilities) return;\n\n        const screenshareVideoTransceiver = this.transceivers.get(\n            getTransceiverKey(SDPStreamMetadataPurpose.Screenshare, \"video\"),\n        );\n\n        // setCodecPreferences isn't supported on FF (as of v113)\n        if (!screenshareVideoTransceiver || !screenshareVideoTransceiver.setCodecPreferences) return;\n\n        const recvCodecs = RTCRtpReceiver.getCapabilities(\"video\")!.codecs;\n        const sendCodecs = RTCRtpSender.getCapabilities(\"video\")!.codecs;\n        const codecs = [];\n\n        for (const codec of [...recvCodecs, ...sendCodecs]) {\n            if (codec.mimeType !== \"video/rtx\") {\n                codecs.push(codec);\n                try {\n                    screenshareVideoTransceiver.setCodecPreferences(codecs);\n                } catch (e) {\n                    // Specifically, Chrome around version 125 and Electron 30 (which is Chromium 124) return an H.264 codec in\n                    // the sender's capabilities but throw when you try to set it. Hence... this mess.\n                    // Specifically, that codec is:\n                    // {\n                    //   clockRate: 90000,\n                    //   mimeType: \"video/H264\",\n                    //   sdpFmtpLine: \"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640034\",\n                    // }\n                    logger.info(\n                        \"Working around buggy WebRTC impl: claimed to support codec but threw when setting codec preferences\",\n                        codec,\n                        e,\n                    );\n                    codecs.pop();\n                }\n            }\n        }\n    }\n\n    private onNegotiationNeeded = async (): Promise<void> => {\n        logger.info(`Call ${this.callId} onNegotiationNeeded() negotiation is needed!`);\n\n        if (this.state !== CallState.CreateOffer && this.opponentVersion === 0) {\n            logger.info(\n                `Call ${this.callId} onNegotiationNeeded() opponent does not support renegotiation: ignoring negotiationneeded event`,\n            );\n            return;\n        }\n\n        this.queueGotLocalOffer();\n    };\n\n    public onHangupReceived = (msg: MCallHangupReject): void => {\n        logger.debug(`Call ${this.callId} onHangupReceived() running`);\n\n        // party ID must match (our chosen partner hanging up the call) or be undefined (we haven't chosen\n        // a partner yet but we're treating the hangup as a reject as per VoIP v0)\n        if (this.partyIdMatches(msg) || this.state === CallState.Ringing) {\n            // default reason is user_hangup\n            this.terminate(CallParty.Remote, msg.reason || CallErrorCode.UserHangup, true);\n        } else {\n            logger.info(\n                `Call ${this.callId} onHangupReceived() ignoring message from party ID ${msg.party_id}: our partner is ${this.opponentPartyId}`,\n            );\n        }\n    };\n\n    public onRejectReceived = (msg: MCallHangupReject): void => {\n        logger.debug(`Call ${this.callId} onRejectReceived() running`);\n\n        // No need to check party_id for reject because if we'd received either\n        // an answer or reject, we wouldn't be in state InviteSent\n\n        const shouldTerminate =\n            // reject events also end the call if it's ringing: it's another of\n            // our devices rejecting the call.\n            [CallState.InviteSent, CallState.Ringing].includes(this.state) ||\n            // also if we're in the init state and it's an inbound call, since\n            // this means we just haven't entered the ringing state yet\n            (this.state === CallState.Fledgling && this.direction === CallDirection.Inbound);\n\n        if (shouldTerminate) {\n            this.terminate(CallParty.Remote, msg.reason || CallErrorCode.UserHangup, true);\n        } else {\n            logger.debug(`Call ${this.callId} onRejectReceived() called in wrong state (state=${this.state})`);\n        }\n    };\n\n    public onAnsweredElsewhere = (msg: MCallAnswer): void => {\n        logger.debug(`Call ${this.callId} onAnsweredElsewhere() running`);\n        this.terminate(CallParty.Remote, CallErrorCode.AnsweredElsewhere, true);\n    };\n\n    /**\n     * @internal\n     */\n    private async sendVoipEvent<K extends keyof Pick<TimelineEvents, CallEventType>>(\n        eventType: K,\n        content: Omit<TimelineEvents[K], \"version\" | \"call_id\" | \"party_id\" | \"conf_id\">,\n    ): Promise<void> {\n        const realContent = {\n            ...content,\n            version: VOIP_PROTO_VERSION,\n            call_id: this.callId,\n            party_id: this.ourPartyId,\n            conf_id: this.groupCallId,\n        } as TimelineEvents[K];\n\n        if (this.opponentDeviceId) {\n            const toDeviceSeq = this.toDeviceSeq++;\n            const content = {\n                ...realContent,\n                device_id: this.client.deviceId,\n                sender_session_id: this.client.getSessionId(),\n                dest_session_id: this.opponentSessionId,\n                seq: toDeviceSeq,\n                [ToDeviceMessageId]: globalThis.crypto.randomUUID(),\n            };\n\n            this.emit(\n                CallEvent.SendVoipEvent,\n                {\n                    type: \"toDevice\",\n                    eventType,\n                    userId: this.invitee || this.getOpponentMember()?.userId,\n                    opponentDeviceId: this.opponentDeviceId,\n                    content,\n                },\n                this,\n            );\n\n            const userId = this.invitee || this.getOpponentMember()!.userId;\n            if (this.client.getUseE2eForGroupCall()) {\n                if (!this.hasOpponentDeviceInfo) {\n                    logger.warn(`Call ${this.callId} sendVoipEvent() failed: we do not have opponentDeviceInfo`);\n                    return;\n                }\n\n                // TODO: Here we were sending the event to the opponent's device as a to-device message with MatrixClient.encryptAndSendToDevice.\n                // However due to the switch to Rust cryptography we need to migrate to the new encryptToDeviceMessages API.\n                throw new Error(\"Unimplemented\");\n            } else {\n                await this.client.sendToDevice(\n                    eventType,\n                    new Map<string, any>([[userId, new Map([[this.opponentDeviceId, content]])]]),\n                );\n            }\n        } else {\n            this.emit(\n                CallEvent.SendVoipEvent,\n                {\n                    type: \"sendEvent\",\n                    eventType,\n                    roomId: this.roomId,\n                    content: realContent,\n                    userId: this.invitee || this.getOpponentMember()?.userId,\n                },\n                this,\n            );\n\n            await this.client.sendEvent(this.roomId!, eventType, realContent);\n        }\n    }\n\n    /**\n     * Queue a candidate to be sent\n     * @param content - The candidate to queue up, or null if candidates have finished being generated\n     *                and end-of-candidates should be signalled\n     */\n    private queueCandidate(content: RTCIceCandidate | null): void {\n        // We partially de-trickle candidates by waiting for `delay` before sending them\n        // amalgamated, in order to avoid sending too many m.call.candidates events and hitting\n        // rate limits in Matrix.\n        // In practice, it'd be better to remove rate limits for m.call.*\n\n        // N.B. this deliberately lets you queue and send blank candidates, which MSC2746\n        // currently proposes as the way to indicate that candidate gathering is complete.\n        // This will hopefully be changed to an explicit rather than implicit notification\n        // shortly.\n        if (content) {\n            this.candidateSendQueue.push(content);\n        } else {\n            this.candidatesEnded = true;\n        }\n\n        // Don't send the ICE candidates yet if the call is in the ringing state: this\n        // means we tried to pick (ie. started generating candidates) and then failed to\n        // send the answer and went back to the ringing state. Queue up the candidates\n        // to send if we successfully send the answer.\n        // Equally don't send if we haven't yet sent the answer because we can send the\n        // first batch of candidates along with the answer\n        if (this.state === CallState.Ringing || !this.inviteOrAnswerSent) return;\n\n        // MSC2746 recommends these values (can be quite long when calling because the\n        // callee will need a while to answer the call)\n        const delay = this.direction === CallDirection.Inbound ? 500 : 2000;\n\n        if (this.candidateSendTries === 0) {\n            setTimeout(() => {\n                this.sendCandidateQueue();\n            }, delay);\n        }\n    }\n\n    // Discard all non-end-of-candidates messages\n    // Return the number of candidate messages that were discarded.\n    // Call this method before sending an invite or answer message\n    private discardDuplicateCandidates(): number {\n        let discardCount = 0;\n        const newQueue: RTCIceCandidate[] = [];\n\n        for (let i = 0; i < this.candidateSendQueue.length; i++) {\n            const candidate = this.candidateSendQueue[i];\n            if (candidate.candidate === \"\") {\n                newQueue.push(candidate);\n            } else {\n                discardCount++;\n            }\n        }\n\n        this.candidateSendQueue = newQueue;\n\n        return discardCount;\n    }\n\n    /*\n     * Transfers this call to another user\n     */\n    public async transfer(targetUserId: string): Promise<void> {\n        // Fetch the target user's global profile info: their room avatar / displayname\n        // could be different in whatever room we share with them.\n        const profileInfo = await this.client.getProfileInfo(targetUserId);\n\n        const replacementId = genCallID();\n\n        const body = {\n            replacement_id: genCallID(),\n            target_user: {\n                id: targetUserId,\n                display_name: profileInfo.displayname,\n                avatar_url: profileInfo.avatar_url,\n            },\n            create_call: replacementId,\n        } as MCallReplacesEvent;\n\n        await this.sendVoipEvent(EventType.CallReplaces, body);\n\n        await this.terminate(CallParty.Local, CallErrorCode.Transferred, true);\n    }\n\n    /*\n     * Transfers this call to the target call, effectively 'joining' the\n     * two calls (so the remote parties on each call are connected together).\n     */\n    public async transferToCall(transferTargetCall: MatrixCall): Promise<void> {\n        const targetUserId = transferTargetCall.getOpponentMember()?.userId;\n        const targetProfileInfo = targetUserId ? await this.client.getProfileInfo(targetUserId) : undefined;\n        const opponentUserId = this.getOpponentMember()?.userId;\n        const transfereeProfileInfo = opponentUserId ? await this.client.getProfileInfo(opponentUserId) : undefined;\n\n        const newCallId = genCallID();\n\n        const bodyToTransferTarget = {\n            // the replacements on each side have their own ID, and it's distinct from the\n            // ID of the new call (but we can use the same function to generate it)\n            replacement_id: genCallID(),\n            target_user: {\n                id: opponentUserId,\n                display_name: transfereeProfileInfo?.displayname,\n                avatar_url: transfereeProfileInfo?.avatar_url,\n            },\n            await_call: newCallId,\n        } as MCallReplacesEvent;\n\n        await transferTargetCall.sendVoipEvent(EventType.CallReplaces, bodyToTransferTarget);\n\n        const bodyToTransferee = {\n            replacement_id: genCallID(),\n            target_user: {\n                id: targetUserId,\n                display_name: targetProfileInfo?.displayname,\n                avatar_url: targetProfileInfo?.avatar_url,\n            },\n            create_call: newCallId,\n        } as MCallReplacesEvent;\n\n        await this.sendVoipEvent(EventType.CallReplaces, bodyToTransferee);\n\n        await this.terminate(CallParty.Local, CallErrorCode.Transferred, true);\n        await transferTargetCall.terminate(CallParty.Local, CallErrorCode.Transferred, true);\n    }\n\n    private async terminate(hangupParty: CallParty, hangupReason: CallErrorCode, shouldEmit: boolean): Promise<void> {\n        if (this.callHasEnded()) return;\n\n        this.hangupParty = hangupParty;\n        this.hangupReason = hangupReason;\n        this.state = CallState.Ended;\n\n        if (this.inviteTimeout) {\n            clearTimeout(this.inviteTimeout);\n            this.inviteTimeout = undefined;\n        }\n        if (this.iceDisconnectedTimeout !== undefined) {\n            clearTimeout(this.iceDisconnectedTimeout);\n            this.iceDisconnectedTimeout = undefined;\n        }\n        if (this.callLengthInterval) {\n            clearInterval(this.callLengthInterval);\n            this.callLengthInterval = undefined;\n        }\n        if (this.stopVideoTrackTimer !== undefined) {\n            clearTimeout(this.stopVideoTrackTimer);\n            this.stopVideoTrackTimer = undefined;\n        }\n\n        for (const [stream, listener] of this.removeTrackListeners) {\n            stream.removeEventListener(\"removetrack\", listener);\n        }\n        this.removeTrackListeners.clear();\n\n        this.callStatsAtEnd = await this.collectCallStats();\n\n        // Order is important here: first we stopAllMedia() and only then we can deleteAllFeeds()\n        this.stopAllMedia();\n        this.deleteAllFeeds();\n\n        if (this.peerConn && this.peerConn.signalingState !== \"closed\") {\n            this.peerConn.close();\n        }\n        this.stats?.removeStatsReportGatherer(this.callId);\n\n        if (shouldEmit) {\n            this.emit(CallEvent.Hangup, this);\n        }\n\n        this.client.callEventHandler!.calls.delete(this.callId);\n    }\n\n    private stopAllMedia(): void {\n        logger.debug(`Call ${this.callId} stopAllMedia() running`);\n\n        for (const feed of this.feeds) {\n            // Slightly awkward as local feed need to go via the correct method on\n            // the MediaHandler so they get removed from MediaHandler (remote tracks\n            // don't)\n            // NB. We clone local streams when passing them to individual calls in a group\n            // call, so we can (and should) stop the clones once we no longer need them:\n            // the other clones will continue fine.\n            if (feed.isLocal() && feed.purpose === SDPStreamMetadataPurpose.Usermedia) {\n                this.client.getMediaHandler().stopUserMediaStream(feed.stream);\n            } else if (feed.isLocal() && feed.purpose === SDPStreamMetadataPurpose.Screenshare) {\n                this.client.getMediaHandler().stopScreensharingStream(feed.stream);\n            } else if (!feed.isLocal()) {\n                logger.debug(`Call ${this.callId} stopAllMedia() stopping stream (streamId=${feed.stream.id})`);\n                for (const track of feed.stream.getTracks()) {\n                    track.stop();\n                }\n            }\n        }\n    }\n\n    private checkForErrorListener(): void {\n        if (this.listeners(EventEmitterEvents.Error).length === 0) {\n            throw new Error(\"You MUST attach an error listener using call.on('error', function() {})\");\n        }\n    }\n\n    private async sendCandidateQueue(): Promise<void> {\n        if (this.candidateSendQueue.length === 0 || this.callHasEnded()) {\n            return;\n        }\n\n        const candidates = this.candidateSendQueue;\n        this.candidateSendQueue = [];\n        ++this.candidateSendTries;\n        const content: Pick<MCallCandidates, \"candidates\"> = {\n            candidates: candidates.map((candidate) => candidate.toJSON()),\n        };\n        if (this.candidatesEnded) {\n            // If there are no more candidates, signal this by adding an empty string candidate\n            content.candidates.push({\n                candidate: \"\",\n            });\n        }\n        logger.debug(`Call ${this.callId} sendCandidateQueue() attempting to send ${candidates.length} candidates`);\n        try {\n            await this.sendVoipEvent(EventType.CallCandidates, content);\n            // reset our retry count if we have successfully sent our candidates\n            // otherwise queueCandidate() will refuse to try to flush the queue\n            this.candidateSendTries = 0;\n\n            // Try to send candidates again just in case we received more candidates while sending.\n            this.sendCandidateQueue();\n        } catch (error) {\n            // don't retry this event: we'll send another one later as we might\n            // have more candidates by then.\n            if (error instanceof MatrixError && error.event) this.client.cancelPendingEvent(error.event);\n\n            // put all the candidates we failed to send back in the queue\n            this.candidateSendQueue.push(...candidates);\n\n            if (this.candidateSendTries > 5) {\n                logger.debug(\n                    `Call ${this.callId} sendCandidateQueue() failed to send candidates on attempt ${this.candidateSendTries}. Giving up on this call.`,\n                    error,\n                );\n\n                const code = CallErrorCode.SignallingFailed;\n                const message = \"Signalling failed\";\n\n                this.emit(CallEvent.Error, new CallError(code, message, <Error>error), this);\n                this.hangup(code, false);\n\n                return;\n            }\n\n            const delayMs = 500 * Math.pow(2, this.candidateSendTries);\n            ++this.candidateSendTries;\n            logger.debug(\n                `Call ${this.callId} sendCandidateQueue() failed to send candidates. Retrying in ${delayMs}ms`,\n                error,\n            );\n            setTimeout(() => {\n                this.sendCandidateQueue();\n            }, delayMs);\n        }\n    }\n\n    /**\n     * Place a call to this room.\n     * @throws if you have not specified a listener for 'error' events.\n     * @throws if have passed audio=false.\n     */\n    public async placeCall(audio: boolean, video: boolean): Promise<void> {\n        if (!audio) {\n            throw new Error(\"You CANNOT start a call without audio\");\n        }\n        this.state = CallState.WaitLocalMedia;\n\n        let callFeed: CallFeed;\n        try {\n            const stream = await this.client.getMediaHandler().getUserMediaStream(audio, video);\n\n            // make sure all the tracks are enabled (same as pushNewLocalFeed -\n            // we probably ought to just have one code path for adding streams)\n            setTracksEnabled(stream.getAudioTracks(), true);\n            setTracksEnabled(stream.getVideoTracks(), true);\n\n            callFeed = new CallFeed({\n                client: this.client,\n                roomId: this.roomId,\n                userId: this.client.getUserId()!,\n                deviceId: this.client.getDeviceId() ?? undefined,\n                stream,\n                purpose: SDPStreamMetadataPurpose.Usermedia,\n                audioMuted: false,\n                videoMuted: false,\n            });\n        } catch (e) {\n            this.getUserMediaFailed(<Error>e);\n            return;\n        }\n\n        try {\n            await this.placeCallWithCallFeeds([callFeed]);\n        } catch (e) {\n            this.placeCallFailed(<Error>e);\n            return;\n        }\n    }\n\n    /**\n     * Place a call to this room with call feed.\n     * @param callFeeds - to use\n     * @throws if you have not specified a listener for 'error' events.\n     * @throws if have passed audio=false.\n     */\n    public async placeCallWithCallFeeds(callFeeds: CallFeed[], requestScreenshareFeed = false): Promise<void> {\n        this.checkForErrorListener();\n        this.direction = CallDirection.Outbound;\n\n        await this.initOpponentCrypto();\n\n        // XXX Find a better way to do this\n        this.client.callEventHandler!.calls.set(this.callId, this);\n\n        // make sure we have valid turn creds. Unless something's gone wrong, it should\n        // poll and keep the credentials valid so this should be instant.\n        const haveTurnCreds = await this.client.checkTurnServers();\n        if (!haveTurnCreds) {\n            logger.warn(\n                `Call ${this.callId} placeCallWithCallFeeds() failed to get TURN credentials! Proceeding with call anyway...`,\n            );\n        }\n\n        // create the peer connection now so it can be gathering candidates while we get user\n        // media (assuming a candidate pool size is configured)\n        this.peerConn = this.createPeerConnection();\n        this.emit(CallEvent.PeerConnectionCreated, this.peerConn, this);\n        this.gotCallFeedsForInvite(callFeeds, requestScreenshareFeed);\n    }\n\n    private createPeerConnection(): RTCPeerConnection {\n        const pc = new window.RTCPeerConnection({\n            iceTransportPolicy: this.forceTURN ? \"relay\" : undefined,\n            iceServers: this.turnServers.length ? this.turnServers : undefined,\n            iceCandidatePoolSize: this.client.iceCandidatePoolSize,\n            bundlePolicy: \"max-bundle\",\n        });\n\n        // 'connectionstatechange' would be better, but firefox doesn't implement that.\n        pc.addEventListener(\"iceconnectionstatechange\", this.onIceConnectionStateChanged);\n        pc.addEventListener(\"signalingstatechange\", this.onSignallingStateChanged);\n        pc.addEventListener(\"icecandidate\", this.gotLocalIceCandidate);\n        pc.addEventListener(\"icegatheringstatechange\", this.onIceGatheringStateChange);\n        pc.addEventListener(\"track\", this.onTrack);\n        pc.addEventListener(\"negotiationneeded\", this.onNegotiationNeeded);\n        pc.addEventListener(\"datachannel\", this.onDataChannel);\n\n        const opponentMember: RoomMember | undefined = this.getOpponentMember();\n        const opponentMemberId = opponentMember ? opponentMember.userId : \"unknown\";\n        this.stats?.addStatsReportGatherer(this.callId, opponentMemberId, pc);\n        return pc;\n    }\n\n    private partyIdMatches(msg: MCallBase): boolean {\n        // They must either match or both be absent (in which case opponentPartyId will be null)\n        // Also we ignore party IDs on the invite/offer if the version is 0, so we must do the same\n        // here and use null if the version is 0 (woe betide any opponent sending messages in the\n        // same call with different versions)\n        const msgPartyId = msg.version === 0 ? null : msg.party_id || null;\n        return msgPartyId === this.opponentPartyId;\n    }\n\n    // Commits to an opponent for the call\n    // ev: An invite or answer event\n    private chooseOpponent(ev: MatrixEvent): void {\n        // I choo-choo-choose you\n        const msg = ev.getContent<MCallInviteNegotiate | MCallAnswer>();\n\n        logger.debug(`Call ${this.callId} chooseOpponent() running (partyId=${msg.party_id})`);\n\n        this.opponentVersion = msg.version;\n        if (this.opponentVersion === 0) {\n            // set to null to indicate that we've chosen an opponent, but because\n            // they're v0 they have no party ID (even if they sent one, we're ignoring it)\n            this.opponentPartyId = null;\n        } else {\n            // set to their party ID, or if they're naughty and didn't send one despite\n            // not being v0, set it to null to indicate we picked an opponent with no\n            // party ID\n            this.opponentPartyId = msg.party_id || null;\n        }\n        this.opponentCaps = msg.capabilities || ({} as CallCapabilities);\n        this.opponentMember = this.client.getRoom(this.roomId)!.getMember(ev.getSender()!) ?? undefined;\n        if (this.opponentMember) {\n            this.stats?.updateOpponentMember(this.callId, this.opponentMember.userId);\n        }\n    }\n\n    private async addBufferedIceCandidates(): Promise<void> {\n        const bufferedCandidates = this.remoteCandidateBuffer.get(this.opponentPartyId!);\n        if (bufferedCandidates) {\n            logger.info(\n                `Call ${this.callId} addBufferedIceCandidates() adding ${bufferedCandidates.length} buffered candidates for opponent ${this.opponentPartyId}`,\n            );\n            await this.addIceCandidates(bufferedCandidates);\n        }\n        this.remoteCandidateBuffer.clear();\n    }\n\n    private async addIceCandidates(candidates: RTCIceCandidate[] | MCallCandidates[\"candidates\"]): Promise<void> {\n        for (const candidate of candidates) {\n            if (\n                (candidate.sdpMid === null || candidate.sdpMid === undefined) &&\n                (candidate.sdpMLineIndex === null || candidate.sdpMLineIndex === undefined)\n            ) {\n                logger.debug(`Call ${this.callId} addIceCandidates() got remote ICE end-of-candidates`);\n            } else {\n                logger.debug(\n                    `Call ${this.callId} addIceCandidates() got remote ICE candidate (sdpMid=${candidate.sdpMid}, candidate=${candidate.candidate})`,\n                );\n            }\n\n            try {\n                await this.peerConn!.addIceCandidate(candidate);\n            } catch (err) {\n                if (!this.ignoreOffer) {\n                    logger.info(`Call ${this.callId} addIceCandidates() failed to add remote ICE candidate`, err);\n                } else {\n                    logger.debug(\n                        `Call ${this.callId} addIceCandidates() failed to add remote ICE candidate because ignoring offer`,\n                        err,\n                    );\n                }\n            }\n        }\n    }\n\n    public get hasPeerConnection(): boolean {\n        return Boolean(this.peerConn);\n    }\n\n    public initStats(stats: GroupCallStats, peerId = \"unknown\"): void {\n        this.stats = stats;\n        this.stats.start();\n    }\n}\n\nexport function setTracksEnabled(tracks: Array<MediaStreamTrack>, enabled: boolean): void {\n    for (const track of tracks) {\n        track.enabled = enabled;\n    }\n}\n\nexport function supportsMatrixCall(): boolean {\n    // typeof prevents Node from erroring on an undefined reference\n    if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n        // NB. We don't log here as apps try to create a call object as a test for\n        // whether calls are supported, so we shouldn't fill the logs up.\n        return false;\n    }\n\n    // Firefox throws on so little as accessing the RTCPeerConnection when operating in a secure mode.\n    // There's some information at https://bugzilla.mozilla.org/show_bug.cgi?id=1542616 though the concern\n    // is that the browser throwing a SecurityError will brick the client creation process.\n    try {\n        const supported = Boolean(\n            window.RTCPeerConnection ??\n            window.RTCSessionDescription ??\n            window.RTCIceCandidate ??\n            navigator.mediaDevices,\n        );\n        if (!supported) {\n            /* istanbul ignore if */ // Adds a lot of noise to test runs, so disable logging there.\n            if (process.env.NODE_ENV !== \"test\") {\n                logger.error(\"WebRTC is not supported in this browser / environment\");\n            }\n            return false;\n        }\n    } catch (e) {\n        logger.error(\"Exception thrown when trying to access WebRTC\", e);\n        return false;\n    }\n\n    return true;\n}\n\n/**\n * DEPRECATED\n * Use client.createCall()\n *\n * Create a new Matrix call for the browser.\n * @param client - The client instance to use.\n * @param roomId - The room the call is in.\n * @param options - DEPRECATED optional options map.\n * @returns the call or null if the browser doesn't support calling.\n */\nexport function createNewMatrixCall(\n    client: MatrixClient,\n    roomId: string,\n    options?: Pick<CallOpts, \"forceTURN\" | \"invitee\" | \"opponentDeviceId\" | \"opponentSessionId\" | \"groupCallId\">,\n): MatrixCall | null {\n    if (!supportsMatrixCall()) return null;\n\n    const optionsForceTURN = options ? options.forceTURN : false;\n\n    const opts: CallOpts = {\n        client: client,\n        roomId: roomId,\n        invitee: options?.invitee,\n        turnServers: client.getTurnServers(),\n        // call level options\n        forceTURN: client.forceTURN || optionsForceTURN,\n        opponentDeviceId: options?.opponentDeviceId,\n        opponentSessionId: options?.opponentSessionId,\n        groupCallId: options?.groupCallId,\n    };\n    const call = new MatrixCall(opts);\n\n    client.reEmitter.reEmit(call, Object.values(CallEvent));\n\n    return call;\n}\n","/*\nCopyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { SDPStreamMetadataPurpose } from \"./callEventTypes.ts\";\nimport { acquireContext, releaseContext } from \"./audioContext.ts\";\nimport { type MatrixClient } from \"../client.ts\";\nimport { type RoomMember } from \"../models/room-member.ts\";\nimport { logger } from \"../logger.ts\";\nimport { TypedEventEmitter } from \"../models/typed-event-emitter.ts\";\nimport { CallEvent, CallState, type MatrixCall } from \"./call.ts\";\n\nconst POLLING_INTERVAL = 200; // ms\nexport const SPEAKING_THRESHOLD = -60; // dB\nconst SPEAKING_SAMPLE_COUNT = 8; // samples\n\nexport interface ICallFeedOpts {\n    client: MatrixClient;\n    roomId?: string;\n    userId: string;\n    deviceId: string | undefined;\n    stream: MediaStream;\n    purpose: SDPStreamMetadataPurpose;\n    /**\n     * Whether or not the remote SDPStreamMetadata says audio is muted\n     */\n    audioMuted: boolean;\n    /**\n     * Whether or not the remote SDPStreamMetadata says video is muted\n     */\n    videoMuted: boolean;\n    /**\n     * The MatrixCall which is the source of this CallFeed\n     */\n    call?: MatrixCall;\n}\n\nexport enum CallFeedEvent {\n    NewStream = \"new_stream\",\n    MuteStateChanged = \"mute_state_changed\",\n    LocalVolumeChanged = \"local_volume_changed\",\n    VolumeChanged = \"volume_changed\",\n    ConnectedChanged = \"connected_changed\",\n    Speaking = \"speaking\",\n    Disposed = \"disposed\",\n}\n\ntype EventHandlerMap = {\n    [CallFeedEvent.NewStream]: (stream: MediaStream) => void;\n    [CallFeedEvent.MuteStateChanged]: (audioMuted: boolean, videoMuted: boolean) => void;\n    [CallFeedEvent.LocalVolumeChanged]: (localVolume: number) => void;\n    [CallFeedEvent.VolumeChanged]: (volume: number) => void;\n    [CallFeedEvent.ConnectedChanged]: (connected: boolean) => void;\n    [CallFeedEvent.Speaking]: (speaking: boolean) => void;\n    [CallFeedEvent.Disposed]: () => void;\n};\n\nexport class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap> {\n    public stream: MediaStream;\n    public sdpMetadataStreamId: string;\n    public userId: string;\n    public readonly deviceId: string | undefined;\n    public purpose: SDPStreamMetadataPurpose;\n    public speakingVolumeSamples: number[];\n\n    private client: MatrixClient;\n    private call?: MatrixCall;\n    private roomId?: string;\n    private audioMuted: boolean;\n    private videoMuted: boolean;\n    private localVolume = 1;\n    private measuringVolumeActivity = false;\n    private audioContext?: AudioContext;\n    private analyser?: AnalyserNode;\n    private frequencyBinCount?: Float32Array<ArrayBuffer>;\n    private speakingThreshold = SPEAKING_THRESHOLD;\n    private speaking = false;\n    private volumeLooperTimeout?: ReturnType<typeof setTimeout>;\n    private _disposed = false;\n    private _connected = false;\n\n    public constructor(opts: ICallFeedOpts) {\n        super();\n\n        this.client = opts.client;\n        this.call = opts.call;\n        this.roomId = opts.roomId;\n        this.userId = opts.userId;\n        this.deviceId = opts.deviceId;\n        this.purpose = opts.purpose;\n        this.audioMuted = opts.audioMuted;\n        this.videoMuted = opts.videoMuted;\n        this.speakingVolumeSamples = new Array(SPEAKING_SAMPLE_COUNT).fill(-Infinity);\n        this.sdpMetadataStreamId = opts.stream.id;\n\n        this.updateStream(null, opts.stream);\n        this.stream = opts.stream; // updateStream does this, but this makes TS happier\n\n        if (this.hasAudioTrack) {\n            this.initVolumeMeasuring();\n        }\n\n        if (opts.call) {\n            opts.call.addListener(CallEvent.State, this.onCallState);\n            this.onCallState(opts.call.state);\n        }\n    }\n\n    public get connected(): boolean {\n        // Local feeds are always considered connected\n        return this.isLocal() || this._connected;\n    }\n\n    private set connected(connected: boolean) {\n        this._connected = connected;\n        this.emit(CallFeedEvent.ConnectedChanged, this.connected);\n    }\n\n    public get hasAudioTrack(): boolean {\n        return this.stream.getAudioTracks().length > 0;\n    }\n\n    private updateStream(oldStream: MediaStream | null, newStream: MediaStream): void {\n        if (newStream === oldStream) return;\n\n        const wasMeasuringVolumeActivity = this.measuringVolumeActivity;\n\n        if (oldStream) {\n            oldStream.removeEventListener(\"addtrack\", this.onAddTrack);\n            this.measureVolumeActivity(false);\n        }\n\n        this.stream = newStream;\n        newStream.addEventListener(\"addtrack\", this.onAddTrack);\n\n        if (this.hasAudioTrack) {\n            this.initVolumeMeasuring();\n            if (wasMeasuringVolumeActivity) this.measureVolumeActivity(true);\n        } else {\n            this.measureVolumeActivity(false);\n        }\n\n        this.emit(CallFeedEvent.NewStream, this.stream);\n    }\n\n    private initVolumeMeasuring(): void {\n        if (!this.hasAudioTrack) return;\n        if (!this.audioContext) this.audioContext = acquireContext();\n\n        this.analyser = this.audioContext.createAnalyser();\n        this.analyser.fftSize = 512;\n        this.analyser.smoothingTimeConstant = 0.1;\n\n        const mediaStreamAudioSourceNode = this.audioContext.createMediaStreamSource(this.stream);\n        mediaStreamAudioSourceNode.connect(this.analyser);\n\n        this.frequencyBinCount = new Float32Array(this.analyser.frequencyBinCount);\n    }\n\n    private onAddTrack = (): void => {\n        this.emit(CallFeedEvent.NewStream, this.stream);\n    };\n\n    private onCallState = (state: CallState): void => {\n        if (state === CallState.Connected) {\n            this.connected = true;\n        } else if (state === CallState.Connecting) {\n            this.connected = false;\n        }\n    };\n\n    /**\n     * Returns callRoom member\n     * @returns member of the callRoom\n     */\n    public getMember(): RoomMember | null {\n        const callRoom = this.client.getRoom(this.roomId);\n        return callRoom?.getMember(this.userId) ?? null;\n    }\n\n    /**\n     * Returns true if CallFeed is local, otherwise returns false\n     * @returns is local?\n     */\n    public isLocal(): boolean {\n        return (\n            this.userId === this.client.getUserId() &&\n            (this.deviceId === undefined || this.deviceId === this.client.getDeviceId())\n        );\n    }\n\n    /**\n     * Returns true if audio is muted or if there are no audio\n     * tracks, otherwise returns false\n     * @returns is audio muted?\n     */\n    public isAudioMuted(): boolean {\n        return this.stream.getAudioTracks().length === 0 || this.audioMuted;\n    }\n\n    /**\n     * Returns true video is muted or if there are no video\n     * tracks, otherwise returns false\n     * @returns is video muted?\n     */\n    public isVideoMuted(): boolean {\n        // We assume only one video track\n        return this.stream.getVideoTracks().length === 0 || this.videoMuted;\n    }\n\n    public isSpeaking(): boolean {\n        return this.speaking;\n    }\n\n    /**\n     * Replaces the current MediaStream with a new one.\n     * The stream will be different and new stream as remote parties are\n     * concerned, but this can be used for convenience locally to set up\n     * volume listeners automatically on the new stream etc.\n     * @param newStream - new stream with which to replace the current one\n     */\n    public setNewStream(newStream: MediaStream): void {\n        this.updateStream(this.stream, newStream);\n    }\n\n    /**\n     * Set one or both of feed's internal audio and video video mute state\n     * Either value may be null to leave it as-is\n     * @param audioMuted - is the feed's audio muted?\n     * @param videoMuted - is the feed's video muted?\n     */\n    public setAudioVideoMuted(audioMuted: boolean | null, videoMuted: boolean | null): void {\n        if (audioMuted !== null) {\n            if (this.audioMuted !== audioMuted) {\n                this.speakingVolumeSamples.fill(-Infinity);\n            }\n            this.audioMuted = audioMuted;\n        }\n        if (videoMuted !== null) this.videoMuted = videoMuted;\n        this.emit(CallFeedEvent.MuteStateChanged, this.audioMuted, this.videoMuted);\n    }\n\n    /**\n     * Starts emitting volume_changed events where the emitter value is in decibels\n     * @param enabled - emit volume changes\n     */\n    public measureVolumeActivity(enabled: boolean): void {\n        if (enabled) {\n            if (!this.analyser || !this.frequencyBinCount || !this.hasAudioTrack) return;\n\n            this.measuringVolumeActivity = true;\n            this.volumeLooper();\n        } else {\n            this.measuringVolumeActivity = false;\n            this.speakingVolumeSamples.fill(-Infinity);\n            this.emit(CallFeedEvent.VolumeChanged, -Infinity);\n        }\n    }\n\n    public setSpeakingThreshold(threshold: number): void {\n        this.speakingThreshold = threshold;\n    }\n\n    private volumeLooper = (): void => {\n        if (!this.analyser) return;\n\n        if (!this.measuringVolumeActivity) return;\n\n        this.analyser.getFloatFrequencyData(this.frequencyBinCount!);\n\n        let maxVolume = -Infinity;\n        for (const volume of this.frequencyBinCount!) {\n            if (volume > maxVolume) {\n                maxVolume = volume;\n            }\n        }\n\n        this.speakingVolumeSamples.shift();\n        this.speakingVolumeSamples.push(maxVolume);\n\n        this.emit(CallFeedEvent.VolumeChanged, maxVolume);\n\n        let newSpeaking = false;\n\n        for (const volume of this.speakingVolumeSamples) {\n            if (volume > this.speakingThreshold) {\n                newSpeaking = true;\n                break;\n            }\n        }\n\n        if (this.speaking !== newSpeaking) {\n            this.speaking = newSpeaking;\n            this.emit(CallFeedEvent.Speaking, this.speaking);\n        }\n\n        this.volumeLooperTimeout = setTimeout(this.volumeLooper, POLLING_INTERVAL);\n    };\n\n    public clone(): CallFeed {\n        const mediaHandler = this.client.getMediaHandler();\n        const stream = this.stream.clone();\n        logger.log(`CallFeed clone() cloning stream (originalStreamId=${this.stream.id}, newStreamId${stream.id})`);\n\n        if (this.purpose === SDPStreamMetadataPurpose.Usermedia) {\n            mediaHandler.userMediaStreams.push(stream);\n        } else {\n            mediaHandler.screensharingStreams.push(stream);\n        }\n\n        return new CallFeed({\n            client: this.client,\n            roomId: this.roomId,\n            userId: this.userId,\n            deviceId: this.deviceId,\n            stream,\n            purpose: this.purpose,\n            audioMuted: this.audioMuted,\n            videoMuted: this.videoMuted,\n        });\n    }\n\n    public dispose(): void {\n        clearTimeout(this.volumeLooperTimeout);\n        this.stream?.removeEventListener(\"addtrack\", this.onAddTrack);\n        this.call?.removeListener(CallEvent.State, this.onCallState);\n        if (this.audioContext) {\n            this.audioContext = undefined;\n            this.analyser = undefined;\n            releaseContext();\n        }\n        this._disposed = true;\n        this.emit(CallFeedEvent.Disposed);\n    }\n\n    public get disposed(): boolean {\n        return this._disposed;\n    }\n\n    private set disposed(value: boolean) {\n        this._disposed = value;\n    }\n\n    public getLocalVolume(): number {\n        return this.localVolume;\n    }\n\n    public setLocalVolume(localVolume: number): void {\n        this.localVolume = localVolume;\n        this.emit(CallFeedEvent.LocalVolumeChanged, localVolume);\n    }\n}\n","import { TypedEventEmitter } from \"../models/typed-event-emitter.ts\";\nimport { CallFeed, SPEAKING_THRESHOLD } from \"./callFeed.ts\";\nimport { type MatrixClient, type IMyDevice } from \"../client.ts\";\nimport {\n    CallErrorCode,\n    CallEvent,\n    type CallEventHandlerMap,\n    CallState,\n    genCallID,\n    type MatrixCall,\n    setTracksEnabled,\n    createNewMatrixCall,\n    CallError,\n} from \"./call.ts\";\nimport { type RoomMember } from \"../models/room-member.ts\";\nimport { type Room } from \"../models/room.ts\";\nimport { RoomStateEvent } from \"../models/room-state.ts\";\nimport { logger } from \"../logger.ts\";\nimport { ReEmitter } from \"../ReEmitter.ts\";\nimport { SDPStreamMetadataPurpose } from \"./callEventTypes.ts\";\nimport { type MatrixEvent } from \"../models/event.ts\";\nimport { EventType } from \"../@types/event.ts\";\nimport { CallEventHandlerEvent } from \"./callEventHandler.ts\";\nimport { GroupCallEventHandlerEvent } from \"./groupCallEventHandler.ts\";\nimport { type IScreensharingOpts } from \"./mediaHandler.ts\";\nimport { mapsEqual } from \"../utils.ts\";\nimport { GroupCallStats } from \"./stats/groupCallStats.ts\";\nimport {\n    type ByteSentStatsReport,\n    type CallFeedReport,\n    type ConnectionStatsReport,\n    StatsReport,\n    type SummaryStatsReport,\n} from \"./stats/statsReport.ts\";\nimport { SummaryStatsReportGatherer } from \"./stats/summaryStatsReportGatherer.ts\";\nimport { CallFeedStatsReporter } from \"./stats/callFeedStatsReporter.ts\";\nimport { KnownMembership } from \"../@types/membership.ts\";\n\nexport enum GroupCallIntent {\n    Ring = \"m.ring\",\n    Prompt = \"m.prompt\",\n    Room = \"m.room\",\n}\n\nexport enum GroupCallType {\n    Video = \"m.video\",\n    Voice = \"m.voice\",\n}\n\nexport enum GroupCallTerminationReason {\n    CallEnded = \"call_ended\",\n}\n\nexport type CallsByUserAndDevice = Map<string, Map<string, MatrixCall>>;\n\n/**\n * Because event names are just strings, they do need\n * to be unique over all event types of event emitter.\n * Some objects could emit more then one set of events.\n */\nexport enum GroupCallEvent {\n    GroupCallStateChanged = \"group_call_state_changed\",\n    ActiveSpeakerChanged = \"active_speaker_changed\",\n    CallsChanged = \"calls_changed\",\n    UserMediaFeedsChanged = \"user_media_feeds_changed\",\n    ScreenshareFeedsChanged = \"screenshare_feeds_changed\",\n    LocalScreenshareStateChanged = \"local_screenshare_state_changed\",\n    LocalMuteStateChanged = \"local_mute_state_changed\",\n    ParticipantsChanged = \"participants_changed\",\n    Error = \"group_call_error\",\n}\n\nexport type GroupCallEventHandlerMap = {\n    [GroupCallEvent.GroupCallStateChanged]: (newState: GroupCallState, oldState: GroupCallState) => void;\n    [GroupCallEvent.ActiveSpeakerChanged]: (activeSpeaker: CallFeed | undefined) => void;\n    [GroupCallEvent.CallsChanged]: (calls: CallsByUserAndDevice) => void;\n    [GroupCallEvent.UserMediaFeedsChanged]: (feeds: CallFeed[]) => void;\n    [GroupCallEvent.ScreenshareFeedsChanged]: (feeds: CallFeed[]) => void;\n    [GroupCallEvent.LocalScreenshareStateChanged]: (\n        isScreensharing: boolean,\n        feed?: CallFeed,\n        sourceId?: string,\n    ) => void;\n    [GroupCallEvent.LocalMuteStateChanged]: (audioMuted: boolean, videoMuted: boolean) => void;\n    [GroupCallEvent.ParticipantsChanged]: (participants: Map<RoomMember, Map<string, ParticipantState>>) => void;\n    /**\n     * Fires whenever an error occurs when call.js encounters an issue with setting up the call.\n     * <p>\n     * The error given will have a code equal to either `MatrixCall.ERR_LOCAL_OFFER_FAILED` or\n     * `MatrixCall.ERR_NO_USER_MEDIA`. `ERR_LOCAL_OFFER_FAILED` is emitted when the local client\n     * fails to create an offer. `ERR_NO_USER_MEDIA` is emitted when the user has denied access\n     * to their audio/video hardware.\n     * @param error - The error raised by MatrixCall.\n     * @example\n     * ```\n     * matrixCall.on(\"error\", function(err){\n     *   console.error(err.code, err);\n     * });\n     * ```\n     */\n    [GroupCallEvent.Error]: (error: GroupCallError) => void;\n};\n\nexport enum GroupCallStatsReportEvent {\n    ConnectionStats = \"GroupCall.connection_stats\",\n    ByteSentStats = \"GroupCall.byte_sent_stats\",\n    SummaryStats = \"GroupCall.summary_stats\",\n    CallFeedStats = \"GroupCall.call_feed_stats\",\n}\n\n/**\n * The final report-events that get consumed by client.\n */\nexport type GroupCallStatsReportEventHandlerMap = {\n    [GroupCallStatsReportEvent.ConnectionStats]: (report: GroupCallStatsReport<ConnectionStatsReport>) => void;\n    [GroupCallStatsReportEvent.ByteSentStats]: (report: GroupCallStatsReport<ByteSentStatsReport>) => void;\n    [GroupCallStatsReportEvent.SummaryStats]: (report: GroupCallStatsReport<SummaryStatsReport>) => void;\n    [GroupCallStatsReportEvent.CallFeedStats]: (report: GroupCallStatsReport<CallFeedReport>) => void;\n};\n\nexport enum GroupCallErrorCode {\n    NoUserMedia = \"no_user_media\",\n    UnknownDevice = \"unknown_device\",\n    PlaceCallFailed = \"place_call_failed\",\n}\n\nexport interface GroupCallStatsReport<\n    T extends ConnectionStatsReport | ByteSentStatsReport | SummaryStatsReport | CallFeedReport,\n> {\n    report: T;\n}\n\nexport class GroupCallError extends Error {\n    public code: string;\n\n    public constructor(code: GroupCallErrorCode, msg: string, err?: Error) {\n        // Still don't think there's any way to have proper nested errors\n        if (err) {\n            super(msg + \": \" + err);\n        } else {\n            super(msg);\n        }\n\n        this.code = code;\n    }\n}\n\nexport class GroupCallUnknownDeviceError extends GroupCallError {\n    public constructor(public userId: string) {\n        super(GroupCallErrorCode.UnknownDevice, \"No device found for \" + userId);\n    }\n}\n\nexport class OtherUserSpeakingError extends Error {\n    public constructor() {\n        super(\"Cannot unmute: another user is speaking\");\n    }\n}\n\nexport interface IGroupCallDataChannelOptions {\n    ordered: boolean;\n    maxPacketLifeTime: number;\n    maxRetransmits: number;\n    protocol: string;\n}\n\nexport interface IGroupCallRoomState {\n    \"m.intent\": GroupCallIntent;\n    \"m.type\": GroupCallType;\n    \"m.terminated\"?: GroupCallTerminationReason;\n    \"io.element.ptt\"?: boolean;\n    // TODO: Specify data-channels\n    \"dataChannelsEnabled\"?: boolean;\n    \"dataChannelOptions\"?: IGroupCallDataChannelOptions;\n\n    \"io.element.livekit_service_url\"?: string;\n}\n\nexport interface IGroupCallRoomMemberFeed {\n    purpose: SDPStreamMetadataPurpose;\n}\n\nexport interface IGroupCallRoomMemberDevice {\n    device_id: string;\n    session_id: string;\n    expires_ts: number;\n    feeds: IGroupCallRoomMemberFeed[];\n}\n\nexport interface IGroupCallRoomMemberCallState {\n    \"m.call_id\": string;\n    \"m.foci\"?: string[];\n    \"m.devices\": IGroupCallRoomMemberDevice[];\n}\n\nexport interface IGroupCallRoomMemberState {\n    \"m.calls\": IGroupCallRoomMemberCallState[];\n}\n\nexport enum GroupCallState {\n    LocalCallFeedUninitialized = \"local_call_feed_uninitialized\",\n    InitializingLocalCallFeed = \"initializing_local_call_feed\",\n    LocalCallFeedInitialized = \"local_call_feed_initialized\",\n    Entered = \"entered\",\n    Ended = \"ended\",\n}\n\nexport interface ParticipantState {\n    sessionId: string;\n    screensharing: boolean;\n}\n\ninterface ICallHandlers {\n    onCallFeedsChanged: (feeds: CallFeed[]) => void;\n    onCallStateChanged: (state: CallState, oldState: CallState | undefined) => void;\n    onCallHangup: (call: MatrixCall) => void;\n    onCallReplaced: (newCall: MatrixCall) => void;\n}\n\nconst DEVICE_TIMEOUT = 1000 * 60 * 60; // 1 hour\n\nfunction getCallUserId(call: MatrixCall): string | null {\n    return call.getOpponentMember()?.userId || call.invitee || null;\n}\n\nexport class GroupCall extends TypedEventEmitter<\n    GroupCallEvent | CallEvent | GroupCallStatsReportEvent,\n    GroupCallEventHandlerMap & CallEventHandlerMap & GroupCallStatsReportEventHandlerMap\n> {\n    // Config\n    public activeSpeakerInterval = 1000;\n    public retryCallInterval = 5000;\n    public participantTimeout = 1000 * 15;\n    public pttMaxTransmitTime = 1000 * 20;\n\n    public activeSpeaker?: CallFeed;\n    public localCallFeed?: CallFeed;\n    public localScreenshareFeed?: CallFeed;\n    public localDesktopCapturerSourceId?: string;\n    public readonly userMediaFeeds: CallFeed[] = [];\n    public readonly screenshareFeeds: CallFeed[] = [];\n    public groupCallId: string;\n    public readonly allowCallWithoutVideoAndAudio: boolean;\n\n    private readonly calls = new Map<string, Map<string, MatrixCall>>(); // user_id -> device_id -> MatrixCall\n    private callHandlers = new Map<string, Map<string, ICallHandlers>>(); // user_id -> device_id -> ICallHandlers\n    private activeSpeakerLoopInterval?: ReturnType<typeof setTimeout>;\n    private retryCallLoopInterval?: ReturnType<typeof setTimeout>;\n    private retryCallCounts: Map<string, Map<string, number>> = new Map(); // user_id -> device_id -> count\n    private reEmitter: ReEmitter;\n    private transmitTimer: ReturnType<typeof setTimeout> | null = null;\n    private participantsExpirationTimer: ReturnType<typeof setTimeout> | null = null;\n    private resendMemberStateTimer: ReturnType<typeof setInterval> | null = null;\n    private initWithAudioMuted = false;\n    private initWithVideoMuted = false;\n    private initCallFeedPromise?: Promise<void>;\n    private _livekitServiceURL?: string;\n\n    private stats: GroupCallStats | undefined;\n    /**\n     * Configure default webrtc stats collection interval in ms\n     * Disable collecting webrtc stats by setting interval to 0\n     */\n    private statsCollectIntervalTime = 0;\n\n    public constructor(\n        private client: MatrixClient,\n        public room: Room,\n        public type: GroupCallType,\n        public isPtt: boolean,\n        public intent: GroupCallIntent,\n        groupCallId?: string,\n        private dataChannelsEnabled?: boolean,\n        private dataChannelOptions?: IGroupCallDataChannelOptions,\n        isCallWithoutVideoAndAudio?: boolean,\n        // this tells the js-sdk not to actually establish any calls to exchange media and just to\n        // create the group call signaling events, with the intention that the actual media will be\n        // handled using livekit. The js-sdk doesn't contain any code to do the actual livekit call though.\n        private useLivekit = false,\n        livekitServiceURL?: string,\n    ) {\n        super();\n        this.reEmitter = new ReEmitter(this);\n        this.groupCallId = groupCallId ?? genCallID();\n        this._livekitServiceURL = livekitServiceURL;\n        this.creationTs =\n            room.currentState.getStateEvents(EventType.GroupCallPrefix, this.groupCallId)?.getTs() ?? null;\n        this.updateParticipants();\n\n        room.on(RoomStateEvent.Update, this.onRoomState);\n        this.on(GroupCallEvent.ParticipantsChanged, this.onParticipantsChanged);\n        this.on(GroupCallEvent.GroupCallStateChanged, this.onStateChanged);\n        this.on(GroupCallEvent.LocalScreenshareStateChanged, this.onLocalFeedsChanged);\n        this.allowCallWithoutVideoAndAudio = !!isCallWithoutVideoAndAudio;\n    }\n\n    private onConnectionStats = (report: ConnectionStatsReport): void => {\n        // Final emit of the summary event, to be consumed by the client\n        this.emit(GroupCallStatsReportEvent.ConnectionStats, { report });\n    };\n\n    private onByteSentStats = (report: ByteSentStatsReport): void => {\n        // Final emit of the summary event, to be consumed by the client\n        this.emit(GroupCallStatsReportEvent.ByteSentStats, { report });\n    };\n\n    private onSummaryStats = (report: SummaryStatsReport): void => {\n        SummaryStatsReportGatherer.extendSummaryReport(report, this.participants);\n        // Final emit of the summary event, to be consumed by the client\n        this.emit(GroupCallStatsReportEvent.SummaryStats, { report });\n    };\n\n    private onCallFeedReport = (report: CallFeedReport): void => {\n        if (this.localCallFeed) {\n            report = CallFeedStatsReporter.expandCallFeedReport(report, [this.localCallFeed], \"from-local-feed\");\n        }\n\n        const callFeeds: CallFeed[] = [];\n        this.forEachCall((call) => {\n            if (call.callId === report.callId) {\n                call.getFeeds().forEach((f) => callFeeds.push(f));\n            }\n        });\n\n        report = CallFeedStatsReporter.expandCallFeedReport(report, callFeeds, \"from-call-feed\");\n        this.emit(GroupCallStatsReportEvent.CallFeedStats, { report });\n    };\n\n    public async create(): Promise<GroupCall> {\n        this.creationTs = Date.now();\n        this.client.groupCallEventHandler!.groupCalls.set(this.room.roomId, this);\n        this.client.emit(GroupCallEventHandlerEvent.Outgoing, this);\n\n        await this.sendCallStateEvent();\n\n        return this;\n    }\n\n    private async sendCallStateEvent(): Promise<void> {\n        const groupCallState: IGroupCallRoomState = {\n            \"m.intent\": this.intent,\n            \"m.type\": this.type,\n            \"io.element.ptt\": this.isPtt,\n            // TODO: Specify data-channels better\n            \"dataChannelsEnabled\": this.dataChannelsEnabled,\n            \"dataChannelOptions\": this.dataChannelsEnabled ? this.dataChannelOptions : undefined,\n        };\n        if (this.livekitServiceURL) {\n            groupCallState[\"io.element.livekit_service_url\"] = this.livekitServiceURL;\n        }\n\n        await this.client.sendStateEvent(this.room.roomId, EventType.GroupCallPrefix, groupCallState, this.groupCallId);\n    }\n\n    public get livekitServiceURL(): string | undefined {\n        return this._livekitServiceURL;\n    }\n\n    public updateLivekitServiceURL(newURL: string): Promise<void> {\n        this._livekitServiceURL = newURL;\n        return this.sendCallStateEvent();\n    }\n\n    private _state = GroupCallState.LocalCallFeedUninitialized;\n\n    /**\n     * The group call's state.\n     */\n    public get state(): GroupCallState {\n        return this._state;\n    }\n\n    private set state(value: GroupCallState) {\n        const prevValue = this._state;\n        if (value !== prevValue) {\n            this._state = value;\n            this.emit(GroupCallEvent.GroupCallStateChanged, value, prevValue);\n        }\n    }\n\n    private _participants = new Map<RoomMember, Map<string, ParticipantState>>();\n\n    /**\n     * The current participants in the call, as a map from members to device IDs\n     * to participant info.\n     */\n    public get participants(): Map<RoomMember, Map<string, ParticipantState>> {\n        return this._participants;\n    }\n\n    private set participants(value: Map<RoomMember, Map<string, ParticipantState>>) {\n        const prevValue = this._participants;\n        const participantStateEqual = (x: ParticipantState, y: ParticipantState): boolean =>\n            x.sessionId === y.sessionId && x.screensharing === y.screensharing;\n        const deviceMapsEqual = (x: Map<string, ParticipantState>, y: Map<string, ParticipantState>): boolean =>\n            mapsEqual(x, y, participantStateEqual);\n\n        // Only update if the map actually changed\n        if (!mapsEqual(value, prevValue, deviceMapsEqual)) {\n            this._participants = value;\n            this.emit(GroupCallEvent.ParticipantsChanged, value);\n        }\n    }\n\n    private _creationTs: number | null = null;\n\n    /**\n     * The timestamp at which the call was created, or null if it has not yet\n     * been created.\n     */\n    public get creationTs(): number | null {\n        return this._creationTs;\n    }\n\n    private set creationTs(value: number | null) {\n        this._creationTs = value;\n    }\n\n    private _enteredViaAnotherSession = false;\n\n    /**\n     * Whether the local device has entered this call via another session, such\n     * as a widget.\n     */\n    public get enteredViaAnotherSession(): boolean {\n        return this._enteredViaAnotherSession;\n    }\n\n    public set enteredViaAnotherSession(value: boolean) {\n        this._enteredViaAnotherSession = value;\n        this.updateParticipants();\n    }\n\n    /**\n     * Executes the given callback on all calls in this group call.\n     * @param f - The callback.\n     */\n    public forEachCall(f: (call: MatrixCall) => void): void {\n        for (const deviceMap of this.calls.values()) {\n            for (const call of deviceMap.values()) f(call);\n        }\n    }\n\n    public getLocalFeeds(): CallFeed[] {\n        const feeds: CallFeed[] = [];\n\n        if (this.localCallFeed) feeds.push(this.localCallFeed);\n        if (this.localScreenshareFeed) feeds.push(this.localScreenshareFeed);\n\n        return feeds;\n    }\n\n    public hasLocalParticipant(): boolean {\n        return (\n            this.participants.get(this.room.getMember(this.client.getUserId()!)!)?.has(this.client.getDeviceId()!) ??\n            false\n        );\n    }\n\n    /**\n     * Determines whether the given call is one that we were expecting to exist\n     * given our knowledge of who is participating in the group call.\n     */\n    private callExpected(call: MatrixCall): boolean {\n        const userId = getCallUserId(call);\n        const member = userId === null ? null : this.room.getMember(userId);\n        const deviceId = call.getOpponentDeviceId();\n        return member !== null && deviceId !== undefined && this.participants.get(member)?.get(deviceId) !== undefined;\n    }\n\n    public async initLocalCallFeed(): Promise<void> {\n        if (this.useLivekit) {\n            logger.info(\"Livekit group call: not starting local call feed.\");\n            return;\n        }\n\n        if (this.state !== GroupCallState.LocalCallFeedUninitialized) {\n            throw new Error(`Cannot initialize local call feed in the \"${this.state}\" state.`);\n        }\n        this.state = GroupCallState.InitializingLocalCallFeed;\n\n        // wraps the real method to serialise calls, because we don't want to try starting\n        // multiple call feeds at once\n        if (this.initCallFeedPromise) return this.initCallFeedPromise;\n\n        try {\n            this.initCallFeedPromise = this.initLocalCallFeedInternal();\n            await this.initCallFeedPromise;\n        } finally {\n            this.initCallFeedPromise = undefined;\n        }\n    }\n\n    private async initLocalCallFeedInternal(): Promise<void> {\n        logger.log(`GroupCall ${this.groupCallId} initLocalCallFeedInternal() running`);\n\n        let stream: MediaStream;\n\n        try {\n            stream = await this.client.getMediaHandler().getUserMediaStream(true, this.type === GroupCallType.Video);\n        } catch (error) {\n            // If is allowed to join a call without a media stream, then we\n            // don't throw an error here. But we need an empty Local Feed to establish\n            // a connection later.\n            if (this.allowCallWithoutVideoAndAudio) {\n                stream = new MediaStream();\n            } else {\n                this.state = GroupCallState.LocalCallFeedUninitialized;\n                throw error;\n            }\n        }\n\n        // The call could've been disposed while we were waiting, and could\n        // also have been started back up again (hello, React 18) so if we're\n        // still in this 'initializing' state, carry on, otherwise bail.\n        if (this._state !== GroupCallState.InitializingLocalCallFeed) {\n            this.client.getMediaHandler().stopUserMediaStream(stream);\n            throw new Error(\"Group call disposed while gathering media stream\");\n        }\n\n        const callFeed = new CallFeed({\n            client: this.client,\n            roomId: this.room.roomId,\n            userId: this.client.getUserId()!,\n            deviceId: this.client.getDeviceId()!,\n            stream,\n            purpose: SDPStreamMetadataPurpose.Usermedia,\n            audioMuted: this.initWithAudioMuted || stream.getAudioTracks().length === 0 || this.isPtt,\n            videoMuted: this.initWithVideoMuted || stream.getVideoTracks().length === 0,\n        });\n\n        setTracksEnabled(stream.getAudioTracks(), !callFeed.isAudioMuted());\n        setTracksEnabled(stream.getVideoTracks(), !callFeed.isVideoMuted());\n\n        this.localCallFeed = callFeed;\n        this.addUserMediaFeed(callFeed);\n\n        this.state = GroupCallState.LocalCallFeedInitialized;\n    }\n\n    public async updateLocalUsermediaStream(stream: MediaStream): Promise<void> {\n        if (this.localCallFeed) {\n            const oldStream = this.localCallFeed.stream;\n            this.localCallFeed.setNewStream(stream);\n            const micShouldBeMuted = this.localCallFeed.isAudioMuted();\n            const vidShouldBeMuted = this.localCallFeed.isVideoMuted();\n            logger.log(\n                `GroupCall ${this.groupCallId} updateLocalUsermediaStream() (oldStreamId=${oldStream.id}, newStreamId=${stream.id}, micShouldBeMuted=${micShouldBeMuted}, vidShouldBeMuted=${vidShouldBeMuted})`,\n            );\n            setTracksEnabled(stream.getAudioTracks(), !micShouldBeMuted);\n            setTracksEnabled(stream.getVideoTracks(), !vidShouldBeMuted);\n            this.client.getMediaHandler().stopUserMediaStream(oldStream);\n        }\n    }\n\n    public async enter(): Promise<void> {\n        if (this.state === GroupCallState.LocalCallFeedUninitialized) {\n            await this.initLocalCallFeed();\n        } else if (this.state !== GroupCallState.LocalCallFeedInitialized) {\n            throw new Error(`Cannot enter call in the \"${this.state}\" state`);\n        }\n\n        logger.log(`GroupCall ${this.groupCallId} enter() running`);\n        this.state = GroupCallState.Entered;\n\n        this.client.on(CallEventHandlerEvent.Incoming, this.onIncomingCall);\n\n        for (const call of this.client.callEventHandler!.calls.values()) {\n            this.onIncomingCall(call);\n        }\n\n        if (!this.useLivekit) {\n            this.retryCallLoopInterval = setInterval(this.onRetryCallLoop, this.retryCallInterval);\n\n            this.activeSpeaker = undefined;\n            this.onActiveSpeakerLoop();\n            this.activeSpeakerLoopInterval = setInterval(this.onActiveSpeakerLoop, this.activeSpeakerInterval);\n        }\n    }\n\n    private dispose(): void {\n        if (this.localCallFeed) {\n            this.removeUserMediaFeed(this.localCallFeed);\n            this.localCallFeed = undefined;\n        }\n\n        if (this.localScreenshareFeed) {\n            this.client.getMediaHandler().stopScreensharingStream(this.localScreenshareFeed.stream);\n            this.removeScreenshareFeed(this.localScreenshareFeed);\n            this.localScreenshareFeed = undefined;\n            this.localDesktopCapturerSourceId = undefined;\n        }\n\n        this.client.getMediaHandler().stopAllStreams();\n\n        if (this.transmitTimer !== null) {\n            clearTimeout(this.transmitTimer);\n            this.transmitTimer = null;\n        }\n\n        if (this.retryCallLoopInterval !== undefined) {\n            clearInterval(this.retryCallLoopInterval);\n            this.retryCallLoopInterval = undefined;\n        }\n\n        if (this.participantsExpirationTimer !== null) {\n            clearTimeout(this.participantsExpirationTimer);\n            this.participantsExpirationTimer = null;\n        }\n\n        if (this.state !== GroupCallState.Entered) {\n            return;\n        }\n\n        this.forEachCall((call) => call.hangup(CallErrorCode.UserHangup, false));\n\n        this.activeSpeaker = undefined;\n        clearInterval(this.activeSpeakerLoopInterval);\n\n        this.retryCallCounts.clear();\n        clearInterval(this.retryCallLoopInterval);\n\n        this.client.removeListener(CallEventHandlerEvent.Incoming, this.onIncomingCall);\n        this.stats?.stop();\n    }\n\n    public leave(): void {\n        this.dispose();\n        this.state = GroupCallState.LocalCallFeedUninitialized;\n    }\n\n    public async terminate(emitStateEvent = true): Promise<void> {\n        this.dispose();\n\n        this.room.off(RoomStateEvent.Update, this.onRoomState);\n        this.client.groupCallEventHandler!.groupCalls.delete(this.room.roomId);\n        this.client.emit(GroupCallEventHandlerEvent.Ended, this);\n        this.state = GroupCallState.Ended;\n\n        if (emitStateEvent) {\n            const existingStateEvent = this.room.currentState.getStateEvents(\n                EventType.GroupCallPrefix,\n                this.groupCallId,\n            )!;\n\n            await this.client.sendStateEvent(\n                this.room.roomId,\n                EventType.GroupCallPrefix,\n                {\n                    ...existingStateEvent.getContent(),\n                    \"m.terminated\": GroupCallTerminationReason.CallEnded,\n                },\n                this.groupCallId,\n            );\n        }\n    }\n\n    /*\n     * Local Usermedia\n     */\n\n    public isLocalVideoMuted(): boolean {\n        if (this.localCallFeed) {\n            return this.localCallFeed.isVideoMuted();\n        }\n\n        return true;\n    }\n\n    public isMicrophoneMuted(): boolean {\n        if (this.localCallFeed) {\n            return this.localCallFeed.isAudioMuted();\n        }\n\n        return true;\n    }\n\n    /**\n     * Sets the mute state of the local participants's microphone.\n     * @param muted - Whether to mute the microphone\n     * @returns Whether muting/unmuting was successful\n     */\n    public async setMicrophoneMuted(muted: boolean): Promise<boolean> {\n        // hasAudioDevice can block indefinitely if the window has lost focus,\n        // and it doesn't make much sense to keep a device from being muted, so\n        // we always allow muted = true changes to go through\n        if (!muted && !(await this.client.getMediaHandler().hasAudioDevice())) {\n            return false;\n        }\n\n        const sendUpdatesBefore = !muted && this.isPtt;\n\n        // set a timer for the maximum transmit time on PTT calls\n        if (this.isPtt) {\n            // Set or clear the max transmit timer\n            if (!muted && this.isMicrophoneMuted()) {\n                this.transmitTimer = setTimeout(() => {\n                    this.setMicrophoneMuted(true);\n                }, this.pttMaxTransmitTime);\n            } else if (muted && !this.isMicrophoneMuted()) {\n                if (this.transmitTimer !== null) clearTimeout(this.transmitTimer);\n                this.transmitTimer = null;\n            }\n        }\n\n        this.forEachCall((call) => call.localUsermediaFeed?.setAudioVideoMuted(muted, null));\n\n        const sendUpdates = async (): Promise<void> => {\n            const updates: Promise<void>[] = [];\n            this.forEachCall((call) => updates.push(call.sendMetadataUpdate()));\n\n            await Promise.all(updates).catch((e) =>\n                logger.info(\n                    `GroupCall ${this.groupCallId} setMicrophoneMuted() failed to send some metadata updates`,\n                    e,\n                ),\n            );\n        };\n\n        if (sendUpdatesBefore) await sendUpdates();\n\n        if (this.localCallFeed) {\n            logger.log(\n                `GroupCall ${this.groupCallId} setMicrophoneMuted() (streamId=${this.localCallFeed.stream.id}, muted=${muted})`,\n            );\n\n            const hasPermission = await this.checkAudioPermissionIfNecessary(muted);\n\n            if (!hasPermission) {\n                return false;\n            }\n\n            this.localCallFeed.setAudioVideoMuted(muted, null);\n            // I don't believe its actually necessary to enable these tracks: they\n            // are the one on the GroupCall's own CallFeed and are cloned before being\n            // given to any of the actual calls, so these tracks don't actually go\n            // anywhere. Let's do it anyway to avoid confusion.\n            setTracksEnabled(this.localCallFeed.stream.getAudioTracks(), !muted);\n        } else {\n            logger.log(`GroupCall ${this.groupCallId} setMicrophoneMuted() no stream muted (muted=${muted})`);\n            this.initWithAudioMuted = muted;\n        }\n\n        this.forEachCall((call) =>\n            setTracksEnabled(call.localUsermediaFeed!.stream.getAudioTracks(), !muted && this.callExpected(call)),\n        );\n        this.emit(GroupCallEvent.LocalMuteStateChanged, muted, this.isLocalVideoMuted());\n\n        if (!sendUpdatesBefore) await sendUpdates();\n\n        return true;\n    }\n\n    /**\n     * If we allow entering a call without a camera and without video, it can happen that the access rights to the\n     * devices have not yet been queried. If a stream does not yet have an audio track, we assume that the rights have\n     * not yet been checked.\n     *\n     * `this.client.getMediaHandler().getUserMediaStream` clones the current stream, so it only wanted to be called when\n     * not Audio Track exists.\n     * As such, this is a compromise, because, the access rights should always be queried before the call.\n     */\n    private async checkAudioPermissionIfNecessary(muted: boolean): Promise<boolean> {\n        // We needed this here to avoid an error in case user join a call without a device.\n        try {\n            if (!muted && this.localCallFeed && !this.localCallFeed.hasAudioTrack) {\n                const stream = await this.client\n                    .getMediaHandler()\n                    .getUserMediaStream(true, !this.localCallFeed.isVideoMuted());\n                if (stream?.getTracks().length === 0) {\n                    // if case permission denied to get a stream stop this here\n                    /* istanbul ignore next */\n                    logger.log(\n                        `GroupCall ${this.groupCallId} setMicrophoneMuted() no device to receive local stream, muted=${muted}`,\n                    );\n                    return false;\n                }\n            }\n        } catch {\n            /* istanbul ignore next */\n            logger.log(\n                `GroupCall ${this.groupCallId} setMicrophoneMuted() no device or permission to receive local stream, muted=${muted}`,\n            );\n            return false;\n        }\n\n        return true;\n    }\n\n    /**\n     * Sets the mute state of the local participants's video.\n     * @param muted - Whether to mute the video\n     * @returns Whether muting/unmuting was successful\n     */\n    public async setLocalVideoMuted(muted: boolean): Promise<boolean> {\n        // hasAudioDevice can block indefinitely if the window has lost focus,\n        // and it doesn't make much sense to keep a device from being muted, so\n        // we always allow muted = true changes to go through\n        if (!muted && !(await this.client.getMediaHandler().hasVideoDevice())) {\n            return false;\n        }\n\n        if (this.localCallFeed) {\n            /* istanbul ignore next */\n            logger.log(\n                `GroupCall ${this.groupCallId} setLocalVideoMuted() (stream=${this.localCallFeed.stream.id}, muted=${muted})`,\n            );\n\n            try {\n                const stream = await this.client.getMediaHandler().getUserMediaStream(true, !muted);\n                await this.updateLocalUsermediaStream(stream);\n                this.localCallFeed.setAudioVideoMuted(null, muted);\n                setTracksEnabled(this.localCallFeed.stream.getVideoTracks(), !muted);\n            } catch {\n                // No permission to video device\n                /* istanbul ignore next */\n                logger.log(\n                    `GroupCall ${this.groupCallId} setLocalVideoMuted() no device or permission to receive local stream, muted=${muted}`,\n                );\n                return false;\n            }\n        } else {\n            logger.log(`GroupCall ${this.groupCallId} setLocalVideoMuted() no stream muted (muted=${muted})`);\n            this.initWithVideoMuted = muted;\n        }\n\n        const updates: Promise<unknown>[] = [];\n        this.forEachCall((call) => updates.push(call.setLocalVideoMuted(muted)));\n        await Promise.all(updates);\n\n        // We setTracksEnabled again, independently from the call doing it\n        // internally, since we might not be expecting the call\n        this.forEachCall((call) =>\n            setTracksEnabled(call.localUsermediaFeed!.stream.getVideoTracks(), !muted && this.callExpected(call)),\n        );\n\n        this.emit(GroupCallEvent.LocalMuteStateChanged, this.isMicrophoneMuted(), muted);\n\n        return true;\n    }\n\n    public async setScreensharingEnabled(enabled: boolean, opts: IScreensharingOpts = {}): Promise<boolean> {\n        if (enabled === this.isScreensharing()) {\n            return enabled;\n        }\n\n        if (enabled) {\n            try {\n                logger.log(\n                    `GroupCall ${this.groupCallId} setScreensharingEnabled() is asking for screensharing permissions`,\n                );\n                const stream = await this.client.getMediaHandler().getScreensharingStream(opts);\n\n                for (const track of stream.getTracks()) {\n                    const onTrackEnded = (): void => {\n                        this.setScreensharingEnabled(false);\n                        track.removeEventListener(\"ended\", onTrackEnded);\n                    };\n\n                    track.addEventListener(\"ended\", onTrackEnded);\n                }\n\n                logger.log(\n                    `GroupCall ${this.groupCallId} setScreensharingEnabled() granted screensharing permissions. Setting screensharing enabled on all calls`,\n                );\n\n                this.localDesktopCapturerSourceId = opts.desktopCapturerSourceId;\n                this.localScreenshareFeed = new CallFeed({\n                    client: this.client,\n                    roomId: this.room.roomId,\n                    userId: this.client.getUserId()!,\n                    deviceId: this.client.getDeviceId()!,\n                    stream,\n                    purpose: SDPStreamMetadataPurpose.Screenshare,\n                    audioMuted: false,\n                    videoMuted: false,\n                });\n                this.addScreenshareFeed(this.localScreenshareFeed);\n\n                this.emit(\n                    GroupCallEvent.LocalScreenshareStateChanged,\n                    true,\n                    this.localScreenshareFeed,\n                    this.localDesktopCapturerSourceId,\n                );\n\n                // TODO: handle errors\n                this.forEachCall((call) => call.pushLocalFeed(this.localScreenshareFeed!.clone()));\n\n                return true;\n            } catch (error) {\n                if (opts.throwOnFail) throw error;\n                logger.error(\n                    `GroupCall ${this.groupCallId} setScreensharingEnabled() enabling screensharing error`,\n                    error,\n                );\n                this.emit(\n                    GroupCallEvent.Error,\n                    new GroupCallError(\n                        GroupCallErrorCode.NoUserMedia,\n                        \"Failed to get screen-sharing stream: \",\n                        error as Error,\n                    ),\n                );\n                return false;\n            }\n        } else {\n            this.forEachCall((call) => {\n                if (call.localScreensharingFeed) call.removeLocalFeed(call.localScreensharingFeed);\n            });\n            this.client.getMediaHandler().stopScreensharingStream(this.localScreenshareFeed!.stream);\n            this.removeScreenshareFeed(this.localScreenshareFeed!);\n            this.localScreenshareFeed = undefined;\n            this.localDesktopCapturerSourceId = undefined;\n            this.emit(GroupCallEvent.LocalScreenshareStateChanged, false, undefined, undefined);\n            return false;\n        }\n    }\n\n    public isScreensharing(): boolean {\n        return !!this.localScreenshareFeed;\n    }\n\n    /*\n     * Call Setup\n     *\n     * There are two different paths for calls to be created:\n     * 1. Incoming calls triggered by the Call.incoming event.\n     * 2. Outgoing calls to the initial members of a room or new members\n     *    as they are observed by the RoomState.members event.\n     */\n\n    private onIncomingCall = (newCall: MatrixCall): void => {\n        // The incoming calls may be for another room, which we will ignore.\n        if (newCall.roomId !== this.room.roomId) {\n            return;\n        }\n\n        if (newCall.state !== CallState.Ringing) {\n            logger.warn(\n                `GroupCall ${this.groupCallId} onIncomingCall() incoming call no longer in ringing state - ignoring`,\n            );\n            return;\n        }\n\n        if (!newCall.groupCallId || newCall.groupCallId !== this.groupCallId) {\n            logger.log(\n                `GroupCall ${this.groupCallId} onIncomingCall() ignored because it doesn't match the current group call`,\n            );\n            newCall.reject();\n            return;\n        }\n\n        const opponentUserId = newCall.getOpponentMember()?.userId;\n        if (opponentUserId === undefined) {\n            logger.warn(`GroupCall ${this.groupCallId} onIncomingCall() incoming call with no member - ignoring`);\n            return;\n        }\n\n        if (this.useLivekit) {\n            logger.info(\"Received incoming call whilst in signaling-only mode! Ignoring.\");\n            return;\n        }\n\n        const deviceMap = this.calls.get(opponentUserId) ?? new Map<string, MatrixCall>();\n        const prevCall = deviceMap.get(newCall.getOpponentDeviceId()!);\n\n        if (prevCall?.callId === newCall.callId) return;\n\n        logger.log(\n            `GroupCall ${this.groupCallId} onIncomingCall() incoming call (userId=${opponentUserId}, callId=${newCall.callId})`,\n        );\n\n        if (prevCall) prevCall.hangup(CallErrorCode.Replaced, false);\n        // We must do this before we start initialising / answering the call as we\n        // need to know it is the active call for this user+deviceId and to not ignore\n        // events from it.\n        deviceMap.set(newCall.getOpponentDeviceId()!, newCall);\n        this.calls.set(opponentUserId, deviceMap);\n\n        this.initCall(newCall);\n\n        const feeds = this.getLocalFeeds().map((feed) => feed.clone());\n        if (!this.callExpected(newCall)) {\n            // Disable our tracks for users not explicitly participating in the\n            // call but trying to receive the feeds\n            for (const feed of feeds) {\n                setTracksEnabled(feed.stream.getAudioTracks(), false);\n                setTracksEnabled(feed.stream.getVideoTracks(), false);\n            }\n        }\n        newCall.answerWithCallFeeds(feeds);\n\n        this.emit(GroupCallEvent.CallsChanged, this.calls);\n    };\n\n    /**\n     * Determines whether a given participant expects us to call them (versus\n     * them calling us).\n     * @param userId - The participant's user ID.\n     * @param deviceId - The participant's device ID.\n     * @returns Whether we need to place an outgoing call to the participant.\n     */\n    private wantsOutgoingCall(userId: string, deviceId: string): boolean {\n        const localUserId = this.client.getUserId()!;\n        const localDeviceId = this.client.getDeviceId()!;\n        return (\n            // If a user's ID is less than our own, they'll call us\n            userId >= localUserId &&\n            // If this is another one of our devices, compare device IDs to tell whether it'll call us\n            (userId !== localUserId || deviceId > localDeviceId)\n        );\n    }\n\n    /**\n     * Places calls to all participants that we're responsible for calling.\n     */\n    private placeOutgoingCalls(): void {\n        let callsChanged = false;\n\n        for (const [{ userId }, participantMap] of this.participants) {\n            const callMap = this.calls.get(userId) ?? new Map<string, MatrixCall>();\n\n            for (const [deviceId, participant] of participantMap) {\n                const prevCall = callMap.get(deviceId);\n\n                if (\n                    prevCall?.getOpponentSessionId() !== participant.sessionId &&\n                    this.wantsOutgoingCall(userId, deviceId)\n                ) {\n                    callsChanged = true;\n\n                    if (prevCall !== undefined) {\n                        logger.debug(\n                            `GroupCall ${this.groupCallId} placeOutgoingCalls() replacing call (userId=${userId}, deviceId=${deviceId}, callId=${prevCall.callId})`,\n                        );\n                        prevCall.hangup(CallErrorCode.NewSession, false);\n                    }\n\n                    const newCall = createNewMatrixCall(this.client, this.room.roomId, {\n                        invitee: userId,\n                        opponentDeviceId: deviceId,\n                        opponentSessionId: participant.sessionId,\n                        groupCallId: this.groupCallId,\n                    });\n\n                    if (newCall === null) {\n                        logger.error(\n                            `GroupCall ${this.groupCallId} placeOutgoingCalls() failed to create call (userId=${userId}, device=${deviceId})`,\n                        );\n                        callMap.delete(deviceId);\n                    } else {\n                        this.initCall(newCall);\n                        callMap.set(deviceId, newCall);\n\n                        logger.debug(\n                            `GroupCall ${this.groupCallId} placeOutgoingCalls() placing call (userId=${userId}, deviceId=${deviceId}, sessionId=${participant.sessionId})`,\n                        );\n\n                        newCall\n                            .placeCallWithCallFeeds(\n                                this.getLocalFeeds().map((feed) => feed.clone()),\n                                participant.screensharing,\n                            )\n                            .then(() => {\n                                if (this.dataChannelsEnabled) {\n                                    newCall.createDataChannel(\"datachannel\", this.dataChannelOptions);\n                                }\n                            })\n                            .catch((e) => {\n                                logger.warn(\n                                    `GroupCall ${this.groupCallId} placeOutgoingCalls() failed to place call (userId=${userId})`,\n                                    e,\n                                );\n\n                                if (e instanceof CallError && e.code === GroupCallErrorCode.UnknownDevice) {\n                                    this.emit(GroupCallEvent.Error, e);\n                                } else {\n                                    this.emit(\n                                        GroupCallEvent.Error,\n                                        new GroupCallError(\n                                            GroupCallErrorCode.PlaceCallFailed,\n                                            `Failed to place call to ${userId}`,\n                                        ),\n                                    );\n                                }\n\n                                newCall.hangup(CallErrorCode.SignallingFailed, false);\n                                if (callMap.get(deviceId) === newCall) callMap.delete(deviceId);\n                            });\n                    }\n                }\n            }\n\n            if (callMap.size > 0) {\n                this.calls.set(userId, callMap);\n            } else {\n                this.calls.delete(userId);\n            }\n        }\n\n        if (callsChanged) this.emit(GroupCallEvent.CallsChanged, this.calls);\n    }\n\n    /*\n     * Room Member State\n     */\n\n    private getMemberStateEvents(): MatrixEvent[];\n    private getMemberStateEvents(userId: string): MatrixEvent | null;\n    private getMemberStateEvents(userId?: string): MatrixEvent[] | MatrixEvent | null {\n        return userId === undefined\n            ? this.room.currentState.getStateEvents(EventType.GroupCallMemberPrefix)\n            : this.room.currentState.getStateEvents(EventType.GroupCallMemberPrefix, userId);\n    }\n\n    private onRetryCallLoop = (): void => {\n        let needsRetry = false;\n\n        for (const [{ userId }, participantMap] of this.participants) {\n            const callMap = this.calls.get(userId);\n            let retriesMap = this.retryCallCounts.get(userId);\n\n            for (const [deviceId, participant] of participantMap) {\n                const call = callMap?.get(deviceId);\n                const retries = retriesMap?.get(deviceId) ?? 0;\n\n                if (\n                    call?.getOpponentSessionId() !== participant.sessionId &&\n                    this.wantsOutgoingCall(userId, deviceId) &&\n                    retries < 3\n                ) {\n                    if (retriesMap === undefined) {\n                        retriesMap = new Map();\n                        this.retryCallCounts.set(userId, retriesMap);\n                    }\n                    retriesMap.set(deviceId, retries + 1);\n                    needsRetry = true;\n                }\n            }\n        }\n\n        if (needsRetry) this.placeOutgoingCalls();\n    };\n\n    private initCall(call: MatrixCall): void {\n        const opponentMemberId = getCallUserId(call);\n\n        if (!opponentMemberId) {\n            throw new Error(\"Cannot init call without user id\");\n        }\n\n        const onCallFeedsChanged = (): void => this.onCallFeedsChanged(call);\n        const onCallStateChanged = (state: CallState, oldState?: CallState): void =>\n            this.onCallStateChanged(call, state, oldState);\n        const onCallHangup = this.onCallHangup;\n        const onCallReplaced = (newCall: MatrixCall): void => this.onCallReplaced(call, newCall);\n\n        let deviceMap = this.callHandlers.get(opponentMemberId);\n        if (deviceMap === undefined) {\n            deviceMap = new Map();\n            this.callHandlers.set(opponentMemberId, deviceMap);\n        }\n\n        deviceMap.set(call.getOpponentDeviceId()!, {\n            onCallFeedsChanged,\n            onCallStateChanged,\n            onCallHangup,\n            onCallReplaced,\n        });\n\n        call.on(CallEvent.FeedsChanged, onCallFeedsChanged);\n        call.on(CallEvent.State, onCallStateChanged);\n        call.on(CallEvent.Hangup, onCallHangup);\n        call.on(CallEvent.Replaced, onCallReplaced);\n\n        call.isPtt = this.isPtt;\n\n        this.reEmitter.reEmit(call, Object.values(CallEvent));\n\n        call.initStats(this.getGroupCallStats());\n\n        onCallFeedsChanged();\n    }\n\n    private disposeCall(call: MatrixCall, hangupReason: CallErrorCode): void {\n        const opponentMemberId = getCallUserId(call);\n        const opponentDeviceId = call.getOpponentDeviceId()!;\n\n        if (!opponentMemberId) {\n            throw new Error(\"Cannot dispose call without user id\");\n        }\n\n        const deviceMap = this.callHandlers.get(opponentMemberId)!;\n        const { onCallFeedsChanged, onCallStateChanged, onCallHangup, onCallReplaced } =\n            deviceMap.get(opponentDeviceId)!;\n\n        call.removeListener(CallEvent.FeedsChanged, onCallFeedsChanged);\n        call.removeListener(CallEvent.State, onCallStateChanged);\n        call.removeListener(CallEvent.Hangup, onCallHangup);\n        call.removeListener(CallEvent.Replaced, onCallReplaced);\n\n        deviceMap.delete(opponentMemberId);\n        if (deviceMap.size === 0) this.callHandlers.delete(opponentMemberId);\n\n        if (call.hangupReason === CallErrorCode.Replaced) {\n            return;\n        }\n\n        const usermediaFeed = this.getUserMediaFeed(opponentMemberId, opponentDeviceId);\n\n        if (usermediaFeed) {\n            this.removeUserMediaFeed(usermediaFeed);\n        }\n\n        const screenshareFeed = this.getScreenshareFeed(opponentMemberId, opponentDeviceId);\n\n        if (screenshareFeed) {\n            this.removeScreenshareFeed(screenshareFeed);\n        }\n    }\n\n    private onCallFeedsChanged = (call: MatrixCall): void => {\n        const opponentMemberId = getCallUserId(call);\n        const opponentDeviceId = call.getOpponentDeviceId()!;\n\n        if (!opponentMemberId) {\n            throw new Error(\"Cannot change call feeds without user id\");\n        }\n\n        const currentUserMediaFeed = this.getUserMediaFeed(opponentMemberId, opponentDeviceId);\n        const remoteUsermediaFeed = call.remoteUsermediaFeed;\n        const remoteFeedChanged = remoteUsermediaFeed !== currentUserMediaFeed;\n\n        const deviceMap = this.calls.get(opponentMemberId);\n        const currentCallForUserDevice = deviceMap?.get(opponentDeviceId);\n        if (currentCallForUserDevice?.callId !== call.callId) {\n            // the call in question is not the current call for this user/deviceId\n            // so ignore feed events from it otherwise we'll remove our real feeds\n            return;\n        }\n\n        if (remoteFeedChanged) {\n            if (!currentUserMediaFeed && remoteUsermediaFeed) {\n                this.addUserMediaFeed(remoteUsermediaFeed);\n            } else if (currentUserMediaFeed && remoteUsermediaFeed) {\n                this.replaceUserMediaFeed(currentUserMediaFeed, remoteUsermediaFeed);\n            } else if (currentUserMediaFeed && !remoteUsermediaFeed) {\n                this.removeUserMediaFeed(currentUserMediaFeed);\n            }\n        }\n\n        const currentScreenshareFeed = this.getScreenshareFeed(opponentMemberId, opponentDeviceId);\n        const remoteScreensharingFeed = call.remoteScreensharingFeed;\n        const remoteScreenshareFeedChanged = remoteScreensharingFeed !== currentScreenshareFeed;\n\n        if (remoteScreenshareFeedChanged) {\n            if (!currentScreenshareFeed && remoteScreensharingFeed) {\n                this.addScreenshareFeed(remoteScreensharingFeed);\n            } else if (currentScreenshareFeed && remoteScreensharingFeed) {\n                this.replaceScreenshareFeed(currentScreenshareFeed, remoteScreensharingFeed);\n            } else if (currentScreenshareFeed && !remoteScreensharingFeed) {\n                this.removeScreenshareFeed(currentScreenshareFeed);\n            }\n        }\n    };\n\n    private onCallStateChanged = (call: MatrixCall, state: CallState, _oldState: CallState | undefined): void => {\n        if (state === CallState.Ended) return;\n\n        const audioMuted = this.localCallFeed!.isAudioMuted();\n\n        if (call.localUsermediaStream && call.isMicrophoneMuted() !== audioMuted) {\n            call.setMicrophoneMuted(audioMuted);\n        }\n\n        const videoMuted = this.localCallFeed!.isVideoMuted();\n\n        if (call.localUsermediaStream && call.isLocalVideoMuted() !== videoMuted) {\n            call.setLocalVideoMuted(videoMuted);\n        }\n\n        const opponentUserId = call.getOpponentMember()?.userId;\n        if (state === CallState.Connected && opponentUserId) {\n            const retriesMap = this.retryCallCounts.get(opponentUserId);\n            retriesMap?.delete(call.getOpponentDeviceId()!);\n            if (retriesMap?.size === 0) this.retryCallCounts.delete(opponentUserId);\n        }\n    };\n\n    private onCallHangup = (call: MatrixCall): void => {\n        if (call.hangupReason === CallErrorCode.Replaced) return;\n\n        const opponentUserId = call.getOpponentMember()?.userId ?? this.room.getMember(call.invitee!)!.userId;\n        const deviceMap = this.calls.get(opponentUserId);\n\n        // Sanity check that this call is in fact in the map\n        if (deviceMap?.get(call.getOpponentDeviceId()!) === call) {\n            this.disposeCall(call, call.hangupReason as CallErrorCode);\n            deviceMap.delete(call.getOpponentDeviceId()!);\n            if (deviceMap.size === 0) this.calls.delete(opponentUserId);\n            this.emit(GroupCallEvent.CallsChanged, this.calls);\n        }\n    };\n\n    private onCallReplaced = (prevCall: MatrixCall, newCall: MatrixCall): void => {\n        const opponentUserId = prevCall.getOpponentMember()!.userId;\n\n        let deviceMap = this.calls.get(opponentUserId);\n        if (deviceMap === undefined) {\n            deviceMap = new Map();\n            this.calls.set(opponentUserId, deviceMap);\n        }\n\n        prevCall.hangup(CallErrorCode.Replaced, false);\n        this.initCall(newCall);\n        deviceMap.set(prevCall.getOpponentDeviceId()!, newCall);\n        this.emit(GroupCallEvent.CallsChanged, this.calls);\n    };\n\n    /*\n     * UserMedia CallFeed Event Handlers\n     */\n\n    public getUserMediaFeed(userId: string, deviceId: string): CallFeed | undefined {\n        return this.userMediaFeeds.find((f) => f.userId === userId && f.deviceId! === deviceId);\n    }\n\n    private addUserMediaFeed(callFeed: CallFeed): void {\n        this.userMediaFeeds.push(callFeed);\n        callFeed.measureVolumeActivity(true);\n        this.emit(GroupCallEvent.UserMediaFeedsChanged, this.userMediaFeeds);\n    }\n\n    private replaceUserMediaFeed(existingFeed: CallFeed, replacementFeed: CallFeed): void {\n        const feedIndex = this.userMediaFeeds.findIndex(\n            (f) => f.userId === existingFeed.userId && f.deviceId! === existingFeed.deviceId,\n        );\n\n        if (feedIndex === -1) {\n            throw new Error(\"Couldn't find user media feed to replace\");\n        }\n\n        this.userMediaFeeds.splice(feedIndex, 1, replacementFeed);\n\n        existingFeed.dispose();\n        replacementFeed.measureVolumeActivity(true);\n        this.emit(GroupCallEvent.UserMediaFeedsChanged, this.userMediaFeeds);\n    }\n\n    private removeUserMediaFeed(callFeed: CallFeed): void {\n        const feedIndex = this.userMediaFeeds.findIndex(\n            (f) => f.userId === callFeed.userId && f.deviceId! === callFeed.deviceId,\n        );\n\n        if (feedIndex === -1) {\n            throw new Error(\"Couldn't find user media feed to remove\");\n        }\n\n        this.userMediaFeeds.splice(feedIndex, 1);\n\n        callFeed.dispose();\n        this.emit(GroupCallEvent.UserMediaFeedsChanged, this.userMediaFeeds);\n\n        if (this.activeSpeaker === callFeed) {\n            this.activeSpeaker = this.userMediaFeeds[0];\n            this.emit(GroupCallEvent.ActiveSpeakerChanged, this.activeSpeaker);\n        }\n    }\n\n    private onActiveSpeakerLoop = (): void => {\n        let topAvg: number | undefined = undefined;\n        let nextActiveSpeaker: CallFeed | undefined = undefined;\n\n        for (const callFeed of this.userMediaFeeds) {\n            if (callFeed.isLocal() && this.userMediaFeeds.length > 1) continue;\n\n            const total = callFeed.speakingVolumeSamples.reduce(\n                (acc, volume) => acc + Math.max(volume, SPEAKING_THRESHOLD),\n            );\n            const avg = total / callFeed.speakingVolumeSamples.length;\n\n            if (!topAvg || avg > topAvg) {\n                topAvg = avg;\n                nextActiveSpeaker = callFeed;\n            }\n        }\n\n        if (nextActiveSpeaker && this.activeSpeaker !== nextActiveSpeaker && topAvg && topAvg > SPEAKING_THRESHOLD) {\n            this.activeSpeaker = nextActiveSpeaker;\n            this.emit(GroupCallEvent.ActiveSpeakerChanged, this.activeSpeaker);\n        }\n    };\n\n    /*\n     * Screenshare Call Feed Event Handlers\n     */\n\n    public getScreenshareFeed(userId: string, deviceId: string): CallFeed | undefined {\n        return this.screenshareFeeds.find((f) => f.userId === userId && f.deviceId! === deviceId);\n    }\n\n    private addScreenshareFeed(callFeed: CallFeed): void {\n        this.screenshareFeeds.push(callFeed);\n        this.emit(GroupCallEvent.ScreenshareFeedsChanged, this.screenshareFeeds);\n    }\n\n    private replaceScreenshareFeed(existingFeed: CallFeed, replacementFeed: CallFeed): void {\n        const feedIndex = this.screenshareFeeds.findIndex(\n            (f) => f.userId === existingFeed.userId && f.deviceId! === existingFeed.deviceId,\n        );\n\n        if (feedIndex === -1) {\n            throw new Error(\"Couldn't find screenshare feed to replace\");\n        }\n\n        this.screenshareFeeds.splice(feedIndex, 1, replacementFeed);\n\n        existingFeed.dispose();\n        this.emit(GroupCallEvent.ScreenshareFeedsChanged, this.screenshareFeeds);\n    }\n\n    private removeScreenshareFeed(callFeed: CallFeed): void {\n        const feedIndex = this.screenshareFeeds.findIndex(\n            (f) => f.userId === callFeed.userId && f.deviceId! === callFeed.deviceId,\n        );\n\n        if (feedIndex === -1) {\n            throw new Error(\"Couldn't find screenshare feed to remove\");\n        }\n\n        this.screenshareFeeds.splice(feedIndex, 1);\n\n        callFeed.dispose();\n        this.emit(GroupCallEvent.ScreenshareFeedsChanged, this.screenshareFeeds);\n    }\n\n    /**\n     * Recalculates and updates the participant map to match the room state.\n     */\n    private updateParticipants(): void {\n        const localMember = this.room.getMember(this.client.getSafeUserId());\n        if (!localMember) {\n            // The client hasn't fetched enough of the room state to get our own member\n            // event. This probably shouldn't happen, but sanity check & exit for now.\n            logger.warn(\n                `GroupCall ${this.groupCallId} updateParticipants() tried to update participants before local room member is available`,\n            );\n            return;\n        }\n\n        if (this.participantsExpirationTimer !== null) {\n            clearTimeout(this.participantsExpirationTimer);\n            this.participantsExpirationTimer = null;\n        }\n\n        if (this.state === GroupCallState.Ended) {\n            this.participants = new Map();\n            return;\n        }\n\n        const participants = new Map<RoomMember, Map<string, ParticipantState>>();\n        const now = Date.now();\n        const entered = this.state === GroupCallState.Entered || this.enteredViaAnotherSession;\n        let nextExpiration = Infinity;\n\n        for (const e of this.getMemberStateEvents()) {\n            const member = this.room.getMember(e.getStateKey()!);\n            const content = e.getContent<Record<any, unknown>>();\n            const calls: Record<any, unknown>[] = Array.isArray(content[\"m.calls\"]) ? content[\"m.calls\"] : [];\n            const call = calls.find((call) => call[\"m.call_id\"] === this.groupCallId);\n            const devices: Record<any, unknown>[] = Array.isArray(call?.[\"m.devices\"]) ? call![\"m.devices\"] : [];\n\n            // Filter out invalid and expired devices\n            let validDevices = devices.filter(\n                (d) =>\n                    typeof d.device_id === \"string\" &&\n                    typeof d.session_id === \"string\" &&\n                    typeof d.expires_ts === \"number\" &&\n                    d.expires_ts > now &&\n                    Array.isArray(d.feeds),\n            ) as unknown as IGroupCallRoomMemberDevice[];\n\n            // Apply local echo for the unentered case\n            if (!entered && member?.userId === this.client.getUserId()!) {\n                validDevices = validDevices.filter((d) => d.device_id !== this.client.getDeviceId()!);\n            }\n\n            // Must have a connected device and be joined to the room\n            if (validDevices.length > 0 && member?.membership === KnownMembership.Join) {\n                const deviceMap = new Map<string, ParticipantState>();\n                participants.set(member, deviceMap);\n\n                for (const d of validDevices) {\n                    deviceMap.set(d.device_id, {\n                        sessionId: d.session_id,\n                        screensharing: d.feeds.some((f) => f.purpose === SDPStreamMetadataPurpose.Screenshare),\n                    });\n                    if (d.expires_ts < nextExpiration) nextExpiration = d.expires_ts;\n                }\n            }\n        }\n\n        // Apply local echo for the entered case\n        if (entered) {\n            let deviceMap = participants.get(localMember);\n            if (deviceMap === undefined) {\n                deviceMap = new Map();\n                participants.set(localMember, deviceMap);\n            }\n\n            if (!deviceMap.has(this.client.getDeviceId()!)) {\n                deviceMap.set(this.client.getDeviceId()!, {\n                    sessionId: this.client.getSessionId(),\n                    screensharing: this.getLocalFeeds().some((f) => f.purpose === SDPStreamMetadataPurpose.Screenshare),\n                });\n            }\n        }\n\n        this.participants = participants;\n        if (nextExpiration < Infinity) {\n            this.participantsExpirationTimer = setTimeout(() => this.updateParticipants(), nextExpiration - now);\n        }\n    }\n\n    /**\n     * Updates the local user's member state with the devices returned by the given function.\n     * @param fn - A function from the current devices to the new devices. If it\n     *   returns null, the update will be skipped.\n     * @param keepAlive - Whether the request should outlive the window.\n     */\n    private async updateDevices(\n        fn: (devices: IGroupCallRoomMemberDevice[]) => IGroupCallRoomMemberDevice[] | null,\n        keepAlive = false,\n    ): Promise<void> {\n        const now = Date.now();\n        const localUserId = this.client.getUserId()!;\n\n        const event = this.getMemberStateEvents(localUserId);\n        const content = event?.getContent<Record<any, unknown>>() ?? {};\n        const calls: Record<any, unknown>[] = Array.isArray(content[\"m.calls\"]) ? content[\"m.calls\"] : [];\n\n        let call: Record<any, unknown> | null = null;\n        const otherCalls: Record<any, unknown>[] = [];\n        for (const c of calls) {\n            if (c[\"m.call_id\"] === this.groupCallId) {\n                call = c;\n            } else {\n                otherCalls.push(c);\n            }\n        }\n        if (call === null) call = {};\n\n        const devices: Record<any, unknown>[] = Array.isArray(call[\"m.devices\"]) ? call[\"m.devices\"] : [];\n\n        // Filter out invalid and expired devices\n        const validDevices = devices.filter(\n            (d) =>\n                typeof d.device_id === \"string\" &&\n                typeof d.session_id === \"string\" &&\n                typeof d.expires_ts === \"number\" &&\n                d.expires_ts > now &&\n                Array.isArray(d.feeds),\n        ) as unknown as IGroupCallRoomMemberDevice[];\n\n        const newDevices = fn(validDevices);\n        if (newDevices === null) return;\n\n        const newCalls = [...(otherCalls as unknown as IGroupCallRoomMemberCallState[])];\n        if (newDevices.length > 0) {\n            newCalls.push({\n                ...call,\n                \"m.call_id\": this.groupCallId,\n                \"m.devices\": newDevices,\n            });\n        }\n\n        const newContent: IGroupCallRoomMemberState = { \"m.calls\": newCalls };\n\n        await this.client.sendStateEvent(this.room.roomId, EventType.GroupCallMemberPrefix, newContent, localUserId, {\n            keepAlive,\n        });\n    }\n\n    private async addDeviceToMemberState(): Promise<void> {\n        await this.updateDevices((devices) => [\n            ...devices.filter((d) => d.device_id !== this.client.getDeviceId()!),\n            {\n                device_id: this.client.getDeviceId()!,\n                session_id: this.client.getSessionId(),\n                expires_ts: Date.now() + DEVICE_TIMEOUT,\n                feeds: this.getLocalFeeds().map((feed) => ({ purpose: feed.purpose })),\n                // TODO: Add data channels\n            },\n        ]);\n    }\n\n    private async updateMemberState(): Promise<void> {\n        // Clear the old update interval before proceeding\n        if (this.resendMemberStateTimer !== null) {\n            clearInterval(this.resendMemberStateTimer);\n            this.resendMemberStateTimer = null;\n        }\n\n        if (this.state === GroupCallState.Entered) {\n            // Add the local device\n            await this.addDeviceToMemberState();\n\n            // Resend the state event every so often so it doesn't become stale\n            this.resendMemberStateTimer = setInterval(\n                async () => {\n                    logger.log(`GroupCall ${this.groupCallId} updateMemberState() resending call member state\"`);\n                    try {\n                        await this.addDeviceToMemberState();\n                    } catch (e) {\n                        logger.error(\n                            `GroupCall ${this.groupCallId} updateMemberState() failed to resend call member state`,\n                            e,\n                        );\n                    }\n                },\n                (DEVICE_TIMEOUT * 3) / 4,\n            );\n        } else {\n            // Remove the local device\n            await this.updateDevices(\n                (devices) => devices.filter((d) => d.device_id !== this.client.getDeviceId()!),\n                true,\n            );\n        }\n    }\n\n    /**\n     * Cleans up our member state by filtering out logged out devices, inactive\n     * devices, and our own device (if we know we haven't entered).\n     */\n    public async cleanMemberState(): Promise<void> {\n        const { devices: myDevices } = await this.client.getDevices();\n        const deviceMap = new Map<string, IMyDevice>(myDevices.map((d) => [d.device_id, d]));\n\n        // updateDevices takes care of filtering out inactive devices for us\n        await this.updateDevices((devices) => {\n            const newDevices = devices.filter((d) => {\n                const device = deviceMap.get(d.device_id);\n                return (\n                    device?.last_seen_ts !== undefined &&\n                    !(\n                        d.device_id === this.client.getDeviceId()! &&\n                        this.state !== GroupCallState.Entered &&\n                        !this.enteredViaAnotherSession\n                    )\n                );\n            });\n\n            // Skip the update if the devices are unchanged\n            return newDevices.length === devices.length ? null : newDevices;\n        });\n    }\n\n    private onRoomState = (): void => this.updateParticipants();\n\n    private onParticipantsChanged = (): void => {\n        // Re-run setTracksEnabled on all calls, so that participants that just\n        // left get denied access to our media, and participants that just\n        // joined get granted access\n        this.forEachCall((call) => {\n            const expected = this.callExpected(call);\n            for (const feed of call.getLocalFeeds()) {\n                setTracksEnabled(feed.stream.getAudioTracks(), !feed.isAudioMuted() && expected);\n                setTracksEnabled(feed.stream.getVideoTracks(), !feed.isVideoMuted() && expected);\n            }\n        });\n\n        if (this.state === GroupCallState.Entered && !this.useLivekit) this.placeOutgoingCalls();\n\n        // Update the participants stored in the stats object\n    };\n\n    private onStateChanged = (newState: GroupCallState, oldState: GroupCallState): void => {\n        if (\n            newState === GroupCallState.Entered ||\n            oldState === GroupCallState.Entered ||\n            newState === GroupCallState.Ended\n        ) {\n            // We either entered, left, or ended the call\n            this.updateParticipants();\n            this.updateMemberState().catch((e) =>\n                logger.error(\n                    `GroupCall ${this.groupCallId} onStateChanged() failed to update member state devices\"`,\n                    e,\n                ),\n            );\n        }\n    };\n\n    private onLocalFeedsChanged = (): void => {\n        if (this.state === GroupCallState.Entered) {\n            this.updateMemberState().catch((e) =>\n                logger.error(\n                    `GroupCall ${this.groupCallId} onLocalFeedsChanged() failed to update member state feeds`,\n                    e,\n                ),\n            );\n        }\n    };\n\n    public getGroupCallStats(): GroupCallStats {\n        if (this.stats === undefined) {\n            const userID = this.client.getUserId() || \"unknown\";\n            this.stats = new GroupCallStats(this.groupCallId, userID, this.statsCollectIntervalTime);\n            this.stats.reports.on(StatsReport.CONNECTION_STATS, this.onConnectionStats);\n            this.stats.reports.on(StatsReport.BYTE_SENT_STATS, this.onByteSentStats);\n            this.stats.reports.on(StatsReport.SUMMARY_STATS, this.onSummaryStats);\n            this.stats.reports.on(StatsReport.CALL_FEED_REPORT, this.onCallFeedReport);\n        }\n        return this.stats;\n    }\n\n    public setGroupCallStatsInterval(interval: number): void {\n        this.statsCollectIntervalTime = interval;\n        if (this.stats !== undefined) {\n            this.stats.stop();\n            this.stats.setInterval(interval);\n            if (interval > 0) {\n                this.stats.start();\n            }\n        }\n    }\n}\n","/*\nCopyright 2015, 2016 OpenMarket Ltd\nCopyright 2017 New Vector Ltd\nCopyright 2019, 2020 The Matrix.org Foundation C.I.C.\nCopyright 2021 - 2022 Šimon Brandner <simon.bra.ag@gmail.com>\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { TypedEventEmitter } from \"../models/typed-event-emitter.ts\";\nimport { GroupCallType, GroupCallState } from \"../webrtc/groupCall.ts\";\nimport { logger } from \"../logger.ts\";\nimport { type MatrixClient } from \"../client.ts\";\n\nexport enum MediaHandlerEvent {\n    LocalStreamsChanged = \"local_streams_changed\",\n}\n\nexport type MediaHandlerEventHandlerMap = {\n    [MediaHandlerEvent.LocalStreamsChanged]: () => void;\n};\n\nexport interface IScreensharingOpts {\n    /**\n     * sourceId for Electron DesktopCapturer\n     */\n    desktopCapturerSourceId?: string;\n    audio?: boolean;\n    // For electron screen capture, there are very few options for detecting electron\n    // apart from inspecting the user agent or just trying getDisplayMedia() and\n    // catching the failure, so we do the latter - this flag tells the function to just\n    // throw an error so we can catch it in this case, rather than logging and emitting.\n    throwOnFail?: boolean;\n}\n\nexport interface AudioSettings {\n    autoGainControl: boolean;\n    echoCancellation: boolean;\n    noiseSuppression: boolean;\n}\n\nexport class MediaHandler extends TypedEventEmitter<\n    MediaHandlerEvent.LocalStreamsChanged,\n    MediaHandlerEventHandlerMap\n> {\n    private audioInput?: string;\n    private audioSettings?: AudioSettings;\n    private videoInput?: string;\n    private localUserMediaStream?: MediaStream;\n    public userMediaStreams: MediaStream[] = [];\n    public screensharingStreams: MediaStream[] = [];\n\n    // Promise chain to serialise calls to getMediaStream\n    private getMediaStreamPromise?: Promise<MediaStream>;\n\n    public constructor(private client: MatrixClient) {\n        super();\n    }\n\n    public restoreMediaSettings(audioInput: string, videoInput: string): void {\n        this.audioInput = audioInput;\n        this.videoInput = videoInput;\n    }\n\n    /**\n     * Set an audio input device to use for MatrixCalls\n     * @param deviceId - the identifier for the device\n     * undefined treated as unset\n     */\n    public async setAudioInput(deviceId: string): Promise<void> {\n        logger.info(`MediaHandler setAudioInput() running (deviceId=${deviceId})`);\n\n        if (this.audioInput === deviceId) return;\n\n        this.audioInput = deviceId;\n        await this.updateLocalUsermediaStreams();\n    }\n\n    /**\n     * Set audio settings for MatrixCalls\n     * @param opts - audio options to set\n     */\n    public async setAudioSettings(opts: AudioSettings): Promise<void> {\n        logger.info(`MediaHandler setAudioSettings() running (opts=${JSON.stringify(opts)})`);\n\n        this.audioSettings = Object.assign({}, opts) as AudioSettings;\n        await this.updateLocalUsermediaStreams();\n    }\n\n    /**\n     * Set a video input device to use for MatrixCalls\n     * @param deviceId - the identifier for the device\n     * undefined treated as unset\n     */\n    public async setVideoInput(deviceId: string): Promise<void> {\n        logger.info(`MediaHandler setVideoInput() running (deviceId=${deviceId})`);\n\n        if (this.videoInput === deviceId) return;\n\n        this.videoInput = deviceId;\n        await this.updateLocalUsermediaStreams();\n    }\n\n    /**\n     * Set media input devices to use for MatrixCalls\n     * @param audioInput - the identifier for the audio device\n     * @param videoInput - the identifier for the video device\n     * undefined treated as unset\n     */\n    public async setMediaInputs(audioInput: string, videoInput: string): Promise<void> {\n        logger.log(`MediaHandler setMediaInputs() running (audioInput: ${audioInput} videoInput: ${videoInput})`);\n        this.audioInput = audioInput;\n        this.videoInput = videoInput;\n        await this.updateLocalUsermediaStreams();\n    }\n\n    /*\n     * Requests new usermedia streams and replace the old ones\n     */\n    public async updateLocalUsermediaStreams(): Promise<void> {\n        if (this.userMediaStreams.length === 0) return;\n\n        const callMediaStreamParams: Map<string, { audio: boolean; video: boolean }> = new Map();\n        for (const call of this.client.callEventHandler!.calls.values()) {\n            callMediaStreamParams.set(call.callId, {\n                audio: call.hasLocalUserMediaAudioTrack,\n                video: call.hasLocalUserMediaVideoTrack,\n            });\n        }\n\n        for (const stream of this.userMediaStreams) {\n            logger.log(`MediaHandler updateLocalUsermediaStreams() stopping all tracks (streamId=${stream.id})`);\n            for (const track of stream.getTracks()) {\n                track.stop();\n            }\n        }\n\n        this.userMediaStreams = [];\n        this.localUserMediaStream = undefined;\n\n        for (const call of this.client.callEventHandler!.calls.values()) {\n            if (call.callHasEnded() || !callMediaStreamParams.has(call.callId)) {\n                continue;\n            }\n\n            const { audio, video } = callMediaStreamParams.get(call.callId)!;\n\n            logger.log(\n                `MediaHandler updateLocalUsermediaStreams() calling getUserMediaStream() (callId=${call.callId})`,\n            );\n            const stream = await this.getUserMediaStream(audio, video);\n\n            if (call.callHasEnded()) {\n                continue;\n            }\n\n            await call.updateLocalUsermediaStream(stream);\n        }\n\n        for (const groupCall of this.client.groupCallEventHandler!.groupCalls.values()) {\n            if (!groupCall.localCallFeed) {\n                continue;\n            }\n\n            logger.log(\n                `MediaHandler updateLocalUsermediaStreams() calling getUserMediaStream() (groupCallId=${groupCall.groupCallId})`,\n            );\n            const stream = await this.getUserMediaStream(true, groupCall.type === GroupCallType.Video);\n\n            if (groupCall.state === GroupCallState.Ended) {\n                continue;\n            }\n\n            await groupCall.updateLocalUsermediaStream(stream);\n        }\n\n        this.emit(MediaHandlerEvent.LocalStreamsChanged);\n    }\n\n    public async hasAudioDevice(): Promise<boolean> {\n        try {\n            const devices = await navigator.mediaDevices.enumerateDevices();\n            return devices.filter((device) => device.kind === \"audioinput\").length > 0;\n        } catch (err) {\n            logger.log(`MediaHandler hasAudioDevice() calling navigator.mediaDevices.enumerateDevices with error`, err);\n            return false;\n        }\n    }\n\n    public async hasVideoDevice(): Promise<boolean> {\n        try {\n            const devices = await navigator.mediaDevices.enumerateDevices();\n            return devices.filter((device) => device.kind === \"videoinput\").length > 0;\n        } catch (err) {\n            logger.log(`MediaHandler hasVideoDevice() calling navigator.mediaDevices.enumerateDevices with error`, err);\n            return false;\n        }\n    }\n\n    /**\n     * @param audio - should have an audio track\n     * @param video - should have a video track\n     * @param reusable - is allowed to be reused by the MediaHandler\n     * @returns based on passed parameters\n     */\n    public async getUserMediaStream(audio: boolean, video: boolean, reusable = true): Promise<MediaStream> {\n        // Serialise calls, othertwise we can't sensibly re-use the stream\n        if (this.getMediaStreamPromise) {\n            this.getMediaStreamPromise = this.getMediaStreamPromise.then(() => {\n                return this.getUserMediaStreamInternal(audio, video, reusable);\n            });\n        } else {\n            this.getMediaStreamPromise = this.getUserMediaStreamInternal(audio, video, reusable);\n        }\n\n        return this.getMediaStreamPromise;\n    }\n\n    private async getUserMediaStreamInternal(audio: boolean, video: boolean, reusable: boolean): Promise<MediaStream> {\n        const shouldRequestAudio = audio && (await this.hasAudioDevice());\n        const shouldRequestVideo = video && (await this.hasVideoDevice());\n\n        let stream: MediaStream;\n\n        let canReuseStream = true;\n        if (this.localUserMediaStream) {\n            // This figures out if we can reuse the current localUsermediaStream\n            // based on whether or not the \"mute state\" (presence of tracks of a\n            // given kind) matches what is being requested\n            if (shouldRequestAudio !== this.localUserMediaStream.getAudioTracks().length > 0) {\n                canReuseStream = false;\n            }\n            if (shouldRequestVideo !== this.localUserMediaStream.getVideoTracks().length > 0) {\n                canReuseStream = false;\n            }\n\n            // This code checks that the device ID is the same as the localUserMediaStream stream, but we update\n            // the localUserMediaStream whenever the device ID changes (apart from when restoring) so it's not\n            // clear why this would ever be different, unless there's a race.\n            if (\n                shouldRequestAudio &&\n                this.localUserMediaStream.getAudioTracks()[0]?.getSettings()?.deviceId !== this.audioInput\n            ) {\n                canReuseStream = false;\n            }\n            if (\n                shouldRequestVideo &&\n                this.localUserMediaStream.getVideoTracks()[0]?.getSettings()?.deviceId !== this.videoInput\n            ) {\n                canReuseStream = false;\n            }\n        } else {\n            canReuseStream = false;\n        }\n\n        if (!canReuseStream) {\n            let constraints: MediaStreamConstraints;\n            try {\n                // Not specifying exact for deviceId means switching devices does not always work,\n                // try with exact and fallback to ideal if it fails\n                constraints = this.getUserMediaContraints(shouldRequestAudio, shouldRequestVideo, true);\n                stream = await navigator.mediaDevices.getUserMedia(constraints);\n            } catch (e) {\n                logger.warn(\n                    `MediaHandler getUserMediaStreamInternal() error (e=${e}), retrying without exact deviceId`,\n                );\n                constraints = this.getUserMediaContraints(shouldRequestAudio, shouldRequestVideo, false);\n                stream = await navigator.mediaDevices.getUserMedia(constraints);\n            }\n            logger.log(\n                `MediaHandler getUserMediaStreamInternal() calling getUserMediaStream (streamId=${\n                    stream.id\n                }, shouldRequestAudio=${shouldRequestAudio}, shouldRequestVideo=${shouldRequestVideo}, constraints=${JSON.stringify(\n                    constraints,\n                )})`,\n            );\n\n            for (const track of stream.getTracks()) {\n                const settings = track.getSettings();\n\n                if (track.kind === \"audio\") {\n                    this.audioInput = settings.deviceId!;\n                } else if (track.kind === \"video\") {\n                    this.videoInput = settings.deviceId!;\n                }\n            }\n\n            if (reusable) {\n                this.localUserMediaStream = stream;\n            }\n        } else {\n            stream = this.localUserMediaStream!.clone();\n            logger.log(\n                `MediaHandler getUserMediaStreamInternal() cloning (oldStreamId=${this.localUserMediaStream?.id} newStreamId=${stream.id} shouldRequestAudio=${shouldRequestAudio} shouldRequestVideo=${shouldRequestVideo})`,\n            );\n\n            if (!shouldRequestAudio) {\n                for (const track of stream.getAudioTracks()) {\n                    stream.removeTrack(track);\n                }\n            }\n\n            if (!shouldRequestVideo) {\n                for (const track of stream.getVideoTracks()) {\n                    stream.removeTrack(track);\n                }\n            }\n        }\n\n        if (reusable) {\n            this.userMediaStreams.push(stream);\n        }\n\n        this.emit(MediaHandlerEvent.LocalStreamsChanged);\n\n        return stream;\n    }\n\n    /**\n     * Stops all tracks on the provided usermedia stream\n     */\n    public stopUserMediaStream(mediaStream: MediaStream): void {\n        logger.log(`MediaHandler stopUserMediaStream() stopping (streamId=${mediaStream.id})`);\n        for (const track of mediaStream.getTracks()) {\n            track.stop();\n        }\n\n        const index = this.userMediaStreams.indexOf(mediaStream);\n\n        if (index !== -1) {\n            logger.debug(\n                `MediaHandler stopUserMediaStream() splicing usermedia stream out stream array (streamId=${mediaStream.id})`,\n                mediaStream.id,\n            );\n            this.userMediaStreams.splice(index, 1);\n        }\n\n        this.emit(MediaHandlerEvent.LocalStreamsChanged);\n\n        if (this.localUserMediaStream === mediaStream) {\n            // if we have this stream cahced, remove it, because we've stopped it\n            this.localUserMediaStream = undefined;\n        } else {\n            // If it's not the same stream. remove any tracks from the cached stream that\n            // we have just stopped, and if we do stop any, call the same method on the\n            // cached stream too in order to stop all its tracks (in case they are different)\n            // and un-cache it.\n            for (const track of mediaStream.getTracks()) {\n                if (this.localUserMediaStream?.getTrackById(track.id)) {\n                    this.stopUserMediaStream(this.localUserMediaStream);\n                    break;\n                }\n            }\n        }\n    }\n\n    /**\n     * @param opts - screensharing stream options\n     * @param reusable - is allowed to be reused by the MediaHandler\n     * @returns based on passed parameters\n     */\n    public async getScreensharingStream(opts: IScreensharingOpts = {}, reusable = true): Promise<MediaStream> {\n        let stream: MediaStream;\n\n        if (this.screensharingStreams.length === 0) {\n            const screenshareConstraints = this.getScreenshareContraints(opts);\n\n            if (opts.desktopCapturerSourceId) {\n                // We are using Electron\n                logger.debug(\n                    `MediaHandler getScreensharingStream() calling getUserMedia() (opts=${JSON.stringify(opts)})`,\n                );\n                stream = await navigator.mediaDevices.getUserMedia(screenshareConstraints);\n            } else {\n                // We are not using Electron\n                logger.debug(\n                    `MediaHandler getScreensharingStream() calling getDisplayMedia() (opts=${JSON.stringify(opts)})`,\n                );\n                stream = await navigator.mediaDevices.getDisplayMedia(screenshareConstraints);\n            }\n        } else {\n            const matchingStream = this.screensharingStreams[this.screensharingStreams.length - 1];\n            logger.log(`MediaHandler getScreensharingStream() cloning (streamId=${matchingStream.id})`);\n            stream = matchingStream.clone();\n        }\n\n        if (reusable) {\n            this.screensharingStreams.push(stream);\n        }\n\n        this.emit(MediaHandlerEvent.LocalStreamsChanged);\n\n        return stream;\n    }\n\n    /**\n     * Stops all tracks on the provided screensharing stream\n     */\n    public stopScreensharingStream(mediaStream: MediaStream): void {\n        logger.debug(`MediaHandler stopScreensharingStream() stopping stream (streamId=${mediaStream.id})`);\n        for (const track of mediaStream.getTracks()) {\n            track.stop();\n        }\n\n        const index = this.screensharingStreams.indexOf(mediaStream);\n\n        if (index !== -1) {\n            logger.debug(`MediaHandler stopScreensharingStream() splicing stream out (streamId=${mediaStream.id})`);\n            this.screensharingStreams.splice(index, 1);\n        }\n\n        this.emit(MediaHandlerEvent.LocalStreamsChanged);\n    }\n\n    /**\n     * Stops all local media tracks\n     */\n    public stopAllStreams(): void {\n        for (const stream of this.userMediaStreams) {\n            logger.log(`MediaHandler stopAllStreams() stopping (streamId=${stream.id})`);\n            for (const track of stream.getTracks()) {\n                track.stop();\n            }\n        }\n\n        for (const stream of this.screensharingStreams) {\n            for (const track of stream.getTracks()) {\n                track.stop();\n            }\n        }\n\n        this.userMediaStreams = [];\n        this.screensharingStreams = [];\n        this.localUserMediaStream = undefined;\n\n        this.emit(MediaHandlerEvent.LocalStreamsChanged);\n    }\n\n    private getUserMediaContraints(audio: boolean, video: boolean, exactDeviceId?: boolean): MediaStreamConstraints {\n        const isWebkit = !!navigator.webkitGetUserMedia;\n        const deviceIdKey = exactDeviceId ? \"exact\" : \"ideal\";\n\n        const audioConstraints: MediaTrackConstraints = {};\n        if (this.audioInput) {\n            audioConstraints.deviceId = { [deviceIdKey]: this.audioInput };\n        }\n        if (this.audioSettings) {\n            audioConstraints.autoGainControl = { ideal: this.audioSettings.autoGainControl };\n            audioConstraints.echoCancellation = { ideal: this.audioSettings.echoCancellation };\n            audioConstraints.noiseSuppression = { ideal: this.audioSettings.noiseSuppression };\n        }\n\n        const videoConstraints: MediaTrackConstraints = {\n            /* We want 640x360.  Chrome will give it only if we ask exactly,\n               FF refuses entirely if we ask exactly, so have to ask for ideal\n               instead\n               XXX: Is this still true?\n             */\n            width: isWebkit ? { exact: 640 } : { ideal: 640 },\n            height: isWebkit ? { exact: 360 } : { ideal: 360 },\n        };\n        if (this.videoInput) {\n            videoConstraints.deviceId = { [deviceIdKey]: this.videoInput };\n        }\n\n        return {\n            audio: audio ? audioConstraints : false,\n            video: video ? videoConstraints : false,\n        };\n    }\n\n    private getScreenshareContraints(opts: IScreensharingOpts): MediaStreamConstraints {\n        const { desktopCapturerSourceId, audio } = opts;\n        if (desktopCapturerSourceId) {\n            return {\n                audio: audio ?? false,\n                video: {\n                    mandatory: {\n                        chromeMediaSource: \"desktop\",\n                        chromeMediaSourceId: desktopCapturerSourceId,\n                    },\n                },\n            };\n        } else {\n            return {\n                audio: audio ?? false,\n                video: true,\n            };\n        }\n    }\n}\n","/*\n* loglevel - https://github.com/pimterry/loglevel\n*\n* Copyright (c) 2013 Tim Perry\n* Licensed under the MIT license.\n*/\n(function (root, definition) {\n    \"use strict\";\n    if (typeof define === 'function' && define.amd) {\n        define(definition);\n    } else if (typeof module === 'object' && module.exports) {\n        module.exports = definition();\n    } else {\n        root.log = definition();\n    }\n}(this, function () {\n    \"use strict\";\n\n    // Slightly dubious tricks to cut down minimized file size\n    var noop = function() {};\n    var undefinedType = \"undefined\";\n    var isIE = (typeof window !== undefinedType) && (typeof window.navigator !== undefinedType) && (\n        /Trident\\/|MSIE /.test(window.navigator.userAgent)\n    );\n\n    var logMethods = [\n        \"trace\",\n        \"debug\",\n        \"info\",\n        \"warn\",\n        \"error\"\n    ];\n\n    var _loggersByName = {};\n    var defaultLogger = null;\n\n    // Cross-browser bind equivalent that works at least back to IE6\n    function bindMethod(obj, methodName) {\n        var method = obj[methodName];\n        if (typeof method.bind === 'function') {\n            return method.bind(obj);\n        } else {\n            try {\n                return Function.prototype.bind.call(method, obj);\n            } catch (e) {\n                // Missing bind shim or IE8 + Modernizr, fallback to wrapping\n                return function() {\n                    return Function.prototype.apply.apply(method, [obj, arguments]);\n                };\n            }\n        }\n    }\n\n    // Trace() doesn't print the message in IE, so for that case we need to wrap it\n    function traceForIE() {\n        if (console.log) {\n            if (console.log.apply) {\n                console.log.apply(console, arguments);\n            } else {\n                // In old IE, native console methods themselves don't have apply().\n                Function.prototype.apply.apply(console.log, [console, arguments]);\n            }\n        }\n        if (console.trace) console.trace();\n    }\n\n    // Build the best logging method possible for this env\n    // Wherever possible we want to bind, not wrap, to preserve stack traces\n    function realMethod(methodName) {\n        if (methodName === 'debug') {\n            methodName = 'log';\n        }\n\n        if (typeof console === undefinedType) {\n            return false; // No method possible, for now - fixed later by enableLoggingWhenConsoleArrives\n        } else if (methodName === 'trace' && isIE) {\n            return traceForIE;\n        } else if (console[methodName] !== undefined) {\n            return bindMethod(console, methodName);\n        } else if (console.log !== undefined) {\n            return bindMethod(console, 'log');\n        } else {\n            return noop;\n        }\n    }\n\n    // These private functions always need `this` to be set properly\n\n    function replaceLoggingMethods() {\n        /*jshint validthis:true */\n        var level = this.getLevel();\n\n        // Replace the actual methods.\n        for (var i = 0; i < logMethods.length; i++) {\n            var methodName = logMethods[i];\n            this[methodName] = (i < level) ?\n                noop :\n                this.methodFactory(methodName, level, this.name);\n        }\n\n        // Define log.log as an alias for log.debug\n        this.log = this.debug;\n\n        // Return any important warnings.\n        if (typeof console === undefinedType && level < this.levels.SILENT) {\n            return \"No console available for logging\";\n        }\n    }\n\n    // In old IE versions, the console isn't present until you first open it.\n    // We build realMethod() replacements here that regenerate logging methods\n    function enableLoggingWhenConsoleArrives(methodName) {\n        return function () {\n            if (typeof console !== undefinedType) {\n                replaceLoggingMethods.call(this);\n                this[methodName].apply(this, arguments);\n            }\n        };\n    }\n\n    // By default, we use closely bound real methods wherever possible, and\n    // otherwise we wait for a console to appear, and then try again.\n    function defaultMethodFactory(methodName, _level, _loggerName) {\n        /*jshint validthis:true */\n        return realMethod(methodName) ||\n               enableLoggingWhenConsoleArrives.apply(this, arguments);\n    }\n\n    function Logger(name, factory) {\n      // Private instance variables.\n      var self = this;\n      /**\n       * The level inherited from a parent logger (or a global default). We\n       * cache this here rather than delegating to the parent so that it stays\n       * in sync with the actual logging methods that we have installed (the\n       * parent could change levels but we might not have rebuilt the loggers\n       * in this child yet).\n       * @type {number}\n       */\n      var inheritedLevel;\n      /**\n       * The default level for this logger, if any. If set, this overrides\n       * `inheritedLevel`.\n       * @type {number|null}\n       */\n      var defaultLevel;\n      /**\n       * A user-specific level for this logger. If set, this overrides\n       * `defaultLevel`.\n       * @type {number|null}\n       */\n      var userLevel;\n\n      var storageKey = \"loglevel\";\n      if (typeof name === \"string\") {\n        storageKey += \":\" + name;\n      } else if (typeof name === \"symbol\") {\n        storageKey = undefined;\n      }\n\n      function persistLevelIfPossible(levelNum) {\n          var levelName = (logMethods[levelNum] || 'silent').toUpperCase();\n\n          if (typeof window === undefinedType || !storageKey) return;\n\n          // Use localStorage if available\n          try {\n              window.localStorage[storageKey] = levelName;\n              return;\n          } catch (ignore) {}\n\n          // Use session cookie as fallback\n          try {\n              window.document.cookie =\n                encodeURIComponent(storageKey) + \"=\" + levelName + \";\";\n          } catch (ignore) {}\n      }\n\n      function getPersistedLevel() {\n          var storedLevel;\n\n          if (typeof window === undefinedType || !storageKey) return;\n\n          try {\n              storedLevel = window.localStorage[storageKey];\n          } catch (ignore) {}\n\n          // Fallback to cookies if local storage gives us nothing\n          if (typeof storedLevel === undefinedType) {\n              try {\n                  var cookie = window.document.cookie;\n                  var cookieName = encodeURIComponent(storageKey);\n                  var location = cookie.indexOf(cookieName + \"=\");\n                  if (location !== -1) {\n                      storedLevel = /^([^;]+)/.exec(\n                          cookie.slice(location + cookieName.length + 1)\n                      )[1];\n                  }\n              } catch (ignore) {}\n          }\n\n          // If the stored level is not valid, treat it as if nothing was stored.\n          if (self.levels[storedLevel] === undefined) {\n              storedLevel = undefined;\n          }\n\n          return storedLevel;\n      }\n\n      function clearPersistedLevel() {\n          if (typeof window === undefinedType || !storageKey) return;\n\n          // Use localStorage if available\n          try {\n              window.localStorage.removeItem(storageKey);\n          } catch (ignore) {}\n\n          // Use session cookie as fallback\n          try {\n              window.document.cookie =\n                encodeURIComponent(storageKey) + \"=; expires=Thu, 01 Jan 1970 00:00:00 UTC\";\n          } catch (ignore) {}\n      }\n\n      function normalizeLevel(input) {\n          var level = input;\n          if (typeof level === \"string\" && self.levels[level.toUpperCase()] !== undefined) {\n              level = self.levels[level.toUpperCase()];\n          }\n          if (typeof level === \"number\" && level >= 0 && level <= self.levels.SILENT) {\n              return level;\n          } else {\n              throw new TypeError(\"log.setLevel() called with invalid level: \" + input);\n          }\n      }\n\n      /*\n       *\n       * Public logger API - see https://github.com/pimterry/loglevel for details\n       *\n       */\n\n      self.name = name;\n\n      self.levels = { \"TRACE\": 0, \"DEBUG\": 1, \"INFO\": 2, \"WARN\": 3,\n          \"ERROR\": 4, \"SILENT\": 5};\n\n      self.methodFactory = factory || defaultMethodFactory;\n\n      self.getLevel = function () {\n          if (userLevel != null) {\n            return userLevel;\n          } else if (defaultLevel != null) {\n            return defaultLevel;\n          } else {\n            return inheritedLevel;\n          }\n      };\n\n      self.setLevel = function (level, persist) {\n          userLevel = normalizeLevel(level);\n          if (persist !== false) {  // defaults to true\n              persistLevelIfPossible(userLevel);\n          }\n\n          // NOTE: in v2, this should call rebuild(), which updates children.\n          return replaceLoggingMethods.call(self);\n      };\n\n      self.setDefaultLevel = function (level) {\n          defaultLevel = normalizeLevel(level);\n          if (!getPersistedLevel()) {\n              self.setLevel(level, false);\n          }\n      };\n\n      self.resetLevel = function () {\n          userLevel = null;\n          clearPersistedLevel();\n          replaceLoggingMethods.call(self);\n      };\n\n      self.enableAll = function(persist) {\n          self.setLevel(self.levels.TRACE, persist);\n      };\n\n      self.disableAll = function(persist) {\n          self.setLevel(self.levels.SILENT, persist);\n      };\n\n      self.rebuild = function () {\n          if (defaultLogger !== self) {\n              inheritedLevel = normalizeLevel(defaultLogger.getLevel());\n          }\n          replaceLoggingMethods.call(self);\n\n          if (defaultLogger === self) {\n              for (var childName in _loggersByName) {\n                _loggersByName[childName].rebuild();\n              }\n          }\n      };\n\n      // Initialize all the internal levels.\n      inheritedLevel = normalizeLevel(\n          defaultLogger ? defaultLogger.getLevel() : \"WARN\"\n      );\n      var initialLevel = getPersistedLevel();\n      if (initialLevel != null) {\n          userLevel = normalizeLevel(initialLevel);\n      }\n      replaceLoggingMethods.call(self);\n    }\n\n    /*\n     *\n     * Top-level API\n     *\n     */\n\n    defaultLogger = new Logger();\n\n    defaultLogger.getLogger = function getLogger(name) {\n        if ((typeof name !== \"symbol\" && typeof name !== \"string\") || name === \"\") {\n            throw new TypeError(\"You must supply a name when creating a logger.\");\n        }\n\n        var logger = _loggersByName[name];\n        if (!logger) {\n            logger = _loggersByName[name] = new Logger(\n                name,\n                defaultLogger.methodFactory\n            );\n        }\n        return logger;\n    };\n\n    // Grab the current global log variable in case of overwrite\n    var _log = (typeof window !== undefinedType) ? window.log : undefined;\n    defaultLogger.noConflict = function() {\n        if (typeof window !== undefinedType &&\n               window.log === defaultLogger) {\n            window.log = _log;\n        }\n\n        return defaultLogger;\n    };\n\n    defaultLogger.getLoggers = function getLoggers() {\n        return _loggersByName;\n    };\n\n    // ES6 default export, for compatibility\n    defaultLogger['default'] = defaultLogger;\n\n    return defaultLogger;\n}));\n","function _typeof(o) {\n  \"@babel/helpers - typeof\";\n\n  return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) {\n    return typeof o;\n  } : function (o) {\n    return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o;\n  }, _typeof(o);\n}\nexport { _typeof as default };","import _typeof from \"./typeof.js\";\nimport toPrimitive from \"./toPrimitive.js\";\nfunction toPropertyKey(t) {\n  var i = toPrimitive(t, \"string\");\n  return \"symbol\" == _typeof(i) ? i : i + \"\";\n}\nexport { toPropertyKey as default };","import _typeof from \"./typeof.js\";\nfunction toPrimitive(t, r) {\n  if (\"object\" != _typeof(t) || !t) return t;\n  var e = t[Symbol.toPrimitive];\n  if (void 0 !== e) {\n    var i = e.call(t, r || \"default\");\n    if (\"object\" != _typeof(i)) return i;\n    throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n  }\n  return (\"string\" === r ? String : Number)(t);\n}\nexport { toPrimitive as default };","import toPropertyKey from \"./toPropertyKey.js\";\nfunction _defineProperty(e, r, t) {\n  return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {\n    value: t,\n    enumerable: !0,\n    configurable: !0,\n    writable: !0\n  }) : e[r] = t, e;\n}\nexport { _defineProperty as default };"],"names":["PushRuleActionName","TweakName","ConditionKind","PushRuleKind","RuleId","M_BEACON_INFO","UnstableValue","M_BEACON","EventType","RelationType","MsgType","RoomType","UNSTABLE_MSC3088_PURPOSE","UNSTABLE_MSC3088_ENABLED","UNSTABLE_MSC3089_TREE_SUBTYPE","UNSTABLE_MSC3089_LEAF","UNSTABLE_MSC3089_BRANCH","UNSTABLE_MSC2716_MARKER","MSC3912_RELATION_BASED_REDACTIONS_PROP","UNSTABLE_ELEMENT_FUNCTIONAL_USERS","EVENT_VISIBILITY_CHANGE_TYPE","PUSHER_ENABLED","LOCAL_NOTIFICATION_SETTINGS_PREFIX","UNSIGNED_THREAD_ID_FIELD","UNSIGNED_MEMBERSHIP_FIELD","NamespacedValue","M_ASSET","M_TIMESTAMP","M_LOCATION","Preset","JoinRule","GuestAccess","HistoryVisibility","M_POLL_RESPONSE","M_POLL_END","ROOM_RETENTION_TYPE","UNREAD_THREAD_NOTIFICATIONS","ServerControlledNamespacedValue","M_TOPIC","hasOptionalStringProperty","metadata","key","logger","error","requiredArrayValue","value","array","Array","isArray","includes","hasRequiredStringProperty","optionalStringArrayProperty","every","v","AutoDiscoveryAction","AutoDiscoveryError","AutoDiscovery","fromDiscoveryConfig","wellknown","_hsVersions$raw","clientConfig","state","FAIL_ERROR","ERROR_INVALID","base_url","PROMPT","FAIL_PROMPT","Promise","resolve","ERROR_INVALID_HS_BASE_URL","hsUrl","this","sanitizeWellKnownUrl","hsVersions","fetchWellKnownObject","raw","ERROR_INVALID_HOMESERVER","hsVersionSet","Set","supportedVersionFound","version","SUPPORTED_MATRIX_VERSIONS","has","ERROR_UNSUPPORTED_HOMESERVER_SPEC_VERSION","SUCCESS","isUrl","failingClientConfig","ERROR_INVALID_IS","ERROR_INVALID_IS_BASE_URL","isResponse","action","ERROR_INVALID_IDENTITY_SERVER","toString","length","Object","keys","forEach","k","notProps","prop","findClientConfig","domain","Error","domainWithProtocol","reason","IGNORE","getRawClientConfig","_response$raw","response","url","_parsed","parsed","URL","e","hostname","protocol","port","path","pathname","saferUrl","endsWith","substring","fetch","resource","options","fetchFn","globalThis","setFetchFn","method","Method","Get","signal","timeoutSignal","status","ERROR_MISSING_WELLKNOWN","err","message","json","name","ERROR_INVALID_JSON","_defineProperty","Invalid","GenericFailure","InvalidHsBaseUrl","InvalidHomeserver","InvalidIsBaseUrl","InvalidIdentityServer","InvalidIs","MissingWellknown","InvalidJson","UnsupportedHomeserverSpecVersion","TURN_CHECK_INTERVAL","PendingEventOrdering","UNSTABLE_MSC4140_DELAYED_EVENTS","UNSTABLE_MSC4354_STICKY_EVENTS","EVENT_ID_PREFIX","ClientEvent","SSO_ACTION_PARAM","MatrixClient","TypedEventEmitter","constructor","opts","_opts$logger","_opts$usingExternalCr","_opts$disableVoip","_opts$unstableMSC","_opts$enableEncrypted","_opts$cryptoCallbacks","super","TypedReEmitter","Map","PushProcessor","MediaHandler","isInitialSyncComplete","supportsMatrixCall","callEventHandler","start","groupCallEventHandler","off","Sync","startCallEventHandler","matrixRTC","startMatrixRTC","_this$getRooms","unreadRooms","getRooms","filter","room","getUnreadNotificationCount","NotificationCountType","Total","currentUserId","getSafeUserId","fixupNotifications","fixupRoomNotifications","baseUrl","utils","idBaseUrl","identityServer","usingExternalCrypto","store","StubStore","deviceId","sessionId","secureRandomString","userId","credentials","http","MatrixHttpApi","accessToken","refreshToken","tokenRefreshFunction","prefix","ClientPrefix","V3","onlyData","extraParams","queryParams","localTimeoutMs","useAuthorizationHeader","pickleKey","legacyPickleKey","useLivekitForGroupCalls","Boolean","scheduler","setProcessFunction","async","getRoom","eventToSend","getRoomId","EventStatus","SENDING","updatePendingEventStatus","res","sendEventHttpRequest","updatePendingEvent","SENT","event_id","disableVoip","CallEventHandler","GroupCallEventHandler","canSupportVoip","on","MatrixRTCSessionManager","serverCapabilitiesService","ServerCapabilities","retentionPolicyService","RetentionPolicyService","_unstable_shouldApplyMessageRetention","unstableMSC1763Retention","timelineSupport","legacyCryptoStore","cryptoStore","verificationMethods","cryptoCallbacks","enableEncryptedStateEvents","forceTURN","iceCandidatePoolSize","undefined","supportsCallTransfer","fallbackICEServerAllowed","isVoipWithNoMediaAllowed","useE2eForGroupCall","livekitServiceURL","roomNameGenerator","toDeviceMessageQueue","ToDeviceMessageQueue","MatrixEventEvent","Decrypted","event","cli","_actions$tweaks","ourUserId","getUserId","eventId","getId","findEventById","info","roomId","isThreadEvent","threadRootId","isThreadRoot","hasReadEvent","thread","getThread","hasUserReadEvent","actions","getPushActionsForEvent","tweaks","highlight","newCount","getUnreadCountForEventContext","Highlight","setThreadUnreadNotificationCount","setUnreadNotificationCount","notify","fixNotificationCountOnDecryption","ignoredInvites","IgnoredInvites","_secretStorage","ServerSideSecretStorageImpl","setMaxListeners","newStore","_store","setUserCreator","User","createUser","startClient","clientRunning","storeUser","supportsVoip","checkTurnServersIntervalID","setInterval","checkTurnServers","syncApi","stop","getVersions","threads","list","fwdPagination","doesServerSupportThread","Thread","setServerSideSupport","setServerSideListSupport","setServerSideFwdPaginationSupport","_this$retentionPolicy","clientOpts","slidingSync","SlidingSyncSdk","buildSyncApiOptions","SyncApi","sync","catch","clientWellKnownPollPeriod","clientWellKnownIntervalID","fetchClientWellKnown","cryptoBackend","canResetEntireTimeline","canResetTimelineCallback","getChild","stopClient","_this$cryptoBackend","_this$syncApi","_this$peekSync","_this$callEventHandle","_this$groupCallEventH","debug","peekSync","stopPeeking","clearInterval","clearStores","args","promises","push","deleteAllData","indexedDB","dbname","_args$cryptoDatabaseP","cryptoDatabasePrefix","RUST_SDK_STORE_PREFIX","_args$cryptoDatabaseP2","prom","reject","req","deleteDatabase","onsuccess","_","onerror","warn","onblocked","deleteRustSdkStore","all","then","_this$credentials$use","_this$credentials","getDomain","_this$credentials2","replace","getUserIdLocalpart","_this$credentials$use2","_this$credentials3","split","getDeviceId","getSessionId","getMediaHandler","mediaHandler","setForceTURN","force","setSupportsCallTransfer","support","getUseE2eForGroupCall","createCall","createNewMatrixCall","createGroupCall","type","isPtt","intent","dataChannelsEnabled","dataChannelOptions","getGroupCallForRoom","GroupCall","create","getLivekitServiceURL","setLivekitServiceURL","newURL","waitUntilRoomReadyForGroupCalls","groupCalls","get","getSyncState","_this$syncApi$getSync","_this$syncApi2","getSyncStateData","SyncState","Prepared","Syncing","isGuest","isGuestAccount","setGuest","guest","getScheduler","retryImmediately","_this$syncApi$retryIm","_this$syncApi3","sendQueue","getNotifTimelineSet","notifTimelineSet","setNotifTimelineSet","set","getCapabilities","caps","getCached","getCachedCapabilities","fetchCapabilities","initRustCrypto","_args$cryptoDatabaseP3","_this$legacyPickleKey","RustCrypto","__webpack_require__","bind","rustCrypto","secretStorage","storePrefix","useIndexedDB","storeKey","storageKey","storePassphrase","storagePassword","legacyMigrationProgressListener","progress","total","emit","CryptoEvent","LegacyCryptoStoreMigrationProgress","setSupportedVerificationMethods","RoomMemberEvent","Membership","onRoomMembership","RoomStateEvent","Events","onRoomStateEvent","Event","onLiveEventFromSync","reEmitter","reEmit","VerificationRequestReceived","UserTrustStatusChanged","KeyBackupStatus","KeyBackupSessionsRemaining","KeyBackupFailed","KeyBackupDecryptionKeyCached","KeysChanged","DevicesUpdated","WillUpdateDevices","DehydratedDeviceCreated","DehydratedDeviceUploaded","RehydrationStarted","RehydrationProgress","RehydrationCompleted","RehydrationError","DehydrationKeyCached","DehydratedDeviceRotationError","getCrypto","isRoomEncrypted","hasEncryptionStateEvent","isKeyBackupKeyStored","isStored","makeKeyBackupPath","$roomId","$sessionId","queryData","deleteKeysFromBackup","authedRequest","Delete","getMediaConfig","useAuthenticatedMedia","V1","MediaPrefix","getVisibleRooms","msc3946ProcessDynamicPredecessor","allRooms","visibleRooms","predecessors","findPredecessorRooms","predecessor","delete","from","getUser","getUsers","setAccountData","eventType","content","retryNetworkOperation","setAccountDataRaw","existingData","getAccountData","deepCompare","updatedResolvers","withResolvers","accountDataListener","getType","addListener","AccountData","result","promise","removeListener","$userId","$type","Put","getAccountDataFromServer","getContent","_data","data","errcode","deleteAccountData","msc3391DeleteAccountDataServerSupport","canSupport","Feature","AccountDataDeletion","ServerSupport","Unsupported","Unstable","getIgnoredUsers","IgnoredUserList","setIgnoredUsers","userIds","ignored_users","u","isUserIgnored","joinRoom","roomIdOrAlias","_roomMember$events$me","_roomMember$events$me2","roomMember","getMember","preJoinMembership","membership","inviter","KnownMembership","Invite","events","member","getSender","JSON","stringify","Join","signPromise","inviteSignUrl","searchParams","requestOtherUrl","Post","viaServers","via","server_name","slice","signedInviteObj","third_party_signed","$roomid","room_id","markRoomAsPendingKeyBundle","maybeAcceptKeyBundle","resolvedRoom","hasMembershipState","createRoom","knockRoom","Knock","$roomIdOrAlias","body","resendEvent","encryptAndSendEvent","cancelPendingEvent","QUEUED","NOT_SENT","ENCRYPTING","eventsBeingEncrypted","removeEventFromQueue","CANCELLED","setRoomName","sendStateEvent","RoomName","setRoomTopic","topic","htmlTopic","ContentHelpers","RoomTopic","getRoomTags","setRoomTag","tagName","$tag","deleteRoomTag","setRoomAccountData","setPowerLevel","powerLevel","_content","_this$getRoom","currentState","getStateEvents","RoomPowerLevels","getStateEvent","MatrixError","users","user","unstable_createLiveBeacon","beaconInfoContent","unstable_setLiveBeacon","sendEvent","threadIdOrEventType","eventTypeOrContent","contentOrTxnId","txnIdOrVoid","threadId","txnId","startsWith","addThreadRelationIfNeeded","sendCompleteEvent","eventObject","_content$mRelates_to","rel_type","_content$mRelates_to2","_this$getRoom2","isReply","_objectSpread","THREAD_RELATION_TYPE","is_falling_back","_thread$lastReply$get","_thread$lastReply","lastReply","ev","isRelation","delayOpts","queryDict","makeTxnId","localEvent","MatrixEvent","assign","user_id","sender","origin_server_ts","Date","getTime","setThread","Replaced","VisibilityChange","BeforeRedaction","targetId","getAssociatedId","target","getPendingEvents","find","once","LocalEventIdReplaced","updateAssociatedId","setTxnId","setStatus","addPendingEvent","delayOptsOrQuery","queryOpts","isSendDelayedEventRequestOpts","cancelled","add","encryptEventIfNeeded","queueEvent","getQueueForEvent","shouldEncryptEventForRoom","encryptEvent","_this$cryptoBackend2","isEncrypted","Reaction","isRedaction","isEncryptionEnabledInRoom","getEncryptedIfNeededEventType","_this$getRoom3","RoomMessageEncrypted","newStatus","queryOrDelayOpts","getTxnId","pathParams","$eventType","getWireType","$stateKey","getStateKey","$txnId","isState","pathTemplate","redacts","$redactsEventId","getWireContent","getUnstableDelayQueryOpts","redactEvent","_eventId","_opts","_opts2","with_rel_types","RelationBasedRedactions","Stable","stable","unstable","RoomRedaction","sendMessage","RoomMessage","sendContent","sendTextMessage","_threadId","sendNotice","_threadId2","sendEmoteMessage","_threadId3","sendImageMessage","text","_threadId4","msgtype","Image","sendStickerMessage","_threadId5","Sticker","sendHtmlMessage","htmlBody","_threadId6","sendHtmlNotice","_threadId7","sendHtmlEmote","_threadId8","_unstable_sendDelayedEvent","doesServerSupportUnstableFeature","UnsupportedDelayedEventsEndpointError","_unstable_sendStickyDelayedEvent","stickDuration","UnsupportedStickyEventsEndpointError","_unstable_sendDelayedStateEvent","stateKey","_unstable_sendStickyEvent","_unstable_getDelayedEvents","delayId","fromToken","delay_id","_unstable_updateDelayedEvent","requestOptions","updateScheduledDelayedEventWithActionInBody","_unstable_cancelScheduledDelayedEvent","updateScheduledDelayedEvent","UpdateDelayedEventAction","Cancel","_unstable_restartScheduledDelayedEvent","Restart","_unstable_sendScheduledDelayedEvent","Send","$delayId","$action","request","sendReceipt","receiptType","unthreaded","$receiptType","$eventId","fullBody","supportsThreads","thread_id","threadIdForReceipt","addLocalEchoReceipt","sendReadReceipt","ReceiptType","Read","hasPendingEvent","setRoomReadMarkers","rmEventId","rrEvent","rpEvent","rrEventId","rpEventId","ReadPrivate","setRoomReadMarkersHttpRequest","sendRtcDecline","notificationEventId","RTCDecline","Reference","getUrlPreview","ts","Math","floor","hash","urlPreviewCache","supportsNewEndpoint","isVersionSupported","resp","priority","sendTyping","isTyping","timeoutMs","typing","timeout","getRoomUpgradeHistory","verifyLinks","currentRoom","findSuccessorRooms","_room$findPredecessor","ret","seenRoomIDs","predecessorRoomId","findPredecessor","_room$findPredecessor2","predecessorRoom","tombstone","RoomTombstone","splice","tombstoneEvent","successorRoom","_successorRoom$findPr","map","ref","size","invite","_this$getRoom$getHist","_this$getRoom4","historyVisibility","getHistoryVisibility","Shared","_this$cryptoBackend3","Invited","Joined","shareRoomHistoryWithUser","membershipChange","inviteByEmail","email","inviteByThreePid","medium","address","_this$identityServer","identityServerUrl","getIdentityServerUrl","params","id_server","getAccessToken","identityAccessToken","leave","Leave","leaveRoomChain","includeFuture","upgradeHistory","eligibleToLeave","populationResults","doLeave","ban","Ban","forget","deleteRoom","$room_id","removeRoom","DeleteRoom","unban","kick","$membership","forceRecalculate","getPushActions","rule","pushProcessor","actionsAndRuleForEvent","setPushDetails","getPushDetailsForEvent","getPushDetails","setProfileInfo","$info","setDisplayName","displayname","displayName","UserEvent","DisplayName","presence","setAvatarUrl","avatar_url","avatarUrl","AvatarUrl","mxcUrlToHttp","mxcUrl","width","height","resizeMethod","allowDirectLinks","allowRedirects","useAuthentication","getHttpUriForMxc","setSyncPresence","_this$syncApi4","setPresence","indexOf","getPresence","scrollback","limit","timeToWaitMs","ongoingScrollbacks","errorTs","timeWaitedMs","now","max","oldState","paginationToken","numAdded","sleep","createMessagesRequest","Direction","Backward","_res$end","_res$end2","matrixEvents","chunk","getEventMapper","stateEvents","setUnknownStateEvents","timelineEvents","threadedEvents","unknownRelations","partitionThreadedEvents","processAggregatedTimelineEvents","addEventsToTimeline","getLiveTimeline","processThreadEvents","relations","aggregateChildEvent","end","storeEvents","eventMapperFor","getEventContext","_this$clientOpts","lazyLoadMembers","Filter","LAZY_LOADING_MESSAGES_FILTER","_res$events_after","_res$events_before","_res$state","events_after","events_before","getEventTimeline","timelineSet","_ref","_timelineSet$getTimel","_timelineSet$room$fin","_await$this$getThread","getTimelineForEvent","getThreadTimeline","mapper","reverse","timeline","_res$end3","getState","EventTimeline","BACKWARDS","addTimeline","initialiseState","FORWARDS","findThreadForEvent","liveTimeline","canContain","recurse","RelationsRecursion","hasServerSideSupport","hasServerSideFwdPaginationSupport","_resOlder$next_batch","_resNewer$next_batch","_timelineSet$getTimel2","resOlder","fetchRelations","id","dir","resNewer","Forward","getRelationsThreadFilter","_timelineSet$thread","processEvent","next_batch","originalEvent","fetchRoomEvent","setPaginationToken","_resOlder$next_batch2","eventsNewer","nextBatch","_timelineSet$thread2","getLatestTimeline","threadListType","_res$chunk","createThreadListMessagesRequest","getFilter","_res$chunk2","_this$clientOpts2","_res$chunk3","messagesPath","timelineFilter","_this$clientOpts3","_timelineFilter$getRo","getRoomTimelineFilterComponent","toJSON","ThreadFilterType","All","_this$clientOpts4","include","threadFilterTypeToFilter","_timelineFilter$getRo2","hasServerSideListSupport","FeatureSupport","_res$chunk4","prev_batch","paginateEventTimeline","eventTimeline","isNotifTimeline","getTimelineSet","backwards","token","getPaginationToken","pendingRequest","paginationRequests","_opts$limit","only","next_token","notifications","noUnsafeEventProps","i","notification","finally","roomState","processThreadRoots","_eventTimeline$getRoo","_eventTimeline$getRoo2","getEventReadUpTo","newToken","_thread$rootEvent","_eventTimeline$getRoo3","rootEvent","it","getServerAggregatedRelation","atEnd","resetNotifTimelineSet","resetLiveTimeline","peekInRoom","_this$peekSync2","peek","setGuestAccess","writePromise","RoomGuestAccess","guest_access","allowJoin","CanJoin","Forbidden","readPromise","allowRead","RoomHistoryVisibility","history_visibility","WorldReadable","requestRegisterEmailToken","clientSecret","sendAttempt","nextLink","requestTokenFromEndpoint","client_secret","send_attempt","next_link","requestRegisterMsisdnToken","phoneCountry","phoneNumber","country","phone_number","requestAdd3pidEmailToken","requestAdd3pidMsisdnToken","requestPasswordEmailToken","requestPasswordMsisdnToken","endpoint","postParams","getRoomPushRule","scope","_this$pushRules$scope","pushRules","rule_id","setRoomMutePushRule","mute","hasDontNotifyRule","roomPushRule","DontNotify","doneResolvers","deletePushRule","RoomSpecific","addPushRule","getPushRules","err2","searchMessageText","roomEvents","search_term","query","search","search_categories","room_events","searchRoomEvents","term","order_by","SearchOrderBy","Recent","event_context","before_limit","after_limit","include_profile","searchResults","_query","results","highlights","processRoomEventsSearch","backPaginateRoomEventsSearch","searchOpts","abortSignal","_roomEvents$results$l","_roomEvents$results","count","hl","resultsLength","sr","SearchResult","fromJson","context","getEvent","getTimeline","setMetadata","syncLeftRooms","syncedLeftRooms","syncLeftRoomsPromise","createFilter","filter_id","storeFilter","filterId","allowCached","$filterId","getOrCreateFilter","filterName","getFilterIdByName","existingId","existingFilter","oldDef","getDefinition","newDef","setFilterIdByName","createdFilter","getOpenIdToken","turnServer","getTurnServers","turnServers","getTurnServersExpiry","turnServersExpiry","pollingTurnServers","credentialsGood","remainingTime","uris","ttl","servers","urls","username","credential","password","TurnServers","httpStatus","TurnServersError","setFallbackICEServerAllowed","allow","isFallbackICEServerAllowed","isSynapseAdministrator","r","admin","whoisSynapseUser","deactivateSynapseUser","_this$getDomain","clientWellKnownPromise","clientWellKnown","ClientWellKnown","getClientWellKnown","waitForClientWellKnown","storeClientOptions","primTypes","serializableOpts","entries","reduce","obj","_unstable_getSharedRooms","sharedRoomsSupport","mutualRoomsSupport","queryMutualRoomsSupport","rooms","tokenQuery","joined","next_batch_token","_unstable_getRTCTransports","rtc_transports","serverVersionsPromise","serverVersions","buildFeatureSupportMap","versions","feature","unstableFeatures","doesServerForceEncryptionForPreset","presetName","versionsPresetName","threadUnstable","threadStable","listUnstable","listStable","fwdPaginationUnstable","fwdPaginationStable","determineFeatureSupport","None","hasLazyLoadMembersEnabled","_this$clientOpts5","setCanResetTimelineCallback","cb","getCanResetTimelineCallback","relationType","_result$next_batch","_result$prev_batch","fetchedEventType","eventResult","allEvents","concat","decryptEventIfNeeded","Replace","prevBatch","generateClientSecret","shouldAttemptDecryption","attemptDecryption","isBeingDecrypted","getDecryptionPromise","termsUrlForService","serviceType","SERVICE_TYPES","IS","getUrl","IdentityPrefix","V2","IM","getHomeserverUrl","stripProto","_this$idBaseUrl","_this$idBaseUrl2","setIdentityServerUrl","setIdBaseUrl","getRefreshToken","_this$http$opts$refre","setAccessToken","isLoggedIn","txnCtr","isUsernameAvailable","available","register","auth","bindThreepids","guestAccessToken","inhibitLogin","session","refresh_token","guest_access_token","inhibit_login","registerRequest","registerGuest","kind","performRefreshRequestWithPrefix","inhibitLogoutEmit","loginFlows","login","loginType","loginRequest","access_token","loginWithPassword","getCasLoginUrl","redirectUrl","getSsoLoginUrl","idpId","href","loginWithToken","logout","abort","deactivateAccount","erase","requestLoginToken","getFallbackAuthUrl","authSessionId","$loginType","_this$identityServer2","invitesNeedingToken","invite_3pid","id_access_token","Experimental","replaceParam","queryString","templatedUrl","$relationType","members","includeMembership","excludeMembership","atEventId","not_membership","at","upgradeRoom","newVersion","additionalCreators","new_version","additional_creators","state_key","encryptStateEventIfNeeded","getWireStateKey","isStateEncryptionEnabledInRoom","roomInitialSync","_limit$toString","FullyRead","getJoinedRooms","getJoinedRoomMembers","publicRooms","_ref2","server","since","_objectWithoutProperties","_excluded","createAlias","alias","$alias","deleteAlias","getLocalAliases","getRoomIdForAlias","getRoomDirectoryVisibility","setRoomDirectoryVisibility","visibility","searchUserDirectory","uploadContent","file","cancelUpload","upload","getCurrentUploads","getProfileInfo","doesServerSupportExtendedProfiles","getExtendedProfileRequestPrefix","getExtendedProfile","getExtendedProfileProperty","storedProfile","getUserProfile","profile","$key","storeUserProfiles","setExtendedProfileProperty","deleteExtendedProfileProperty","patchExtendedProfile","Patch","setExtendedProfile","getThreePids","addThreePidOnly","bindThreePid","unbindThreePid","deleteThreePid","setPassword","authDict","newPassword","logoutDevices","new_password","logout_devices","getDevices","getDevice","$device_id","setDeviceDetails","deleteDevice","deleteMultipleDevices","devices","getPushers","pushers","pusher","hasOwnProperty","setPusher","removePusher","pushKey","appId","pushkey","app_id","setLocalNotificationSettings","notificationSettings","rules","setPushRules","rewriteDefaultRules","updateCachedPushRuleKeys","ruleId","$kind","$ruleId","setPushRuleEnabled","enabled","setPushRuleActions","uploadKeysRequest","uploadKeySignatures","downloadKeysForUsers","device_keys","claimOneTimeKeys","keyAlgorithm","queries","safeSet","one_time_keys","getKeyChanges","oldToken","qps","to","uploadDeviceSigningKeys","registerWithIdentityServer","hsOpenIdToken","uri","requestEmailToken","idServerRequest","requestMsisdnToken","submitMsisdnToken","sid","msisdnToken","submitMsisdnTokenOtherUrl","getIdentityHashDetails","identityHashedLookup","addressPairs","hashes","localMapping","addr","p","toLowerCase","med","hashBuffer","sha256","hashed","encodeUnpaddedBase64Url","unhashed","foundAddresses","mxid","plainAddress","lookupThreePid","bulkLookupThreePids","v1results","mapping","originalQuery","threepids","getIdentityAccount","sendToDevice","contentMap","messages","targets","deviceMessages","encryptAndSendToDevice","payload","batch","encryptToDeviceMessages","queueToDevice","queueBatch","getThirdpartyProtocols","getThirdpartyLocation","$protocol","getThirdpartyUser","getTerms","agreeToTerms","termsUrls","headers","Authorization","user_accepts","reportEvent","score","reportRoom","getRoomHierarchy","maxDepth","suggestedOnly","suggested_only","String","max_depth","unstableCreateFileTree","preset","PrivateChat","power_level_content_override","DEFAULT_TREE_POWER_LEVELS_TEMPLATE","creation_content","Space","initial_state","RoomEncryption","algorithm","MSC3089TreeSpace","unstableGetFileTreeSpace","_purposeEvent$getCont","_createEvent$getConte","getMyMembership","createEvent","RoomCreate","purposeEvent","proxyBaseUrl","pos","clientTimeout","_this$clientOpts6","threadSupport","supportsIntentionalMentions","IntentionalMentions","getRoomSummary","paramOpts","toStartOfTimeline","processThreadedEvents","processBeaconEvents","processPollEvents","whoami","timestampToEvent","timestamp","getAuthMetadata","useStable","authMetadata","isValidAuthMetadata","OAuth2Error","OpSupport","fromEntries","inMainTimelineForReceipt","MAIN_ROOM_TIMELINE","relationEventId","makeHtmlMessage","Text","format","formatted_body","makeHtmlNotice","Notice","makeHtmlEmote","Emote","makeTextMessage","makeNotice","makeEmoteMessage","makeTopicContent","renderings","isProvided","mimetype","_M_TIMESTAMP$findIn2","location","findIn","description","_M_TIMESTAMP$findIn","live","asset","assetType","OLM_RECOVERY_KEY_PREFIX","encodeRecoveryKey","_base58key$match","buf","Uint8Array","parity","bs58","encode","match","join","DecryptionFailureCode","DeviceIsolationModeKind","AllDevicesIsolationMode","errorOnVerifiedUserProblems","UserVerificationStatus","crossSigningVerified","crossSigningVerifiedBefore","known","needsUserApproval","isVerified","isCrossSigningVerified","wasCrossSigningVerified","isTofu","DeviceVerificationStatus","_opts$signedByOwner","_opts$crossSigningVer","_opts$tofu","_opts$localVerified","_opts$trustCrossSigne","signedByOwner","tofu","localVerified","trustCrossSignedDevices","ImportRoomKeyStage","CrossSigningKey","EventShieldColour","EventShieldReason","DecryptionKeyDoesNotMatchError","InvalidCryptoStoreState","InvalidCryptoStoreError","TooNew","ClientStoppedError","clientEndpoint","s","profileFieldsFilterName","setProp","keyNesting","val","nestedKeys","currentObj","jsonObj","setDefinition","getFilterId","definition","roomFilterJson","roomFilterFields","not_rooms","roomFilter","FilterComponent","roomTimelineFilter","filterRoomTimeline","setTimelineLimit","setUnreadThreadNotifications","_this$definition","_this$definition2","setLazyLoadMembers","setIncludeLeaveRooms","includeLeave","setUnstableMSC4429SyncUserProfiles","ids","_profileFieldsFilterN","field","lazy_load_members","FetchHttpApi","_opts$includeFilename","_opts$abortController","_opts$type","_opts$name","includeFilename","abortController","AbortController","contentType","fileName","loaded","uploadResolvers","XMLHttpRequest","xhr","timeoutFn","timeoutTimer","callbacks","onreadystatechange","readyState","DONE","DOMException","statusText","responseText","parseErrorResponse","parse","ConnectionError","onprogress","_opts$progressHandler","progressHandler","call","encodeURIComponent","open","setRequestHeader","send","addEventListener","filename","removeElement","uploads","elem","IndexedDBStoreWorker","postMessage","command","_this$backend","_this$backend2","_this$backend3","_this$backend4","_this$backend5","_this$backend6","_this$backend7","_this$backend8","_this$backend9","_this$backend0","_this$backend1","_this$backend10","_this$backend11","_this$backend12","_this$backend13","_this$backend14","_this$backend15","_this$backend16","_this$backend17","msg","backend","LocalIndexedDBStoreBackend","connect","onClose","isNewlyCreated","clearDatabase","getSavedSync","setSyncData","syncToDatabase","getUserPresenceEvents","getNextBatchToken","getOutOfBandMembers","clearOutOfBandMembers","setOutOfBandMembers","getClientOptions","saveToDeviceBatches","getOldestToDeviceBatch","removeToDeviceBatch","removeUserProfiles","seq","loglevel","methodName","logLevel","loggerName","unshift","console","log","getPrefixedLogger","prefixLogger","childPrefix","childLogger","methodFactory","rebuild","setLevel","DEBUG","LogSpan","parent","trace","DEFAULT_EXPIRE_DURATION","MembershipKind","CallMembership","membershipDataFromMatrixEvent","matrixEvent","evType","RTCMembership","checkRtcMembershipData","RTC","GroupCallMemberPrefix","checkSessionsMembershipData","Session","ex","MatrixRTCMembershipParseError","parseFromEvent","membershipData","rtcBackendIdentity","computeRtcIdentityRaw","device_id","equal","a","b","matrixEventData","slotId","_this$logger2","application","slot_id","call_id","compatibilityAdaptedId","_this$logger","computeSlotId","callIntent","applicationData","slotDescription","slotIdToDescription","memberId","_data$membershipID","membershipID","createdTs","_data$created_ts","getTs","created_ts","getAbsoluteExpiry","_data$expires","expires","getMsUntilExpiry","absExpiry","isExpired","getTransport","oldestMembership","focus_active","focus_selection","foci_preferred","transports","MatrixRTCSessionEvent","DEFAULT_SESSION_MEMBERSHIPS_FOR_SLOT_OPTS","listenForStickyEvents","listenForMemberStateEvents","MatrixRTCSession","membershipStatus","_this$membershipManag","membershipManager","probablyLeft","_this$membershipManag2","_this$membershipManag3","callId","_this$slotDescription","sessionMembershipsForSlot","rootLogger","callMemberEvents","_unstable_getStickyEvents","callMemberStateEvents","callMemberStateEvent","some","stickyEvent","msc4354_sticky_key","collectMembersEvents","callMemberships","memberEvent","quickFilterNonRelevantContents","isValidMembership","computeBackendIdentityAndVerifyMemberEvents","sort","m","sessionForSlot","client","roomSubset","calculateMembershipsOpts","counters","roomEventEncryptionKeysSent","roomEventEncryptionKeysReceived","totals","roomEventEncryptionKeysReceivedTotalAge","ensureRecalculateSessionMembers","added","updated","removed","flatMap","current","previous","recalculateSessionMembers","_this$encryptionManag","membershipNeedsRecalculation","oldMemberships","memberships","_this$membershipManag4","_this$membershipManag5","logDurationSync","MembershipsChanged","onRTCSessionMemberUpdate","ownMembership","_this$joinConfig","pendingNotificationToSend","joinConfig","notificationType","sendCallNotify","encryptionManager","onMembershipsUpdate","setExpiryTimer","Members","onRoomMemberUpdate","RoomStickyEventsEvent","Update","onStickyEventUpdate","initialMembershipCalculated","isJoined","_this$membershipManag6","_this$membershipManag7","_this$membershipManag8","expiryTimeout","clearTimeout","joinRTCSession","ownMembershipIdentity","fociPreferred","multiSfuFocus","_this$joinConfig2","unstableSendStickyEvents","StickyEventMembershipManager","MembershipManager","MembershipManagerEvent","ProbablyLeft","StatusChanged","DelayIdChanged","statistics","transport","ToDeviceKeyTransport","RTCEncryptionManager","keyBin","encryptionKeyIndex","EncryptionKeyChanged","MembershipManagerError","JoinStateChanged","joinRoomSession","leaveRoomSession","leavePromise","getFocusInUse","getOldestMembership","getConsensusCallIntent","_this$memberships$fin","getFirstCallIntent","updateCallIntent","_this$membershipManag9","_this$membershipManag0","reemitEncryptionKeys","_this$encryptionManag2","getEncryptionKeys","keyRing","keyInfo","keyIndex","soonestExpiry","thisExpiry","setTimeout","parentEventId","user_ids","notification_type","sender_ts","lifetime","RTCNotification","sendNotificationEvent","newResult","DidSendCallNotification","errorLegacy","errorNew","recalculateSessionMembersPromise","eventKeysCount","_membership$userId","MatrixRTCSessionManagerEvents","refreshRoom","unstableStickyExpiresAt","_this$client$getRooms","roomSessions","Room","onRoom","onEvent","onRoomState","sess","values","clear","getActiveRoomSession","getRoomSession","isNewSession","wasActiveAndKnown","_onRTCSessionMemberUpdate","nowActive","SessionEnded","SessionStarted","BeaconEvent","isTimestampInDuration","startTimestamp","durationMs","getBeaconInfoIdentifier","Beacon","_latestLocationEvent","LocationUpdate","latestLocationState","setBeaconInfo","isLive","_isLive","identifier","beaconInfoId","beaconInfoOwner","beaconInfoEventType","beaconInfo","_beaconInfo","parseBeaconContent","latestLocationEvent","update","beaconInfoEvent","clearLatestLocation","destroy","livenessWatchTimeout","Destroy","monitorLiveness","checkLiveness","expiryInMs","addLocations","beaconLocationEvents","_validLocationEvents$","sortEventsByLatestContentTimestamp","parseBeaconInfoContent","prevLiveness","LivenessChange","debuglog","DuplicateStrategy","EventTimelineSet","_this$room$relations","_this$room","_room$client","displayPendingEvents","pendingEvents","timelines","_eventIdToTimeline","RelationsContainer","getTimelines","setFilter","setLiveTimeline","eventIdToTimeline","replaceEventId","oldEventId","newEventId","existingTimeline","backPaginationToken","forwardPaginationToken","resetAllTimelines","oldTimeline","newTimeline","forkLive","fork","RoomEvent","TimelineReset","tl","getEvents","addToState","direction","inverseDirection","didUpdate","lastEventWasNew","addEventToTimeline","neighbour","getNeighbouringTimeline","existingIsLive","timelineIsLive","backwardsIsLive","forwardsIsLive","setNeighbouringTimeline","addLiveEvent","duplicateStrategy","fromCache","timelineWasEmpty","tlEvents","j","setEventMetadata","_this$thread","aggregateParentEvent","_this$thread2","eventDebugString","addEvent","liveEvent","Timeline","insertEventIntoTimeline","_this$thread3","_this$thread4","parentEvent","insertIndex","insertEvent","handleRemoteEcho","removeEvent","compareEventOrdering","eventId1","eventId2","timeline1","timeline2","idx1","idx2","idx","evId","difference","shouldLiveInRoom","shouldLiveInThread","eventShouldLiveIn","_this$room2","stateContext","eventTimelineSet","_eventTimelineSet$roo","_eventTimelineSet$roo2","startState","RoomState","endState","f","toISOString","_this$startState","_this$endState","setStateEvents","forkState","clone","getBaseIndex","baseIndex","startToken","endToken","prevTimeline","nextTimeline","_roomState","getUnfilteredTimelineSet","RoomMember","MESSAGE_VISIBLE","freeze","visible","MAX_STICKY_DURATION_MS","_this$sender","_this$target","affectsSelf","changed","newSender","getSentinelMember","newTarget","forwardLooking","SentinelUpdated","_this$getTs","internaliseString","_event$content","_event$content2","txn_id","age","getAge","localTimestamp","unstableStickyInfo","duration_ttl_ms","min","duration_ms","unstableExtensibleEvent","_ExtensibleEvents$par","_hasCachedExtEv","_cachedExtEv","ExtensibleEvents","getEffectiveEvent","invalidateExtensibleEvent","clearEvent","getDate","getDetails","_this$getDate","ToDeviceMessageId","getOriginalContent","_this$event$content","_this$clearEvent$cont","_localRedactionEvent","_replacingEvent","_this$_replacingEvent","_this$getWireContent","relatesTo","unsigned","getUnsigned","replyEventId","_this$getWireContent$","_this$getWireContent2","getPrevContent","prev_content","getDirectionalContent","getLocalAge","getMembershipAtEvent","makeEncrypted","cryptoType","cryptoContent","senderCurve25519Key","claimedEd25519Key","decryptionPromise","isDecryptionFailure","_decryptionFailureReason","decryptionFailureReason","isRedacted","crypto","retryDecryption","decryptionLoop","getKeyRequestRecipients","decryptEvent","isRetry","setClearData","detailedError","DecryptionError","detailedString","setClearDataForDecryptionFailure","code","UNKNOWN_ERROR","decryptionResult","_decryptionResult$sen","_decryptionResult$cla","keyForwardedBy","getClearContent","getSenderKey","getKeysClaimed","ed25519","getClaimedEd25519Key","getForwardingCurve25519KeyChain","isKeySourceUntrusted","getKeyForwardingUser","setUnsigned","unmarkLocallyRedacted","redacted_because","markLocallyRedacted","redactionEvent","applyVisibilityEvent","visibilityChange","_visibilityChange$vis","_visibilityChange$rea","change","messageVisibility","makeRedacted","REDACT_KEEP_KEYS","keeps","REDACT_KEEP_CONTENT_MAP","moveAllRelatedToMainTimeline","moveToMainTimeline","_event$getRelation","getRelation","asVisibilityChange","matches","relation","isVisibilityEvent","getRedactionEvent","_this$clearEvent","_this$event$unsigned","_this$clearEvent$unsi","_this$clearEvent2","pushDetails","pushActions","_this$getAge","oldUnsigned","oldId","isSending","Status","replaceLocalEventId","relType","_this$getWireContent3","_this$getWireContent$2","makeReplaced","newEvent","getAssociatedStatus","_this$getUnsigned$mR","replacingEventId","replaceRelation","replacingEvent","replacingEventDate","Number","isFinite","_this$_replacingEvent2","localRedactionEvent","hasAssociation","flagCancelled","_isCancelled","isCancelled","toSnapshot","isEquivalentTo","otherEvent","myProps","deepSortedObjectEntries","theirProps","decrypted","encrypted","stopReEmitting","ThreadEvent","setThreadId","_this$event$msc4354_s","_this$event$unsigned2","msc4354_sticky","msc4354_sticky_duration_ttl_ms","RoomJoinRules","join_rule","events_default","redact","state_default","users_default","scopeToEventTypeMap","PolicyScope","PolicyRuleUser","PolicyRuleRoom","Server","PolicyRuleServer","addRule","entity","getOrCreateTargetRoom","recommendation","PolicyRecommendation","removeRule","addSource","sources","getOrCreateSourceRooms","withIgnoreInvitesPolicies","ignoreInvitesPolicies","getRuleForInvite","policyRooms","senderServer","roomServer","entities","glob","regexp","RegExp","globToRegexp","test","getIgnoreInvitesPolicies","hasChanges","sourceRooms","getPoliciesAndIgnoreInvitesPolicies","policies","IGNORE_INVITES_ACCOUNT_EVENT_KEY","POLICIES_ACCOUNT_EVENT_TYPE","altName","_this$client$getAccou","hasIgnoreInvitesPolicies","PollEvent","filterResponseRelations","relationEvents","pollEndTimestamp","responseEvents","Poll","matrixClient","undecryptableEventIds","previousCount","undecryptableRelationsCount","undecryptableRelationEventIds","UndecryptableRelations","pollEvent","pollId","endEventId","_this$endEvent","endEvent","isEnded","isFetchingResponses","_isFetchingResponses","getResponses","responses","fetchResponses","onNewRelation","_this$endEvent2","validateEndEvent","refilterResponsesOnEnd","End","MAX_SAFE_INTEGER","countUndecryptableEvents","Responses","_this$endEvent3","_allRelations$nextBat","allRelations","relationsNextBatch","Relations","pollEndEvent","pollCloseTimestamp","_this$endEvent4","getRelations","_this$responses","roomCurrentState","endEventSender","maySendRedactionForEvent","isPollEvent","M_POLL_START","RelationsEvent","altEventTypes","onEventStatus","redactedEvent","Annotation","removeAnnotationFromAggregation","updateTargetEventReplacement","onBeforeRedaction","Redaction","relationEventIds","matchesEventType","targetEventType","altTargetEventTypes","addAnnotationToAggregation","Add","maybeEmitCreated","Remove","eventsForKey","annotationsByKey","sortedAnnotationsByKey","aEvents","eventsFromSender","annotationsBySender","_event$getRelation2","getSortedAnnotationsByKey","getAnnotationsBySender","getLastReplacement","targetEvent","minTs","lastReplacement","last","setTargetEvent","updateId","replacementUpdateId","creationEmitted","RelationsCreated","rawDisplayName","updateModifiedTime","markOutOfBand","_isOutOfBand","isOutOfBand","setMembershipEvent","_event$getDirectional","_event$getDirectional2","oldMembership","disambiguate","shouldDisambiguate","oldName","calculateDisplayName","removeDirectionOverrideChars","removeHiddenChars","Name","recalculateDisambiguatedName","_this$events$member$g","newDisambiguate","powerLevelEvent","oldPowerLevel","PowerLevel","setTypingEvent","oldTyping","typingList","Typing","modified","getLastModifiedTime","isKicked","getDMInviter","memberContent","inviteSender","prev_sender","is_direct","getAvatarUrl","allowDefault","rawUrl","getMxcAvatarUrl","httpUrl","MXID_PATTERN","LTR_RTL_PATTERN","selfUserId","strippedDisplayName","getUserIdsWithDisplayName","OobStatus","oobMemberFlags","NotStarted","getRoomVersion","getVersionWarning","getJoinedMemberCount","summaryJoinedMemberCount","joinedMemberCount","getMembers","setJoinedMemberCount","getInvitedMemberCount","summaryInvitedMemberCount","invitedMemberCount","setInvitedMemberCount","getMembersExcept","excludedIds","sentinel","sentinels","hasLiveBeacons","_this$liveBeaconIds","liveBeaconIds","_liveBeaconIds","copy","eventsByStateKey","Finished","_copy$getMember","unknownStateEvents","markerFoundOptions","affectedDisplayNames","processedMemberUserIds","setBeacon","lastStateEvent","getStateEventMatching","setStateEvent","_event$getContent$dis","newDisplayName","oldDisplayName","userIdsToDisplayNames","strippedOld","strippedNew","updateDisplayNameCache","updateThirdPartyTokenCache","onBeaconLivenessChange","getOrCreateMember","updateMember","creators","getCreators","oldLastModified","pl","powerLevelForUserId","Marker","affectedUserIds","_this$displayNameToUs","displayNameToUserIds","beacons","beaconByEventIdDict","dict","beacon","processBeaconRelation","beaconInfoEventId","relatedToEventId","NewMember","beaconIdentifier","_event$getRedactionEv","New","BeaconLiveness","_this$events$get$get","_this$events$get","pwrLvlEvent","needsOutOfBandMembers","outOfBandMembersReady","markOutOfBandMembersStarted","InProgress","markOutOfBandMembersFailed","setOutOfBandMember","stateEvent","existingMember","getInviteForThreePidToken","tokenToInvite","_this$displayNameToUs2","mxEvent","maySendEvent","hasSufficientPowerLevelFor","powerLevelsEvent","powerLevels","requiredLevel","isNumber","maySendMessage","maySendEventOfType","mayClientSendStateEvent","stateEventType","maySendStateEvent","_roomMember$powerLeve","eventsLevels","stateDefault","eventsDefault","isSafeInteger","mayTriggerNotifOfType","notifLevelKey","notifLevel","getJoinRule","_joinRuleEvent$getCon","joinRuleEvent","_historyVisibilityEve","historyVisibilityEvent","getGuestAccess","_guestAccessEvent$get","guestAccessEvent","predecessorEvent","RoomPredecessor","predecessor_room_id","last_known_event_id","via_servers","third_party_invite","signed","RoomThirdPartyInvite","strippedOldName","existingUserIds","filteredUserIDs","strippedDisplayname","_this$displayNameToUs3","arr","roomVersion","roomCreateEvent","shouldUseHydraForRoomVersion","roomCreateSender","c","Infinity","evContent","isInteger","loggerInstance","assertIsUserId","RoomStickyEventsStore","removedEvents","nextStickyEventExpiryTs","innerEvents","stickyEventsMap","_this$stickyEventsMap","innerMapKey","currentEvent","previousEvents","unkeyedStickyEvents","scheduleStickyTimer","sortStickyEvent","eventA","eventB","_eventB$getId","_eventA$getId","stickyMapKey","stickyKey","getStickyEvents","innerMap","getKeyedStickyEvent","_this$stickyEventsMap2","getUnkeyedStickyEvent","addStickyEvent","_this$stickyEventsMap3","_this$stickyEventsMap4","_this$stickyEventsMap5","currentEventSet","prevEvent","addStickyEvents","stickyEventTimer","cleanExpiredStickyEvents","handleRedaction","redactEventId","_innerMap$get","_this$stickyEventsMap6","mapKey","newEvents","SAFE_ROOM_VERSIONS","ReadReceipt","myUserId","_this$client","RoomReceipts","clearNotificationsOnReceipt","updateThreadRootEvents","_timeline$getEvents","roomEvent","clearEventMetadata","threadsTimelineSets","recreateEvent","_this$threadsTimeline","_this$threadsTimeline2","updateThreadRootEvent","hasCurrentUserParticipated","redactId","stickyEvents","applyEventAsRedaction","getTimelineSets","t","toRedact","pendingEventOrdering","Chronological","normalizedName","Receipt","onReceipt","timelineSets","fixUpLegacyTimelineFields","Detached","pendingEventList","decrypt","serializedEvent","membersPromise","retention","RoomRetentionPolicy","createThreadsTimelineSets","_this$client2","threadTimelineSetsPromise","createThreadTimelineSet","My","decryptCriticalEvents","readReceiptEventId","readReceiptTimelineIndex","findIndex","decryptionPromises","allSettled","decryptAllEvents","getCreator","_createEvent$getSende","getVersion","getRecommendedVersion","capabilities","versionCap","default","safeVer","RoomVersionStability","checkVersionAgainstCapability","urgent","needsUpgrade","currentVersion","userMayUpgradeRoom","removePendingEvent","savePendingEvents","_this$pendingEventLis","_this$pendingEventLis2","getPendingEvent","_this$pendingEventLis3","_this$pendingEventLis4","getLastActiveTimestamp","MIN_SAFE_INTEGER","getLastLiveEvent","_lastRoomEvent$getTs","_lastThreadEvent$getT","lastRoomEvent","lastThread","getLastThread","lastThreadEvent","getThreads","_threadEvent$getTs","_lastThreadEvent$getT2","threadEvent","_this$selfMembership","selfMembership","me","_this$heroes","getInvitedAndJoinedMemberCount","heroes","guessDMUserId","inviterId","anyMember","getFunctionalMembers","mFunctionalMembers","service_members","getAvatarFallbackMember","_this$heroes2","functionalMembers","nonFunctionalMemberCount","nonFunctionalHeroes","h","hasHeroes","hero","fromMSC4186","heroMember","availableMember","nonFunctionalMembers","availableUser","updateMyMembership","prevMembership","cleanupAfterLeaving","MyMembership","loadMembersFromServer","lastSyncToken","getSyncToken","loadMembers","fromServer","rawMembersEvents","memberEvents","membersLoaded","loadMembersIfNeeded","inMemoryUpdate","recalculate","oobMembers","_m$events$member","clearLoadedMembersIfNeeded","refreshLiveTimeline","liveTimelineBefore","backwardPaginationToken","eventsBefore","mostRecentEventInTimeline","TimelineRefresh","setTimelineNeedsRefresh","previousOldState","previousCurrentState","OldStateUpdated","CurrentStateUpdated","threadIds","hasUnthreadedReceipt","receiptGroup","userReceipt","singleReceipt","typedSingleReceipt","getThreadUnreadNotificationCount","_this$getThread","maxHistory","highlightCount","_pushActions$tweaks","timelineNeedsRefresh","getTimelineNeedsRefresh","getRoomUnreadNotificationCount","threadNotification","threadNotifications","_threadNotification$t","_this$notificationCou","notificationCounts","_this$threadNotificat","_this$threadNotificat2","hasThreadUnreadNotification","_notification$highlig","_notification$total","_this$threadNotificat3","_this$threadNotificat4","UnreadNotifications","threadsAggregateNotificationType","_threadNotification$h","_threadNotification$t2","resetThreadUnreadNotificationCountFromSync","exceptThreadIds","notifs","setBumpStamp","bumpStamp","getBumpStamp","setUnread","setSummary","summary","_summary$mHeroes","joinedCount","invitedCount","Summary","setMSC4186SummaryData","setBlacklistUnverifiedDevices","blacklistUnverifiedDevices","getBlacklistUnverifiedDevices","mainUrl","_this$currentState$ge","RoomAvatar","getCanonicalAlias","_this$currentState$ge2","canonicalAlias","RoomCanonicalAlias","getAltAliases","_this$currentState$ge3","altAliases","alt_aliases","_this$retention$shoul","_this$retention","shouldEventBeRetained","_this$threads$get","getJoinedMembers","getMembersWithMembership","getEncryptionTargetMembers","shouldEncryptForInvitedMembers","_ev$getContent","getDefaultRoomName","calculateRoomName","getOrCreateFilteredTimelineSet","prepopulateTimeline","useSyncEvents","filteredTimelineSets","unfilteredLiveTimeline","livePaginationToken","getThreadListFilter","filterType","FILTER_RELATED_BY_REL_TYPES","FILTER_RELATED_BY_SENDERS","currentUserParticipated","createThread","fetchRoomThreads","threadsReady","fetchRoomThreadList","allThreadsFilter","threadRoots","threadAMetadata","threadBMetadata","latest_event","latestMyThreadsRootEvent","_this$threadsTimeline3","Ignore","threadRelationship","_this$threadsTimeline4","current_user_participated","NewReply","onThreadReply","onThreadUpdate","onThreadDelete","processPollEvent","maybeDecryptedEvent","poll","polls","removeFilteredTimelineSet","roots","_this$client3","isThreadRelation","_this$findEventById","addThreadedEvents","addEvents","_this$findEventById2","_this$retention$shoul2","_this$retention2","tryApplyRedaction","eventsByThread","_eventsByThread","threadEvents","_this$cachedThreadRea","relatedEvents","getAllChildEventsForEvent","receipts","cachedThreadReadReceipts","initialEventsFetched","currentStateEvent","visibilityEvents","redactVisibilityChangeEvent","processLiveEvent","applyNewVisibilityEvent","applyPendingVisibilityEvents","transaction_id","tid","txnToEvent","addLiveEventOptions","addReceipt","synthesizeReceipt","aggregateNonLiveRelation","LocalEchoUpdated","isEventEncrypted","setPendingEvents","getEventForTxnId","remoteEvent","oldStatus","allowed","ALLOWED_TRANSITIONS","removedEvent","revertRedactionLocalEcho","RedactionCancelled","assertTimelineSetsAreLive","addLiveEvents","_this$retention$shoul3","_this$retention3","findThreadRoots","neighbouringEvents","_eventsByThread$threa","existingEvent","memo","synthetic","roomReceipts","_receipt$thread_id","_this$unthreadedRecei","_this$unthreadedRecei2","receipt","receiptForMainTimeline","receiptDestination","_this$cachedThreadRea2","addReceiptToStructure","lastEvent","oldestThreadedReceiptTs","unthreadedReceipts","addEphemeralEvents","removeEvents","eventIds","removedAny","membershipEvent","invite_room_state","strippedEvent","normalize","RoomSummary","title","addTags","tags","Tags","addAccountData","accountData","_unstable_getKeyedStickyEvent","_unstable_getUnkeyedStickyEvent","_unstable_addStickyEvents","_this$retention$shoul4","_this$retention4","canInvite","getTypeWarning","isSpaceRoom","isCallRoom","UnstableCall","isElementVideoRoom","ElementVideo","RoomNameType","Actual","Generated","subtype","memberNamesToRoomName","names","EmptyRoom","ignoreRoomNameEvent","_this$currentState$ge4","inviteJoinCount","excludedUserIds","otherNames","otherMembers","collator","Intl","Collator","compare","thirdPartyInvites","thirdPartyNames","display_name","leftNames","visibilityEventsOnOriginalEvent","index","originalEventId","newVisibility","visibilityEvent","getOldestThreadedReceiptTs","getLastUnthreadedReceiptFor","unreadThreads","leftEventId","rightEventId","_this$getLiveTimeline","countWithoutMe","eventMapper","jsonContext","eventsAfter","EventContext","ourEvent","setPaginateToken","rank","_opts$pendingEventOrd","processRootEventPromise","redaction","replyCount","updatePendingReplyCount","_currentUserParticipated","_this$lastEvent","updateThreadMetadata","shouldSendLocalEchoReceipt","onEcho","onRedaction","onLocalEcho","onTimelineReset","onTimelineEvent","processReceipts","fetchRootEvent","eventData","setPreferUnstable","_this$client$canSuppo","_this$getReadReceiptF","oldReceiptEventId","getReadReceiptForUserId","receiptEvent","isNewestReply","addRelatedThreadEvent","fetchEditsWhereNeeded","_this$client$canSuppo2","_this$replayEvents","_this$timelineSet$rel","replayEvents","getRootEventBundledRelationship","processRootEvent","bundledRelationship","_this$lastEvent2","lastPendingEvent","pendingReplyCount","oldLive","newLive","newBackward","oldForward","updateThreadFromRootEvent","initalEventFetchProm","_this$client$canSuppo3","isAnEncryptedThreadMessage","editEvent","_event$event","replyToEvent","_this$lastPendingEven","ignoreSynthesized","isCurrentUser","beforeFirstThreadedReceipt","lastReplyId","readUpToId","unthreadedReceipt","_this$timeline","_ev$getId","_this$lastReply$getTs","_this$lastReply","_this$room$getLastUnt","_this$room$getLastUnt2","_this$lastReply$getTs2","_this$lastReply2","unthreadedReceiptTs","beforeLastUnthreadedReceipt","EventEmitterEvents","EventEmitter","listener","emitPromised","listeners","l","listenerCount","prependListener","prependOnceListener","removeAllListeners","rawListeners","Presence","CurrentlyActive","LastPresenceTs","setPresenceEvent","firstFire","eventsToFire","currently_active","currentlyActive","status_msg","presenceStatusMsg","lastActiveAgo","last_active_ago","lastPresenceTs","eventToFire","setRawDisplayName","oldUrl","getLastActiveTs","OAuthGrantType","len","chars","randomValueCutoff","entropyBuffer","entropyBufferPos","getRandomValues","randomByte","secureRandomStringFrom","UPPERCASE","MatrixScheduler","RETRY_BACKOFF_RATELIMIT","attempts","calculateRetryBackoff","QUEUE_MESSAGES","retryAlgorithm","queueAlgorithm","queueName","peekNextEvent","queues","procFn","removeNextEvent","resolvers","processQueue","waitTimeMs","clearQueue","disableQueue","element","fn","startProcessingQueues","eventResolvers","activeQueues","queue","shift","SECRET_STORAGE_ALGORITHM_V1_AES","accountDataAdapter","getDefaultKeyId","_defaultKey$key","defaultKey","setDefaultKeyId","keyId","newValue","addKey","passphrase","iv","mac","calculateKeyCheck","getKey","hasKey","checkKey","trimTrailingEquals","secret","defaultKeyId","encryption","getSecretStorageKey","encrypt","secretInfo","encInfo","ciphertext","decryption","returned","privateKey","encryptAESSecretStorageItem","decryptAESSecretStorageItem","input","charCodeAt","CapabilityPoller","cached","SlidingSyncState","ExtensionState","SlidingSyncEvent","ReceiptAccumulator","MapWithDefault","setUnthreaded","unthreadedReadReceipts","setThreaded","threadedReadReceipts","getOrCreate","allUnthreaded","allThreaded","receiptsForThread","consumeEphemeralEvents","isSupportedReceiptType","buildAccumulatedReceiptEvent","receiptEventContent","receiptData","recursiveMapToObject","Category","SyncAccumulator","maxTimelineEntries","accumulate","syncResponse","fromDatabase","accumulateRooms","accumulateAccountData","account_data","accumulateRoom","knock","category","knockRooms","accumulateInviteState","accumulateKnockState","inviteRooms","accumulateJoinState","joinRooms","invite_state","currentData","hasAdded","knock_state","_data$ephemeral","_data$state","_data$orgMatrixMsc","_data$timeline","_data$msc4354_sticky","_currentState","_timeline","_accountData","_unreadNotifications","_unreadThreadNotifications","_summary","_receipts","_stickyEvents","unread_notifications","_sum$HEROES_KEY","_sum$JOINED_COUNT_KEY","_sum$INVITED_COUNT_KE","HEROES_KEY","INVITED_COUNT_KEY","JOINED_COUNT_KEY","acc","sum","ephemeral","limited","setState","_data$timeline$prev_b","transformedEvent","_e$unsigned","_localTs","expiresTs","getJSON","forDatabase","_roomData$_stickyEven","roomData","roomJson","unread_thread_notifications","msgData","rollBackState","timelineEvent","prevStateEvent","deepCopy","accData","roomsData","removeEventsFromRoom","eventMap","MSC2716_ROOM_VERSIONS","getFilterName","suffix","SetPresence","defaultClientOpts","initialSyncLimit","resolveInvitesToProfiles","pollTimeout","defaultSyncApiOpts","syncOpts","_roomId","shouldAbortSync","recoverFromSyncStartupError","savedSyncPromise","ThreadUnreadNotifications","buildDefaultFilter","_this$opts$unstableMS","unstableMSC4429SyncUserProfileFields","startKeepAlives","_createAndReEmitRoom","markerEvent","onMarkerStateEvent","HistoryImportedWithinTimeline","_data$rooms","leaveRooms","mapSyncResponseToRoomArray","leaveObj","isBrandNewRoom","mapAndInjectRoomEvents","storeRoom","processEventsForNotifs","_this$_peekRoom","_peekRoom","_this$_peekRoom2","oldStateEvents","presenceEvent","resolveInvites","peekPoll","peekRoom","_this$abortController","syncState","_this$syncStateData","syncStateData","keepaliveProm","updateSyncState","_globalThis$window","_globalThis$window$ad","running","window","onOnline","doSync","savedSyncTokenPromise","getSavedSyncToken","tok","savedSync","syncFromCache","prepareSyncFilterLazyLoading","prepareSyncFilterUserProfiles","currentSyncRequest","firstSyncFilter","savedSyncToken","initialFilter","doSyncRequest","_globalThis$window2","_globalThis$window2$r","_this$abortController2","removeEventListener","keepAliveTimer","connectionReturnedResolvers","nextSyncToken","setSyncToken","syncEventData","catchingUp","processSyncResponse","storeIsInvalid","syncOptions","syncToken","onSyncError","failedSyncCount","oldSyncToken","SyncUnexpectedError","hasSyncedBefore","onSyncCompleted","wantsSave","save","Stopped","_this$abortController3","getSyncParams","getGuestFilter","disablePresence","set_presence","Offline","_cacheBuster","Reconnecting","keepAlivePromise","Catchup","_data$presence","_data$account_data","_data$users","_this$syncOpts$crypto","_data$device_unused_f","prevEventsMap","storeAccountDataEvents","accountDataEvent","PushRules","userUpdate","usersToRemove","profilesToAmend","userData","profile_updates","existingProfile","_userData$profile_upd","UserProfileUpdate","to_device","toDeviceMessages","receivedToDeviceMessages","preprocessToDeviceMessages","rawEvent","encryptionInfo","processToDeviceMessages","notifEvents","inviteObj","mapSyncEventsFormat","injectRoomEvents","_joinObj$UNREAD_THREA","joinObj","stateAfterEvents","ephemeralEvents","accountDataEvents","eventsFormingFinalState","_joinObj$unread_notif","_joinObj$unread_notif2","notification_count","highlight_count","unreadThreadNotifications","unreadNotification","_unreadNotification$n","hasNoNotifications","_unreadNotification$h","_syncEventData$oldSyn","onCryptoEvent","stickyEventsAndStickyEventsFromTheTimeline","emitEvent","knockObj","_client$getNotifTimel","device_lists","processDeviceLists","processKeyCounts","device_one_time_keys_count","device_unused_fallback_key_types","delay","random","pokeKeepAlive","connDidFail","_this$abortController4","success","unsafeProp","requestedProfileInfo","inviteEvent","findEncryptionEvent","wrappedRoom","stateEventList","stateAfterEventList","timelineEventList","eitherStateEventList","newState","old","_client$getUser","cancelledKeyVerificationTxns","processedMessage","processedEvent","toDeviceEvent","deprecatedCompatibilityEvent","senderCurve25519KeyBase64","ToDeviceEvent","ReceivedToDeviceMessage","interns","str","encodeParams","urlSearchParams","URLSearchParams","append","encodeUri","variables","checkObjectHasKeys","x","y","isNaN","prototype","pairs","lexicographicCompare","unhomoglyph","removeHiddenCharsRegex","escapeRegExp","string","ensureNoTrailingSlash","ms","logDuration","block","isNullOrUndefined","promiseMapSeries","o","promiseTry","simpleRetryOperation","promiseFn","shouldRetry","promiseRetry","attempt","retries","factor","minTimeout","maxTimeout","DEFAULT_ALPHABET","fromCharCode","alphabetPad","n","alphabet","padEnd","baseToString","BigInt","_alphabet","d","abs","stringToBase","charIndex","averageBetweenStrings","padN","baseA","baseB","avg","nextString","prevString","recursivelyAssign","source","ignoreNullish","sourceKey","sourceValue","getContentTimestampWithFallback","left","right","mapsEqual","eq","v1","v2","processMapToObjectValue","targetMap","createDefault","aesKey","hmacKey","deriveKeys","decodeBase64","subtle","verify","plaintext","counter","TextDecoder","decode","CallState","CallType","CallDirection","CallParty","CallEvent","CallErrorCode","CALL_TIMEOUT_MS","CallError","genCallID","getCodecParamMods","mediaType","codec","enableDtx","maxAverageBitrate","getTransceiverKey","purpose","MatrixCall","_opts$forceTURN","Fledgling","candidate","candidatesEnded","sdpMid","callHasEnded","queueCandidate","_this$peerConn","peerConn","iceGatheringState","LocalOfferFailed","terminate","Local","successor","getUserMediaFailed","NoUserMedia","placeCallFailed","IceFailed","_this$peerConn2","_this$peerConn3","_this$peerConn$iceCon","_this$peerConn4","_this$peerConn5","_this$peerConn8","iceConnectionState","connectionState","iceDisconnectedTimeout","iceReconnectionTimeOut","Connected","callLengthInterval","callStartTime","LengthChanged","round","_this$peerConn6","_this$peerConn7","restartIce","hangup","_this$peerConn9","_this$peerConn0","_this$peerConn1","Connecting","feed","getRemoteFeeds","setAudioVideoMuted","_this$peerConn10","signalingState","streams","track","stream","pushRemoteFeed","removeTrackListeners","onRemoveTrack","getTracks","deleteFeedByStream","DataChannel","channel","CreateOffer","opponentVersion","queueGotLocalOffer","partyIdMatches","Ringing","Remote","UserHangup","party_id","opponentPartyId","InviteSent","Inbound","AnsweredElsewhere","invitee","ourPartyId","opponentDeviceId","opponentSessionId","groupCallId","isOnlyDataChannelAllowed","placeVoiceCall","placeCall","placeVideoCall","createDataChannel","label","dataChannel","getOpponentMember","opponentMember","getOpponentDeviceId","getOpponentSessionId","opponentCanBeTransferred","opponentCaps","opponentSupportsDTMF","getRemoteAssertedIdentity","remoteAssertedIdentity","_state","State","hasUserMediaVideoSender","hasRemoteUserMediaVideoTrack","Video","Voice","hasLocalUserMediaVideoTrack","_this$localUsermediaS","localUsermediaStream","getVideoTracks","_feed$stream","SDPStreamMetadataPurpose","Usermedia","hasLocalUserMediaAudioTrack","_this$localUsermediaS2","getAudioTracks","hasRemoteUserMediaAudioTrack","_feed$stream2","hasUserMediaAudioSender","_this$transceivers$ge","transceivers","_this$transceivers$ge2","localUsermediaFeed","getLocalFeeds","localScreensharingFeed","Screenshare","_this$localUsermediaF","localScreensharingStream","_this$localScreenshar","remoteUsermediaFeed","remoteScreensharingFeed","remoteUsermediaStream","_this$remoteUsermedia","remoteScreensharingStream","_this$remoteScreensha","getFeedByStreamId","streamId","getFeeds","feeds","isLocal","initOpponentCrypto","_this$getOpponentMemb","hasOpponentDeviceInfo","GroupCallUnknownDeviceError","getLocalSDPStreamMetadata","updateStreamIds","localFeed","sdpMetadataStreamId","audio_muted","isAudioMuted","video_muted","isVideoMuted","noIncomingFeeds","opponentSupportsSDPStreamMetadata","pushRemoteFeedWithoutMetadata","remoteSDPStreamMetadata","audioMuted","videoMuted","CallFeed","FeedsChanged","active","_this$feeds$find","oldRemoteStream","pushNewLocalFeed","addToPeerConnection","setTracksEnabled","pushLocalFeed","callFeed","tKey","transceiver","replaceTrack","addTrack","newTransceiver","getTransceivers","removeLocalFeed","audioTransceiverKey","videoTransceiverKey","transceiverKey","removeTrack","stopScreensharingStream","deleteFeed","deleteAllFeeds","dispose","getCurrentCallStats","callStatsAtEnd","collectCallStats","statsReport","getStats","stats","item","initWithInvite","_this$feeds$find2","sdpStreamMetadata","SDPStreamMetadataKey","updateRemoteSDPStreamMetadata","createPeerConnection","PeerConnectionCreated","chooseOpponent","setRemoteDescription","offer","addBufferedIceCandidates","SetRemoteDescription","remoteStream","ringingTimer","_this$stats","hangupParty","Ended","stopAllMedia","close","removeStatsReportGatherer","Hangup","onState","initWithHangup","shouldAnswerWithMediaType","wantedValue","valueOfTheOtherSide","answer","audio","video","inviteOrAnswerSent","waitForLocalAVStream","WaitLocalMedia","prevState","answerWithAudio","answerWithVideo","_this$client$getDevic","getUserMediaStream","answerWithCallFeeds","callFeeds","queueGotCallFeedsForAnswer","replacedBy","newCall","Outbound","suppressEvent","sendVoipEvent","CallHangup","CallReject","upgradeCall","getAudio","getVideo","updateLocalUsermediaStream","isScreensharing","setScreensharingEnabled","setScreensharingEnabledWithoutMetadataSupport","audioTransceiver","videoTransceiver","getScreensharingStream","_this$localUsermediaS3","_this$transceivers$ge4","_this$transceivers$ge3","forceAudio","forceVideo","audioEnabled","remoteOnHold","videoEnabled","oldSender","setLocalVideoMuted","muted","_this$localUsermediaF3","_this$localUsermediaF2","stopVideoTrackTimer","hasVideoDevice","isLocalVideoMuted","updateMuteStatus","sendMetadataUpdate","_this$localUsermediaF4","_this$localUsermediaF5","setMicrophoneMuted","_this$localUsermediaF6","hasAudioDevice","isMicrophoneMuted","_this$localUsermediaF7","_this$localUsermediaF8","isRemoteOnHold","setRemoteOnHold","onHold","RemoteHoldUnhold","isLocalOnHold","callOnHold","currentDirection","sendDtmfDigit","digit","getSenders","_sender$track","dtmf","insertDTMF","micShouldBeMuted","vidShouldBeMuted","CallSDPStreamMetadataChanged","gotCallFeedsForInvite","requestScreenshareFeed","addTransceiver","sendAnswer","answerContent","sdp","localDescription","discardCount","discardDuplicateCandidates","CallAnswer","SendAnswer","UnknownDevices","sendCandidateQueue","responsePromiseChain","gotCallFeedsForAnswer","mungeSdp","mods","parseSdp","media","payloadTypeToCodecMap","codecToPayloadTypeMap","rtp","mod","extraConfig","found","fmtp","config","writeSdp","createOffer","createAnswer","CreateAnswer","getRidOfRTXCodecs","setLocalDescription","SetLocalDescription","onRemoteIceCandidatesReceived","candidates","fromPartyId","addIceCandidates","bufferedCandidates","remoteCandidateBuffer","onAnswerReceived","isSettingRemoteAnswerPending","CallSelectAnswer","selected_party_id","onSelectAnswerReceived","selectedPartyId","onNegotiateReceived","polite","readyForOffer","makingOffer","offerCollision","ignoreOffer","prevLocalOnHold","_localDescription","CallNegotiate","newLocalOnHold","LocalHoldUnhold","HoldUnhold","_streamId","onSDPStreamMetadataChangedReceived","onAssertedIdentityReceived","asserted_identity","AssertedIdentityChanged","wrappedGotLocalOffer","gotLocalOffer","getLocalOfferFailed","CallInvite","_localDescription2","_localDescription3","SignallingFailed","SendInvite","inviteTimeout","InviteTimeout","RTCRtpReceiver","RTCRtpSender","screenshareVideoTransceiver","setCodecPreferences","recvCodecs","codecs","sendCodecs","mimeType","pop","realContent","conf_id","_this$getOpponentMemb2","toDeviceSeq","sender_session_id","dest_session_id","randomUUID","SendVoipEvent","_this$getOpponentMemb3","candidateSendQueue","candidateSendTries","newQueue","transfer","targetUserId","profileInfo","replacementId","replacement_id","target_user","create_call","CallReplaces","Transferred","transferToCall","transferTargetCall","_transferTargetCall$g","_this$getOpponentMemb4","targetProfileInfo","opponentUserId","transfereeProfileInfo","newCallId","bodyToTransferTarget","await_call","bodyToTransferee","hangupReason","shouldEmit","_this$stats2","calls","stopUserMediaStream","checkForErrorListener","CallCandidates","delayMs","pow","_this$client$getDevic2","placeCallWithCallFeeds","_this$stats3","pc","RTCPeerConnection","iceTransportPolicy","iceServers","bundlePolicy","onIceConnectionStateChanged","onSignallingStateChanged","gotLocalIceCandidate","onIceGatheringStateChange","onTrack","onNegotiationNeeded","onDataChannel","opponentMemberId","addStatsReportGatherer","_getMember","_this$stats4","updateOpponentMember","sdpMLineIndex","addIceCandidate","hasPeerConnection","initStats","peerId","tracks","document","_window$RTCPeerConnec","RTCSessionDescription","RTCIceCandidate","navigator","mediaDevices","optionsForceTURN","CallFeedEvent","NewStream","connected","analyser","measuringVolumeActivity","getFloatFrequencyData","frequencyBinCount","maxVolume","volume","speakingVolumeSamples","VolumeChanged","newSpeaking","speakingThreshold","speaking","Speaking","volumeLooperTimeout","volumeLooper","fill","updateStream","hasAudioTrack","initVolumeMeasuring","onCallState","_connected","ConnectedChanged","oldStream","newStream","wasMeasuringVolumeActivity","onAddTrack","measureVolumeActivity","audioContext","acquireContext","createAnalyser","fftSize","smoothingTimeConstant","createMediaStreamSource","Float32Array","_callRoom$getMember","callRoom","isSpeaking","setNewStream","MuteStateChanged","setSpeakingThreshold","threshold","userMediaStreams","screensharingStreams","_this$stream","_this$call","releaseContext","_disposed","Disposed","disposed","getLocalVolume","localVolume","setLocalVolume","LocalVolumeChanged","GroupCallIntent","GroupCallType","GroupCallTerminationReason","GroupCallEvent","GroupCallStatsReportEvent","GroupCallErrorCode","GroupCallError","UnknownDevice","GroupCallState","DEVICE_TIMEOUT","getCallUserId","_call$getOpponentMemb","isCallWithoutVideoAndAudio","useLivekit","_room$currentState$ge","_room$currentState$ge2","report","ConnectionStats","ByteSentStats","SummaryStatsReportGatherer","extendSummaryReport","participants","SummaryStats","localCallFeed","CallFeedStatsReporter","expandCallFeedReport","forEachCall","CallFeedStats","LocalCallFeedUninitialized","_newCall$getOpponentM","_this$calls$get","deviceMap","prevCall","initCall","callExpected","CallsChanged","needsRetry","participantMap","callMap","retriesMap","retryCallCounts","participant","_retriesMap$get","_retriesMap","wantsOutgoingCall","placeOutgoingCalls","currentUserMediaFeed","getUserMediaFeed","remoteFeedChanged","currentCallForUserDevice","addUserMediaFeed","replaceUserMediaFeed","removeUserMediaFeed","currentScreenshareFeed","getScreenshareFeed","addScreenshareFeed","replaceScreenshareFeed","removeScreenshareFeed","_oldState","_call$getOpponentMemb2","_call$getOpponentMemb3","_call$getOpponentMemb4","disposeCall","topAvg","nextActiveSpeaker","userMediaFeeds","SPEAKING_THRESHOLD","activeSpeaker","ActiveSpeakerChanged","updateParticipants","expected","Entered","updateMemberState","ReEmitter","_livekitServiceURL","creationTs","GroupCallPrefix","ParticipantsChanged","onParticipantsChanged","GroupCallStateChanged","onStateChanged","LocalScreenshareStateChanged","onLocalFeedsChanged","allowCallWithoutVideoAndAudio","GroupCallEventHandlerEvent","Outgoing","sendCallStateEvent","groupCallState","updateLivekitServiceURL","prevValue","_participants","participantStateEqual","screensharing","deviceMapsEqual","_creationTs","enteredViaAnotherSession","_enteredViaAnotherSession","localScreenshareFeed","hasLocalParticipant","_this$participants$ge","_this$participants$ge2","_this$participants$ge3","initLocalCallFeed","InitializingLocalCallFeed","initCallFeedPromise","initLocalCallFeedInternal","MediaStream","initWithAudioMuted","initWithVideoMuted","LocalCallFeedInitialized","enter","CallEventHandlerEvent","Incoming","onIncomingCall","retryCallLoopInterval","onRetryCallLoop","retryCallInterval","onActiveSpeakerLoop","activeSpeakerLoopInterval","activeSpeakerInterval","localDesktopCapturerSourceId","stopAllStreams","transmitTimer","participantsExpirationTimer","emitStateEvent","existingStateEvent","CallEnded","sendUpdatesBefore","pttMaxTransmitTime","_call$localUsermediaF","sendUpdates","updates","checkAudioPermissionIfNecessary","LocalMuteStateChanged","onTrackEnded","desktopCapturerSourceId","throwOnFail","localUserId","localDeviceId","callsChanged","_this$calls$get2","NewSession","PlaceCallFailed","getMemberStateEvents","onCallFeedsChanged","onCallStateChanged","onCallHangup","onCallReplaced","callHandlers","getGroupCallStats","usermediaFeed","screenshareFeed","UserMediaFeedsChanged","existingFeed","replacementFeed","feedIndex","screenshareFeeds","ScreenshareFeedsChanged","localMember","entered","nextExpiration","validDevices","session_id","expires_ts","updateDevices","keepAlive","_event$getContent","otherCalls","newDevices","newCalls","newContent","addDeviceToMemberState","resendMemberStateTimer","cleanMemberState","myDevices","device","last_seen_ts","userID","GroupCallStats","statsCollectIntervalTime","reports","StatsReport","CONNECTION_STATS","onConnectionStats","BYTE_SENT_STATS","onByteSentStats","SUMMARY_STATS","onSummaryStats","CALL_FEED_REPORT","onCallFeedReport","setGroupCallStatsInterval","interval","MediaHandlerEvent","restoreMediaSettings","audioInput","videoInput","setAudioInput","updateLocalUsermediaStreams","setAudioSettings","audioSettings","setVideoInput","setMediaInputs","callMediaStreamParams","localUserMediaStream","groupCall","LocalStreamsChanged","enumerateDevices","reusable","getMediaStreamPromise","getUserMediaStreamInternal","shouldRequestAudio","shouldRequestVideo","canReuseStream","_this$localUserMediaS","_this$localUserMediaS2","getSettings","_this$localUserMediaS3","constraints","getUserMediaContraints","getUserMedia","settings","mediaStream","_this$localUserMediaS4","getTrackById","screenshareConstraints","getScreenshareContraints","getDisplayMedia","matchingStream","exactDeviceId","isWebkit","webkitGetUserMedia","deviceIdKey","audioConstraints","autoGainControl","ideal","echoCancellation","noiseSuppression","videoConstraints","exact","mandatory","chromeMediaSource","chromeMediaSourceId","__WEBPACK_AMD_DEFINE_FACTORY__","__WEBPACK_AMD_DEFINE_RESULT__","noop","undefinedType","isIE","userAgent","logMethods","_loggersByName","defaultLogger","bindMethod","Function","apply","arguments","traceForIE","realMethod","replaceLoggingMethods","level","getLevel","levels","SILENT","enableLoggingWhenConsoleArrives","defaultMethodFactory","_level","_loggerName","Logger","factory","inheritedLevel","defaultLevel","userLevel","self","persistLevelIfPossible","levelNum","levelName","toUpperCase","localStorage","ignore","cookie","getPersistedLevel","storedLevel","cookieName","exec","clearPersistedLevel","removeItem","normalizeLevel","TypeError","TRACE","INFO","WARN","ERROR","persist","setDefaultLevel","resetLevel","enableAll","disableAll","childName","initialLevel","getLogger","_log","noConflict","getLoggers","exports","module","_typeof","Symbol","iterator","toPropertyKey","toPrimitive","defineProperty","enumerable","configurable","writable"],"sourceRoot":""}