Source code
Revision control
Copy as Markdown
Other Tools
/* 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
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
IPPStartupCache: "resource:///modules/ipprotection/IPPStartupCache.sys.mjs",
IPProtectionService:
"resource:///modules/ipprotection/IPProtectionService.sys.mjs",
IPPSignInWatcher: "resource:///modules/ipprotection/IPPSignInWatcher.sys.mjs",
});
const LOG_PREF = "browser.ipProtection.log";
ChromeUtils.defineLazyGetter(lazy, "logConsole", function () {
return console.createInstance({
prefix: "IPPEnrollAndEntitleManager",
maxLogLevel: Services.prefs.getBoolPref(LOG_PREF, false) ? "Debug" : "Warn",
});
});
/**
* This class manages the enrolling and entitlement.
*/
class IPPEnrollAndEntitleManagerSingleton extends EventTarget {
#runningPromise = null;
#isEnrolled = false;
#entitlement = null;
constructor() {
super();
this.handleEvent = this.#handleEvent.bind(this);
}
init() {
// We will use data from the cache until we are fully functional. Then we
// will recompute the state in `initOnStartupCompleted`.
this.#entitlement = lazy.IPPStartupCache.entitlement;
this.#isEnrolled = !!this.#entitlement;
lazy.IPPSignInWatcher.addEventListener(
"IPPSignInWatcher:StateChanged",
this.handleEvent
);
}
initOnStartupCompleted() {
if (!lazy.IPPSignInWatcher.isSignedIn) {
return;
}
try {
// This bit must be async because we want to trigger the updateState at
// the end of the rest of the initialization.
lazy.IPProtectionService.guardian
.isLinkedToGuardian(/* only cache: */ true)
.then(
async isEnrolled => {
this.#isEnrolled = isEnrolled;
if (isEnrolled) {
const data =
await IPPEnrollAndEntitleManagerSingleton.#maybeEntitle();
this.#setEntitlement(data.entitlement);
} else {
this.#setEntitlement(null);
}
},
() => {
// In case we were using cached values, it's time to reset them.
this.#isEnrolled = false;
this.#setEntitlement(null);
}
);
} catch (_) {
// In case we were using cached values, it's time to reset them.
this.#isEnrolled = false;
this.#setEntitlement(null);
}
}
uninit() {
lazy.IPPSignInWatcher.removeEventListener(
"IPPSignInWatcher:StateChanged",
this.handleEvent
);
this.#entitlement = null;
this.#isEnrolled = false;
}
#handleEvent(_event) {
if (!lazy.IPPSignInWatcher.isSignedIn) {
this.#isEnrolled = false;
this.#setEntitlement(null);
return;
}
this.maybeEnrollAndEntitle();
}
maybeEnrollAndEntitle() {
if (this.#runningPromise) {
return this.#runningPromise;
}
if (this.#entitlement && this.#isEnrolled) {
return Promise.resolve({ isEnrolledAndEntitled: true });
}
const enrollAndEntitle = async () => {
if (!this.#isEnrolled) {
const data = await IPPEnrollAndEntitleManagerSingleton.#maybeEnroll();
if (!data.isEnrolled) {
return { isEnrolledAndEntitled: false, error: data.error };
}
this.#isEnrolled = true;
}
const data = await IPPEnrollAndEntitleManagerSingleton.#maybeEntitle();
if (!data.entitlement) {
return { isEnrolledAndEntitled: false, error: data.error };
}
this.#setEntitlement(data.entitlement);
return { isEnrolledAndEntitled: true };
};
this.#runningPromise = enrollAndEntitle().finally(() => {
this.#runningPromise = null;
});
return this.#runningPromise;
}
// This method is static because we don't want to change the internal state
// of the singleton.
static async #maybeEnroll() {
let isEnrolled = false;
try {
isEnrolled = await lazy.IPProtectionService.guardian.isLinkedToGuardian(
/* only cache: */ false
);
} catch (error) {
return { isEnrolled, error: error?.message };
}
if (isEnrolled) {
return { isEnrolled };
}
try {
const enrollment = await lazy.IPProtectionService.guardian.enroll();
isEnrolled = !!enrollment?.ok;
lazy.logConsole.debug(
"Guardian:",
isEnrolled ? "Enrolled" : "Enrollment Failed"
);
return { isEnrolled, error: enrollment?.error };
} catch (error) {
return { isEnrolled, error: error?.message };
}
}
// This method is static because we don't want to change the internal state
// of the singleton.
static async #maybeEntitle() {
let { status, entitlement, error } =
await lazy.IPProtectionService.guardian.fetchUserInfo();
lazy.logConsole.debug("Entitlement:", { status, entitlement, error });
// If we see an error during the READY state, let's trigger an error state.
if (error || !entitlement || status != 200) {
return { entitlement: null, error: error || `Status: ${status}` };
}
return { entitlement };
}
#setEntitlement(entitlement) {
this.#entitlement = entitlement;
lazy.IPPStartupCache.storeEntitlement(this.#entitlement);
lazy.IPProtectionService.updateState();
this.dispatchEvent(
new CustomEvent("IPPEnrollAndEntitleManager:StateChanged", {
bubbles: true,
composed: true,
})
);
}
get isEnrolledAndEntitled() {
return this.#isEnrolled && !!this.#entitlement;
}
/**
* Checks if a user has upgraded.
*
* @returns {boolean}
*/
get hasUpgraded() {
return this.#entitlement?.subscribed;
}
/**
* Checks if the entitlement exists and it contains a UUID
*/
get hasEntitlementUid() {
return !!this.#entitlement?.uid;
}
/**
* Checks if we have the entitlement
*/
get hasEntitlement() {
return !!this.#entitlement;
}
/**
* Checks if we're running the Alpha variant based on
* available features
*/
get isAlpha() {
return (
!this.#entitlement?.autostart &&
!this.#entitlement?.website_inclusion &&
!this.#entitlement?.location_controls
);
}
async refetchEntitlement() {
this.#setEntitlement(null);
await this.maybeEnrollAndEntitle();
}
resetEntitlement() {
this.#setEntitlement(null);
}
}
const IPPEnrollAndEntitleManager = new IPPEnrollAndEntitleManagerSingleton();
export { IPPEnrollAndEntitleManager };