mirror of
https://github.com/vector-im/hydrogen-web.git
synced 2024-12-25 12:35:01 +01:00
253 lines
8.9 KiB
JavaScript
253 lines
8.9 KiB
JavaScript
|
/*
|
||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||
|
|
||
|
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.
|
||
|
*/
|
||
|
|
||
|
import {EventEmitter} from "../../utils/EventEmitter.js";
|
||
|
import {SummaryData, processStateEvent} from "./RoomSummary.js";
|
||
|
import {Heroes} from "./members/Heroes.js";
|
||
|
import {MemberChange, RoomMember, EVENT_TYPE as MEMBER_EVENT_TYPE} from "./members/RoomMember.js";
|
||
|
|
||
|
export class Invite extends EventEmitter {
|
||
|
constructor({roomId, user, hsApi, emitCollectionRemove, clock}) {
|
||
|
super();
|
||
|
this._roomId = roomId;
|
||
|
this._user = user;
|
||
|
this._hsApi = hsApi;
|
||
|
this._emitCollectionRemove = emitCollectionRemove;
|
||
|
this._clock = clock;
|
||
|
this._inviteData = null;
|
||
|
}
|
||
|
|
||
|
get id() {
|
||
|
return this._roomId;
|
||
|
}
|
||
|
|
||
|
get name() {
|
||
|
return this._inviteData.name || this._inviteData.canonicalAlias;
|
||
|
}
|
||
|
|
||
|
get isDirectMessage() {
|
||
|
return this._inviteData.isDirectMessage;
|
||
|
}
|
||
|
|
||
|
get avatarUrl() {
|
||
|
return this._inviteData.avatarUrl;
|
||
|
}
|
||
|
|
||
|
get timestamp() {
|
||
|
return this._inviteData.timestamp;
|
||
|
}
|
||
|
|
||
|
get isEncrypted() {
|
||
|
return this._inviteData.isEncrypted;
|
||
|
}
|
||
|
|
||
|
get inviter() {
|
||
|
return this._inviter;
|
||
|
}
|
||
|
|
||
|
get joinRule() {
|
||
|
return this._inviteData.joinRule;
|
||
|
}
|
||
|
|
||
|
async accept() {
|
||
|
|
||
|
}
|
||
|
|
||
|
async reject() {
|
||
|
|
||
|
}
|
||
|
|
||
|
load(inviteData) {
|
||
|
this._inviteData = inviteData;
|
||
|
this._inviter = inviteData.inviter ? new RoomMember(inviteData.inviter) : null;
|
||
|
}
|
||
|
|
||
|
async writeSync(membership, roomResponse, txn, log) {
|
||
|
if (membership === "invite") {
|
||
|
return log.wrap("new invite", async log => {
|
||
|
log.set("id", this.id);
|
||
|
const inviteState = roomResponse["invite_state"]?.events;
|
||
|
if (!Array.isArray(inviteState)) {
|
||
|
return null;
|
||
|
}
|
||
|
const summaryData = this._createSummaryData(inviteState);
|
||
|
let heroes;
|
||
|
if (!summaryData.name && !summaryData.canonicalAlias) {
|
||
|
heroes = await this._createHeroes(inviteState);
|
||
|
}
|
||
|
const myInvite = this._getMyInvite(inviteState);
|
||
|
if (!myInvite) {
|
||
|
return null;
|
||
|
}
|
||
|
const inviter = this._getInviter(myInvite, inviteState);
|
||
|
const inviteData = this._createData(inviteState, myInvite, inviter, summaryData, heroes);
|
||
|
txn.invites.set(inviteData);
|
||
|
return {inviteData, inviter};
|
||
|
});
|
||
|
} else {
|
||
|
return log.wrap("remove invite", log => {
|
||
|
log.set("id", this.id);
|
||
|
log.set("membership", membership);
|
||
|
txn.invites.remove(this.id);
|
||
|
return null;
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
afterSync(changes, room) {
|
||
|
if (changes) {
|
||
|
this._inviteData = changes.inviteData;
|
||
|
this._inviter = changes.inviter;
|
||
|
// emit update/add
|
||
|
} else {
|
||
|
this._emitCollectionRemove(this);
|
||
|
this.emit("change");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_createData(inviteState, myInvite, inviter, summaryData, heroes) {
|
||
|
const name = heroes ? heroes.roomName : summaryData.name;
|
||
|
const avatarUrl = heroes ? heroes.roomAvatarUrl : summaryData.avatarUrl;
|
||
|
return {
|
||
|
roomId: this.id,
|
||
|
isEncrypted: !!summaryData.encryption,
|
||
|
isDirectMessage: this._isDirectMessage(myInvite),
|
||
|
// type:
|
||
|
name,
|
||
|
avatarUrl,
|
||
|
canonicalAlias: summaryData.canonicalAlias,
|
||
|
timestamp: this._clock.now(),
|
||
|
joinRule: this._getJoinRule(inviteState),
|
||
|
inviter: inviter?.serialize(),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
_isDirectMessage(myInvite) {
|
||
|
return !!(myInvite?.content?.is_direct);
|
||
|
}
|
||
|
|
||
|
_createSummaryData(inviteState) {
|
||
|
return inviteState.reduce(processStateEvent, new SummaryData(null, this.id));
|
||
|
}
|
||
|
|
||
|
async _createHeroes(inviteState) {
|
||
|
const members = inviteState.filter(e => e.type === MEMBER_EVENT_TYPE);
|
||
|
const otherMembers = members.filter(e => e.state_key !== this._user.id);
|
||
|
const memberChanges = otherMembers.reduce((map, e) => {
|
||
|
const member = RoomMember.fromMemberEvent(this.id, e);
|
||
|
map.set(member.userId, new MemberChange(member, null));
|
||
|
return map;
|
||
|
}, new Map());
|
||
|
const otherUserIds = otherMembers.map(e => e.state_key);
|
||
|
const heroes = new Heroes(this.id);
|
||
|
const changes = await heroes.calculateChanges(otherUserIds, memberChanges, null);
|
||
|
// we don't get an actual lazy-loading m.heroes summary on invites,
|
||
|
// so just count the members by hand
|
||
|
const countSummary = new SummaryData(null, this.id);
|
||
|
countSummary.joinCount = members.reduce((sum, e) => sum + (e.content?.membership === "join" ? 1 : 0), 0);
|
||
|
countSummary.inviteCount = members.reduce((sum, e) => sum + (e.content?.membership === "invite" ? 1 : 0), 0);
|
||
|
heroes.applyChanges(changes, countSummary);
|
||
|
return heroes;
|
||
|
}
|
||
|
|
||
|
_getMyInvite(inviteState) {
|
||
|
return inviteState.find(e => e.type === MEMBER_EVENT_TYPE && e.state_key === this._user.id);
|
||
|
}
|
||
|
|
||
|
_getInviter(myInvite, inviteState) {
|
||
|
const inviterMemberEvent = inviteState.find(e => e.type === MEMBER_EVENT_TYPE && e.state_key === myInvite.sender);
|
||
|
if (inviterMemberEvent) {
|
||
|
return RoomMember.fromMemberEvent(this.id, inviterMemberEvent);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_getJoinRule(inviteState) {
|
||
|
const event = inviteState.find(e => e.type === "m.room.join_rules");
|
||
|
if (event) {
|
||
|
return event.content?.join_rule;
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
import {NullLogItem} from "../../logging/NullLogger.js";
|
||
|
import {Clock as MockClock} from "../../mocks/Clock.js";
|
||
|
import {default as roomInviteFixture} from "../../fixtures/matrix/invites/room.js";
|
||
|
import {default as dmInviteFixture} from "../../fixtures/matrix/invites/dm.js";
|
||
|
|
||
|
export function tests() {
|
||
|
|
||
|
function createStorage() {
|
||
|
const invitesMap = new Map();
|
||
|
return {
|
||
|
invitesMap,
|
||
|
invites: {
|
||
|
set(invite) {
|
||
|
invitesMap.set(invite.roomId, invite);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const roomId = "!123:hs.tld";
|
||
|
const aliceAvatarUrl = "mxc://hs.tld/def456";
|
||
|
const roomAvatarUrl = "mxc://hs.tld/roomavatar";
|
||
|
|
||
|
return {
|
||
|
"invite for room has correct fields": async assert => {
|
||
|
const invite = new Invite({
|
||
|
roomId,
|
||
|
clock: new MockClock(1001),
|
||
|
user: {id: "@bob:hs.tld"}
|
||
|
});
|
||
|
const txn = createStorage();
|
||
|
const changes = await invite.writeSync("invite", roomInviteFixture, txn, new NullLogItem());
|
||
|
assert.equal(txn.invitesMap.get(roomId).roomId, roomId);
|
||
|
invite.afterSync(changes);
|
||
|
assert.equal(invite.name, "Invite example");
|
||
|
assert.equal(invite.avatarUrl, roomAvatarUrl);
|
||
|
assert.equal(invite.joinRule, "invite");
|
||
|
assert.equal(invite.timestamp, 1001);
|
||
|
assert.equal(invite.isEncrypted, false);
|
||
|
assert.equal(invite.isDirectMessage, false);
|
||
|
assert(invite.inviter);
|
||
|
assert.equal(invite.inviter.userId, "@alice:hs.tld");
|
||
|
assert.equal(invite.inviter.displayName, "Alice");
|
||
|
assert.equal(invite.inviter.avatarUrl, aliceAvatarUrl);
|
||
|
},
|
||
|
"invite for encrypted DM has correct fields": async assert => {
|
||
|
const invite = new Invite({
|
||
|
roomId,
|
||
|
clock: new MockClock(1003),
|
||
|
user: {id: "@bob:hs.tld"}
|
||
|
});
|
||
|
const txn = createStorage();
|
||
|
const changes = await invite.writeSync("invite", dmInviteFixture, txn, new NullLogItem());
|
||
|
assert.equal(txn.invitesMap.get(roomId).roomId, roomId);
|
||
|
invite.afterSync(changes);
|
||
|
assert.equal(invite.name, "Alice");
|
||
|
assert.equal(invite.avatarUrl, aliceAvatarUrl);
|
||
|
assert.equal(invite.timestamp, 1003);
|
||
|
assert.equal(invite.isEncrypted, true);
|
||
|
assert.equal(invite.isDirectMessage, true);
|
||
|
assert(invite.inviter);
|
||
|
assert.equal(invite.inviter.userId, "@alice:hs.tld");
|
||
|
assert.equal(invite.inviter.displayName, "Alice");
|
||
|
assert.equal(invite.inviter.avatarUrl, aliceAvatarUrl);
|
||
|
},
|
||
|
}
|
||
|
}
|