TimerFeed.sys.mjs (5747B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 const lazy = {}; 6 7 // eslint-disable-next-line mozilla/use-static-import 8 const { AppConstants } = ChromeUtils.importESModule( 9 "resource://gre/modules/AppConstants.sys.mjs" 10 ); 11 12 ChromeUtils.defineESModuleGetters(lazy, { 13 PersistentCache: "resource://newtab/lib/PersistentCache.sys.mjs", 14 }); 15 16 ChromeUtils.defineLazyGetter(lazy, "gNewTabStrings", () => { 17 return new Localization(["browser/newtab/newtab.ftl"], true); 18 }); 19 20 import { 21 actionTypes as at, 22 actionCreators as ac, 23 } from "resource://newtab/common/Actions.mjs"; 24 25 const PREF_TIMER_ENABLED = "widgets.timer.enabled"; 26 const PREF_SYSTEM_TIMER_ENABLED = "widgets.system.timer.enabled"; 27 const PREF_TIMER_SHOW_NOTIFICATIONS = 28 "widgets.focusTimer.showSystemNotifications"; 29 const CACHE_KEY = "timer_widget"; 30 31 const AlertNotification = Components.Constructor( 32 "@mozilla.org/alert-notification;1", 33 "nsIAlertNotification", 34 "initWithObject" 35 ); 36 37 /** 38 * Class for the Timer widget, which manages the changes to the Timer widget 39 * and syncs with PersistentCache 40 */ 41 export class TimerFeed { 42 constructor() { 43 this.initialized = false; 44 this.cache = this.PersistentCache(CACHE_KEY, true); 45 this.notifiedThisCycle = false; 46 } 47 48 resetNotificationFlag() { 49 this.notifiedThisCycle = false; 50 } 51 52 async showSystemNotification(title, body) { 53 const prefs = this.store.getState()?.Prefs.values; 54 55 if (!prefs[PREF_TIMER_SHOW_NOTIFICATIONS]) { 56 return; 57 } 58 59 try { 60 const alertsService = Cc["@mozilla.org/alerts-service;1"].getService( 61 Ci.nsIAlertsService 62 ); 63 64 /** 65 * @backward-compat { version 147 } 66 * Remove `alertsService.showAlertNotification` call once Firefox 147 67 * makes it to the release channel. 68 */ 69 70 if (Services.vc.compare(AppConstants.MOZ_APP_VERSION, "147.0a1") >= 0) { 71 alertsService.showAlert( 72 new AlertNotification({ 73 title, 74 text: body, 75 }) 76 ); 77 } else { 78 alertsService.showAlertNotification(null, title, body, false, "", null); 79 } 80 } catch (err) { 81 console.error("Failed to show system notification", err); 82 } 83 } 84 85 get enabled() { 86 const prefs = this.store.getState()?.Prefs.values; 87 const nimbusTimerEnabled = prefs.widgetsConfig?.timerEnabled; 88 const nimbusTimerTrainhopEnabled = 89 prefs.trainhopConfig?.widgets?.timerEnabled; 90 91 return ( 92 prefs?.[PREF_TIMER_ENABLED] && 93 (prefs?.[PREF_SYSTEM_TIMER_ENABLED] || 94 nimbusTimerEnabled || 95 nimbusTimerTrainhopEnabled) 96 ); 97 } 98 99 async init() { 100 this.initialized = true; 101 await this.syncTimer(true); 102 } 103 104 async syncTimer(isStartup = false) { 105 const cachedData = (await this.cache.get()) || {}; 106 const { timer } = cachedData; 107 if (timer) { 108 this.update(timer, isStartup); 109 } 110 } 111 112 update(data, isStartup = false) { 113 this.store.dispatch( 114 ac.BroadcastToContent({ 115 type: at.WIDGETS_TIMER_SET, 116 data, 117 meta: isStartup, 118 }) 119 ); 120 } 121 122 /** 123 * @param {object} action - The action object containing pref change data 124 * @param {string} action.data.name - The name of the pref that changed 125 */ 126 async onPrefChangedAction(action) { 127 switch (action.data.name) { 128 case PREF_TIMER_ENABLED: 129 case PREF_SYSTEM_TIMER_ENABLED: 130 case "trainhopConfig": 131 case "widgetsConfig": { 132 if (this.enabled && !this.initialized) { 133 await this.init(); 134 } 135 break; 136 } 137 } 138 } 139 140 async onAction(action) { 141 switch (action.type) { 142 case at.INIT: 143 if (this.enabled) { 144 await this.init(); 145 } 146 break; 147 case at.PREF_CHANGED: 148 await this.onPrefChangedAction(action); 149 break; 150 case at.WIDGETS_TIMER_END: 151 { 152 const prevState = this.store.getState().TimerWidget; 153 await this.cache.set("timer", { ...prevState, ...action.data }); 154 this.update({ ...prevState, ...action.data }); 155 const { timerType } = action.data; 156 157 const l10nId = 158 timerType === "break" 159 ? "newtab-widget-timer-notification-break" 160 : "newtab-widget-timer-notification-focus"; 161 const [titleMessage, bodyMessage] = 162 await lazy.gNewTabStrings.formatMessages([ 163 { id: "newtab-widget-timer-notification-title" }, 164 { id: l10nId }, 165 ]); 166 const title = titleMessage?.value || "Timer"; 167 const body = bodyMessage?.value || "Timer ended"; 168 169 if (!this.notifiedThisCycle) { 170 this.notifiedThisCycle = true; 171 this.showSystemNotification(title, body); 172 } 173 } 174 break; 175 case at.WIDGETS_TIMER_SET_TYPE: 176 case at.WIDGETS_TIMER_SET_DURATION: 177 case at.WIDGETS_TIMER_PAUSE: 178 case at.WIDGETS_TIMER_PLAY: 179 { 180 this.resetNotificationFlag(); 181 const prevState = this.store.getState().TimerWidget; 182 await this.cache.set("timer", { ...prevState, ...action.data }); 183 this.update({ ...prevState, ...action.data }); 184 } 185 break; 186 case at.WIDGETS_TIMER_RESET: 187 { 188 this.resetNotificationFlag(); 189 const prevState = this.store.getState().TimerWidget; 190 await this.cache.set("timer", { ...prevState, ...action.data }); 191 this.update({ ...prevState, ...action.data }); 192 } 193 break; 194 } 195 } 196 } 197 198 TimerFeed.prototype.PersistentCache = (...args) => { 199 return new lazy.PersistentCache(...args); 200 };