Merge branch 'master' into bwindels/e2ee

This commit is contained in:
Bruno Windels 2020-08-27 12:52:42 +02:00
commit a6daa13d1d
6 changed files with 85 additions and 37 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "hydrogen-web", "name": "hydrogen-web",
"version": "0.0.32", "version": "0.0.33",
"description": "A javascript matrix client prototype, trying to minize RAM usage by offloading as much as possible to IndexedDB", "description": "A javascript matrix client prototype, trying to minize RAM usage by offloading as much as possible to IndexedDB",
"main": "index.js", "main": "index.js",
"directories": { "directories": {

View file

@ -57,24 +57,20 @@ export class SessionViewModel extends ViewModel {
} }
_closeCurrentRoom() { _closeCurrentRoom() {
if (this._currentRoomViewModel) { this._currentRoomTileViewModel?.close();
this._currentRoomViewModel = this.disposeTracked(this._currentRoomViewModel); this._currentRoomViewModel = this.disposeTracked(this._currentRoomViewModel);
this.emitChange("currentRoom");
}
} }
_openRoom(room, roomTileVM) { _openRoom(room, roomTileVM) {
if (this._currentRoomTileViewModel) { this._closeCurrentRoom();
this._currentRoomTileViewModel.close();
}
this._currentRoomTileViewModel = roomTileVM; this._currentRoomTileViewModel = roomTileVM;
if (this._currentRoomViewModel) {
this._currentRoomViewModel = this.disposeTracked(this._currentRoomViewModel);
}
this._currentRoomViewModel = this.track(new RoomViewModel(this.childOptions({ this._currentRoomViewModel = this.track(new RoomViewModel(this.childOptions({
room, room,
ownUserId: this._session.user.id, ownUserId: this._session.user.id,
closeCallback: () => this._closeCurrentRoom(), closeCallback: () => {
this._closeCurrentRoom();
this.emitChange("currentRoom");
},
}))); })));
this._currentRoomViewModel.load(); this._currentRoomViewModel.load();
this.emitChange("currentRoom"); this.emitChange("currentRoom");

View file

@ -49,34 +49,61 @@ export class RoomTileViewModel extends ViewModel {
} }
compare(other) { compare(other) {
/*
put unread rooms first
then put rooms with a timestamp first, and sort by name
then sort by name for rooms without a timestamp
*/
const myRoom = this._room; const myRoom = this._room;
const theirRoom = other._room; const theirRoom = other._room;
let buf = "";
function log(...args) {
buf = buf + args.map(a => a+"").join(" ") + "\n";
}
function logResult(result) {
if (result === 0) {
log("rooms are equal (should not happen)", result);
} else if (result > 0) {
log(`${theirRoom.name || theirRoom.id} comes first`, result);
} else {
log(`${myRoom.name || myRoom.id} comes first`, result);
}
console.info(buf);
return result;
}
log(`comparing ${myRoom.name || theirRoom.id} and ${theirRoom.name || theirRoom.id} ...`);
log("comparing isUnread...");
if (isSortedAsUnread(this) !== isSortedAsUnread(other)) { if (isSortedAsUnread(this) !== isSortedAsUnread(other)) {
if (isSortedAsUnread(this)) { if (isSortedAsUnread(this)) {
return -1; return logResult(-1);
} }
return 1; return logResult(1);
} }
const myTimestamp = myRoom.lastMessageTimestamp; const myTimestamp = myRoom.lastMessageTimestamp;
const theirTimestamp = theirRoom.lastMessageTimestamp; const theirTimestamp = theirRoom.lastMessageTimestamp;
// rooms with a timestamp come before rooms without one const myTimestampValid = Number.isSafeInteger(myTimestamp);
if ((myTimestamp === null) !== (theirTimestamp === null)) { const theirTimestampValid = Number.isSafeInteger(theirTimestamp);
if (theirTimestamp === null) { // if either does not have a timestamp, put the one with a timestamp first
return -1; if (myTimestampValid !== theirTimestampValid) {
log("checking if either does not have lastMessageTimestamp ...", myTimestamp, theirTimestamp);
if (!theirTimestampValid) {
return logResult(-1);
} }
return 1; return logResult(1);
} }
const timeDiff = theirTimestamp - myTimestamp; const timeDiff = theirTimestamp - myTimestamp;
if (timeDiff === 0) { if (timeDiff === 0 || !theirTimestampValid || !myTimestampValid) {
log("checking name ...", myTimestamp, theirTimestamp);
// sort alphabetically // sort alphabetically
const nameCmp = this.name.localeCompare(other.name); const nameCmp = this.name.localeCompare(other.name);
if (nameCmp === 0) { if (nameCmp === 0) {
return this._room.id.localeCompare(other._room.id); return logResult(this._room.id.localeCompare(other._room.id));
} }
return nameCmp; return logResult(nameCmp);
} }
return timeDiff; log("checking timestamp ...");
return logResult(timeDiff);
} }
get isOpen() { get isOpen() {

View file

@ -22,12 +22,12 @@ function calculateRoomName(sortedMembers, summary) {
if (sortedMembers.length > 1) { if (sortedMembers.length > 1) {
const lastMember = sortedMembers[sortedMembers.length - 1]; const lastMember = sortedMembers[sortedMembers.length - 1];
const firstMembers = sortedMembers.slice(0, sortedMembers.length - 1); const firstMembers = sortedMembers.slice(0, sortedMembers.length - 1);
return firstMembers.map(m => m.displayName).join(", ") + " and " + lastMember.displayName; return firstMembers.map(m => m.name).join(", ") + " and " + lastMember.name;
} else { } else {
return sortedMembers[0].displayName; return sortedMembers[0].name;
} }
} else if (sortedMembers.length < countWithoutMe) { } else if (sortedMembers.length < countWithoutMe) {
return sortedMembers.map(m => m.displayName).join(", ") + ` and ${countWithoutMe} others`; return sortedMembers.map(m => m.name).join(", ") + ` and ${countWithoutMe} others`;
} else { } else {
// Empty Room // Empty Room
return null; return null;
@ -81,7 +81,7 @@ export class Heroes {
for (const member of updatedHeroMembers) { for (const member of updatedHeroMembers) {
this._members.set(member.userId, member); this._members.set(member.userId, member);
} }
const sortedMembers = Array.from(this._members.values()).sort((a, b) => a.displayName.localeCompare(b.displayName)); const sortedMembers = Array.from(this._members.values()).sort((a, b) => a.name.localeCompare(b.name));
this._roomName = calculateRoomName(sortedMembers, summary); this._roomName = calculateRoomName(sortedMembers, summary);
} }
@ -92,7 +92,6 @@ export class Heroes {
get roomAvatarUrl() { get roomAvatarUrl() {
if (this._members.size === 1) { if (this._members.size === 1) {
for (const member of this._members.values()) { for (const member of this._members.values()) {
console.log("roomAvatarUrl", member, member.avatarUrl);
return member.avatarUrl; return member.avatarUrl;
} }
} }

View file

@ -27,21 +27,33 @@ export class RoomMember {
if (typeof userId !== "string") { if (typeof userId !== "string") {
return; return;
} }
return this._fromMemberEventContent(roomId, userId, memberEvent.content); const content = memberEvent.content;
const prevContent = memberEvent.unsigned?.prev_content;
const membership = content?.membership;
// fall back to prev_content for these as synapse doesn't (always?)
// put them on content for "leave" memberships
const displayName = content?.displayname || prevContent?.displayname;
const avatarUrl = content?.avatar_url || prevContent?.avatar_url;
return this._validateAndCreateMember(roomId, userId, membership, displayName, avatarUrl);
} }
/**
* Creates a (historical) member from a member event that is the next member event
* after the point in time where we need a member for. This will use `prev_content`.
*/
static fromReplacingMemberEvent(roomId, memberEvent) { static fromReplacingMemberEvent(roomId, memberEvent) {
const userId = memberEvent && memberEvent.state_key; const userId = memberEvent && memberEvent.state_key;
if (typeof userId !== "string") { if (typeof userId !== "string") {
return; return;
} }
return this._fromMemberEventContent(roomId, userId, memberEvent.prev_content); const content = memberEvent.unsigned?.prev_content
return this._validateAndCreateMember(roomId, userId,
content?.membership,
content?.displayname,
content?.avatar_url
);
} }
static _fromMemberEventContent(roomId, userId, content) { static _validateAndCreateMember(roomId, userId, membership, displayName, avatarUrl) {
const membership = content?.membership;
const avatarUrl = content?.avatar_url;
const displayName = content?.displayname;
if (typeof membership !== "string") { if (typeof membership !== "string") {
return; return;
} }
@ -54,10 +66,23 @@ export class RoomMember {
}); });
} }
/**
* @return {String?} the display name, if any
*/
get displayName() { get displayName() {
return this._data.displayName; return this._data.displayName;
} }
/**
* @return {String} the display name or userId
*/
get name() {
return this._data.displayName || this._data.userId;
}
/**
* @return {String?} the avatar mxc url, if any
*/
get avatarUrl() { get avatarUrl() {
return this._data.avatarUrl; return this._data.avatarUrl;
} }

View file

@ -142,8 +142,9 @@ export class GapWriter {
return RoomMember.fromReplacingMemberEvent(this._roomId, event)?.serialize(); return RoomMember.fromReplacingMemberEvent(this._roomId, event)?.serialize();
} }
} }
// assuming the member hasn't changed within the chunk, just take it from state if it's there // assuming the member hasn't changed within the chunk, just take it from state if it's there.
const stateMemberEvent = state.find(isOurUser); // Don't assume state is set though, as it can be empty at the top of the timeline in some circumstances
const stateMemberEvent = state?.find(isOurUser);
if (stateMemberEvent) { if (stateMemberEvent) {
return RoomMember.fromMemberEvent(this._roomId, stateMemberEvent)?.serialize(); return RoomMember.fromMemberEvent(this._roomId, stateMemberEvent)?.serialize();
} }