diff --git a/src/lib.ts b/src/lib.ts index 0fc6f539..8c5c4e8e 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -15,11 +15,19 @@ limitations under the License. */ export {Logger} from "./logging/Logger"; +export type {ILogItem} from "./logging/types"; export {IDBLogPersister} from "./logging/IDBLogPersister"; export {ConsoleReporter} from "./logging/ConsoleReporter"; export {Platform} from "./platform/web/Platform.js"; export {Client, LoadStatus} from "./matrix/Client.js"; export {RoomStatus} from "./matrix/room/common"; +// export everything needed to observe state events on all rooms using session.observeRoomState +export type {RoomStateHandler} from "./matrix/room/common"; +export type {MemberChange} from "./matrix/room/members/RoomMember"; +export type {Transaction} from "./matrix/storage/idb/Transaction"; +export type {Room} from "./matrix/room/Room"; +export type {StateEvent} from "./matrix/storage/types"; + // export main view & view models export {createNavigation, createRouter} from "./domain/navigation/index.js"; export {RootViewModel} from "./domain/RootViewModel.js"; diff --git a/src/matrix/RoomStateHandlerSet.ts b/src/matrix/RoomStateHandlerSet.ts new file mode 100644 index 00000000..cf202097 --- /dev/null +++ b/src/matrix/RoomStateHandlerSet.ts @@ -0,0 +1,37 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import type {ILogItem} from "../logging/types"; +import type {StateEvent} from "./storage/types"; +import type {Transaction} from "./storage/idb/Transaction"; +import type {Room} from "./room/Room"; +import type {MemberChange} from "./room/members/RoomMember"; +import type {RoomStateHandler} from "./room/common"; +import {BaseObservable} from "../observable/BaseObservable"; + +/** keeps track of all handlers registered with Session.observeRoomState */ +export class RoomStateHandlerSet extends BaseObservable implements RoomStateHandler { + handleRoomState(room: Room, stateEvent: StateEvent, txn: Transaction, log: ILogItem) { + for(let h of this._handlers) { + h.handleRoomState(room, stateEvent, txn, log); + } + } + updateRoomMembers(room: Room, memberChanges: Map) { + for(let h of this._handlers) { + h.updateRoomMembers(room, memberChanges); + } + } +} diff --git a/src/matrix/Session.js b/src/matrix/Session.js index 9d63d335..6211c456 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -48,6 +48,7 @@ import {SecretStorage} from "./ssss/SecretStorage"; import {ObservableValue} from "../observable/value/ObservableValue"; import {RetainedObservableValue} from "../observable/value/RetainedObservableValue"; import {CallHandler} from "./calls/CallHandler"; +import {RoomStateHandlerSet} from "./RoomStateHandlerSet"; const PICKLE_KEY = "DEFAULT_KEY"; const PUSHER_KEY = "pusher"; @@ -101,6 +102,8 @@ export class Session { }], forceTURN: false, }); + this._roomStateHandler = new RoomStateHandlerSet(); + this.observeRoomState(this._callHandler); this._deviceMessageHandler = new DeviceMessageHandler({storage, callHandler: this._callHandler}); this._olm = olm; this._olmUtil = null; @@ -595,7 +598,7 @@ export class Session { user: this._user, createRoomEncryption: this._createRoomEncryption, platform: this._platform, - callHandler: this._callHandler + roomStateHandler: this._roomStateHandler }); } @@ -937,6 +940,10 @@ export class Session { return observable; } + observeRoomState(roomStateHandler) { + return this._roomStateHandler.subscribe(roomStateHandler); + } + /** Creates an empty (summary isn't loaded) the archived room if it isn't loaded already, assuming sync will either remove it (when rejoining) or diff --git a/src/matrix/calls/CallHandler.ts b/src/matrix/calls/CallHandler.ts index 07ed8492..2386076b 100644 --- a/src/matrix/calls/CallHandler.ts +++ b/src/matrix/calls/CallHandler.ts @@ -35,6 +35,7 @@ import type {Options as GroupCallOptions} from "./group/GroupCall"; import type {Transaction} from "../storage/idb/Transaction"; import type {CallEntry} from "../storage/idb/stores/CallStore"; import type {Clock} from "../../platform/web/dom/Clock"; +import type {RoomStateHandler} from "../room/common"; export type Options = Omit & { clock: Clock @@ -44,7 +45,7 @@ function getRoomMemberKey(roomId: string, userId: string): string { return JSON.stringify(roomId)+`,`+JSON.stringify(userId); } -export class CallHandler { +export class CallHandler implements RoomStateHandler { // group calls by call id private readonly _calls: ObservableMap = new ObservableMap(); // map of `"roomId","userId"` to set of conf_id's they are in @@ -143,18 +144,12 @@ export class CallHandler { // TODO: check and poll turn server credentials here /** @internal */ - handleRoomState(room: Room, events: StateEvent[], txn: Transaction, log: ILogItem) { - // first update call events - for (const event of events) { - if (event.type === EventType.GroupCall) { - this.handleCallEvent(event, room.id, txn, log); - } + handleRoomState(room: Room, event: StateEvent, txn: Transaction, log: ILogItem) { + if (event.type === EventType.GroupCall) { + this.handleCallEvent(event, room.id, txn, log); } - // then update members - for (const event of events) { - if (event.type === EventType.GroupCallMember) { - this.handleCallMemberEvent(event, room.id, log); - } + if (event.type === EventType.GroupCallMember) { + this.handleCallMemberEvent(event, room.id, log); } } diff --git a/src/matrix/room/Room.js b/src/matrix/room/Room.js index 34f35af8..556cef7a 100644 --- a/src/matrix/room/Room.js +++ b/src/matrix/room/Room.js @@ -30,7 +30,7 @@ const EVENT_ENCRYPTED_TYPE = "m.room.encrypted"; export class Room extends BaseRoom { constructor(options) { super(options); - this._callHandler = options.callHandler; + this._roomStateHandler = options.roomStateHandler; // TODO: pass pendingEvents to start like pendingOperations? const {pendingEvents} = options; const relationWriter = new RelationWriter({ @@ -179,7 +179,7 @@ export class Room extends BaseRoom { removedPendingEvents = await this._sendQueue.removeRemoteEchos(roomResponse.timeline.events, txn, log); } const powerLevelsEvent = this._getPowerLevelsEvent(roomResponse); - this._updateCallHandler(roomResponse, txn, log); + this._updateRoomStateHandler(roomResponse, txn, log); return { summaryChanges, roomEncryption, @@ -217,9 +217,7 @@ export class Room extends BaseRoom { if (this._memberList) { this._memberList.afterSync(memberChanges); } - if (this._callHandler) { - this._callHandler.updateRoomMembers(this, memberChanges); - } + this._roomStateHandler.updateRoomMembers(this, memberChanges); if (this._observedMembers) { this._updateObservedMembers(memberChanges); } @@ -447,17 +445,19 @@ export class Room extends BaseRoom { return this._sendQueue.pendingEvents; } - _updateCallHandler(roomResponse, txn, log) { - if (this._callHandler) { - const stateEvents = roomResponse.state?.events; - if (stateEvents?.length) { - this._callHandler.handleRoomState(this, stateEvents, txn, log); + _updateRoomStateHandler(roomResponse, txn, log) { + const stateEvents = roomResponse.state?.events; + if (stateEvents) { + for (let i = 0; i < stateEvents.length; i++) { + this._roomStateHandler.handleRoomState(this, stateEvents[i], txn, log); } - let timelineEvents = roomResponse.timeline?.events; - if (timelineEvents) { - const timelineStateEvents = timelineEvents.filter(e => typeof e.state_key === "string"); - if (timelineEvents.length !== 0) { - this._callHandler.handleRoomState(this, timelineStateEvents, txn, log); + } + let timelineEvents = roomResponse.timeline?.events; + if (timelineEvents) { + for (let i = 0; i < timelineEvents.length; i++) { + const event = timelineEvents[i]; + if (typeof event.state_key === "string") { + this._roomStateHandler.handleRoomState(this, event, txn, log); } } } diff --git a/src/matrix/room/common.ts b/src/matrix/room/common.ts index 57ab7023..2fdc2483 100644 --- a/src/matrix/room/common.ts +++ b/src/matrix/room/common.ts @@ -14,6 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ +import type {Room} from "./Room"; +import type {StateEvent} from "../storage/types"; +import type {Transaction} from "../storage/idb/Transaction"; +import type {ILogItem} from "../../logging/types"; +import type {MemberChange} from "./members/RoomMember"; + export function getPrevContentFromStateEvent(event) { // where to look for prev_content is a bit of a mess, // see https://matrix.to/#/!NasysSDfxKxZBzJJoE:matrix.org/$DvrAbZJiILkOmOIuRsNoHmh2v7UO5CWp_rYhlGk34fQ?via=matrix.org&via=pixie.town&via=amorgan.xyz @@ -40,3 +46,8 @@ export enum RoomType { Private, Public } + +export interface RoomStateHandler { + handleRoomState(room: Room, stateEvent: StateEvent, txn: Transaction, log: ILogItem); + updateRoomMembers(room: Room, memberChanges: Map); +}