add session.observeRoomState to observe state changes in all rooms

and use it for calls
this won't be called for state already received and stored in storage,
that you need to still do yourself
This commit is contained in:
Bruno Windels 2022-05-12 11:58:28 +02:00
parent ec1568cf1c
commit d727dfd843
6 changed files with 86 additions and 28 deletions

View file

@ -15,11 +15,19 @@ limitations under the License.
*/ */
export {Logger} from "./logging/Logger"; export {Logger} from "./logging/Logger";
export type {ILogItem} from "./logging/types";
export {IDBLogPersister} from "./logging/IDBLogPersister"; export {IDBLogPersister} from "./logging/IDBLogPersister";
export {ConsoleReporter} from "./logging/ConsoleReporter"; export {ConsoleReporter} from "./logging/ConsoleReporter";
export {Platform} from "./platform/web/Platform.js"; export {Platform} from "./platform/web/Platform.js";
export {Client, LoadStatus} from "./matrix/Client.js"; export {Client, LoadStatus} from "./matrix/Client.js";
export {RoomStatus} from "./matrix/room/common"; 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 main view & view models
export {createNavigation, createRouter} from "./domain/navigation/index.js"; export {createNavigation, createRouter} from "./domain/navigation/index.js";
export {RootViewModel} from "./domain/RootViewModel.js"; export {RootViewModel} from "./domain/RootViewModel.js";

View file

@ -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<RoomStateHandler> 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<string, MemberChange>) {
for(let h of this._handlers) {
h.updateRoomMembers(room, memberChanges);
}
}
}

View file

@ -48,6 +48,7 @@ import {SecretStorage} from "./ssss/SecretStorage";
import {ObservableValue} from "../observable/value/ObservableValue"; import {ObservableValue} from "../observable/value/ObservableValue";
import {RetainedObservableValue} from "../observable/value/RetainedObservableValue"; import {RetainedObservableValue} from "../observable/value/RetainedObservableValue";
import {CallHandler} from "./calls/CallHandler"; import {CallHandler} from "./calls/CallHandler";
import {RoomStateHandlerSet} from "./RoomStateHandlerSet";
const PICKLE_KEY = "DEFAULT_KEY"; const PICKLE_KEY = "DEFAULT_KEY";
const PUSHER_KEY = "pusher"; const PUSHER_KEY = "pusher";
@ -101,6 +102,8 @@ export class Session {
}], }],
forceTURN: false, forceTURN: false,
}); });
this._roomStateHandler = new RoomStateHandlerSet();
this.observeRoomState(this._callHandler);
this._deviceMessageHandler = new DeviceMessageHandler({storage, callHandler: this._callHandler}); this._deviceMessageHandler = new DeviceMessageHandler({storage, callHandler: this._callHandler});
this._olm = olm; this._olm = olm;
this._olmUtil = null; this._olmUtil = null;
@ -595,7 +598,7 @@ export class Session {
user: this._user, user: this._user,
createRoomEncryption: this._createRoomEncryption, createRoomEncryption: this._createRoomEncryption,
platform: this._platform, platform: this._platform,
callHandler: this._callHandler roomStateHandler: this._roomStateHandler
}); });
} }
@ -937,6 +940,10 @@ export class Session {
return observable; return observable;
} }
observeRoomState(roomStateHandler) {
return this._roomStateHandler.subscribe(roomStateHandler);
}
/** /**
Creates an empty (summary isn't loaded) the archived room if it isn't 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 loaded already, assuming sync will either remove it (when rejoining) or

View file

@ -35,6 +35,7 @@ import type {Options as GroupCallOptions} from "./group/GroupCall";
import type {Transaction} from "../storage/idb/Transaction"; import type {Transaction} from "../storage/idb/Transaction";
import type {CallEntry} from "../storage/idb/stores/CallStore"; import type {CallEntry} from "../storage/idb/stores/CallStore";
import type {Clock} from "../../platform/web/dom/Clock"; import type {Clock} from "../../platform/web/dom/Clock";
import type {RoomStateHandler} from "../room/common";
export type Options = Omit<GroupCallOptions, "emitUpdate" | "createTimeout"> & { export type Options = Omit<GroupCallOptions, "emitUpdate" | "createTimeout"> & {
clock: Clock clock: Clock
@ -44,7 +45,7 @@ function getRoomMemberKey(roomId: string, userId: string): string {
return JSON.stringify(roomId)+`,`+JSON.stringify(userId); return JSON.stringify(roomId)+`,`+JSON.stringify(userId);
} }
export class CallHandler { export class CallHandler implements RoomStateHandler {
// group calls by call id // group calls by call id
private readonly _calls: ObservableMap<string, GroupCall> = new ObservableMap<string, GroupCall>(); private readonly _calls: ObservableMap<string, GroupCall> = new ObservableMap<string, GroupCall>();
// map of `"roomId","userId"` to set of conf_id's they are in // 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 // TODO: check and poll turn server credentials here
/** @internal */ /** @internal */
handleRoomState(room: Room, events: StateEvent[], txn: Transaction, log: ILogItem) { handleRoomState(room: Room, event: StateEvent, txn: Transaction, log: ILogItem) {
// first update call events if (event.type === EventType.GroupCall) {
for (const event of events) { this.handleCallEvent(event, room.id, txn, log);
if (event.type === EventType.GroupCall) {
this.handleCallEvent(event, room.id, txn, log);
}
} }
// then update members if (event.type === EventType.GroupCallMember) {
for (const event of events) { this.handleCallMemberEvent(event, room.id, log);
if (event.type === EventType.GroupCallMember) {
this.handleCallMemberEvent(event, room.id, log);
}
} }
} }

