Bug 1814091 - Move CanvasContext.getPreferredFormat to GPU.getPreferredCanvasFormat...
[gecko.git] / services / fxaccounts / FxAccountsProfile.sys.mjs
blob7e8da5cf0301695019e7ed6ef45745e5255a1bd7
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/. */
5 /**
6  * Firefox Accounts Profile helper.
7  *
8  * This class abstracts interaction with the profile server for an account.
9  * It will handle things like fetching profile data, listening for updates to
10  * the user's profile in open browser tabs, and cacheing/invalidating profile data.
11  */
13 const { ON_PROFILE_CHANGE_NOTIFICATION, log } = ChromeUtils.import(
14   "resource://gre/modules/FxAccountsCommon.js"
16 import { getFxAccountsSingleton } from "resource://gre/modules/FxAccounts.sys.mjs";
18 const fxAccounts = getFxAccountsSingleton();
20 const lazy = {};
22 ChromeUtils.defineESModuleGetters(lazy, {
23   FxAccountsProfileClient:
24     "resource://gre/modules/FxAccountsProfileClient.sys.mjs",
25 });
27 export var FxAccountsProfile = function(options = {}) {
28   this._currentFetchPromise = null;
29   this._cachedAt = 0; // when we saved the cached version.
30   this._isNotifying = false; // are we sending a notification?
31   this.fxai = options.fxai || fxAccounts._internal;
32   this.client =
33     options.profileClient ||
34     new lazy.FxAccountsProfileClient({
35       fxai: this.fxai,
36       serverURL: options.profileServerUrl,
37     });
39   // An observer to invalidate our _cachedAt optimization. We use a weak-ref
40   // just incase this.tearDown isn't called in some cases.
41   Services.obs.addObserver(this, ON_PROFILE_CHANGE_NOTIFICATION, true);
42   // for testing
43   if (options.channel) {
44     this.channel = options.channel;
45   }
48 FxAccountsProfile.prototype = {
49   // If we get subsequent requests for a profile within this period, don't bother
50   // making another request to determine if it is fresh or not.
51   PROFILE_FRESHNESS_THRESHOLD: 120000, // 2 minutes
53   observe(subject, topic, data) {
54     // If we get a profile change notification from our webchannel it means
55     // the user has just changed their profile via the web, so we want to
56     // ignore our "freshness threshold"
57     if (topic == ON_PROFILE_CHANGE_NOTIFICATION && !this._isNotifying) {
58       log.debug("FxAccountsProfile observed profile change");
59       this._cachedAt = 0;
60     }
61   },
63   tearDown() {
64     this.fxai = null;
65     this.client = null;
66     Services.obs.removeObserver(this, ON_PROFILE_CHANGE_NOTIFICATION);
67   },
69   _notifyProfileChange(uid) {
70     this._isNotifying = true;
71     Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION, uid);
72     this._isNotifying = false;
73   },
75   // Cache fetched data and send out a notification so that UI can update.
76   _cacheProfile(response) {
77     return this.fxai.withCurrentAccountState(async state => {
78       const profile = response.body;
79       const userData = await state.getUserAccountData();
80       if (profile.uid != userData.uid) {
81         throw new Error(
82           "The fetched profile does not correspond with the current account."
83         );
84       }
85       let profileCache = {
86         profile,
87         etag: response.etag,
88       };
89       await state.updateUserAccountData({ profileCache });
90       if (profile.email != userData.email) {
91         await this.fxai._handleEmailUpdated(profile.email);
92       }
93       log.debug("notifying profile changed for user ${uid}", userData);
94       this._notifyProfileChange(userData.uid);
95       return profile;
96     });
97   },
99   async _getProfileCache() {
100     let data = await this.fxai.currentAccountState.getUserAccountData([
101       "profileCache",
102     ]);
103     return data ? data.profileCache : null;
104   },
106   async _fetchAndCacheProfileInternal() {
107     try {
108       const profileCache = await this._getProfileCache();
109       const etag = profileCache ? profileCache.etag : null;
110       let response;
111       try {
112         response = await this.client.fetchProfile(etag);
113       } catch (err) {
114         await this.fxai._handleTokenError(err);
115         // _handleTokenError always re-throws.
116         throw new Error("not reached!");
117       }
119       // response may be null if the profile was not modified (same ETag).
120       if (!response) {
121         return null;
122       }
123       return await this._cacheProfile(response);
124     } finally {
125       this._cachedAt = Date.now();
126       this._currentFetchPromise = null;
127     }
128   },
130   _fetchAndCacheProfile() {
131     if (!this._currentFetchPromise) {
132       this._currentFetchPromise = this._fetchAndCacheProfileInternal();
133     }
134     return this._currentFetchPromise;
135   },
137   // Returns cached data right away if available, otherwise returns null - if
138   // it returns null, or if the profile is possibly stale, it attempts to
139   // fetch the latest profile data in the background. After data is fetched a
140   // notification will be sent out if the profile has changed.
141   async getProfile() {
142     const profileCache = await this._getProfileCache();
143     if (!profileCache) {
144       // fetch and cache it in the background.
145       this._fetchAndCacheProfile().catch(err => {
146         log.error("Background refresh of initial profile failed", err);
147       });
148       return null;
149     }
150     if (Date.now() > this._cachedAt + this.PROFILE_FRESHNESS_THRESHOLD) {
151       // Note that _fetchAndCacheProfile isn't returned, so continues
152       // in the background.
153       this._fetchAndCacheProfile().catch(err => {
154         log.error("Background refresh of profile failed", err);
155       });
156     } else {
157       log.trace("not checking freshness of profile as it remains recent");
158     }
159     return profileCache.profile;
160   },
162   // Get the user's profile data, fetching from the network if necessary.
163   // Most callers should instead use `getProfile()`; this methods exists to support
164   // callers who need to await the underlying network request.
165   async ensureProfile({ staleOk = false, forceFresh = false } = {}) {
166     if (staleOk && forceFresh) {
167       throw new Error("contradictory options specified");
168     }
169     const profileCache = await this._getProfileCache();
170     if (
171       forceFresh ||
172       !profileCache ||
173       (Date.now() > this._cachedAt + this.PROFILE_FRESHNESS_THRESHOLD &&
174         !staleOk)
175     ) {
176       const profile = await this._fetchAndCacheProfile().catch(err => {
177         log.error("Background refresh of profile failed", err);
178       });
179       if (profile) {
180         return profile;
181       }
182     }
183     log.trace("not checking freshness of profile as it remains recent");
184     return profileCache ? profileCache.profile : null;
185   },
187   QueryInterface: ChromeUtils.generateQI([
188     "nsIObserver",
189     "nsISupportsWeakReference",
190   ]),