2016-05-10 04:09:05 +08:00
|
|
|
import _ from 'underscore'
|
|
|
|
import {
|
|
|
|
Thread,
|
|
|
|
Actions,
|
|
|
|
SoundRegistry,
|
|
|
|
NativeNotifications,
|
|
|
|
DatabaseStore,
|
|
|
|
} from 'nylas-exports';
|
|
|
|
|
|
|
|
export class Notifier {
|
|
|
|
constructor() {
|
|
|
|
this.unlisteners = [];
|
2016-12-03 03:40:59 +08:00
|
|
|
this.unlisteners.push(Actions.didPassivelyReceiveCreateDeltas.listen(this._onNewMailReceived, this));
|
2016-05-10 04:09:05 +08:00
|
|
|
this.activationTime = Date.now();
|
2016-07-09 02:30:33 +08:00
|
|
|
this.unnotifiedQueue = [];
|
|
|
|
this.hasScheduledNotify = false;
|
2016-07-01 08:44:38 +08:00
|
|
|
|
|
|
|
this.activeNotifications = {};
|
|
|
|
this.unlisteners.push(DatabaseStore.listen(this._onDatabaseUpdated, this));
|
2016-05-10 04:09:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
unlisten() {
|
|
|
|
for (const unlisten of this.unlisteners) {
|
|
|
|
unlisten();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-01 08:44:38 +08:00
|
|
|
_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];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-10 04:09:05 +08:00
|
|
|
_notifyAll() {
|
|
|
|
NativeNotifications.displayNotification({
|
2016-07-09 02:30:33 +08:00
|
|
|
title: `${this.unnotifiedQueue.length} Unread Messages`,
|
2016-05-10 04:09:05 +08:00
|
|
|
tag: 'unread-update',
|
|
|
|
});
|
2016-07-09 02:30:33 +08:00
|
|
|
this.unnotifiedQueue = [];
|
2016-05-10 04:09:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
_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
|
|
|
|
}
|
|
|
|
|
2016-07-01 08:44:38 +08:00
|
|
|
const notification = NativeNotifications.displayNotification({
|
2016-05-10 04:09:05 +08:00
|
|
|
title: title,
|
|
|
|
subtitle: subtitle,
|
|
|
|
body: body,
|
|
|
|
canReply: true,
|
|
|
|
tag: 'unread-update',
|
|
|
|
onActivate: ({response, activationType}) => {
|
|
|
|
if ((activationType === 'replied') && response && _.isString(response)) {
|
|
|
|
Actions.sendQuickReply({thread, message}, response);
|
|
|
|
} else {
|
|
|
|
NylasEnv.displayWindow()
|
|
|
|
}
|
|
|
|
|
2016-10-01 15:08:19 +08:00
|
|
|
if (!thread) {
|
|
|
|
NylasEnv.showErrorDialog(`Can't find that thread`)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
Actions.ensureCategoryIsFocused('inbox', thread.accountId);
|
|
|
|
Actions.setFocus({collection: 'thread', item: thread});
|
2016-05-10 04:09:05 +08:00
|
|
|
},
|
|
|
|
});
|
2016-07-01 08:44:38 +08:00
|
|
|
|
|
|
|
if (!this.activeNotifications[thread.id]) {
|
|
|
|
this.activeNotifications[thread.id] = [notification];
|
|
|
|
} else {
|
|
|
|
this.activeNotifications[thread.id].push(notification);
|
|
|
|
}
|
2016-05-10 04:09:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
_notifyMessages() {
|
2016-07-09 02:30:33 +08:00
|
|
|
if (this.unnotifiedQueue.length >= 5) {
|
2016-05-10 04:09:05 +08:00
|
|
|
this._notifyAll()
|
2016-07-09 02:30:33 +08:00
|
|
|
} else if (this.unnotifiedQueue.length > 0) {
|
|
|
|
this._notifyOne(this.unnotifiedQueue.shift());
|
2016-05-10 04:09:05 +08:00
|
|
|
}
|
|
|
|
|
2016-07-09 02:30:33 +08:00
|
|
|
this.hasScheduledNotify = false;
|
|
|
|
if (this.unnotifiedQueue.length > 0) {
|
|
|
|
setTimeout(() => this._notifyMessages(), 2000);
|
|
|
|
this.hasScheduledNotify = true;
|
2016-05-10 04:09:05 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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: _.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] || _.findWhere(incomingThreads, {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].categoryNamed('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) {
|
2016-07-09 02:30:33 +08:00
|
|
|
this.unnotifiedQueue.push({message: msg, thread: resolvedThreads[msg.threadId]});
|
2016-05-10 04:09:05 +08:00
|
|
|
}
|
2016-07-09 02:30:33 +08:00
|
|
|
if (!this.hasScheduledNotify) {
|
2016-05-10 04:09:05 +08:00
|
|
|
if (NylasEnv.config.get("core.notifications.sounds")) {
|
|
|
|
SoundRegistry.playSound('new-mail');
|
|
|
|
}
|
|
|
|
this._notifyMessages();
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export const config = {
|
|
|
|
enabled: {
|
|
|
|
'type': 'boolean',
|
|
|
|
'default': true,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
export function activate() {
|
|
|
|
this.notifier = new Notifier();
|
|
|
|
}
|
|
|
|
|
|
|
|
export function deactivate() {
|
|
|
|
this.notifier.unlisten();
|
|
|
|
}
|