2021-09-11 16:57:35 +03:00
|
|
|
import EventEmitter from 'events';
|
2022-02-15 13:48:25 +02:00
|
|
|
import renderAvatar from '../../app/atoms/avatar/render';
|
|
|
|
import { cssColorMXID } from '../../util/colorMXID';
|
2022-01-29 16:20:51 +02:00
|
|
|
import { selectRoom } from '../action/navigation';
|
2021-09-11 16:57:35 +03:00
|
|
|
import cons from './cons';
|
2022-01-29 16:20:51 +02:00
|
|
|
import navigation from './navigation';
|
|
|
|
import settings from './settings';
|
2021-09-11 16:57:35 +03:00
|
|
|
|
2022-03-18 05:37:11 +02:00
|
|
|
import NotificationSound from '../../../public/sound/notification.ogg';
|
|
|
|
|
2021-12-07 17:34:07 +02:00
|
|
|
function isNotifEvent(mEvent) {
|
|
|
|
const eType = mEvent.getType();
|
|
|
|
if (!cons.supportEventTypes.includes(eType)) return false;
|
|
|
|
if (eType === 'm.room.member') return false;
|
|
|
|
|
|
|
|
if (mEvent.isRedacted()) return false;
|
|
|
|
if (mEvent.getRelation()?.rel_type === 'm.replace') return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-03-15 13:51:36 +02:00
|
|
|
function isMutedRule(rule) {
|
|
|
|
return rule.actions[0] === 'dont_notify' && rule.conditions[0].kind === 'event_match';
|
|
|
|
}
|
|
|
|
|
|
|
|
function findMutedRule(overrideRules, roomId) {
|
|
|
|
return overrideRules.find((rule) => (
|
|
|
|
rule.rule_id === roomId
|
|
|
|
&& isMutedRule(rule)
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2021-09-11 16:57:35 +03:00
|
|
|
class Notifications extends EventEmitter {
|
|
|
|
constructor(roomList) {
|
|
|
|
super();
|
|
|
|
|
|
|
|
this.matrixClient = roomList.matrixClient;
|
|
|
|
this.roomList = roomList;
|
|
|
|
|
|
|
|
this.roomIdToNoti = new Map();
|
|
|
|
|
|
|
|
this._initNoti();
|
|
|
|
this._listenEvents();
|
|
|
|
|
2022-01-29 16:20:51 +02:00
|
|
|
// Ask for permission by default after loading
|
|
|
|
window.Notification?.requestPermission();
|
|
|
|
|
2021-09-11 16:57:35 +03:00
|
|
|
// TODO:
|
|
|
|
window.notifications = this;
|
|
|
|
}
|
|
|
|
|
|
|
|
_initNoti() {
|
|
|
|
const addNoti = (roomId) => {
|
|
|
|
const room = this.matrixClient.getRoom(roomId);
|
2022-03-15 13:51:36 +02:00
|
|
|
if (this.getNotiType(room.roomId) === cons.notifs.MUTE) return;
|
2021-09-11 16:57:35 +03:00
|
|
|
if (this.doesRoomHaveUnread(room) === false) return;
|
2022-03-15 13:51:36 +02:00
|
|
|
|
2021-09-11 16:57:35 +03:00
|
|
|
const total = room.getUnreadNotificationCount('total');
|
|
|
|
const highlight = room.getUnreadNotificationCount('highlight');
|
2022-03-13 11:42:54 +02:00
|
|
|
this._setNoti(room.roomId, total ?? 0, highlight ?? 0);
|
2021-09-11 16:57:35 +03:00
|
|
|
};
|
|
|
|
[...this.roomList.rooms].forEach(addNoti);
|
|
|
|
[...this.roomList.directs].forEach(addNoti);
|
|
|
|
}
|
|
|
|
|
|
|
|
doesRoomHaveUnread(room) {
|
|
|
|
const userId = this.matrixClient.getUserId();
|
|
|
|
const readUpToId = room.getEventReadUpTo(userId);
|
2021-12-03 15:02:10 +02:00
|
|
|
const liveEvents = room.getLiveTimeline().getEvents();
|
2021-09-11 16:57:35 +03:00
|
|
|
|
2021-12-07 17:34:07 +02:00
|
|
|
if (liveEvents[liveEvents.length - 1]?.getSender() === userId) {
|
2021-09-11 16:57:35 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-12-03 15:02:10 +02:00
|
|
|
for (let i = liveEvents.length - 1; i >= 0; i -= 1) {
|
|
|
|
const event = liveEvents[i];
|
2021-09-11 16:57:35 +03:00
|
|
|
if (event.getId() === readUpToId) return false;
|
2021-12-07 17:34:07 +02:00
|
|
|
if (isNotifEvent(event)) return true;
|
2021-09-11 16:57:35 +03:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-03-15 13:51:36 +02:00
|
|
|
getNotiType(roomId) {
|
|
|
|
const mx = this.matrixClient;
|
|
|
|
const pushRule = mx.getRoomPushRule('global', roomId);
|
|
|
|
|
|
|
|
if (pushRule === undefined) {
|
|
|
|
const overrideRules = mx.getAccountData('m.push_rules')?.getContent()?.global?.override;
|
|
|
|
if (overrideRules === undefined) return cons.notifs.DEFAULT;
|
|
|
|
|
|
|
|
const isMuted = findMutedRule(overrideRules, roomId);
|
|
|
|
|
|
|
|
return isMuted ? cons.notifs.MUTE : cons.notifs.DEFAULT;
|
|
|
|
}
|
|
|
|
if (pushRule.actions[0] === 'notify') return cons.notifs.ALL_MESSAGES;
|
|
|
|
return cons.notifs.MENTIONS_AND_KEYWORDS;
|
|
|
|
}
|
|
|
|
|
2021-09-11 16:57:35 +03:00
|
|
|
getNoti(roomId) {
|
|
|
|
return this.roomIdToNoti.get(roomId) || { total: 0, highlight: 0, from: null };
|
|
|
|
}
|
|
|
|
|
2021-09-12 18:14:13 +03:00
|
|
|
getTotalNoti(roomId) {
|
|
|
|
const { total } = this.getNoti(roomId);
|
|
|
|
return total;
|
|
|
|
}
|
|
|
|
|
|
|
|
getHighlightNoti(roomId) {
|
|
|
|
const { highlight } = this.getNoti(roomId);
|
|
|
|
return highlight;
|
|
|
|
}
|
|
|
|
|
|
|
|
getFromNoti(roomId) {
|
|
|
|
const { from } = this.getNoti(roomId);
|
|
|
|
return from;
|
|
|
|
}
|
|
|
|
|
2021-09-11 16:57:35 +03:00
|
|
|
hasNoti(roomId) {
|
|
|
|
return this.roomIdToNoti.has(roomId);
|
|
|
|
}
|
|
|
|
|
2021-12-08 10:19:47 +02:00
|
|
|
deleteNoti(roomId) {
|
|
|
|
if (this.hasNoti(roomId)) {
|
|
|
|
const noti = this.getNoti(roomId);
|
|
|
|
this._deleteNoti(roomId, noti.total, noti.highlight);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-13 11:42:54 +02:00
|
|
|
_setNoti(roomId, total, highlight) {
|
|
|
|
const addNoti = (id, t, h, fromId) => {
|
|
|
|
const prevTotal = this.roomIdToNoti.get(id)?.total ?? null;
|
|
|
|
const noti = this.getNoti(id);
|
2021-10-28 12:38:26 +03:00
|
|
|
|
2022-03-13 11:42:54 +02:00
|
|
|
noti.total += t;
|
|
|
|
noti.highlight += h;
|
2021-10-28 12:38:26 +03:00
|
|
|
|
2022-03-13 11:42:54 +02:00
|
|
|
if (fromId) {
|
|
|
|
if (noti.from === null) noti.from = new Set();
|
|
|
|
noti.from.add(fromId);
|
|
|
|
}
|
|
|
|
this.roomIdToNoti.set(id, noti);
|
|
|
|
this.emit(cons.events.notifications.NOTI_CHANGED, id, noti.total, prevTotal);
|
|
|
|
};
|
2021-10-28 12:38:26 +03:00
|
|
|
|
2021-09-11 16:57:35 +03:00
|
|
|
const noti = this.getNoti(roomId);
|
2022-03-13 11:42:54 +02:00
|
|
|
const addT = total - noti.total;
|
|
|
|
const addH = highlight - noti.highlight;
|
|
|
|
if (addT < 0 || addH < 0) return;
|
|
|
|
|
|
|
|
addNoti(roomId, addT, addH);
|
2022-03-14 14:01:57 +02:00
|
|
|
const allParentSpaces = this.roomList.getAllParentSpaces(roomId);
|
2022-03-13 11:42:54 +02:00
|
|
|
allParentSpaces.forEach((spaceId) => {
|
|
|
|
addNoti(spaceId, addT, addH, roomId);
|
|
|
|
});
|
2021-09-11 16:57:35 +03:00
|
|
|
}
|
|
|
|
|
2022-03-13 11:42:54 +02:00
|
|
|
_deleteNoti(roomId, total, highlight) {
|
|
|
|
const removeNoti = (id, t, h, fromId) => {
|
|
|
|
if (this.roomIdToNoti.has(id) === false) return;
|
|
|
|
|
|
|
|
const noti = this.getNoti(id);
|
|
|
|
const prevTotal = noti.total;
|
|
|
|
noti.total -= t;
|
|
|
|
noti.highlight -= h;
|
|
|
|
if (noti.total < 0) {
|
|
|
|
noti.total = 0;
|
|
|
|
noti.highlight = 0;
|
|
|
|
}
|
|
|
|
if (fromId && noti.from !== null) {
|
|
|
|
if (!this.hasNoti(fromId)) noti.from.delete(fromId);
|
|
|
|
}
|
|
|
|
if (noti.from === null || noti.from.size === 0) {
|
|
|
|
this.roomIdToNoti.delete(id);
|
|
|
|
this.emit(cons.events.notifications.FULL_READ, id);
|
|
|
|
this.emit(cons.events.notifications.NOTI_CHANGED, id, null, prevTotal);
|
|
|
|
} else {
|
|
|
|
this.roomIdToNoti.set(id, noti);
|
|
|
|
this.emit(cons.events.notifications.NOTI_CHANGED, id, noti.total, prevTotal);
|
|
|
|
}
|
|
|
|
};
|
2021-09-11 16:57:35 +03:00
|
|
|
|
2022-03-13 11:42:54 +02:00
|
|
|
removeNoti(roomId, total, highlight);
|
2022-03-14 14:01:57 +02:00
|
|
|
const allParentSpaces = this.roomList.getAllParentSpaces(roomId);
|
2022-03-13 11:42:54 +02:00
|
|
|
allParentSpaces.forEach((spaceId) => {
|
|
|
|
removeNoti(spaceId, total, highlight, roomId);
|
|
|
|
});
|
2021-09-11 16:57:35 +03:00
|
|
|
}
|
|
|
|
|
2022-01-29 16:20:51 +02:00
|
|
|
async _displayPopupNoti(mEvent, room) {
|
2022-03-18 05:37:11 +02:00
|
|
|
if (!settings.showNotifications && !settings.isNotificationSounds) return;
|
2022-01-29 16:20:51 +02:00
|
|
|
|
|
|
|
const actions = this.matrixClient.getPushActionsForEvent(mEvent);
|
|
|
|
if (!actions?.notify) return;
|
|
|
|
|
|
|
|
if (navigation.selectedRoomId === room.roomId && document.visibilityState === 'visible') return;
|
|
|
|
|
|
|
|
if (mEvent.isEncrypted()) {
|
|
|
|
await mEvent.attemptDecryption(this.matrixClient.crypto);
|
|
|
|
}
|
|
|
|
|
2022-03-18 05:37:11 +02:00
|
|
|
if (settings.showNotifications) {
|
|
|
|
let title;
|
|
|
|
if (!mEvent.sender || room.name === mEvent.sender.name) {
|
|
|
|
title = room.name;
|
|
|
|
} else if (mEvent.sender) {
|
|
|
|
title = `${mEvent.sender.name} (${room.name})`;
|
|
|
|
}
|
2022-01-29 16:20:51 +02:00
|
|
|
|
2022-03-18 05:37:11 +02:00
|
|
|
const iconSize = 36;
|
|
|
|
const icon = await renderAvatar({
|
|
|
|
text: mEvent.sender.name,
|
|
|
|
bgColor: cssColorMXID(mEvent.getSender()),
|
|
|
|
imageSrc: mEvent.sender?.getAvatarUrl(this.matrixClient.baseUrl, iconSize, iconSize, 'crop'),
|
|
|
|
size: iconSize,
|
|
|
|
borderRadius: 8,
|
|
|
|
scale: 8,
|
|
|
|
});
|
|
|
|
|
|
|
|
const noti = new window.Notification(title, {
|
|
|
|
body: mEvent.getContent().body,
|
|
|
|
icon,
|
|
|
|
silent: settings.isNotificationSounds,
|
|
|
|
});
|
|
|
|
if (settings.isNotificationSounds) {
|
|
|
|
noti.onshow = () => this._playNotiSounds();
|
|
|
|
}
|
|
|
|
noti.onclick = () => selectRoom(room.roomId, mEvent.getId());
|
|
|
|
} else {
|
|
|
|
this._playNotiSounds();
|
|
|
|
}
|
|
|
|
}
|
2022-02-15 13:48:25 +02:00
|
|
|
|
2022-03-18 05:37:11 +02:00
|
|
|
_playNotiSounds() {
|
|
|
|
if (!this._notiAudio) {
|
|
|
|
this._notiAudio = new Audio(NotificationSound);
|
|
|
|
}
|
|
|
|
this._notiAudio.play();
|
2022-01-29 16:20:51 +02:00
|
|
|
}
|
|
|
|
|
2021-09-11 16:57:35 +03:00
|
|
|
_listenEvents() {
|
|
|
|
this.matrixClient.on('Room.timeline', (mEvent, room) => {
|
2022-03-13 13:10:26 +02:00
|
|
|
if (room.isSpaceRoom()) return;
|
2021-12-07 17:34:07 +02:00
|
|
|
if (!isNotifEvent(mEvent)) return;
|
2022-03-15 13:51:36 +02:00
|
|
|
|
2021-12-03 15:02:10 +02:00
|
|
|
const liveEvents = room.getLiveTimeline().getEvents();
|
2021-09-11 16:57:35 +03:00
|
|
|
|
2021-12-03 15:02:10 +02:00
|
|
|
const lastTimelineEvent = liveEvents[liveEvents.length - 1];
|
2021-09-11 16:57:35 +03:00
|
|
|
if (lastTimelineEvent.getId() !== mEvent.getId()) return;
|
|
|
|
if (mEvent.getSender() === this.matrixClient.getUserId()) return;
|
|
|
|
|
|
|
|
const total = room.getUnreadNotificationCount('total');
|
|
|
|
const highlight = room.getUnreadNotificationCount('highlight');
|
|
|
|
|
2022-03-15 13:51:36 +02:00
|
|
|
if (this.getNotiType(room.roomId) === cons.notifs.MUTE) {
|
|
|
|
this.deleteNoti(room.roomId, total ?? 0, highlight ?? 0);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-13 11:42:54 +02:00
|
|
|
this._setNoti(room.roomId, total ?? 0, highlight ?? 0);
|
2022-01-29 16:20:51 +02:00
|
|
|
|
|
|
|
if (this.matrixClient.getSyncState() === 'SYNCING') {
|
|
|
|
this._displayPopupNoti(mEvent, room);
|
|
|
|
}
|
2021-09-11 16:57:35 +03:00
|
|
|
});
|
|
|
|
|
2022-03-15 13:51:36 +02:00
|
|
|
this.matrixClient.on('accountData', (mEvent, oldMEvent) => {
|
|
|
|
if (mEvent.getType() === 'm.push_rules') {
|
|
|
|
const override = mEvent?.getContent()?.global?.override;
|
|
|
|
const oldOverride = oldMEvent?.getContent()?.global?.override;
|
|
|
|
if (!override || !oldOverride) return;
|
|
|
|
|
|
|
|
const isMuteToggled = (rule, otherOverride) => {
|
|
|
|
const roomId = rule.rule_id;
|
|
|
|
const room = this.matrixClient.getRoom(roomId);
|
|
|
|
if (room === null) return false;
|
|
|
|
if (room.isSpaceRoom()) return false;
|
|
|
|
|
|
|
|
const isMuted = isMutedRule(rule);
|
|
|
|
if (!isMuted) return false;
|
|
|
|
const isOtherMuted = findMutedRule(otherOverride, roomId);
|
|
|
|
if (isOtherMuted) return false;
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
const mutedRules = override.filter((rule) => isMuteToggled(rule, oldOverride));
|
|
|
|
const unMutedRules = oldOverride.filter((rule) => isMuteToggled(rule, override));
|
|
|
|
|
|
|
|
mutedRules.forEach((rule) => {
|
|
|
|
this.emit(cons.events.notifications.MUTE_TOGGLED, rule.rule_id, true);
|
|
|
|
this.deleteNoti(rule.rule_id);
|
|
|
|
});
|
|
|
|
unMutedRules.forEach((rule) => {
|
|
|
|
this.emit(cons.events.notifications.MUTE_TOGGLED, rule.rule_id, false);
|
|
|
|
const room = this.matrixClient.getRoom(rule.rule_id);
|
|
|
|
if (!this.doesRoomHaveUnread(room)) return;
|
|
|
|
const total = room.getUnreadNotificationCount('total');
|
|
|
|
const highlight = room.getUnreadNotificationCount('highlight');
|
|
|
|
this._setNoti(room.roomId, total ?? 0, highlight ?? 0);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-09-11 16:57:35 +03:00
|
|
|
this.matrixClient.on('Room.receipt', (mEvent, room) => {
|
|
|
|
if (mEvent.getType() === 'm.receipt') {
|
2022-03-13 13:10:26 +02:00
|
|
|
if (room.isSpaceRoom()) return;
|
2021-09-11 16:57:35 +03:00
|
|
|
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;
|
|
|
|
|
2021-12-08 10:19:47 +02:00
|
|
|
this.deleteNoti(room.roomId);
|
2021-09-11 16:57:35 +03:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
this.matrixClient.on('Room.myMembership', (room, membership) => {
|
|
|
|
if (membership === 'leave' && this.hasNoti(room.roomId)) {
|
2021-12-08 10:19:47 +02:00
|
|
|
this.deleteNoti(room.roomId);
|
2021-09-11 16:57:35 +03:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default Notifications;
|