extract function to iterate over room response state events

This commit is contained in:
Bruno Windels 2022-05-12 17:26:29 +02:00
parent d727dfd843
commit db05338596
4 changed files with 91 additions and 43 deletions

View file

@ -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

View file

@ -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);
});
}
}
}
}

View file

@ -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);

View file

@ -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<string, MemberChange>);
type RoomResponse = {
state?: {
events?: Array<StateEvent>
},
timeline?: {
events?: Array<StateEvent>
}
}
/** 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");
});
}
}
}