2015-03-26 11:33:38 +03:00
|
|
|
/* 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/. */
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Firefox Accounts Profile helper.
|
|
|
|
*
|
|
|
|
* This class abstracts interaction with the profile server for an account.
|
|
|
|
* It will handle things like fetching profile data, listening for updates to
|
|
|
|
* the user's profile in open browser tabs, and cacheing/invalidating profile data.
|
|
|
|
*/
|
|
|
|
|
|
|
|
this.EXPORTED_SYMBOLS = ["FxAccountsProfile"];
|
|
|
|
|
|
|
|
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
|
|
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
|
|
Cu.import("resource://gre/modules/FxAccountsCommon.js");
|
2015-06-18 12:28:11 +03:00
|
|
|
Cu.import("resource://gre/modules/FxAccounts.jsm");
|
2015-03-26 11:33:38 +03:00
|
|
|
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsProfileClient",
|
|
|
|
"resource://gre/modules/FxAccountsProfileClient.jsm");
|
|
|
|
|
2017-01-10 20:09:02 +03:00
|
|
|
this.FxAccountsProfile = function(options = {}) {
|
2015-07-15 02:50:59 +03:00
|
|
|
this._currentFetchPromise = null;
|
2017-01-09 23:56:28 +03:00
|
|
|
this._cachedAt = 0; // when we saved the cached version.
|
2015-07-15 02:50:59 +03:00
|
|
|
this._isNotifying = false; // are we sending a notification?
|
2015-06-18 12:28:11 +03:00
|
|
|
this.fxa = options.fxa || fxAccounts;
|
2015-03-26 11:33:38 +03:00
|
|
|
this.client = options.profileClient || new FxAccountsProfileClient({
|
2015-06-18 12:28:11 +03:00
|
|
|
fxa: this.fxa,
|
2015-03-26 11:33:38 +03:00
|
|
|
serverURL: options.profileServerUrl,
|
|
|
|
});
|
|
|
|
|
2015-07-15 02:50:59 +03:00
|
|
|
// An observer to invalidate our _cachedAt optimization. We use a weak-ref
|
|
|
|
// just incase this.tearDown isn't called in some cases.
|
|
|
|
Services.obs.addObserver(this, ON_PROFILE_CHANGE_NOTIFICATION, true);
|
2015-03-26 11:33:38 +03:00
|
|
|
// for testing
|
|
|
|
if (options.channel) {
|
|
|
|
this.channel = options.channel;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.FxAccountsProfile.prototype = {
|
2015-07-15 02:50:59 +03:00
|
|
|
// If we get subsequent requests for a profile within this period, don't bother
|
|
|
|
// making another request to determine if it is fresh or not.
|
|
|
|
PROFILE_FRESHNESS_THRESHOLD: 120000, // 2 minutes
|
|
|
|
|
|
|
|
observe(subject, topic, data) {
|
|
|
|
// If we get a profile change notification from our webchannel it means
|
|
|
|
// the user has just changed their profile via the web, so we want to
|
|
|
|
// ignore our "freshness threshold"
|
|
|
|
if (topic == ON_PROFILE_CHANGE_NOTIFICATION && !this._isNotifying) {
|
|
|
|
log.debug("FxAccountsProfile observed profile change");
|
|
|
|
this._cachedAt = 0;
|
|
|
|
}
|
|
|
|
},
|
2015-03-26 11:33:38 +03:00
|
|
|
|
2017-01-10 20:09:02 +03:00
|
|
|
tearDown() {
|
2015-06-18 12:28:11 +03:00
|
|
|
this.fxa = null;
|
2015-03-26 11:33:38 +03:00
|
|
|
this.client = null;
|
2015-07-15 02:50:59 +03:00
|
|
|
Services.obs.removeObserver(this, ON_PROFILE_CHANGE_NOTIFICATION);
|
2015-03-26 11:33:38 +03:00
|
|
|
},
|
|
|
|
|
2017-01-10 20:09:02 +03:00
|
|
|
_notifyProfileChange(uid) {
|
2015-07-15 02:50:59 +03:00
|
|
|
this._isNotifying = true;
|
2015-03-26 11:33:38 +03:00
|
|
|
Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION, uid);
|
2015-07-15 02:50:59 +03:00
|
|
|
this._isNotifying = false;
|
2015-03-26 11:33:38 +03:00
|
|
|
},
|
|
|
|
|
2017-01-09 23:56:28 +03:00
|
|
|
// Cache fetched data and send out a notification so that UI can update.
|
|
|
|
_cacheProfile(response) {
|
|
|
|
let profileCache = {
|
|
|
|
profile: response.body,
|
|
|
|
etag: response.etag
|
|
|
|
};
|
|
|
|
|
|
|
|
return this.fxa.setProfileCache(profileCache)
|
|
|
|
.then(() => {
|
|
|
|
return this.fxa.getSignedInUser();
|
|
|
|
})
|
2015-06-18 12:28:11 +03:00
|
|
|
.then(userData => {
|
|
|
|
log.debug("notifying profile changed for user ${uid}", userData);
|
|
|
|
this._notifyProfileChange(userData.uid);
|
2017-01-09 23:56:28 +03:00
|
|
|
return response.body;
|
2015-03-26 11:33:38 +03:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2017-01-09 23:56:28 +03:00
|
|
|
_fetchAndCacheProfileInternal() {
|
2017-01-23 19:25:04 +03:00
|
|
|
let onFinally = () => {
|
|
|
|
this._cachedAt = Date.now();
|
|
|
|
this._currentFetchPromise = null;
|
|
|
|
}
|
2017-01-09 23:56:28 +03:00
|
|
|
return this.fxa.getProfileCache()
|
|
|
|
.then(profileCache => {
|
|
|
|
const etag = profileCache ? profileCache.etag : null;
|
|
|
|
return this.client.fetchProfile(etag);
|
|
|
|
})
|
|
|
|
.then(response => {
|
2017-02-07 20:55:01 +03:00
|
|
|
// response may be null if the profile was not modified (same ETag).
|
|
|
|
return response ? this._cacheProfile(response) : null;
|
2017-01-09 23:56:28 +03:00
|
|
|
})
|
|
|
|
.then(body => { // finally block
|
2017-01-23 19:25:04 +03:00
|
|
|
onFinally();
|
2017-02-07 20:55:01 +03:00
|
|
|
// body may be null if the profile was not modified
|
2017-01-09 23:56:28 +03:00
|
|
|
return body;
|
2017-01-23 19:25:04 +03:00
|
|
|
}, err => {
|
|
|
|
onFinally();
|
2017-02-07 20:55:01 +03:00
|
|
|
throw err;
|
2015-03-26 11:33:38 +03:00
|
|
|
});
|
2017-01-09 23:56:28 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
_fetchAndCacheProfile() {
|
|
|
|
if (!this._currentFetchPromise) {
|
|
|
|
this._currentFetchPromise = this._fetchAndCacheProfileInternal();
|
2015-07-15 02:50:59 +03:00
|
|
|
}
|
2017-01-09 23:56:28 +03:00
|
|
|
return this._currentFetchPromise;
|
2015-03-26 11:33:38 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
// Returns cached data right away if available, then fetches the latest profile
|
|
|
|
// data in the background. After data is fetched a notification will be sent
|
|
|
|
// out if the profile has changed.
|
2017-01-10 20:09:02 +03:00
|
|
|
getProfile() {
|
2017-01-09 23:56:28 +03:00
|
|
|
return this.fxa.getProfileCache()
|
|
|
|
.then(profileCache => {
|
|
|
|
if (profileCache) {
|
2015-07-15 02:50:59 +03:00
|
|
|
if (Date.now() > this._cachedAt + this.PROFILE_FRESHNESS_THRESHOLD) {
|
|
|
|
// Note that _fetchAndCacheProfile isn't returned, so continues
|
|
|
|
// in the background.
|
|
|
|
this._fetchAndCacheProfile().catch(err => {
|
2017-01-23 19:25:04 +03:00
|
|
|
log.error("Background refresh of profile failed", err);
|
2015-07-15 02:50:59 +03:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
log.trace("not checking freshness of profile as it remains recent");
|
|
|
|
}
|
2017-01-09 23:56:28 +03:00
|
|
|
return profileCache.profile;
|
2015-03-26 11:33:38 +03:00
|
|
|
}
|
|
|
|
return this._fetchAndCacheProfile();
|
|
|
|
});
|
|
|
|
},
|
2015-07-15 02:50:59 +03:00
|
|
|
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([
|
|
|
|
Ci.nsIObserver,
|
|
|
|
Ci.nsISupportsWeakReference,
|
|
|
|
]),
|
2015-03-26 11:33:38 +03:00
|
|
|
};
|