View file

@ -30,7 +30,7 @@ const EVENT_ENCRYPTED_TYPE = "m.room.encrypted";
export class Room extends BaseRoom { export class Room extends BaseRoom {
constructor(options) { constructor(options) {
super(options); super(options);
this._callHandler = options.callHandler; this._roomStateHandler = options.roomStateHandler;
// TODO: pass pendingEvents to start like pendingOperations? // TODO: pass pendingEvents to start like pendingOperations?
const {pendingEvents} = options; const {pendingEvents} = options;
const relationWriter = new RelationWriter({ const relationWriter = new RelationWriter({
@ -179,7 +179,7 @@ export class Room extends BaseRoom {
removedPendingEvents = await this._sendQueue.removeRemoteEchos(roomResponse.timeline.events, txn, log); removedPendingEvents = await this._sendQueue.removeRemoteEchos(roomResponse.timeline.events, txn, log);
} }
const powerLevelsEvent = this._getPowerLevelsEvent(roomResponse); const powerLevelsEvent = this._getPowerLevelsEvent(roomResponse);
this._updateCallHandler(roomResponse, txn, log); this._updateRoomStateHandler(roomResponse, txn, log);
return { return {
summaryChanges, summaryChanges,
roomEncryption, roomEncryption,
@ -217,9 +217,7 @@ export class Room extends BaseRoom {
if (this._memberList) { if (this._memberList) {
this._memberList.afterSync(memberChanges); this._memberList.afterSync(memberChanges);
} }
if (this._callHandler) { this._roomStateHandler.updateRoomMembers(this, memberChanges);
this._callHandler.updateRoomMembers(this, memberChanges);
}
if (this._observedMembers) { if (this._observedMembers) {
this._updateObservedMembers(memberChanges); this._updateObservedMembers(memberChanges);
} }
@ -447,17 +445,19 @@ export class Room extends BaseRoom {
return this._sendQueue.pendingEvents; return this._sendQueue.pendingEvents;
} }
_updateCallHandler(roomResponse, txn, log) { _updateRoomStateHandler(roomResponse, txn, log) {
if (this._callHandler) { const stateEvents = roomResponse.state?.events;
const stateEvents = roomResponse.state?.events; if (stateEvents) {
if (stateEvents?.length) { for (let i = 0; i < stateEvents.length; i++) {
this._callHandler.handleRoomState(this, stateEvents, txn, log); this._roomStateHandler.handleRoomState(this, stateEvents[i], txn, log);
} }
let timelineEvents = roomResponse.timeline?.events; }
if (timelineEvents) { let timelineEvents = roomResponse.timeline?.events;
const timelineStateEvents = timelineEvents.filter(e => typeof e.state_key === "string"); if (timelineEvents) {
if (timelineEvents.length !== 0) { for (let i = 0; i < timelineEvents.length; i++) {
this._callHandler.handleRoomState(this, timelineStateEvents, txn, log); const event = timelineEvents[i];
if (typeof event.state_key === "string") {
this._roomStateHandler.handleRoomState(this, event, txn, log);
} }
} }
} }

View file

@ -14,6 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License. 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) { export function getPrevContentFromStateEvent(event) {
// where to look for prev_content is a bit of a mess, // 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 // 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, Private,
Public Public
} }
export interface RoomStateHandler {
handleRoomState(room: Room, stateEvent: StateEvent, txn: Transaction, log: ILogItem);
updateRoomMembers(room: Room, memberChanges: Map<string, MemberChange>);
}