diff --git a/src/app/organisms/invite-list/InviteList.jsx b/src/app/organisms/invite-list/InviteList.jsx index e9a5ade..231928f 100644 --- a/src/app/organisms/invite-list/InviteList.jsx +++ b/src/app/organisms/invite-list/InviteList.jsx @@ -56,9 +56,10 @@ function InviteList({ isOpen, onRequestClose }) { function renderRoomTile(roomId) { const mx = initMatrix.matrixClient; const myRoom = mx.getRoom(roomId); + if (!myRoom) return null; const roomName = myRoom.name; let roomAlias = myRoom.getCanonicalAlias(); - if (roomAlias === null) roomAlias = myRoom.roomId; + if (!roomAlias) roomAlias = myRoom.roomId; const inviterName = myRoom.getMember(mx.getUserId())?.events?.member?.getSender?.() ?? ''; return ( { const myRoom = initMatrix.matrixClient.getRoom(roomId); + if (myRoom === null) return null; const roomName = myRoom.name; return ( ) diff --git a/src/app/organisms/navigation/Directs.jsx b/src/app/organisms/navigation/Directs.jsx index c2a9798..e65c8af 100644 --- a/src/app/organisms/navigation/Directs.jsx +++ b/src/app/organisms/navigation/Directs.jsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; @@ -9,12 +10,12 @@ import { roomIdByActivity } from '../../../util/sort'; import RoomsCategory from './RoomsCategory'; const drawerPostie = new Postie(); -function Directs() { +function Directs({ size }) { const mx = initMatrix.matrixClient; const { roomList, notifications } = initMatrix; const [directIds, setDirectIds] = useState([]); - useEffect(() => setDirectIds([...roomList.directs].sort(roomIdByActivity)), []); + useEffect(() => setDirectIds([...roomList.directs].sort(roomIdByActivity)), [size]); useEffect(() => { const handleTimeline = (event, room, toStartOfTimeline, removed, data) => { @@ -63,5 +64,8 @@ function Directs() { return ; } +Directs.propTypes = { + size: PropTypes.number.isRequired, +}; export default Directs; diff --git a/src/app/organisms/navigation/Drawer.jsx b/src/app/organisms/navigation/Drawer.jsx index 8e3e4ea..5e6badc 100644 --- a/src/app/organisms/navigation/Drawer.jsx +++ b/src/app/organisms/navigation/Drawer.jsx @@ -42,12 +42,15 @@ function Drawer() { const [spaceId] = useSelectedSpace(); const [, forceUpdate] = useForceUpdate(); const scrollRef = useRef(null); + const { roomList } = initMatrix; useEffect(() => { - const { roomList } = initMatrix; - roomList.on(cons.events.roomList.ROOMLIST_UPDATED, forceUpdate); + const handleUpdate = () => { + forceUpdate(); + }; + roomList.on(cons.events.roomList.ROOMLIST_UPDATED, handleUpdate); return () => { - roomList.removeListener(cons.events.roomList.ROOMLIST_UPDATED, forceUpdate); + roomList.removeListener(cons.events.roomList.ROOMLIST_UPDATED, handleUpdate); }; }, []); @@ -68,7 +71,7 @@ function Drawer() { { selectedTab !== cons.tabs.DIRECTS ? - : + : } diff --git a/src/client/action/room.js b/src/client/action/room.js index 5bc9bf1..bb96f99 100644 --- a/src/client/action/room.js +++ b/src/client/action/room.js @@ -113,17 +113,20 @@ async function join(roomIdOrAlias, isDM, via) { * @param {string} roomId * @param {boolean} isDM */ -function leave(roomId) { +async function leave(roomId) { const mx = initMatrix.matrixClient; const isDM = initMatrix.roomList.directs.has(roomId); - mx.leave(roomId) - .then(() => { - appDispatcher.dispatch({ - type: cons.actions.room.LEAVE, - roomId, - isDM, - }); - }).catch(); + try { + await mx.leave(roomId); + await mx.forget(roomId); + appDispatcher.dispatch({ + type: cons.actions.room.LEAVE, + roomId, + isDM, + }); + } catch { + console.error('Unable to leave room.'); + } } async function create(options, isDM = false) { diff --git a/src/client/state/RoomList.js b/src/client/state/RoomList.js index 8b7cdc1..6ea4dad 100644 --- a/src/client/state/RoomList.js +++ b/src/client/state/RoomList.js @@ -6,6 +6,21 @@ function isMEventSpaceChild(mEvent) { return mEvent.getType() === 'm.space.child' && Object.keys(mEvent.getContent()).length > 0; } +/** + * @param {() => boolean} callback if return true wait will over else callback will be called again. + * @param {number} timeout timeout to callback + * @param {number} maxTry maximum callback try > 0. -1 means no limit + */ +async function waitFor(callback, timeout = 400, maxTry = -1) { + if (maxTry === 0) return false; + const isOver = async () => new Promise((resolve) => { + setTimeout(() => resolve(callback()), timeout); + }); + + if (await isOver()) return true; + return waitFor(callback, timeout, maxTry - 1); +} + class RoomList extends EventEmitter { constructor(matrixClient) { super(); @@ -228,6 +243,7 @@ class RoomList extends EventEmitter { } _isDMInvite(room) { + if (this.mDirects.has(room.roomId)) return true; const me = room.getMember(this.matrixClient.getUserId()); const myEventContent = me.events.member.getContent(); return myEventContent.membership === 'invite' && myEventContent.is_direct; @@ -243,22 +259,11 @@ class RoomList extends EventEmitter { 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)) { + if (myRoom.getMyMembership() === 'join') { 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); } @@ -298,23 +303,17 @@ class RoomList extends EventEmitter { } }); - this.matrixClient.on('Room.myMembership', (room, membership, prevMembership) => { + this.matrixClient.on('Room.myMembership', async (room, membership, prevMembership) => { // room => prevMembership = null | invite | join | leave | kick | ban | unban // room => membership = invite | join | leave | kick | ban | unban const { roomId } = room; + const isRoomReady = () => this.matrixClient.getRoom(roomId) !== null; + if (['join', 'invite'].includes(membership) && isRoomReady() === false) { + if (await waitFor(isRoomReady, 200, 100) === false) return; + } 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); @@ -324,88 +323,53 @@ class RoomList extends EventEmitter { 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 (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); - if (this.processingRooms.has(roomId)) { - const procRoomInfo = this.processingRooms.get(roomId); - - if (procRoomInfo.isDM) this.directs.add(roomId); - else if (room.isSpaceRoom()) this.addToSpaces(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.addToSpaces(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; + this.emit(cons.events.roomList.INVITELIST_UPDATED, roomId); } - // 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); - } + if (['leave', 'kick', 'ban'].includes(membership)) { + if (this.directs.has(roomId)) this.directs.delete(roomId); + else if (this.spaces.has(roomId)) this.deleteFromSpaces(roomId); + else this.rooms.delete(roomId); + this.emit(cons.events.roomList.ROOM_LEAVED, 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.deleteFromSpaces(roomId); - else this.rooms.delete(roomId); - this.emit(cons.events.roomList.ROOM_LEAVED, roomId); + + // when user create room/DM OR accept room/dm invite from this client. + // we will update this.rooms/this.directs with user action + if (membership === 'join' && this.processingRooms.has(roomId)) { + const procRoomInfo = this.processingRooms.get(roomId); + + if (procRoomInfo.isDM) this.directs.add(roomId); + else if (room.isSpaceRoom()) this.addToSpaces(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 (this.mDirects.has(roomId) && membership === 'join') { + this.directs.add(roomId); + this.emit(cons.events.roomList.ROOM_JOINED, roomId); + this.emit(cons.events.roomList.ROOMLIST_UPDATED); + return; + } + if (membership === 'join') { if (room.isSpaceRoom()) this.addToSpaces(roomId); else this.rooms.add(roomId); this.emit(cons.events.roomList.ROOM_JOINED, roomId); + this.emit(cons.events.roomList.ROOMLIST_UPDATED); } - this.emit(cons.events.roomList.ROOMLIST_UPDATED); }); } }