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,
isInitialSync, isTimelineOpen,
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,
// but don't make any in-memory changes yet
let heroChanges;
@ -60,7 +60,7 @@ export class Room extends EventEmitter {
if (!this._heroes) {
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;
if (roomResponse.timeline && roomResponse.timeline.events) {
@ -71,22 +71,22 @@ export class Room extends EventEmitter {
newTimelineEntries: entries,
newLiveKey,
removedPendingEvents,
changedMembers,
memberChanges,
heroChanges
};
}
/** @package */
afterSync({summaryChanges, newTimelineEntries, newLiveKey, removedPendingEvents, changedMembers, heroChanges}) {
afterSync({summaryChanges, newTimelineEntries, newLiveKey, removedPendingEvents, memberChanges, heroChanges}) {
this._syncWriter.afterSync(newLiveKey);
if (changedMembers.length) {
if (memberChanges.size) {
if (this._changedMembersDuringSync) {
for (const member of changedMembers) {
this._changedMembersDuringSync.set(member.userId, member);
for (const [userId, memberChange] of memberChanges.entries()) {
this._changedMembersDuringSync.set(userId, memberChange.member);
}
}
if (this._memberList) {
this._memberList.afterSync(changedMembers);
this._memberList.afterSync(memberChanges);
}
}
let emitChange = false;

View file

@ -42,11 +42,11 @@ export class Heroes {
/**
* @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
* @return {Promise}
*/
async calculateChanges(newHeroes, changedMembers, txn) {
async calculateChanges(newHeroes, memberChanges, txn) {
const updatedHeroMembers = new Map();
const removedUserIds = [];
// remove non-present members
@ -56,9 +56,9 @@ export class Heroes {
}
}
// update heroes with synced member changes
for (const member of changedMembers) {
if (this._members.has(member.userId) || newHeroes.indexOf(member.userId) !== -1) {
updatedHeroMembers.set(member.userId, member);
for (const [userId, memberChange] of memberChanges.entries()) {
if (this._members.has(userId) || newHeroes.indexOf(userId) !== -1) {
updatedHeroMembers.set(userId, memberChange.member);
}
}
// load member for new heroes from storage

View file

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

View file

@ -99,3 +99,30 @@ export class RoomMember {
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 {FragmentBoundaryEntry} from "../entries/FragmentBoundaryEntry.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
// when first syncing the room
@ -102,13 +102,13 @@ export class SyncWriter {
if (event.type === MEMBER_EVENT_TYPE) {
const userId = event.state_key;
if (userId) {
const member = RoomMember.fromMemberEvent(this._roomId, event);
if (member) {
const memberChange = new MemberChange(this._roomId, event);
if (memberChange.member) {
// as this is sync, we can just replace the member
// if it is there already
txn.roomMembers.set(member.serialize());
txn.roomMembers.set(memberChange.member.serialize());
return memberChange;
}
return member;
}
} else {
txn.roomState.set(this._roomId, event);
@ -116,22 +116,22 @@ export class SyncWriter {
}
_writeStateEvents(roomResponse, txn) {
const changedMembers = [];
const memberChanges = new Map();
// persist state
const {state} = roomResponse;
if (Array.isArray(state?.events)) {
for (const event of state.events) {
const member = this._writeStateEvent(event, txn);
if (member) {
changedMembers.push(member);
const memberChange = this._writeStateEvent(event, txn);
if (memberChange) {
memberChanges.set(memberChange.userId, memberChange);
}
}
}
return changedMembers;
return memberChanges;
}
async _writeTimeline(entries, timeline, currentKey, txn) {
const changedMembers = [];
const memberChanges = new Map();
if (timeline.events) {
const events = deduplicateEvents(timeline.events);
for(const event of events) {
@ -148,14 +148,14 @@ export class SyncWriter {
// process live state events first, so new member info is available
if (typeof event.state_key === "string") {
const member = this._writeStateEvent(event, txn);
if (member) {
changedMembers.push(member);
const memberChange = this._writeStateEvent(event, txn);
if (memberChange) {
memberChanges.set(memberChange.userId, memberChange);
}
}
}
}
return {currentKey, changedMembers};
return {currentKey, memberChanges};
}
async _findMemberData(userId, events, txn) {
@ -198,12 +198,14 @@ export class SyncWriter {
}
// important this happens before _writeTimeline so
// 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);
currentKey = timelineResult.currentKey;
changedMembers.push(...timelineResult.changedMembers);
return {entries, newLiveKey: currentKey, changedMembers};
// merge member changes from state and timeline, giving precedence to the latter
for (const [userId, memberChange] of timelineResult.memberChanges.entries()) {
memberChanges.set(userId, memberChange);
}
return {entries, newLiveKey: currentKey, memberChanges};
}
afterSync(newLiveKey) {