monitor-card.mjs (14621B)
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 MONITOR_URL = RPMGetStringPref( 6 "browser.contentblocking.report.monitor.url", 7 "" 8 ); 9 const MONITOR_SIGN_IN_URL = RPMGetStringPref( 10 "browser.contentblocking.report.monitor.sign_in_url", 11 "" 12 ); 13 const HOW_IT_WORKS_URL_PREF = RPMGetFormatURLPref( 14 "browser.contentblocking.report.monitor.how_it_works.url" 15 ); 16 const MONITOR_PREFERENCES_URL = RPMGetFormatURLPref( 17 "browser.contentblocking.report.monitor.preferences_url" 18 ); 19 const MONITOR_HOME_PAGE_URL = RPMGetFormatURLPref( 20 "browser.contentblocking.report.monitor.home_page_url" 21 ); 22 23 export default class MonitorClass { 24 constructor(doc) { 25 this.doc = doc; 26 } 27 28 init() { 29 // Wait for monitor data and display the card. 30 this.getMonitorData(); 31 32 let monitorAboutLink = this.doc.getElementById("monitor-link"); 33 monitorAboutLink.addEventListener("click", () => { 34 this.doc.sendTelemetryEvent("clickMtrAboutLink"); 35 }); 36 37 const storedEmailLink = this.doc.getElementById( 38 "monitor-stored-emails-link" 39 ); 40 storedEmailLink.href = MONITOR_PREFERENCES_URL; 41 storedEmailLink.addEventListener( 42 "click", 43 this.onClickMonitorButton.bind(this) 44 ); 45 46 const knownBreachesLink = this.doc.getElementById( 47 "monitor-known-breaches-link" 48 ); 49 knownBreachesLink.href = MONITOR_HOME_PAGE_URL; 50 knownBreachesLink.addEventListener( 51 "click", 52 this.onClickMonitorButton.bind(this) 53 ); 54 55 const exposedPasswordsLink = this.doc.getElementById( 56 "monitor-exposed-passwords-link" 57 ); 58 exposedPasswordsLink.href = MONITOR_HOME_PAGE_URL; 59 exposedPasswordsLink.addEventListener( 60 "click", 61 this.onClickMonitorButton.bind(this) 62 ); 63 } 64 65 onClickMonitorButton(evt) { 66 RPMSendAsyncMessage("ClearMonitorCache"); 67 switch (evt.currentTarget.id) { 68 case "monitor-partial-breaches-link": 69 this.doc.sendTelemetryEvent("clickMtrReportLink", "resolve_breaches"); 70 break; 71 case "monitor-breaches-link": 72 if (evt.currentTarget.classList.contains("no-breaches-resolved")) { 73 this.doc.sendTelemetryEvent("clickMtrReportLink", "manage_breaches"); 74 } else { 75 this.doc.sendTelemetryEvent("clickMtrReportLink", "view_report"); 76 } 77 break; 78 case "monitor-stored-emails-link": 79 this.doc.sendTelemetryEvent("clickMtrReportLink", "stored_emails"); 80 break; 81 case "monitor-known-breaches-link": { 82 const knownBreaches = this.doc.querySelector( 83 "span[data-type='known-breaches']" 84 ); 85 if (knownBreaches.classList.contains("known-resolved-breaches")) { 86 this.doc.sendTelemetryEvent( 87 "clickMtrReportLink", 88 "known_resolved_breaches" 89 ); 90 } else if ( 91 knownBreaches.classList.contains("known-unresolved-breaches") 92 ) { 93 this.doc.sendTelemetryEvent( 94 "clickMtrReportLink", 95 "known_unresolved_breaches" 96 ); 97 } 98 break; 99 } 100 case "monitor-exposed-passwords-link": { 101 const exposedPasswords = this.doc.querySelector( 102 "span[data-type='exposed-passwords']" 103 ); 104 if ( 105 exposedPasswords.classList.contains("passwords-exposed-all-breaches") 106 ) { 107 this.doc.sendTelemetryEvent( 108 "clickMtrReportLink", 109 "exposed_passwords_all_breaches" 110 ); 111 } else if ( 112 exposedPasswords.classList.contains( 113 "passwords-exposed-unresolved-breaches" 114 ) 115 ) { 116 this.doc.sendTelemetryEvent( 117 "clickMtrReportLink", 118 "exposed_passwords_unresolved_breaches" 119 ); 120 } 121 break; 122 } 123 } 124 } 125 126 /** 127 * Retrieves the monitor data and displays this data in the card. 128 */ 129 getMonitorData() { 130 RPMSendQuery("FetchMonitorData", {}).then(monitorData => { 131 // Once data for the user is retrieved, display the monitor card. 132 this.buildContent(monitorData); 133 134 // Show the Monitor card. 135 const monitorUI = this.doc.querySelector(".card.monitor-card.loading"); 136 monitorUI.classList.remove("loading"); 137 }); 138 } 139 140 buildContent(monitorData) { 141 const headerContent = this.doc.querySelector( 142 "#monitor-header-content span" 143 ); 144 const monitorCard = this.doc.querySelector(".card.monitor-card"); 145 if (!monitorData.error) { 146 monitorCard.classList.add("has-logins"); 147 this.doc.l10n.setAttributes( 148 headerContent, 149 "monitor-header-content-signed-in" 150 ); 151 this.renderContentForUserWithAccount(monitorData); 152 } else { 153 monitorCard.classList.add("no-logins"); 154 const signUpForMonitorLink = this.doc.getElementById( 155 "sign-up-for-monitor-link" 156 ); 157 signUpForMonitorLink.href = this.buildMonitorUrl(monitorData.userEmail); 158 this.doc.l10n.setAttributes(signUpForMonitorLink, "monitor-sign-up-link"); 159 this.doc.l10n.setAttributes( 160 headerContent, 161 "monitor-header-content-no-account" 162 ); 163 signUpForMonitorLink.addEventListener("click", () => { 164 this.doc.sendTelemetryEvent("clickMtrSignupButton"); 165 }); 166 } 167 } 168 169 /** 170 * Builds the appropriate URL that takes the user to the Monitor website's 171 * sign-up/sign-in page. 172 * 173 * @param {string | null} email 174 * Optional. The email used to direct the user to the Monitor website's OAuth 175 * sign-in flow. If null, then direct user to just the Monitor website. 176 * 177 * @returns {string} URL to Monitor website. 178 */ 179 buildMonitorUrl(email = null) { 180 return email 181 ? `${MONITOR_SIGN_IN_URL}${encodeURIComponent(email)}` 182 : MONITOR_URL; 183 } 184 185 renderContentForUserWithAccount(monitorData) { 186 const { 187 numBreaches, 188 numBreachesResolved, 189 passwords, 190 passwordsResolved, 191 monitoredEmails, 192 } = monitorData; 193 const monitorCardBody = this.doc.querySelector( 194 ".card.monitor-card .card-body" 195 ); 196 monitorCardBody.classList.remove("hidden"); 197 198 const howItWorksLink = this.doc.getElementById("monitor-link"); 199 howItWorksLink.href = HOW_IT_WORKS_URL_PREF; 200 201 const storedEmail = this.doc.querySelector( 202 "span[data-type='stored-emails']" 203 ); 204 storedEmail.textContent = monitoredEmails; 205 const infoMonitoredAddresses = this.doc.getElementById( 206 "info-monitored-addresses" 207 ); 208 this.doc.l10n.setAttributes( 209 infoMonitoredAddresses, 210 "info-monitored-emails", 211 { count: monitoredEmails } 212 ); 213 214 const knownBreaches = this.doc.querySelector( 215 "span[data-type='known-breaches']" 216 ); 217 const exposedPasswords = this.doc.querySelector( 218 "span[data-type='exposed-passwords']" 219 ); 220 221 const infoKnownBreaches = this.doc.getElementById("info-known-breaches"); 222 const infoExposedPasswords = this.doc.getElementById( 223 "info-exposed-passwords" 224 ); 225 226 const breachesWrapper = this.doc.querySelector(".monitor-breaches-wrapper"); 227 const partialBreachesWrapper = this.doc.querySelector( 228 ".monitor-partial-breaches-wrapper" 229 ); 230 const breachesTitle = this.doc.getElementById("monitor-breaches-title"); 231 const breachesIcon = this.doc.getElementById("monitor-breaches-icon"); 232 const breachesDesc = this.doc.getElementById( 233 "monitor-breaches-description" 234 ); 235 const breachesLink = this.doc.getElementById("monitor-breaches-link"); 236 if (numBreaches) { 237 if (!numBreachesResolved) { 238 partialBreachesWrapper.classList.add("hidden"); 239 knownBreaches.textContent = numBreaches; 240 knownBreaches.classList.add("known-unresolved-breaches"); 241 knownBreaches.classList.remove("known-resolved-breaches"); 242 this.doc.l10n.setAttributes( 243 infoKnownBreaches, 244 "info-known-breaches-found", 245 { count: numBreaches } 246 ); 247 exposedPasswords.textContent = passwords; 248 exposedPasswords.classList.add("passwords-exposed-all-breaches"); 249 exposedPasswords.classList.remove( 250 "passwords-exposed-unresolved-breaches" 251 ); 252 this.doc.l10n.setAttributes( 253 infoExposedPasswords, 254 "info-exposed-passwords-found", 255 { count: passwords } 256 ); 257 258 breachesIcon.setAttribute( 259 "src", 260 "chrome://browser/skin/protections/new-feature.svg" 261 ); 262 this.doc.l10n.setAttributes( 263 breachesTitle, 264 "monitor-breaches-unresolved-title" 265 ); 266 this.doc.l10n.setAttributes( 267 breachesDesc, 268 "monitor-breaches-unresolved-description" 269 ); 270 this.doc.l10n.setAttributes( 271 breachesLink, 272 "monitor-manage-breaches-link" 273 ); 274 breachesLink.classList.add("no-breaches-resolved"); 275 } else if (numBreaches == numBreachesResolved) { 276 partialBreachesWrapper.classList.add("hidden"); 277 knownBreaches.textContent = numBreachesResolved; 278 knownBreaches.classList.remove("known-unresolved-breaches"); 279 knownBreaches.classList.add("known-resolved-breaches"); 280 this.doc.l10n.setAttributes( 281 infoKnownBreaches, 282 "info-known-breaches-resolved", 283 { count: numBreachesResolved } 284 ); 285 let unresolvedPasswords = passwords - passwordsResolved; 286 exposedPasswords.textContent = unresolvedPasswords; 287 exposedPasswords.classList.remove("passwords-exposed-all-breaches"); 288 exposedPasswords.classList.add("passwords-exposed-unresolved-breaches"); 289 this.doc.l10n.setAttributes( 290 infoExposedPasswords, 291 "info-exposed-passwords-resolved", 292 { count: unresolvedPasswords } 293 ); 294 295 breachesIcon.setAttribute( 296 "src", 297 "chrome://browser/skin/protections/resolved-breach.svg" 298 ); 299 this.doc.l10n.setAttributes( 300 breachesTitle, 301 "monitor-breaches-resolved-title" 302 ); 303 this.doc.l10n.setAttributes( 304 breachesDesc, 305 "monitor-breaches-resolved-description" 306 ); 307 this.doc.l10n.setAttributes(breachesLink, "monitor-view-report-link"); 308 } else { 309 breachesWrapper.classList.add("hidden"); 310 knownBreaches.textContent = numBreachesResolved; 311 knownBreaches.classList.remove("known-unresolved-breaches"); 312 knownBreaches.classList.add("known-resolved-breaches"); 313 this.doc.l10n.setAttributes( 314 infoKnownBreaches, 315 "info-known-breaches-resolved", 316 { count: numBreachesResolved } 317 ); 318 let unresolvedPasswords = passwords - passwordsResolved; 319 exposedPasswords.textContent = unresolvedPasswords; 320 exposedPasswords.classList.remove("passwords-exposed-all-breaches"); 321 exposedPasswords.classList.add("passwords-exposed-unresolved-breaches"); 322 this.doc.l10n.setAttributes( 323 infoExposedPasswords, 324 "info-exposed-passwords-resolved", 325 { count: unresolvedPasswords } 326 ); 327 328 const partialBreachesTitle = document.getElementById( 329 "monitor-partial-breaches-title" 330 ); 331 this.doc.l10n.setAttributes( 332 partialBreachesTitle, 333 "monitor-partial-breaches-title", 334 { 335 numBreaches, 336 numBreachesResolved, 337 } 338 ); 339 340 const progressBar = this.doc.querySelector(".progress-bar"); 341 const partialBreachesMotivationTitle = document.getElementById( 342 "monitor-partial-breaches-motivation-title" 343 ); 344 345 let percentageResolved = Math.floor( 346 (numBreachesResolved / numBreaches) * 100 347 ); 348 progressBar.setAttribute("value", 100 - percentageResolved); 349 switch (true) { 350 case percentageResolved > 0 && percentageResolved < 25: 351 this.doc.l10n.setAttributes( 352 partialBreachesMotivationTitle, 353 "monitor-partial-breaches-motivation-title-start" 354 ); 355 break; 356 357 case percentageResolved >= 25 && percentageResolved < 75: 358 this.doc.l10n.setAttributes( 359 partialBreachesMotivationTitle, 360 "monitor-partial-breaches-motivation-title-middle" 361 ); 362 break; 363 364 case percentageResolved >= 75 && percentageResolved < 100: 365 this.doc.l10n.setAttributes( 366 partialBreachesMotivationTitle, 367 "monitor-partial-breaches-motivation-title-end" 368 ); 369 break; 370 } 371 372 const partialBreachesPercentage = document.getElementById( 373 "monitor-partial-breaches-percentage" 374 ); 375 this.doc.l10n.setAttributes( 376 partialBreachesPercentage, 377 "monitor-partial-breaches-percentage", 378 { percentageResolved } 379 ); 380 381 const partialBreachesLink = document.getElementById( 382 "monitor-partial-breaches-link" 383 ); 384 partialBreachesLink.setAttribute("href", MONITOR_HOME_PAGE_URL); 385 partialBreachesLink.addEventListener( 386 "click", 387 this.onClickMonitorButton.bind(this) 388 ); 389 } 390 } else { 391 partialBreachesWrapper.classList.add("hidden"); 392 knownBreaches.textContent = numBreaches; 393 knownBreaches.classList.add("known-unresolved-breaches"); 394 knownBreaches.classList.remove("known-resolved-breaches"); 395 this.doc.l10n.setAttributes( 396 infoKnownBreaches, 397 "info-known-breaches-found", 398 { count: numBreaches } 399 ); 400 exposedPasswords.textContent = passwords; 401 exposedPasswords.classList.add("passwords-exposed-all-breaches"); 402 exposedPasswords.classList.remove( 403 "passwords-exposed-unresolved-breaches" 404 ); 405 this.doc.l10n.setAttributes( 406 infoExposedPasswords, 407 "info-exposed-passwords-found", 408 { count: passwords } 409 ); 410 411 breachesIcon.setAttribute( 412 "src", 413 "chrome://browser/skin/protections/resolved-breach.svg" 414 ); 415 this.doc.l10n.setAttributes(breachesTitle, "monitor-no-breaches-title"); 416 this.doc.l10n.setAttributes( 417 breachesDesc, 418 "monitor-no-breaches-description" 419 ); 420 this.doc.l10n.setAttributes(breachesLink, "monitor-view-report-link"); 421 } 422 423 breachesLink.setAttribute("href", MONITOR_HOME_PAGE_URL); 424 breachesLink.addEventListener( 425 "click", 426 this.onClickMonitorButton.bind(this) 427 ); 428 } 429 }