import DiscourseURL from 'discourse/lib/url'; import KeyValueStore from 'discourse/lib/key-value-store'; import { onPageChange } from 'discourse/lib/page-tracker'; let primaryTab = false; let liveEnabled = false; let havePermission = null; let mbClientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; let lastAction = -1; const focusTrackerKey = "focus-tracker"; const idleThresholdTime = 1000 * 10; // 10 seconds const context = "discourse_desktop_notifications_"; const keyValueStore = new KeyValueStore(context); // Called from an initializer function init(messageBus) { liveEnabled = false; mbClientId = messageBus.clientId; if (!Discourse.User.current()) { return; } try { keyValueStore.getItem(focusTrackerKey); } catch (e) { Em.Logger.info('Discourse desktop notifications are disabled - localStorage denied.'); return; } if (!("Notification" in window)) { Em.Logger.info('Discourse desktop notifications are disabled - not supported by browser'); return; } try { if (Notification.permission === "granted") { havePermission = true; } else if (Notification.permission === "denied") { havePermission = false; return; } } catch (e) { Em.Logger.warn('Unexpected error, Notification is defined on window but not a responding correctly ' + e); } 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() { window.addEventListener("storage", function(e) { // note: This event only fires when other tabs setItem() const key = e.key; if (key !== `${context}${focusTrackerKey}`) { return true; } primaryTab = false; }); window.addEventListener("focus", function() { if (!primaryTab) { primaryTab = true; keyValueStore.setItem(focusTrackerKey, mbClientId); } }); if (document && (typeof document.hidden !== "undefined") && document["hidden"]) { primaryTab = false; } else { primaryTab = true; keyValueStore.setItem(focusTrackerKey, mbClientId); } if (document) { document.addEventListener("scroll", resetIdle); } onPageChange(resetIdle); } function resetIdle() { lastAction = Date.now(); } function isIdle() { return lastAction + idleThresholdTime < Date.now(); } // Call-in point from message bus function onNotification(data) { if (!liveEnabled) { return; } if (!primaryTab) { return; } if (!isIdle()) { return; } if (keyValueStore.getItem('notifications-disabled')) { return; } const notificationTitle = I18n.t(i18nKey(data.notification_type), { site_title: Discourse.SiteSettings.title, topic: data.topic_title, username: data.username }); const notificationBody = data.excerpt; const notificationIcon = Discourse.SiteSettings.logo_small_url || Discourse.SiteSettings.logo_url; const notificationTag = "discourse-notification-" + Discourse.SiteSettings.title + "-" + data.topic_id; requestPermission().then(function() { // This shows the notification! const notification = new Notification(notificationTitle, { body: notificationBody, icon: notificationIcon, tag: notificationTag }); function clickEventHandler() { DiscourseURL.routeTo(data.post_url); // 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); }); } // 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_type) { return "notifications.popup." + Discourse.Site.current().get("notificationLookup")[notification_type]; } function alertChannel(user) { return `/notification-alert/${user.get('id')}`; } function unsubscribe(bus, user) { bus.unsubscribe(alertChannel(user)); } export { context, init, onNotification, unsubscribe, alertChannel };