From a8884277772fdced671c6bd1ad968139447298f8 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 11 Sep 2021 19:27:35 +0530 Subject: [PATCH] Added Notification.js for noti mapping (#82) --- src/client/initMatrix.js | 2 + src/client/state/Notifications.js | 146 ++++++++++++++++++++++++++++++ src/client/state/cons.js | 4 + 3 files changed, 152 insertions(+) create mode 100644 src/client/state/Notifications.js diff --git a/src/client/initMatrix.js b/src/client/initMatrix.js index 26d07e6c..91a41ea4 100644 --- a/src/client/initMatrix.js +++ b/src/client/initMatrix.js @@ -4,6 +4,7 @@ import * as sdk from 'matrix-js-sdk'; import { secret } from './state/auth'; import RoomList from './state/RoomList'; import RoomsInput from './state/RoomsInput'; +import Notifications from './state/Notifications'; global.Olm = require('@matrix-org/olm'); @@ -56,6 +57,7 @@ class InitMatrix extends EventEmitter { if (prevState === null) { this.roomList = new RoomList(this.matrixClient); this.roomsInput = new RoomsInput(this.matrixClient); + this.notifications = new Notifications(this.roomList); this.emit('init_loading_finished'); } }, diff --git a/src/client/state/Notifications.js b/src/client/state/Notifications.js new file mode 100644 index 00000000..f3b56ace --- /dev/null +++ b/src/client/state/Notifications.js @@ -0,0 +1,146 @@ +import EventEmitter from 'events'; +import cons from './cons'; + +class Notifications extends EventEmitter { + constructor(roomList) { + super(); + + this.matrixClient = roomList.matrixClient; + this.roomList = roomList; + + this.roomIdToNoti = new Map(); + + this._initNoti(); + this._listenEvents(); + + // TODO: + window.notifications = this; + } + + _initNoti() { + const addNoti = (roomId) => { + const room = this.matrixClient.getRoom(roomId); + if (this.doesRoomHaveUnread(room) === false) return; + const total = room.getUnreadNotificationCount('total'); + const highlight = room.getUnreadNotificationCount('highlight'); + const noti = this.getNoti(room.roomId); + this._setNoti(room.roomId, total - noti.total, highlight - noti.highlight); + }; + [...this.roomList.rooms].forEach(addNoti); + [...this.roomList.directs].forEach(addNoti); + } + + doesRoomHaveUnread(room) { + const userId = this.matrixClient.getUserId(); + const readUpToId = room.getEventReadUpTo(userId); + const supportEvents = ['m.room.message', 'm.room.encrypted', 'm.sticker']; + + if (room.timeline.length + && room.timeline[room.timeline.length - 1].sender + && room.timeline[room.timeline.length - 1].sender.userId === userId + && room.timeline[room.timeline.length - 1].getType() !== 'm.room.member') { + return false; + } + + for (let i = room.timeline.length - 1; i >= 0; i -= 1) { + const event = room.timeline[i]; + + if (event.getId() === readUpToId) return false; + + if (supportEvents.includes(event.getType())) { + return true; + } + } + return true; + } + + getNoti(roomId) { + return this.roomIdToNoti.get(roomId) || { total: 0, highlight: 0, from: null }; + } + + hasNoti(roomId) { + return this.roomIdToNoti.has(roomId); + } + + _setNoti(roomId, total, highlight, childId) { + const noti = this.getNoti(roomId); + + noti.total += total; + noti.highlight += highlight; + if (childId) { + if (noti.from === null) noti.from = new Set(); + noti.from.add(childId); + } + + this.roomIdToNoti.set(roomId, noti); + this.emit(cons.events.notification.NOTI_CHANGED, roomId); + + const parentIds = this.roomList.roomIdToParents.get(roomId); + if (typeof parentIds === 'undefined') return; + [...parentIds].forEach((parentId) => this._setNoti(parentId, total, highlight, roomId)); + } + + _deleteNoti(roomId, total, highlight, childId) { + if (this.roomIdToNoti.has(roomId) === false) return; + + const noti = this.getNoti(roomId); + noti.total -= total; + noti.highlight -= highlight; + if (childId && noti.from !== null) { + noti.from.delete(childId); + } + if (noti.from === null || noti.from.size === 0) { + this.roomIdToNoti.delete(roomId); + this.emit(cons.events.notification.FULL_READ, roomId); + } else { + this.roomIdToNoti.set(roomId, noti); + this.emit(cons.events.notification.NOTI_CHANGED, roomId); + } + + const parentIds = this.roomList.roomIdToParents.get(roomId); + if (typeof parentIds === 'undefined') return; + [...parentIds].forEach((parentId) => this._deleteNoti(parentId, total, highlight, roomId)); + } + + _listenEvents() { + this.matrixClient.on('Room.timeline', (mEvent, room) => { + const supportEvents = ['m.room.message', 'm.room.encrypted', 'm.sticker']; + if (!supportEvents.includes(mEvent.getType())) return; + + const lastTimelineEvent = room.timeline[room.timeline.length - 1]; + if (lastTimelineEvent.getId() !== mEvent.getId()) return; + if (mEvent.getSender() === this.matrixClient.getUserId()) return; + + const total = room.getUnreadNotificationCount('total'); + const highlight = room.getUnreadNotificationCount('highlight'); + + const noti = this.getNoti(room.roomId); + this._setNoti(room.roomId, total - noti.total, highlight - noti.highlight); + }); + + this.matrixClient.on('Room.receipt', (mEvent, room) => { + if (mEvent.getType() === 'm.receipt') { + if (typeof mEvent.event.room_id === 'string') return; + + const content = mEvent.getContent(); + const readedEventId = Object.keys(content)[0]; + const readerUserId = Object.keys(content[readedEventId]['m.read'])[0]; + if (readerUserId !== this.matrixClient.getUserId()) return; + + if (this.hasNoti(room.roomId)) { + const noti = this.getNoti(room.roomId); + this._deleteNoti(room.roomId, noti.total, noti.highlight); + } + } + }); + + this.matrixClient.on('Room.myMembership', (room, membership) => { + if (membership === 'leave' && this.hasNoti(room.roomId)) { + const noti = this.getNoti(room.roomId); + this._deleteNoti(room.roomId, noti.total, noti.highlight); + } + }); + } +} + +export default Notifications; diff --git a/src/client/state/cons.js b/src/client/state/cons.js index 6c00668a..6211348b 100644 --- a/src/client/state/cons.js +++ b/src/client/state/cons.js @@ -71,6 +71,10 @@ const cons = { EVENT_ARRIVED: 'EVENT_ARRIVED', SPACE_SHORTCUT_UPDATED: 'SPACE_SHORTCUT_UPDATED', }, + notification: { + NOTI_CHANGED: 'NOTI_CHANGED', + FULL_READ: 'FULL_READ', + }, roomTimeline: { EVENT: 'EVENT', PAGINATED: 'PAGINATED',