Mailspring/app/internal_packages/unread-notifications/lib/main.es6

209 lines
6.2 KiB
JavaScript

import _ from 'underscore'
import {
Thread,
Actions,
SoundRegistry,
NativeNotifications,
DatabaseStore,
} from 'nylas-exports';
export class Notifier {
constructor() {
this.unlisteners = [];
this.unlisteners.push(Actions.onNewMailDeltas.listen(this._onNewMailReceived, this));
this.activationTime = Date.now();
this.unnotifiedQueue = [];
this.hasScheduledNotify = false;
this.activeNotifications = {};
this.unlisteners.push(DatabaseStore.listen(this._onDatabaseUpdated, this));
}
unlisten() {
for (const unlisten of this.unlisteners) {
unlisten();
}
}
_onDatabaseUpdated({objectClass, objects}) {
if (objectClass === 'Thread') {
objects
.filter((thread) => !thread.unread)
.forEach((thread) => this._onThreadIsRead(thread));
}
}
_onThreadIsRead({id: threadId}) {
if (threadId in this.activeNotifications) {
this.activeNotifications[threadId].forEach((n) => n.close());
delete this.activeNotifications[threadId];
}
}
_notifyAll() {
NativeNotifications.displayNotification({
title: `${this.unnotifiedQueue.length} Unread Messages`,
tag: 'unread-update',
});
this.unnotifiedQueue = [];
}
_notifyOne({message, thread}) {
const from = (message.from[0]) ? message.from[0].displayName() : "Unknown";
const title = from;
let subtitle = null;
let body = null;
if (message.subject && message.subject.length > 0) {
subtitle = message.subject;
body = message.snippet;
} else {
subtitle = message.snippet
body = null
}
const notification = NativeNotifications.displayNotification({
title: title,
subtitle: subtitle,
body: body,
canReply: true,
tag: 'unread-update',
onActivate: ({response, activationType}) => {
if ((activationType === 'replied') && response && typeof response === 'string') {
Actions.sendQuickReply({thread, message}, response);
} else {
NylasEnv.displayWindow()
}
if (!thread) {
NylasEnv.showErrorDialog(`Can't find that thread`)
return
}
Actions.ensureCategoryIsFocused('inbox', thread.accountId);
Actions.setFocus({collection: 'thread', item: thread});
},
});
if (!this.activeNotifications[thread.id]) {
this.activeNotifications[thread.id] = [notification];
} else {
this.activeNotifications[thread.id].push(notification);
}
}
_notifyMessages() {
if (this.unnotifiedQueue.length >= 5) {
this._notifyAll()
} else if (this.unnotifiedQueue.length > 0) {
this._notifyOne(this.unnotifiedQueue.shift());
}
this.hasScheduledNotify = false;
if (this.unnotifiedQueue.length > 0) {
setTimeout(() => this._notifyMessages(), 2000);
this.hasScheduledNotify = true;
}
}
// https://phab.nylas.com/D2188
_onNewMessagesMissingThreads(messages) {
setTimeout(() => {
const threads = {}
for (const {threadId} of messages) {
threads[threadId] = threads[threadId] || DatabaseStore.find(Thread, threadId);
}
Promise.props(threads).then((resolvedThreads) => {
const resolved = messages.filter((msg) => resolvedThreads[msg.threadId]);
if (resolved.length > 0) {
this._onNewMailReceived({message: resolved, thread: Object.values(resolvedThreads)});
}
});
}, 10000);
}
_onNewMailReceived(incoming) {
return new Promise((resolve) => {
if (NylasEnv.config.get('core.notifications.enabled') === false) {
resolve();
return;
}
const incomingMessages = incoming.message || [];
const incomingThreads = incoming.thread || [];
// Filter for new messages that are not sent by the current user
const newUnread = incomingMessages.filter((msg) => {
const isUnread = msg.unread === true;
const isNew = msg.date && msg.date.valueOf() >= this.activationTime;
const isFromMe = msg.isFromMe();
return isUnread && isNew && !isFromMe;
});
if (newUnread.length === 0) {
resolve();
return;
}
// For each message, find it's corresponding thread. First, look to see
// if it's already in the `incoming` payload (sent via delta sync
// at the same time as the message.) If it's not, try loading it from
// the local cache.
// Note we may receive multiple unread msgs for the same thread.
// Using a map and ?= to avoid repeating work.
const threads = {}
for (const {threadId} of newUnread) {
threads[threadId] = threads[threadId] || incomingThreads.find(t => t.id === threadId)
threads[threadId] = threads[threadId] || DatabaseStore.find(Thread, threadId);
}
Promise.props(threads).then((resolvedThreads) => {
// Filter new unread messages to just the ones in the inbox
const newUnreadInInbox = newUnread.filter((msg) =>
resolvedThreads[msg.threadId] && resolvedThreads[msg.threadId].categories.find(c => c.role === 'inbox')
)
// Filter messages that we can't decide whether to display or not
// since no associated Thread object has arrived yet.
const newUnreadMissingThreads = newUnread.filter((msg) => !resolvedThreads[msg.threadId])
if (newUnreadMissingThreads.length > 0) {
this._onNewMessagesMissingThreads(newUnreadMissingThreads);
}
if (newUnreadInInbox.length === 0) {
resolve();
return;
}
for (const msg of newUnreadInInbox) {
this.unnotifiedQueue.push({message: msg, thread: resolvedThreads[msg.threadId]});
}
if (!this.hasScheduledNotify) {
if (NylasEnv.config.get("core.notifications.sounds")) {
this._playNewMailSound = this._playNewMailSound || _.debounce(() => SoundRegistry.playSound('new-mail'), 5000, true);
this._playNewMailSound();
}
this._notifyMessages();
}
resolve();
});
});
}
}
export const config = {
enabled: {
'type': 'boolean',
'default': true,
},
};
export function activate() {
this.notifier = new Notifier();
}
export function deactivate() {
this.notifier.unlisten();
}