2020-08-05 18:38:55 +02:00
|
|
|
/*
|
|
|
|
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2020-03-14 20:46:49 +01:00
|
|
|
function applySyncResponse(data, roomResponse, membership) {
|
|
|
|
if (roomResponse.summary) {
|
|
|
|
data = updateSummary(data, roomResponse.summary);
|
|
|
|
}
|
|
|
|
if (membership !== data.membership) {
|
|
|
|
data = data.cloneIfNeeded();
|
|
|
|
data.membership = membership;
|
|
|
|
}
|
|
|
|
// state comes before timeline
|
|
|
|
if (roomResponse.state) {
|
|
|
|
data = roomResponse.state.events.reduce(processEvent, data);
|
|
|
|
}
|
|
|
|
if (roomResponse.timeline) {
|
|
|
|
data = roomResponse.timeline.events.reduce(processEvent, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
function processEvent(data, event) {
|
|
|
|
if (event.type === "m.room.encryption") {
|
|
|
|
if (!data.isEncrypted) {
|
|
|
|
data = data.cloneIfNeeded();
|
|
|
|
data.isEncrypted = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (event.type === "m.room.name") {
|
|
|
|
const newName = event.content && event.content.name;
|
|
|
|
if (newName !== data.name) {
|
|
|
|
data = data.cloneIfNeeded();
|
|
|
|
data.name = newName;
|
|
|
|
}
|
|
|
|
} else if (event.type === "m.room.message") {
|
|
|
|
const content = event.content;
|
|
|
|
const body = content && content.body;
|
|
|
|
const msgtype = content && content.msgtype;
|
|
|
|
if (msgtype === "m.text") {
|
|
|
|
data = data.cloneIfNeeded();
|
|
|
|
data.lastMessageBody = body;
|
|
|
|
}
|
|
|
|
} else if (event.type === "m.room.canonical_alias") {
|
|
|
|
const content = event.content;
|
|
|
|
data = data.cloneIfNeeded();
|
|
|
|
data.canonicalAlias = content.alias;
|
|
|
|
data.altAliases = content.alt_aliases;
|
|
|
|
}
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateSummary(data, summary) {
|
|
|
|
const heroes = summary["m.heroes"];
|
|
|
|
const inviteCount = summary["m.joined_member_count"];
|
|
|
|
const joinCount = summary["m.invited_member_count"];
|
|
|
|
|
|
|
|
if (heroes) {
|
|
|
|
data = data.cloneIfNeeded();
|
|
|
|
data.heroes = heroes;
|
|
|
|
}
|
|
|
|
if (Number.isInteger(inviteCount)) {
|
|
|
|
data = data.cloneIfNeeded();
|
|
|
|
data.inviteCount = inviteCount;
|
|
|
|
}
|
|
|
|
if (Number.isInteger(joinCount)) {
|
|
|
|
data = data.cloneIfNeeded();
|
|
|
|
data.joinCount = joinCount;
|
|
|
|
}
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
class SummaryData {
|
|
|
|
constructor(copy, roomId) {
|
|
|
|
this.roomId = copy ? copy.roomId : roomId;
|
|
|
|
this.name = copy ? copy.name : null;
|
|
|
|
this.lastMessageBody = copy ? copy.lastMessageBody : null;
|
|
|
|
this.unreadCount = copy ? copy.unreadCount : null;
|
|
|
|
this.mentionCount = copy ? copy.mentionCount : null;
|
|
|
|
this.isEncrypted = copy ? copy.isEncrypted : null;
|
|
|
|
this.isDirectMessage = copy ? copy.isDirectMessage : null;
|
|
|
|
this.membership = copy ? copy.membership : null;
|
|
|
|
this.inviteCount = copy ? copy.inviteCount : 0;
|
|
|
|
this.joinCount = copy ? copy.joinCount : 0;
|
|
|
|
this.heroes = copy ? copy.heroes : null;
|
|
|
|
this.canonicalAlias = copy ? copy.canonicalAlias : null;
|
|
|
|
this.altAliases = copy ? copy.altAliases : null;
|
2020-06-26 23:26:24 +02:00
|
|
|
this.hasFetchedMembers = copy ? copy.hasFetchedMembers : false;
|
2020-03-14 20:46:49 +01:00
|
|
|
this.cloned = copy ? true : false;
|
|
|
|
}
|
|
|
|
|
|
|
|
cloneIfNeeded() {
|
|
|
|
if (this.cloned) {
|
|
|
|
return this;
|
|
|
|
} else {
|
|
|
|
return new SummaryData(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
serialize() {
|
|
|
|
const {cloned, ...serializedProps} = this;
|
|
|
|
return serializedProps;
|
|
|
|
}
|
|
|
|
}
|
2018-12-21 14:35:24 +01:00
|
|
|
|
2020-04-20 21:26:39 +02:00
|
|
|
export class RoomSummary {
|
2019-02-04 23:30:10 +01:00
|
|
|
constructor(roomId) {
|
2020-03-14 20:46:49 +01:00
|
|
|
this._data = new SummaryData(null, roomId);
|
2018-12-21 14:35:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
get name() {
|
2020-03-14 20:46:49 +01:00
|
|
|
if (this._data.name) {
|
|
|
|
return this._data.name;
|
2019-10-13 07:48:33 +02:00
|
|
|
}
|
2020-03-14 20:46:49 +01:00
|
|
|
if (this._data.canonicalAlias) {
|
|
|
|
return this._data.canonicalAlias;
|
2019-10-13 07:48:33 +02:00
|
|
|
}
|
2020-03-14 20:46:49 +01:00
|
|
|
if (Array.isArray(this._data.altAliases) && this._data.altAliases.length !== 0) {
|
|
|
|
return this._data.altAliases[0];
|
2019-10-13 07:48:33 +02:00
|
|
|
}
|
2020-03-14 20:46:49 +01:00
|
|
|
if (Array.isArray(this._data.heroes) && this._data.heroes.length !== 0) {
|
|
|
|
return this._data.heroes.join(", ");
|
2019-10-13 07:48:33 +02:00
|
|
|
}
|
2020-03-14 20:46:49 +01:00
|
|
|
return this._data.roomId;
|
2018-12-21 14:35:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
get lastMessage() {
|
2020-03-14 20:46:49 +01:00
|
|
|
return this._data.lastMessageBody;
|
2018-12-21 14:35:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
get inviteCount() {
|
2020-03-14 20:46:49 +01:00
|
|
|
return this._data.inviteCount;
|
2018-12-21 14:35:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
get joinCount() {
|
2020-03-14 20:46:49 +01:00
|
|
|
return this._data.joinCount;
|
2018-12-21 14:35:24 +01:00
|
|
|
}
|
|
|
|
|
2020-06-26 23:26:24 +02:00
|
|
|
get hasFetchedMembers() {
|
|
|
|
return this._data.hasFetchedMembers;
|
|
|
|
}
|
|
|
|
|
|
|
|
writeHasFetchedMembers(value, txn) {
|
|
|
|
const data = new SummaryData(this._data);
|
|
|
|
data.hasFetchedMembers = value;
|
|
|
|
txn.roomSummary.set(data.serialize());
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
2020-03-14 20:46:49 +01:00
|
|
|
writeSync(roomResponse, membership, txn) {
|
|
|
|
// clear cloned flag, so cloneIfNeeded makes a copy and
|
|
|
|
// this._data is not modified if any field is changed.
|
|
|
|
this._data.cloned = false;
|
|
|
|
const data = applySyncResponse(this._data, roomResponse, membership);
|
|
|
|
if (data !== this._data) {
|
|
|
|
// need to think here how we want to persist
|
|
|
|
// things like unread status (as read marker, or unread count)?
|
|
|
|
// we could very well load additional things in the load method
|
|
|
|
// ... the trade-off is between constantly writing the summary
|
|
|
|
// on every sync, or doing a bit of extra reading on load
|
|
|
|
// and have in-memory only variables for visualization
|
|
|
|
txn.roomSummary.set(data.serialize());
|
|
|
|
return data;
|
2018-12-21 14:35:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-26 23:26:24 +02:00
|
|
|
applyChanges(data) {
|
2020-03-14 20:46:49 +01:00
|
|
|
this._data = data;
|
|
|
|
}
|
2018-12-21 14:35:24 +01:00
|
|
|
|
2020-03-14 20:46:49 +01:00
|
|
|
async load(summary) {
|
|
|
|
this._data = new SummaryData(summary);
|
2018-12-21 14:35:24 +01:00
|
|
|
}
|
2020-03-14 20:46:49 +01:00
|
|
|
}
|
2018-12-21 14:35:24 +01:00
|
|
|
|
2020-03-14 20:46:49 +01:00
|
|
|
export function tests() {
|
|
|
|
return {
|
|
|
|
"membership trigger change": function(assert) {
|
|
|
|
const summary = new RoomSummary("id");
|
2020-03-14 21:38:37 +01:00
|
|
|
let written = false;
|
|
|
|
const changes = summary.writeSync({}, "join", {roomSummary: {set: () => { written = true; }}});
|
2020-03-14 20:46:49 +01:00
|
|
|
assert(changes);
|
2020-03-14 21:38:37 +01:00
|
|
|
assert(written);
|
|
|
|
assert.equal(changes.membership, "join");
|
2019-10-13 07:48:33 +02:00
|
|
|
}
|
2020-03-14 20:46:49 +01:00
|
|
|
}
|
2018-12-21 14:35:24 +01:00
|
|
|
}
|