289 lines
9.9 KiB
JavaScript
289 lines
9.9 KiB
JavaScript
|
import EventEmitter from 'events';
|
||
|
import appDispatcher from '../dispatcher';
|
||
|
import cons from './cons';
|
||
|
|
||
|
class RoomList extends EventEmitter {
|
||
|
constructor(matrixClient) {
|
||
|
super();
|
||
|
this.matrixClient = matrixClient;
|
||
|
this.mDirects = this.getMDirects();
|
||
|
|
||
|
this.inviteDirects = new Set();
|
||
|
this.inviteSpaces = new Set();
|
||
|
this.inviteRooms = new Set();
|
||
|
|
||
|
this.directs = new Set();
|
||
|
this.spaces = new Set();
|
||
|
this.rooms = new Set();
|
||
|
|
||
|
this.processingRooms = new Map();
|
||
|
|
||
|
this._populateRooms();
|
||
|
this._listenEvents();
|
||
|
|
||
|
appDispatcher.register(this.roomActions.bind(this));
|
||
|
}
|
||
|
|
||
|
roomActions(action) {
|
||
|
const addRoom = (roomId, isDM) => {
|
||
|
const myRoom = this.matrixClient.getRoom(roomId);
|
||
|
if (myRoom === null) return false;
|
||
|
|
||
|
if (isDM) this.directs.add(roomId);
|
||
|
else if (myRoom.isSpaceRoom()) this.spaces.add(roomId);
|
||
|
else this.rooms.add(roomId);
|
||
|
return true;
|
||
|
};
|
||
|
const actions = {
|
||
|
[cons.actions.room.JOIN]: () => {
|
||
|
if (addRoom(action.roomId, action.isDM)) {
|
||
|
setTimeout(() => {
|
||
|
this.emit(cons.events.roomList.ROOM_JOINED, action.roomId);
|
||
|
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
|
||
|
}, 100);
|
||
|
} else {
|
||
|
this.processingRooms.set(action.roomId, {
|
||
|
roomId: action.roomId,
|
||
|
isDM: action.isDM,
|
||
|
task: 'JOIN',
|
||
|
});
|
||
|
}
|
||
|
},
|
||
|
[cons.actions.room.CREATE]: () => {
|
||
|
if (addRoom(action.roomId, action.isDM)) {
|
||
|
setTimeout(() => {
|
||
|
this.emit(cons.events.roomList.ROOM_CREATED, action.roomId);
|
||
|
this.emit(cons.events.roomList.ROOM_JOINED, action.roomId);
|
||
|
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
|
||
|
}, 100);
|
||
|
} else {
|
||
|
this.processingRooms.set(action.roomId, {
|
||
|
roomId: action.roomId,
|
||
|
isDM: action.isDM,
|
||
|
task: 'CREATE',
|
||
|
});
|
||
|
}
|
||
|
},
|
||
|
};
|
||
|
actions[action.type]?.();
|
||
|
}
|
||
|
|
||
|
getMDirects() {
|
||
|
const mDirectsId = new Set();
|
||
|
const mDirect = this.matrixClient
|
||
|
.getAccountData('m.direct')
|
||
|
?.getContent();
|
||
|
|
||
|
if (typeof mDirect === 'undefined') return mDirectsId;
|
||
|
|
||
|
Object.keys(mDirect).forEach((direct) => {
|
||
|
mDirect[direct].forEach((directId) => mDirectsId.add(directId));
|
||
|
});
|
||
|
|
||
|
return mDirectsId;
|
||
|
}
|
||
|
|
||
|
_populateRooms() {
|
||
|
this.directs.clear();
|
||
|
this.spaces.clear();
|
||
|
this.rooms.clear();
|
||
|
this.inviteDirects.clear();
|
||
|
this.inviteSpaces.clear();
|
||
|
this.inviteRooms.clear();
|
||
|
this.matrixClient.getRooms().forEach((room) => {
|
||
|
const { roomId } = room;
|
||
|
const tombstone = room.currentState.events.get('m.room.tombstone');
|
||
|
if (typeof tombstone !== 'undefined') {
|
||
|
const repRoomId = tombstone.get('').getContent().replacement_room;
|
||
|
const repRoomMembership = this.matrixClient.getRoom(repRoomId)?.getMyMembership();
|
||
|
if (repRoomMembership === 'join') return;
|
||
|
}
|
||
|
|
||
|
if (room.getMyMembership() === 'invite') {
|
||
|
if (this._isDMInvite(room)) this.inviteDirects.add(roomId);
|
||
|
else if (room.isSpaceRoom()) this.inviteSpaces.add(roomId);
|
||
|
else this.inviteRooms.add(roomId);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (room.getMyMembership() !== 'join') return;
|
||
|
|
||
|
if (this.mDirects.has(roomId)) this.directs.add(roomId);
|
||
|
else if (room.isSpaceRoom()) this.spaces.add(roomId);
|
||
|
else this.rooms.add(roomId);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
_isDMInvite(room) {
|
||
|
const me = room.getMember(this.matrixClient.getUserId());
|
||
|
const myEventContent = me.events.member.getContent();
|
||
|
return myEventContent.membership === 'invite' && myEventContent.is_direct;
|
||
|
}
|
||
|
|
||
|
_listenEvents() {
|
||
|
// Update roomList when m.direct changes
|
||
|
this.matrixClient.on('accountData', (event) => {
|
||
|
if (event.getType() !== 'm.direct') return;
|
||
|
|
||
|
const latestMDirects = this.getMDirects();
|
||
|
|
||
|
latestMDirects.forEach((directId) => {
|
||
|
const myRoom = this.matrixClient.getRoom(directId);
|
||
|
if (this.mDirects.has(directId)) return;
|
||
|
|
||
|
// Update mDirects
|
||
|
this.mDirects.add(directId);
|
||
|
|
||
|
if (myRoom === null) return;
|
||
|
|
||
|
if (this._isDMInvite(myRoom)) return;
|
||
|
|
||
|
if (myRoom.getMyMembership === 'join' && !this.directs.has(directId)) {
|
||
|
this.directs.add(directId);
|
||
|
}
|
||
|
|
||
|
// Newly added room.
|
||
|
// at this time my membership can be invite | join
|
||
|
if (myRoom.getMyMembership() === 'join' && this.rooms.has(directId)) {
|
||
|
// found a DM which accidentally gets added to this.rooms
|
||
|
this.rooms.delete(directId);
|
||
|
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
|
||
|
this.matrixClient.on('Room.name', () => {
|
||
|
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
|
||
|
});
|
||
|
this.matrixClient.on('Room.receipt', (event) => {
|
||
|
if (event.getType() === 'm.receipt') {
|
||
|
const evContent = event.getContent();
|
||
|
const userId = Object.keys(evContent[Object.keys(evContent)[0]]['m.read'])[0];
|
||
|
if (userId !== this.matrixClient.getUserId()) return;
|
||
|
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
this.matrixClient.on('RoomState.events', (event) => {
|
||
|
if (event.getType() !== 'm.room.join_rules') return;
|
||
|
|
||
|
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
|
||
|
});
|
||
|
|
||
|
this.matrixClient.on('Room.myMembership', (room, membership, prevMembership) => {
|
||
|
// room => prevMembership = null | invite | join | leave | kick | ban | unban
|
||
|
// room => membership = invite | join | leave | kick | ban | unban
|
||
|
const { roomId } = room;
|
||
|
|
||
|
if (membership === 'unban') return;
|
||
|
|
||
|
// When user_reject/sender_undo room invite
|
||
|
if (prevMembership === 'invite') {
|
||
|
if (this.inviteDirects.has(roomId)) this.inviteDirects.delete(roomId);
|
||
|
else if (this.inviteSpaces.has(roomId)) this.inviteSpaces.delete(roomId);
|
||
|
else this.inviteRooms.delete(roomId);
|
||
|
|
||
|
this.emit(cons.events.roomList.INVITELIST_UPDATED, roomId);
|
||
|
}
|
||
|
|
||
|
// When user get invited
|
||
|
if (membership === 'invite') {
|
||
|
if (this._isDMInvite(room)) this.inviteDirects.add(roomId);
|
||
|
else if (room.isSpaceRoom()) this.inviteSpaces.add(roomId);
|
||
|
else this.inviteRooms.add(roomId);
|
||
|
|
||
|
this.emit(cons.events.roomList.INVITELIST_UPDATED, roomId);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// When user join room (first time) or start DM.
|
||
|
if ((prevMembership === null || prevMembership === 'invite') && membership === 'join') {
|
||
|
// when user create room/DM OR accept room/dm invite from this client.
|
||
|
// we will update this.rooms/this.directs with user action
|
||
|
if (this.directs.has(roomId) || this.spaces.has(roomId) || this.rooms.has(roomId)) return;
|
||
|
|
||
|
if (this.processingRooms.has(roomId)) {
|
||
|
const procRoomInfo = this.processingRooms.get(roomId);
|
||
|
|
||
|
if (procRoomInfo.isDM) this.directs.add(roomId);
|
||
|
else if (room.isSpaceRoom()) this.spaces.add(roomId);
|
||
|
else this.rooms.add(roomId);
|
||
|
|
||
|
if (procRoomInfo.task === 'CREATE') this.emit(cons.events.roomList.ROOM_CREATED, roomId);
|
||
|
this.emit(cons.events.roomList.ROOM_JOINED, roomId);
|
||
|
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
|
||
|
|
||
|
this.processingRooms.delete(roomId);
|
||
|
return;
|
||
|
}
|
||
|
if (room.isSpaceRoom()) {
|
||
|
this.spaces.add(roomId);
|
||
|
|
||
|
this.emit(cons.events.roomList.ROOM_JOINED, roomId);
|
||
|
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// below code intented to work when user create room/DM
|
||
|
// OR accept room/dm invite from other client.
|
||
|
// and we have to update our client. (it's ok to have 10sec delay)
|
||
|
|
||
|
// create a buffer of 10sec and HOPE client.accoundData get updated
|
||
|
// then accoundData event listener will update this.mDirects.
|
||
|
// and we will be able to know if it's a DM.
|
||
|
// ----------
|
||
|
// less likely situation:
|
||
|
// if we don't get accountData with 10sec then:
|
||
|
// we will temporary add it to this.rooms.
|
||
|
// and in future when accountData get updated
|
||
|
// accountData listener will automatically goona REMOVE it from this.rooms
|
||
|
// and will ADD it to this.directs
|
||
|
// and emit the cons.events.roomList.ROOMLIST_UPDATED to update the UI.
|
||
|
|
||
|
setTimeout(() => {
|
||
|
if (this.directs.has(roomId) || this.spaces.has(roomId) || this.rooms.has(roomId)) return;
|
||
|
if (this.mDirects.has(roomId)) this.directs.add(roomId);
|
||
|
else this.rooms.add(roomId);
|
||
|
|
||
|
this.emit(cons.events.roomList.ROOM_JOINED, roomId);
|
||
|
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
|
||
|
}, 10000);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// when room is a DM add/remove it from DM's and return.
|
||
|
if (this.directs.has(roomId)) {
|
||
|
if (membership === 'leave' || membership === 'kick' || membership === 'ban') {
|
||
|
this.directs.delete(roomId);
|
||
|
this.emit(cons.events.roomList.ROOM_LEAVED, roomId);
|
||
|
}
|
||
|
}
|
||
|
if (this.mDirects.has(roomId)) {
|
||
|
if (membership === 'join') {
|
||
|
this.directs.add(roomId);
|
||
|
this.emit(cons.events.roomList.ROOM_JOINED, roomId);
|
||
|
}
|
||
|
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
|
||
|
return;
|
||
|
}
|
||
|
// when room is not a DM add/remove it from rooms.
|
||
|
if (membership === 'leave' || membership === 'kick' || membership === 'ban') {
|
||
|
if (room.isSpaceRoom()) this.spaces.delete(roomId);
|
||
|
else this.rooms.delete(roomId);
|
||
|
this.emit(cons.events.roomList.ROOM_LEAVED, roomId);
|
||
|
}
|
||
|
if (membership === 'join') {
|
||
|
if (room.isSpaceRoom()) this.spaces.add(roomId);
|
||
|
else this.rooms.add(roomId);
|
||
|
this.emit(cons.events.roomList.ROOM_JOINED, roomId);
|
||
|
}
|
||
|
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
|
||
|
});
|
||
|
|
||
|
this.matrixClient.on('Room.timeline', () => {
|
||
|
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
export default RoomList;
|