Files
binoc-central-mirror/mail/modules/mailInstrumentation.js
T
2020-05-10 13:52:36 -04:00

238 lines
8.0 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Thunderbird UI Instrumentation, currently just the account setup process.
*/
/* :::::::: Constants and Helpers ::::::::::::::: */
this.EXPORTED_SYMBOLS = ["mailInstrumentationManager"];
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
var nsIMFNService = Ci.nsIMsgFolderNotificationService;
Cu.import("resource:///modules/errUtils.js");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource:///modules/mailServices.js");
/* :::::::: The Module ::::::::::::::: */
var mailInstrumentationManager =
{
// JS object containing the current state object
_currentState: null,
/**
* The string containing the JSON stringified representation of the last
* state we uploaded.
*/
_lastStateString: null,
// if true, need to remove ourselves as a folder notification listener
_mfnListener: false,
// if true, we need to remove our observers in uninit.
_observersRegistered: false,
observe: function (aSubject, aTopic, aState) {
if (aTopic == "mail:composeSendSucceeded")
mailInstrumentationManager.addEvent("msgSent", true);
else if (aTopic == "mail:setAsDefault")
mailInstrumentationManager.addEvent("setAsDefault", true);
},
msgAdded: function (aMsg) {
MailServices.mfn.removeListener(this);
this._mfnListener = false;
mailInstrumentationManager.addEvent("msgDownloaded", true);
},
_accountsChanged: function() {
// check if there are at least two accounts - one is local folders account
if (Services.prefs.getCharPref("mail.accountmanager.accounts").includes(',', 1)) {
mailInstrumentationManager.addEvent("accountAdded", true);
mailInstrumentationManager._removeObserver(
"mail.accountmanager.accounts",
mailInstrumentationManager._accountsChanged);
}
},
_smtpServerAdded: function() {
mailInstrumentationManager.addEvent("smtpServerAdded", true);
mailInstrumentationManager._removeObserver("mail.smtpservers",
mailInstrumentationManager._smtpServerAdded);
},
_userOptedIn: function() {
try {
if (Services.prefs.getBoolPref("mail.instrumentation.userOptedIn"))
mailInstrumentationManager._postStateObject();
} catch (ex) {logException(ex);}
},
/**
* Loads the last saved state. This should only be called by
* _init and a unit test.
*/
_loadState: function minst_loadState() {
let data = Services.prefs.getCharPref("mail.instrumentation.lastNotificationSent");
if (data) {
try {
// parse the session state into JS objects
this._currentState = JSON.parse(data);
return;
} catch (ex) {}
}
this._currentState = this._createStateObject();
},
/**
* Writes the state object to disk.
*/
_postStateObject: function minst_postStateObject() {
// This method runs for the smtp server before the account has been set up.
if (MailServices.accounts.accounts.length == 0)
return;
let defaultAccount = MailServices.accounts.defaultAccount;
if (!defaultAccount) {
return;
}
if (!this._currentState.userEmailHash) {
let identity = defaultAccount.defaultIdentity;
if (identity) // When we have only a feed account, there is no identity.
this._currentState.userEmailHash = this._hashEmailAddress(identity.email);
}
let data = JSON.stringify(this._currentState);
// post data only if state changed since last write.
if (data == this._lastStateString)
return;
this._lastStateString = data;
let userOptedIn = Services.prefs.getBoolPref("mail.instrumentation.userOptedIn");
if (userOptedIn)
this._postData();
},
/**
* @return an empty state object that can be populated with window states.
*/
_createStateObject: function minst_createStateObject() {
return {
rev: 0,
userEmailHash: "",
// these will be a tuple, time stamp and answer, indexed by question key.
events: new Object,
};
},
// Convert each hashed byte into 2-hex strings, then combine them.
_bytesAsHex: function minst_bytesAsHex(bytes) {
return Array.from(bytes).
map(byte => ("0" + byte.charCodeAt().toString(16)).slice(-2)).join("");
},
/**
* Return sha-256 hash of the passed in e-mail address
*/
_hashEmailAddress: function minst_hashEmailAddress(address) {
let ch = Cc["@mozilla.org/security/hash;1"]
.createInstance(Ci.nsICryptoHash);
ch.init(ch.SHA256);
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
let byteArray = converter.convertToByteArray(address, {});
ch.update(byteArray, byteArray.length);
let hashedData = ch.finish(false);
return this._bytesAsHex(hashedData);
},
_postData: function minst_postData() {
let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
let url = Services.prefs.getCharPref("mail.instrumentation.postUrl");
if (!url.length)
return;
let dataToPost = this._lastStateString;
req.open("POST", url, true);
req.onerror = this._onError;
req.onload = this._onLoad;
req.send(dataToPost);
},
_onError: function minst_onError(e) {
logException(e);
},
_onLoad: function minst_onLoad() {
Services.prefs.setCharPref("mail.instrumentation.lastNotificationSent",
this._lastStateString);
},
// keeps track of whether or not we've removed the observer for a given
// pref name.
_prefsObserved : new Map(),
_addObserver : function(pref, observer) {
Services.prefs.addObserver(pref, observer, false);
this._prefsObserved.set(pref, true);
},
_removeObserver : function(pref, observer) {
if (this._prefsObserved.has(pref)) {
Services.prefs.removeObserver(pref, observer);
this._prefsObserved.set(pref, false);
}
},
/* ........ Public API ................*/
/**
* This is called to initialize the instrumentation.
*/
init: function minst_init() {
// If we're done with instrumentation, or this is not a first run,
// we should just return immediately.
if (!Services.prefs.getBoolPref("mail.instrumentation.askUser"))
return;
if (MailServices.accounts.accounts.length > 0)
return;
this._loadState();
Services.obs.addObserver(this, "mail:composeSendSucceeded", false);
Services.obs.addObserver(this, "mail:setAsDefault", false);
Services.prefs.addObserver("mail.accountmanager.accounts",
this._accountsChanged, false);
Services.prefs.addObserver("mail.instrumentation.userOptedIn",
this._userOptedIn, false);
Services.prefs.addObserver("mail.smtpservers", this._smtpServerAdded, false);
MailServices.mfn.addListener(this, nsIMFNService.msgAdded);
this._observersRegistered = true;
this._mfnListener = true;
},
uninit: function() {
if (!this._observersRegistered)
return;
Services.obs.removeObserver(this, "mail:composeSendSucceeded");
Services.obs.removeObserver(this, "mail:setAsDefault");
if (this._mfnListener)
MailServices.mfn.removeListener(this);
Services.prefs.removeObserver("mail.accountmanager.accounts", this);
Services.prefs.removeObserver("mail.instrumentation.userOptedIn", this);
Services.prefs.removeObserver("mail.smtpservers", this);
},
/**
* This adds an event to the current state, if it doesn't exist.
*/
addEvent: function minst_addEvent(aEventKey, aData) {
try {
if (!(aEventKey in this._currentState.events)) {
let newEvent = new Object;
newEvent.time = Date.now();
newEvent.data = aData;
this._currentState.events[aEventKey] = newEvent;
this._postStateObject();
}
} catch(ex) {logException(ex);}
},
};