From db053385962e0272c86f0ca9b998633f8325a3a1 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 12 May 2022 17:26:29 +0200 Subject: [PATCH] extract function to iterate over room response state events --- src/matrix/room/ArchivedRoom.js | 8 ++-- src/matrix/room/Room.js | 32 +++++++-------- src/matrix/room/RoomSummary.js | 25 ++---------- src/matrix/room/common.ts | 69 ++++++++++++++++++++++++++++++++- 4 files changed, 91 insertions(+), 43 deletions(-) diff --git a/src/matrix/room/ArchivedRoom.js b/src/matrix/room/ArchivedRoom.js index 1a23d25b..86595163 100644 --- a/src/matrix/room/ArchivedRoom.js +++ b/src/matrix/room/ArchivedRoom.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {reduceStateEvents} from "./RoomSummary.js"; +import {iterateResponseStateEvents} from "./common"; import {BaseRoom} from "./BaseRoom.js"; import {RoomMember, EVENT_TYPE as MEMBER_EVENT_TYPE} from "./members/RoomMember.js"; @@ -173,15 +173,15 @@ export class ArchivedRoom extends BaseRoom { } function findKickDetails(roomResponse, ownUserId) { - const kickEvent = reduceStateEvents(roomResponse, (kickEvent, event) => { + let kickEvent; + iterateResponseStateEvents(roomResponse, event => { if (event.type === MEMBER_EVENT_TYPE) { // did we get kicked? if (event.state_key === ownUserId && event.sender !== event.state_key) { kickEvent = event; } } - return kickEvent; - }, null); + }); if (kickEvent) { return { // this is different from the room membership in the sync section, which can only be leave diff --git a/src/matrix/room/Room.js b/src/matrix/room/Room.js index 556cef7a..a2eadfeb 100644 --- a/src/matrix/room/Room.js +++ b/src/matrix/room/Room.js @@ -23,6 +23,7 @@ import {WrappedError} from "../error.js" import {Heroes} from "./members/Heroes.js"; import {AttachmentUpload} from "./AttachmentUpload.js"; import {DecryptionSource} from "../e2ee/common.js"; +import {iterateResponseStateEvents} from "./common.js"; import {PowerLevels, EVENT_TYPE as POWERLEVELS_EVENT_TYPE } from "./PowerLevels.js"; const EVENT_ENCRYPTED_TYPE = "m.room.encrypted"; @@ -179,7 +180,7 @@ export class Room extends BaseRoom { removedPendingEvents = await this._sendQueue.removeRemoteEchos(roomResponse.timeline.events, txn, log); } const powerLevelsEvent = this._getPowerLevelsEvent(roomResponse); - this._updateRoomStateHandler(roomResponse, txn, log); + this._runRoomStateHandlers(roomResponse, txn, log); return { summaryChanges, roomEncryption, @@ -275,8 +276,13 @@ export class Room extends BaseRoom { } _getPowerLevelsEvent(roomResponse) { - const isPowerlevelEvent = event => event.state_key === "" && event.type === POWERLEVELS_EVENT_TYPE; - const powerLevelEvent = roomResponse.timeline?.events.find(isPowerlevelEvent) ?? roomResponse.state?.events.find(isPowerlevelEvent); + let powerLevelEvent; + iterateResponseStateEvents(roomResponse, event => { + if(event.state_key === "" && event.type === POWERLEVELS_EVENT_TYPE) { + powerLevelEvent = event; + } + + }); return powerLevelEvent; } @@ -445,20 +451,12 @@ export class Room extends BaseRoom { return this._sendQueue.pendingEvents; } - _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) { - 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); - } + /** global room state handlers, run during write sync step */ + _runRoomStateHandlers(roomResponse, txn, log) { + iterateResponseStateEvents(roomResponse, event => { + this._roomStateHandler.handleRoomState(this, event, txn, log); + }); + } } } } diff --git a/src/matrix/room/RoomSummary.js b/src/matrix/room/RoomSummary.js index a3dec467..62608683 100644 --- a/src/matrix/room/RoomSummary.js +++ b/src/matrix/room/RoomSummary.js @@ -15,7 +15,7 @@ limitations under the License. */ import {MEGOLM_ALGORITHM} from "../e2ee/common.js"; - +import {iterateResponseStateEvents} from "./common"; function applyTimelineEntries(data, timelineEntries, isInitialSync, canMarkUnread, ownUserId) { if (timelineEntries.length) { @@ -27,25 +27,6 @@ function applyTimelineEntries(data, timelineEntries, isInitialSync, canMarkUnrea return data; } -export function reduceStateEvents(roomResponse, callback, value) { - const stateEvents = roomResponse?.state?.events; - // state comes before timeline - if (Array.isArray(stateEvents)) { - value = stateEvents.reduce(callback, value); - } - const timelineEvents = roomResponse?.timeline?.events; - // and after that state events in the timeline - if (Array.isArray(timelineEvents)) { - value = timelineEvents.reduce((data, event) => { - if (typeof event.state_key === "string") { - value = callback(value, event); - } - return value; - }, value); - } - return value; -} - function applySyncResponse(data, roomResponse, membership, ownUserId) { if (roomResponse.summary) { data = updateSummary(data, roomResponse.summary); @@ -60,7 +41,9 @@ function applySyncResponse(data, roomResponse, membership, ownUserId) { // process state events in state and in timeline. // non-state events are handled by applyTimelineEntries // so decryption is handled properly - data = reduceStateEvents(roomResponse, (data, event) => processStateEvent(data, event, ownUserId), data); + iterateResponseStateEvents(roomResponse, event => { + data = processStateEvent(data, event, ownUserId); + }); const unreadNotifications = roomResponse.unread_notifications; if (unreadNotifications) { data = processNotificationCounts(data, unreadNotifications); diff --git a/src/matrix/room/common.ts b/src/matrix/room/common.ts index 2fdc2483..38070925 100644 --- a/src/matrix/room/common.ts +++ b/src/matrix/room/common.ts @@ -15,7 +15,7 @@ limitations under the License. */ import type {Room} from "./Room"; -import type {StateEvent} from "../storage/types"; +import type {StateEvent, TimelineEvent} from "../storage/types"; import type {Transaction} from "../storage/idb/Transaction"; import type {ILogItem} from "../../logging/types"; import type {MemberChange} from "./members/RoomMember"; @@ -50,4 +50,71 @@ export enum RoomType { export interface RoomStateHandler { handleRoomState(room: Room, stateEvent: StateEvent, txn: Transaction, log: ILogItem); updateRoomMembers(room: Room, memberChanges: Map); +type RoomResponse = { + state?: { + events?: Array + }, + timeline?: { + events?: Array + } +} + +/** iterates over any state events in a sync room response, in the order that they should be applied (from older to younger events) */ +export function iterateResponseStateEvents(roomResponse: RoomResponse, callback: (StateEvent) => void) { + // first iterate over state events, they precede the timeline + const stateEvents = roomResponse.state?.events; + if (stateEvents) { + for (let i = 0; i < stateEvents.length; i++) { + callback(stateEvents[i]); + } + } + // now see if there are any state events within the timeline + 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") { + callback(event); + } + } + } +} + +export function tests() { + return { + "test iterateResponseStateEvents with both state and timeline sections": assert => { + const roomResponse = { + state: { + events: [ + {type: "m.room.member", state_key: "1"}, + {type: "m.room.member", state_key: "2", content: {a: 1}}, + ] + }, + timeline: { + events: [ + {type: "m.room.message"}, + {type: "m.room.member", state_key: "3"}, + {type: "m.room.message"}, + {type: "m.room.member", state_key: "2", content: {a: 2}}, + ] + } + } as unknown as RoomResponse; + const expectedStateKeys = ["1", "2", "3", "2"]; + const expectedAForMember2 = [1, 2]; + iterateResponseStateEvents(roomResponse, event => { + assert.strictEqual(event.type, "m.room.member"); + assert.strictEqual(expectedStateKeys.shift(), event.state_key); + if (event.state_key === "2") { + assert.strictEqual(expectedAForMember2.shift(), event.content.a); + } + }); + assert.strictEqual(expectedStateKeys.length, 0); + assert.strictEqual(expectedAForMember2.length, 0); + }, + "test iterateResponseStateEvents with empty response": assert => { + iterateResponseStateEvents({}, () => { + assert.fail("no events expected"); + }); + } + } }