259 lines
7.5 KiB
JavaScript
259 lines
7.5 KiB
JavaScript
|
|
let primaryTab = false;
|
|
let liveEnabled = false;
|
|
let havePermission = null;
|
|
let mbClientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
|
|
let lastAction = -1;
|
|
|
|
const focusTrackerKey = "focus-tracker";
|
|
const seenDataKey = "seen-notifications";
|
|
const recentUpdateThreshold = 1000 * 60 * 2; // 2 minutes
|
|
const idleThresholdTime = 1000 * 10; // 10 seconds
|
|
const INVITED_TYPE = 8;
|
|
let notificationTagName; // "discourse-notification-popup-" + Discourse.SiteSettings.title;
|
|
|
|
// Called from an initializer
|
|
function init(messageBus) {
|
|
liveEnabled = false;
|
|
mbClientId = messageBus.clientId;
|
|
|
|
if (!Discourse.User.current()) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
localStorage.getItem(focusTrackerKey);
|
|
} catch (e) {
|
|
Em.Logger.info('Discourse desktop notifications are disabled - localStorage denied.');
|
|
return;
|
|
}
|
|
|
|
if (!Notification) {
|
|
Em.Logger.info('Discourse desktop notifications are disabled - not supported by browser');
|
|
return;
|
|
}
|
|
|
|
if (Notification.permission === "granted") {
|
|
havePermission = true;
|
|
} else if (Notification.permission === "denied") {
|
|
havePermission = false;
|
|
return;
|
|
}
|
|
|
|
liveEnabled = true;
|
|
try {
|
|
// Preliminary checks passed, continue with setup
|
|
setupNotifications();
|
|
} catch (e) {
|
|
Em.Logger.error(e);
|
|
}
|
|
}
|
|
|
|
// This function is only called if permission was granted
|
|
function setupNotifications() {
|
|
// Load up the current state of the notifications
|
|
const seenData = JSON.parse(localStorage.getItem(seenDataKey));
|
|
let markAllSeen = true;
|
|
if (seenData) {
|
|
const lastUpdatedAt = new Date(seenData.updated_at);
|
|
if (lastUpdatedAt.getTime() + recentUpdateThreshold > new Date().getTime()) {
|
|
// The following conditions are met:
|
|
// - This is a new Discourse tab
|
|
// - The seen notification data was updated in the last 2 minutes
|
|
// Therefore, there is no need to reset the data.
|
|
markAllSeen = false;
|
|
}
|
|
}
|
|
if (markAllSeen) {
|
|
Discourse.ajax("/notifications.json?silent=true").then(function(result) {
|
|
updateSeenNotificationDatesFrom(result);
|
|
});
|
|
}
|
|
|
|
notificationTagName = "discourse-notification-popup-" + Discourse.SiteSettings.title;
|
|
|
|
|
|
window.addEventListener("storage", function(e) {
|
|
// note: This event only fires when other tabs setItem()
|
|
const key = e.key;
|
|
if (key !== focusTrackerKey) {
|
|
return true;
|
|
}
|
|
primaryTab = false;
|
|
});
|
|
|
|
window.addEventListener("focus", function() {
|
|
if (!primaryTab) {
|
|
primaryTab = true;
|
|
localStorage.setItem(focusTrackerKey, mbClientId);
|
|
}
|
|
});
|
|
|
|
if (document && (typeof document.hidden !== "undefined") && document["hidden"]) {
|
|
primaryTab = false;
|
|
} else {
|
|
primaryTab = true;
|
|
localStorage.setItem(focusTrackerKey, mbClientId);
|
|
}
|
|
|
|
if (document) {
|
|
document.addEventListener("scroll", resetIdle);
|
|
}
|
|
window.addEventListener("mouseover", resetIdle);
|
|
Discourse.PageTracker.on("change", resetIdle);
|
|
}
|
|
|
|
function resetIdle() {
|
|
lastAction = Date.now();
|
|
}
|
|
function isIdle() {
|
|
return lastAction + idleThresholdTime < Date.now();
|
|
}
|
|
|
|
// Call-in point from message bus
|
|
function onNotification(currentUser) {
|
|
if (!liveEnabled) { return; }
|
|
if (!primaryTab) { return; }
|
|
|
|
const blueNotifications = currentUser.get('unread_notifications');
|
|
const greenNotifications = currentUser.get('unread_private_messages');
|
|
|
|
if (blueNotifications > 0 || greenNotifications > 0) {
|
|
Discourse.ajax("/notifications.json?silent=true").then(function(result) {
|
|
|
|
const unread = result.filter(n => !n.read);
|
|
const unseen = updateSeenNotificationDatesFrom(result);
|
|
const unreadCount = unread.length;
|
|
const unseenCount = unseen.length;
|
|
|
|
|
|
// If all notifications are seen, don't display
|
|
if (unreadCount === 0 || unseenCount === 0) {
|
|
return;
|
|
}
|
|
// If active in last 10 seconds, don't display
|
|
if (!isIdle()) {
|
|
return;
|
|
}
|
|
|
|
let bodyParts = [];
|
|
|
|
unread.forEach(function(n) {
|
|
const i18nOpts = {
|
|
username: n.data['display_username'],
|
|
topic: n.data['topic_title'],
|
|
badge: n.data['badge_name']
|
|
};
|
|
|
|
bodyParts.push(I18n.t(i18nKey(n), i18nOpts));
|
|
});
|
|
|
|
const notificationTitle = I18n.t('notifications.popup_title', { count: unreadCount, site_title: Discourse.SiteSettings.title });
|
|
const notificationBody = bodyParts.join("\n");
|
|
const notificationIcon = Discourse.SiteSettings.logo_small_url || Discourse.SiteSettings.logo_url;
|
|
|
|
requestPermission().then(function() {
|
|
// This shows the notification!
|
|
const notification = new Notification(notificationTitle, {
|
|
body: notificationBody,
|
|
icon: notificationIcon,
|
|
tag: notificationTagName
|
|
});
|
|
|
|
const firstUnseen = unseen[0];
|
|
|
|
function clickEventHandler() {
|
|
Discourse.URL.routeTo(_notificationUrl(firstUnseen));
|
|
// Cannot delay this until the page renders
|
|
// due to trigger-based permissions
|
|
window.focus();
|
|
}
|
|
|
|
notification.addEventListener('click', clickEventHandler);
|
|
setTimeout(function() {
|
|
notification.close();
|
|
notification.removeEventListener('click', clickEventHandler);
|
|
}, 10 * 1000);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
const DATA_VERSION = 2;
|
|
function updateSeenNotificationDatesFrom(notifications) {
|
|
const oldSeenData = JSON.parse(localStorage.getItem(seenDataKey));
|
|
const oldSeenNotificationDates = (oldSeenData && oldSeenData.v === DATA_VERSION) ? oldSeenData.data : [];
|
|
let newSeenNotificationDates = [];
|
|
let previouslyUnseenNotifications = [];
|
|
|
|
notifications.forEach(function(notification) {
|
|
const dateString = new Date(notification.created_at).toUTCString();
|
|
|
|
if (oldSeenNotificationDates.indexOf(dateString) === -1) {
|
|
previouslyUnseenNotifications.push(notification);
|
|
}
|
|
newSeenNotificationDates.push(dateString);
|
|
});
|
|
|
|
localStorage.setItem(seenDataKey, JSON.stringify({
|
|
data: newSeenNotificationDates,
|
|
updated_at: new Date(),
|
|
v: DATA_VERSION
|
|
}));
|
|
return previouslyUnseenNotifications;
|
|
}
|
|
|
|
// Utility function
|
|
// Wraps Notification.requestPermission in a Promise
|
|
function requestPermission() {
|
|
if (havePermission === true) {
|
|
return Ember.RSVP.resolve();
|
|
} else if (havePermission === false) {
|
|
return Ember.RSVP.reject();
|
|
} else {
|
|
return new Ember.RSVP.Promise(function(resolve, reject) {
|
|
Notification.requestPermission(function(status) {
|
|
if (status === "granted") {
|
|
resolve();
|
|
} else {
|
|
reject();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
function i18nKey(notification) {
|
|
let key = "notifications.popup." + Discourse.Site.current().get("notificationLookup")[notification.notification_type];
|
|
if (notification.data.display_username && notification.data.original_username &&
|
|
notification.data.display_username !== notification.data.original_username) {
|
|
key += "_mul";
|
|
}
|
|
return key;
|
|
}
|
|
|
|
// Exported for controllers/notification.js.es6
|
|
function notificationUrl(it) {
|
|
var badgeId = it.get("data.badge_id");
|
|
if (badgeId) {
|
|
var badgeName = it.get("data.badge_name");
|
|
return Discourse.getURL('/badges/' + badgeId + '/' + badgeName.replace(/[^A-Za-z0-9_]+/g, '-').toLowerCase());
|
|
}
|
|
|
|
var topicId = it.get('topic_id');
|
|
if (topicId) {
|
|
return Discourse.Utilities.postUrl(it.get("slug"), topicId, it.get("post_number"));
|
|
}
|
|
|
|
if (it.get('notification_type') === INVITED_TYPE) {
|
|
return Discourse.getURL('/my/invited');
|
|
}
|
|
}
|
|
|
|
function _notificationUrl(notificationJson) {
|
|
const it = Em.Object.create(notificationJson);
|
|
return notificationUrl(it);
|
|
}
|
|
|
|
export { init, notificationUrl, onNotification };
|