forked from mystiq/hydrogen-web
extract function to iterate over room response state events
This commit is contained in:
parent
d727dfd843
commit
db05338596
4 changed files with 91 additions and 43 deletions
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {reduceStateEvents} from "./RoomSummary.js";
|
import {iterateResponseStateEvents} from "./common";
|
||||||
import {BaseRoom} from "./BaseRoom.js";
|
import {BaseRoom} from "./BaseRoom.js";
|
||||||
import {RoomMember, EVENT_TYPE as MEMBER_EVENT_TYPE} from "./members/RoomMember.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) {
|
function findKickDetails(roomResponse, ownUserId) {
|
||||||
const kickEvent = reduceStateEvents(roomResponse, (kickEvent, event) => {
|
let kickEvent;
|
||||||
|
iterateResponseStateEvents(roomResponse, event => {
|
||||||
if (event.type === MEMBER_EVENT_TYPE) {
|
if (event.type === MEMBER_EVENT_TYPE) {
|
||||||
// did we get kicked?
|
// did we get kicked?
|
||||||
if (event.state_key === ownUserId && event.sender !== event.state_key) {
|
if (event.state_key === ownUserId && event.sender !== event.state_key) {
|
||||||
kickEvent = event;
|
kickEvent = event;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return kickEvent;
|
});
|
||||||
}, null);
|
|
||||||
if (kickEvent) {
|
if (kickEvent) {
|
||||||
return {
|
return {
|
||||||
// this is different from the room membership in the sync section, which can only be leave
|
// this is different from the room membership in the sync section, which can only be leave
|
||||||
|
|
|
@ -23,6 +23,7 @@ import {WrappedError} from "../error.js"
|
||||||
import {Heroes} from "./members/Heroes.js";
|
import {Heroes} from "./members/Heroes.js";
|
||||||
import {AttachmentUpload} from "./AttachmentUpload.js";
|
import {AttachmentUpload} from "./AttachmentUpload.js";
|
||||||
import {DecryptionSource} from "../e2ee/common.js";
|
import {DecryptionSource} from "../e2ee/common.js";
|
||||||
|
import {iterateResponseStateEvents} from "./common.js";
|
||||||
import {PowerLevels, EVENT_TYPE as POWERLEVELS_EVENT_TYPE } from "./PowerLevels.js";
|
import {PowerLevels, EVENT_TYPE as POWERLEVELS_EVENT_TYPE } from "./PowerLevels.js";
|
||||||
|
|
||||||
const EVENT_ENCRYPTED_TYPE = "m.room.encrypted";
|
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);
|
removedPendingEvents = await this._sendQueue.removeRemoteEchos(roomResponse.timeline.events, txn, log);
|
||||||
}
|
}
|
||||||
const powerLevelsEvent = this._getPowerLevelsEvent(roomResponse);
|
const powerLevelsEvent = this._getPowerLevelsEvent(roomResponse);
|
||||||
this._updateRoomStateHandler(roomResponse, txn, log);
|
this._runRoomStateHandlers(roomResponse, txn, log);
|
||||||
return {
|
return {
|
||||||
summaryChanges,
|
summaryChanges,
|
||||||
roomEncryption,
|
roomEncryption,
|
||||||
|
@ -275,8 +276,13 @@ export class Room extends BaseRoom {
|
||||||
}
|
}
|
||||||
|
|
||||||
_getPowerLevelsEvent(roomResponse) {
|
_getPowerLevelsEvent(roomResponse) {
|
||||||
const isPowerlevelEvent = event => event.state_key === "" && event.type === POWERLEVELS_EVENT_TYPE;
|
let powerLevelEvent;
|
||||||
const powerLevelEvent = roomResponse.timeline?.events.find(isPowerlevelEvent) ?? roomResponse.state?.events.find(isPowerlevelEvent);
|
iterateResponseStateEvents(roomResponse, event => {
|
||||||
|
if(event.state_key === "" && event.type === POWERLEVELS_EVENT_TYPE) {
|
||||||
|
powerLevelEvent = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
return powerLevelEvent;
|
return powerLevelEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,20 +451,12 @@ export class Room extends BaseRoom {
|
||||||
return this._sendQueue.pendingEvents;
|
return this._sendQueue.pendingEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateRoomStateHandler(roomResponse, txn, log) {
|
/** global room state handlers, run during write sync step */
|
||||||
const stateEvents = roomResponse.state?.events;
|
_runRoomStateHandlers(roomResponse, txn, log) {
|
||||||
if (stateEvents) {
|
iterateResponseStateEvents(roomResponse, event => {
|
||||||
for (let i = 0; i < stateEvents.length; i++) {
|
this._roomStateHandler.handleRoomState(this, event, txn, log);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {MEGOLM_ALGORITHM} from "../e2ee/common.js";
|
import {MEGOLM_ALGORITHM} from "../e2ee/common.js";
|
||||||
|
import {iterateResponseStateEvents} from "./common";
|
||||||
|
|
||||||
function applyTimelineEntries(data, timelineEntries, isInitialSync, canMarkUnread, ownUserId) {
|
function applyTimelineEntries(data, timelineEntries, isInitialSync, canMarkUnread, ownUserId) {
|
||||||
if (timelineEntries.length) {
|
if (timelineEntries.length) {
|
||||||
|
@ -27,25 +27,6 @@ function applyTimelineEntries(data, timelineEntries, isInitialSync, canMarkUnrea
|
||||||
return data;
|
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) {
|
function applySyncResponse(data, roomResponse, membership, ownUserId) {
|
||||||
if (roomResponse.summary) {
|
if (roomResponse.summary) {
|
||||||
data = updateSummary(data, 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.
|
// process state events in state and in timeline.
|
||||||
// non-state events are handled by applyTimelineEntries
|
// non-state events are handled by applyTimelineEntries
|
||||||
// so decryption is handled properly
|
// 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;
|
const unreadNotifications = roomResponse.unread_notifications;
|
||||||
if (unreadNotifications) {
|
if (unreadNotifications) {
|
||||||
data = processNotificationCounts(data, unreadNotifications);
|
data = processNotificationCounts(data, unreadNotifications);
|
||||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {Room} from "./Room";
|
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 {Transaction} from "../storage/idb/Transaction";
|
||||||
import type {ILogItem} from "../../logging/types";
|
import type {ILogItem} from "../../logging/types";
|
||||||
import type {MemberChange} from "./members/RoomMember";
|
import type {MemberChange} from "./members/RoomMember";
|
||||||
|
@ -50,4 +50,71 @@ export enum RoomType {
|
||||||
export interface RoomStateHandler {
|
export interface RoomStateHandler {
|
||||||
handleRoomState(room: Room, stateEvent: StateEvent, txn: Transaction, log: ILogItem);
|
handleRoomState(room: Room, stateEvent: StateEvent, txn: Transaction, log: ILogItem);
|
||||||
updateRoomMembers(room: Room, memberChanges: Map<string, MemberChange>);
|
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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue