pass memberchanges around instead of members

so we can easily tell how their membership changes, (e.g. join -> left)
which we'll need for device tracking.

Not adding this to RoomMember because RoomMember also needs to be
able to represent a member loaded from storage which doesn't contain
this error. A MemberChange exists only within a sync.
This commit is contained in:
Bruno Windels 2020-08-31 09:50:57 +02:00
parent 164384f312
commit 8482bc95ec
5 changed files with 64 additions and 35 deletions

View file

@ -51,7 +51,7 @@ export class Room extends EventEmitter {
membership, membership,
isInitialSync, isTimelineOpen, isInitialSync, isTimelineOpen,
txn); txn);
const {entries, newLiveKey, changedMembers} = await this._syncWriter.writeSync(roomResponse, txn); const {entries, newLiveKey, memberChanges} = await this._syncWriter.writeSync(roomResponse, txn);
// fetch new members while we have txn open, // fetch new members while we have txn open,
// but don't make any in-memory changes yet // but don't make any in-memory changes yet
let heroChanges; let heroChanges;
@ -60,7 +60,7 @@ export class Room extends EventEmitter {
if (!this._heroes) { if (!this._heroes) {
this._heroes = new Heroes(this._roomId); this._heroes = new Heroes(this._roomId);
} }
heroChanges = await this._heroes.calculateChanges(summaryChanges.heroes, changedMembers, txn); heroChanges = await this._heroes.calculateChanges(summaryChanges.heroes, memberChanges, txn);
} }
let removedPendingEvents; let removedPendingEvents;
if (roomResponse.timeline && roomResponse.timeline.events) { if (roomResponse.timeline && roomResponse.timeline.events) {
@ -71,22 +71,22 @@ export class Room extends EventEmitter {
newTimelineEntries: entries, newTimelineEntries: entries,
newLiveKey, newLiveKey,
removedPendingEvents, removedPendingEvents,
changedMembers, memberChanges,
heroChanges heroChanges
}; };
} }
/** @package */ /** @package */
afterSync({summaryChanges, newTimelineEntries, newLiveKey, removedPendingEvents, changedMembers, heroChanges}) { afterSync({summaryChanges, newTimelineEntries, newLiveKey, removedPendingEvents, memberChanges, heroChanges}) {
this._syncWriter.afterSync(newLiveKey); this._syncWriter.afterSync(newLiveKey);
if (changedMembers.length) { if (memberChanges.size) {
if (this._changedMembersDuringSync) { if (this._changedMembersDuringSync) {
for (const member of changedMembers) { for (const [userId, memberChange] of memberChanges.entries()) {
this._changedMembersDuringSync.set(member.userId, member); this._changedMembersDuringSync.set(userId, memberChange.member);
} }
} }
if (this._memberList) { if (this._memberList) {
this._memberList.afterSync(changedMembers); this._memberList.afterSync(memberChanges);
} }
} }
let emitChange = false; let emitChange = false;

View file

@ -42,11 +42,11 @@ export class Heroes {
/** /**
* @param {string[]} newHeroes array of user ids * @param {string[]} newHeroes array of user ids
* @param {RoomMember[]} changedMembers array of changed members in this sync * @param {Map<string, MemberChange>} memberChanges map of changed memberships
* @param {Transaction} txn * @param {Transaction} txn
* @return {Promise} * @return {Promise}
*/ */
async calculateChanges(newHeroes, changedMembers, txn) { async calculateChanges(newHeroes, memberChanges, txn) {
const updatedHeroMembers = new Map(); const updatedHeroMembers = new Map();
const removedUserIds = []; const removedUserIds = [];
// remove non-present members // remove non-present members
@ -56,9 +56,9 @@ export class Heroes {
} }
} }
// update heroes with synced member changes // update heroes with synced member changes
for (const member of changedMembers) { for (const [userId, memberChange] of memberChanges.entries()) {
if (this._members.has(member.userId) || newHeroes.indexOf(member.userId) !== -1) { if (this._members.has(userId) || newHeroes.indexOf(userId) !== -1) {
updatedHeroMembers.set(member.userId, member); updatedHeroMembers.set(userId, memberChange.member);
} }
} }
// load member for new heroes from storage // load member for new heroes from storage

View file

@ -26,9 +26,9 @@ export class MemberList {
this._retentionCount = 1; this._retentionCount = 1;
} }
afterSync(updatedMembers) { afterSync(memberChanges) {
for (const member of updatedMembers) { for (const [userId, memberChange] of memberChanges.entries()) {
this._members.add(member.userId, member); this._members.add(userId, memberChange.member);
} }
} }

View file

@ -99,3 +99,30 @@ export class RoomMember {
return this._data; return this._data;
} }
} }
export class MemberChange {
constructor(roomId, memberEvent) {
this._roomId = roomId;
this._memberEvent = memberEvent;
this._member = null;
}
get member() {
if (!this._member) {
this._member = RoomMember.fromMemberEvent(this._roomId, this._memberEvent);
}
return this._member;
}
userId() {
return this._memberEvent.state_key;
}
previousMembership() {
return this._memberEvent.unsigned?.prev_content?.membership;
}
membership() {
return this._memberEvent.content?.membership;
}
}

View file

@ -18,7 +18,7 @@ import {EventKey} from "../EventKey.js";
import {EventEntry} from "../entries/EventEntry.js"; import {EventEntry} from "../entries/EventEntry.js";
import {FragmentBoundaryEntry} from "../entries/FragmentBoundaryEntry.js"; import {FragmentBoundaryEntry} from "../entries/FragmentBoundaryEntry.js";
import {createEventEntry} from "./common.js"; import {createEventEntry} from "./common.js";
import {RoomMember, EVENT_TYPE as MEMBER_EVENT_TYPE} from "../../members/RoomMember.js"; import {MemberChange, RoomMember, EVENT_TYPE as MEMBER_EVENT_TYPE} from "../../members/RoomMember.js";
// Synapse bug? where the m.room.create event appears twice in sync response // Synapse bug? where the m.room.create event appears twice in sync response
// when first syncing the room // when first syncing the room
@ -102,13 +102,13 @@ export class SyncWriter {
if (event.type === MEMBER_EVENT_TYPE) { if (event.type === MEMBER_EVENT_TYPE) {
const userId = event.state_key; const userId = event.state_key;
if (userId) { if (userId) {
const member = RoomMember.fromMemberEvent(this._roomId, event); const memberChange = new MemberChange(this._roomId, event);
if (member) { if (memberChange.member) {
// as this is sync, we can just replace the member // as this is sync, we can just replace the member
// if it is there already // if it is there already
txn.roomMembers.set(member.serialize()); txn.roomMembers.set(memberChange.member.serialize());
return memberChange;
} }
return member;
} }
} else { } else {
txn.roomState.set(this._roomId, event); txn.roomState.set(this._roomId, event);
@ -116,22 +116,22 @@ export class SyncWriter {
} }
_writeStateEvents(roomResponse, txn) { _writeStateEvents(roomResponse, txn) {
const changedMembers = []; const memberChanges = new Map();
// persist state // persist state
const {state} = roomResponse; const {state} = roomResponse;
if (Array.isArray(state?.events)) { if (Array.isArray(state?.events)) {
for (const event of state.events) { for (const event of state.events) {
const member = this._writeStateEvent(event, txn); const memberChange = this._writeStateEvent(event, txn);
if (member) { if (memberChange) {
changedMembers.push(member); memberChanges.set(memberChange.userId, memberChange);
} }
} }
} }
return changedMembers; return memberChanges;
} }
async _writeTimeline(entries, timeline, currentKey, txn) { async _writeTimeline(entries, timeline, currentKey, txn) {
const changedMembers = []; const memberChanges = new Map();
if (timeline.events) { if (timeline.events) {
const events = deduplicateEvents(timeline.events); const events = deduplicateEvents(timeline.events);
for(const event of events) { for(const event of events) {
@ -148,14 +148,14 @@ export class SyncWriter {
// process live state events first, so new member info is available // process live state events first, so new member info is available
if (typeof event.state_key === "string") { if (typeof event.state_key === "string") {
const member = this._writeStateEvent(event, txn); const memberChange = this._writeStateEvent(event, txn);
if (member) { if (memberChange) {
changedMembers.push(member); memberChanges.set(memberChange.userId, memberChange);
} }
} }
} }
} }
return {currentKey, changedMembers}; return {currentKey, memberChanges};
} }
async _findMemberData(userId, events, txn) { async _findMemberData(userId, events, txn) {
@ -198,12 +198,14 @@ export class SyncWriter {
} }
// important this happens before _writeTimeline so // important this happens before _writeTimeline so
// members are available in the transaction // members are available in the transaction
const changedMembers = this._writeStateEvents(roomResponse, txn); const memberChanges = this._writeStateEvents(roomResponse, txn);
const timelineResult = await this._writeTimeline(entries, timeline, currentKey, txn); const timelineResult = await this._writeTimeline(entries, timeline, currentKey, txn);
currentKey = timelineResult.currentKey; currentKey = timelineResult.currentKey;
changedMembers.push(...timelineResult.changedMembers); // merge member changes from state and timeline, giving precedence to the latter
for (const [userId, memberChange] of timelineResult.memberChanges.entries()) {
return {entries, newLiveKey: currentKey, changedMembers}; memberChanges.set(userId, memberChange);
}
return {entries, newLiveKey: currentKey, memberChanges};
} }
afterSync(newLiveKey) { afterSync(newLiveKey) {