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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
GuardianClient:
"moz-src:///toolkit/components/ipprotection/GuardianClient.sys.mjs",
IPPEnrollAndEntitleManager:
"moz-src:///toolkit/components/ipprotection/IPPEnrollAndEntitleManager.sys.mjs",
IPPNimbusHelper:
"moz-src:///toolkit/components/ipprotection/IPPNimbusHelper.sys.mjs",
IPPSignInWatcher:
"moz-src:///toolkit/components/ipprotection/IPPSignInWatcher.sys.mjs",
IPPStartupCache:
"moz-src:///toolkit/components/ipprotection/IPPStartupCache.sys.mjs",
});
const ENABLED_PREF = "browser.ipProtection.enabled";
/**
* @typedef {object} IPProtectionStates
* List of the possible states of the IPProtectionService.
* @property {string} UNINITIALIZED
* The service has not been initialized yet.
* @property {string} UNAVAILABLE
* The user is not eligible (via nimbus) or still not signed in. No UI is available.
* @property {string} UNAUTHENTICATED
* The user is signed out but eligible (via nimbus). The panel should show the login view.
* @property {string} READY
* Ready to be activated.
*
* Note: If you update this list of states, make sure to update the
* corresponding documentation in the `docs` folder as well.
*/
export const IPProtectionStates = Object.freeze({
UNINITIALIZED: "uninitialized",
UNAVAILABLE: "unavailable",
UNAUTHENTICATED: "unauthenticated",
READY: "ready",
});
/**
* A singleton service that manages proxy integration and backend functionality.
*
* @fires IPProtectionServiceSingleton#"IPProtectionService:StateChanged"
* When the proxy state machine changes state. Check the `state` attribute to
* know the current state.
*/
class IPProtectionServiceSingleton extends EventTarget {
#state = IPProtectionStates.UNINITIALIZED;
#guardian = null;
#helpers = [];
/**
* Returns the state of the service. See the description of the state
* machine.
*
* @returns {string} - the current state from IPProtectionStates.
*/
get state() {
return this.#state;
}
get guardian() {
if (!this.#guardian) {
this.#guardian = new lazy.GuardianClient();
}
return this.#guardian;
}
constructor() {
super();
this.updateState = this.#updateState.bind(this);
this.setState = this.#setState.bind(this);
}
/**
* Sets the list of helpers to be initialized/uninitialized with the service.
*
* @param {Array} helpers
*/
setHelpers(helpers) {
this.#helpers = helpers;
}
/**
* Setups the IPProtectionService if enabled.
*/
async init() {
if (
this.#state !== IPProtectionStates.UNINITIALIZED ||
!this.featureEnabled
) {
return;
}
this.#helpers.forEach(helper => helper.init());
this.#updateState();
if (lazy.IPPStartupCache.isStartupCompleted) {
this.initOnStartupCompleted();
}
}
/**
* Removes the UI widget.
*/
uninit() {
if (this.#state === IPProtectionStates.UNINITIALIZED) {
return;
}
this.#guardian = null;
this.#helpers.forEach(helper => helper.uninit());
this.#setState(IPProtectionStates.UNINITIALIZED);
}
async initOnStartupCompleted() {
await Promise.allSettled(
this.#helpers.map(helper => helper.initOnStartupCompleted?.())
);
}
/**
* Recomputes the current state synchronously using the latest helper data.
* Callers should update their own inputs before invoking this.
*/
#updateState() {
this.#setState(this.#computeState());
}
/**
* Checks observed statuses or with Guardian to get the current state.
*
* @returns {Promise<IPProtectionStates>}
*/
#computeState() {
// The IPP feature is disabled.
if (!this.featureEnabled) {
return IPProtectionStates.UNINITIALIZED;
}
// Maybe we have to use the cached state, because we are not initialized yet.
if (!lazy.IPPStartupCache.isStartupCompleted) {
return lazy.IPPStartupCache.state;
}
// If the device is not eligible no UI is shown.
if (!lazy.IPPNimbusHelper.isEligible) {
return IPProtectionStates.UNAVAILABLE;
}
// For non authenticated users, we don't know yet their enroll state so the UI
// is shown and they have to login.
if (!lazy.IPPSignInWatcher.isSignedIn) {
return IPProtectionStates.UNAUTHENTICATED;
}
// If the current account is not enrolled and entitled, the UI is shown and
// they have to opt-in.
// If they are currently enrolling, they have already opted-in.
if (
!lazy.IPPEnrollAndEntitleManager.isEnrolledAndEntitled &&
!lazy.IPPEnrollAndEntitleManager.isEnrolling
) {
return IPProtectionStates.UNAUTHENTICATED;
}
// The proxy can be activated.
return IPProtectionStates.READY;
}
/**
* Sets the current state and triggers the state change event if needed.
*
* @param {IPProtectionStates} newState
*/
#setState(newState) {
if (newState === this.#state) {
return;
}
let prevState = this.#state;
this.#state = newState;
this.#stateChanged(newState, prevState);
}
/**
* Handles side effects of a state change and dispatches the StateChanged event.
*
* @param {IPProtectionStates} state
* @param {IPProtectionStates} prevState
*/
#stateChanged(state, prevState) {
this.dispatchEvent(
new CustomEvent("IPProtectionService:StateChanged", {
bubbles: true,
composed: true,
detail: {
state,
prevState,
},
})
);
}
}
const IPProtectionService = new IPProtectionServiceSingleton();
XPCOMUtils.defineLazyPreferenceGetter(
IPProtectionService,
"featureEnabled",
ENABLED_PREF,
false,
(_pref, _oldVal, featureEnabled) => {
if (featureEnabled) {
IPProtectionService.init();
} else {
IPProtectionService.uninit();
}
}
);
export { IPProtectionService };