MomentsPageHub.sys.mjs (4791B)
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 ChromeUtils.defineESModuleGetters(lazy, { 8 setInterval: "resource://gre/modules/Timer.sys.mjs", 9 clearInterval: "resource://gre/modules/Timer.sys.mjs", 10 }); 11 12 // Frequency at which to check for new messages 13 const SYSTEM_TICK_INTERVAL = 5 * 60 * 1000; 14 const HOMEPAGE_OVERRIDE_PREF = "browser.startup.homepage_override.once"; 15 16 export class _MomentsPageHub { 17 constructor() { 18 this.id = "moments-page-hub"; 19 this.state = {}; 20 this.checkHomepageOverridePref = this.checkHomepageOverridePref.bind(this); 21 this._initialized = false; 22 } 23 24 async init( 25 waitForInitialized, 26 { handleMessageRequest, addImpression, blockMessageById, sendTelemetry } 27 ) { 28 if (this._initialized) { 29 return; 30 } 31 32 this._initialized = true; 33 this._handleMessageRequest = handleMessageRequest; 34 this._addImpression = addImpression; 35 this._blockMessageById = blockMessageById; 36 this._sendTelemetry = sendTelemetry; 37 38 // Need to wait for ASRouter to initialize before trying to fetch messages 39 await waitForInitialized; 40 41 this.messageRequest({ 42 triggerId: "momentsUpdate", 43 template: "update_action", 44 }); 45 46 const _intervalId = lazy.setInterval( 47 () => this.checkHomepageOverridePref(), 48 SYSTEM_TICK_INTERVAL 49 ); 50 this.state = { _intervalId }; 51 } 52 53 _sendPing(ping) { 54 this._sendTelemetry({ 55 type: "MOMENTS_PAGE_TELEMETRY", 56 data: { action: "moments_user_event", ...ping }, 57 }); 58 } 59 60 sendUserEventTelemetry(message) { 61 this._sendPing({ 62 message_id: message.id, 63 bucket_id: message.id, 64 event: "MOMENTS_PAGE_SET", 65 }); 66 } 67 68 /** 69 * If we don't have `expire` defined with the message it could be because 70 * it depends on user dependent parameters. Since the message matched 71 * targeting we calculate `expire` based on the current timestamp and the 72 * `expireDelta` which defines for how long it should be available. 73 * 74 * @param expireDelta {number} - Offset in milliseconds from the current date 75 */ 76 getExpirationDate(expireDelta) { 77 return Date.now() + expireDelta; 78 } 79 80 executeAction(message) { 81 const { id, data } = message.content.action; 82 switch (id) { 83 case "moments-wnp": { 84 const { url, expireDelta } = data; 85 let { expire } = data; 86 if (!expire) { 87 expire = this.getExpirationDate(expireDelta); 88 } 89 // In order to reset this action we can dispatch a new message that 90 // will overwrite the prev value with an expiration date from the past. 91 Services.prefs.setStringPref( 92 HOMEPAGE_OVERRIDE_PREF, 93 JSON.stringify({ message_id: message.id, url, expire }) 94 ); 95 // Add impression and block immediately after taking the action 96 this.sendUserEventTelemetry(message); 97 this._addImpression(message); 98 this._blockMessageById(message.id); 99 break; 100 } 101 } 102 } 103 104 _recordReachEvent(message) { 105 Glean.messagingExperiments.reachMomentsPage.record({ 106 value: message.experimentSlug, 107 branches: message.branchSlug, 108 }); 109 } 110 111 async messageRequest({ triggerId, template }) { 112 const timerId = Glean.messagingSystem.messageRequestTime.start(); 113 const messages = await this._handleMessageRequest({ 114 triggerId, 115 template, 116 returnAll: true, 117 }); 118 Glean.messagingSystem.messageRequestTime.stopAndAccumulate(timerId); 119 120 // Record the "reach" event for all the messages with `forReachEvent`, 121 // only execute action for the first message without forReachEvent. 122 const nonReachMessages = []; 123 for (const message of messages) { 124 if (message.forReachEvent) { 125 if (!message.forReachEvent.sent) { 126 this._recordReachEvent(message); 127 message.forReachEvent.sent = true; 128 } 129 } else { 130 nonReachMessages.push(message); 131 } 132 } 133 if (nonReachMessages.length) { 134 this.executeAction(nonReachMessages[0]); 135 } 136 } 137 138 /** 139 * Pref is set via Remote Settings message. We want to continously 140 * monitor new messages that come in to ensure the one with the 141 * highest priority is set. 142 */ 143 checkHomepageOverridePref() { 144 this.messageRequest({ 145 triggerId: "momentsUpdate", 146 template: "update_action", 147 }); 148 } 149 150 uninit() { 151 lazy.clearInterval(this.state._intervalId); 152 this.state = {}; 153 this._initialized = false; 154 } 155 } 156 157 /** 158 * MomentsPageHub - singleton instance of _MomentsPageHub that can initiate 159 * message requests and render messages. 160 */ 161 export const MomentsPageHub = new _MomentsPageHub();