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
import { IPPAuthProvider } from "moz-src:///toolkit/components/ipprotection/IPPAuthProvider.sys.mjs";
import { GuardianClient } from "moz-src:///toolkit/components/ipprotection/fxa/GuardianClient.sys.mjs";
const lazy = {};
ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () =>
ChromeUtils.importESModule(
"resource://gre/modules/FxAccounts.sys.mjs"
).getFxAccountsSingleton()
);
ChromeUtils.defineESModuleGetters(lazy, {
IPPSignInWatcher:
"moz-src:///toolkit/components/ipprotection/fxa/IPPSignInWatcher.sys.mjs",
IPProtectionService:
"moz-src:///toolkit/components/ipprotection/IPProtectionService.sys.mjs",
IPPStartupCache:
"moz-src:///toolkit/components/ipprotection/IPPStartupCache.sys.mjs",
});
/**
* Base class for FxA-backed IPPAuthProvider implementations.
* Provides shared OAuth token retrieval, Guardian proxy methods,
* sign-in watcher access, and entitlement lifecycle management.
*/
export class IPPFxaBaseAuthProvider extends IPPAuthProvider {
#entitlement = null;
#signInWatcher = null;
#guardian = new GuardianClient();
/**
* @param {object} [signInWatcher] - Custom sign-in watcher. Defaults to IPPSignInWatcher.
*/
constructor(signInWatcher = null) {
super();
this.#signInWatcher = signInWatcher;
this.handleEvent = this.#handleEvent.bind(this);
}
get guardian() {
return this.#guardian;
}
get signInWatcher() {
return this.#signInWatcher ?? lazy.IPPSignInWatcher;
}
get entitlement() {
return this.#entitlement;
}
/**
* Updates the stored entitlement and persists it to the startup cache.
*
* @param {object|null} entitlement
*/
_setEntitlement(entitlement) {
this.#entitlement = entitlement;
lazy.IPPStartupCache.storeEntitlement(entitlement);
}
init() {
this.#entitlement = lazy.IPPStartupCache.entitlement;
this.signInWatcher.addEventListener(
"IPPSignInWatcher:StateChanged",
this.handleEvent
);
}
initOnStartupCompleted() {
if (!this.signInWatcher.isSignedIn) {
return;
}
this.updateEntitlement();
}
uninit() {
this.signInWatcher.removeEventListener(
"IPPSignInWatcher:StateChanged",
this.handleEvent
);
this.#entitlement = null;
}
#handleEvent() {
if (!this.signInWatcher.isSignedIn) {
this._setEntitlement(null);
lazy.IPProtectionService.updateState();
return;
}
this.updateEntitlement();
}
updateEntitlement() {}
async getEntitlement() {
try {
using tokenHandle = await this.getToken();
const { status, entitlement, error } =
await this.guardian.fetchUserInfo(tokenHandle);
if (error || !entitlement || status != 200) {
return { error: error || `Status: ${status}` };
}
return { entitlement };
} catch (error) {
return { error: error.message };
}
}
get hasUpgraded() {
return this.entitlement?.subscribed ?? false;
}
get maxBytes() {
return this.entitlement?.maxBytes ?? null;
}
/**
* Retrieves an FxA OAuth token and returns a disposable handle that revokes
* it on disposal.
*
* @param {AbortSignal} [abortSignal]
* @returns {Promise<{token: string} & Disposable>}
*/
async getToken(abortSignal = null) {
let tasks = [
lazy.fxAccounts.getOAuthToken({
}),
];
if (abortSignal) {
abortSignal.throwIfAborted();
tasks.push(
new Promise((_, rej) => {
abortSignal?.addEventListener("abort", rej, { once: true });
})
);
}
const token = await Promise.race(tasks);
if (!token) {
return null;
}
return {
token,
[Symbol.dispose]: () => {
lazy.fxAccounts.removeCachedOAuthToken({ token });
},
};
}
async fetchProxyPass(abortSignal = null) {
using tokenHandle = await this.getToken(abortSignal);
return await this.#guardian.fetchProxyPass(tokenHandle, abortSignal);
}
async fetchProxyUsage(abortSignal = null) {
using tokenHandle = await this.getToken(abortSignal);
return await this.#guardian.fetchProxyUsage(tokenHandle, abortSignal);
}
get excludedUrlPrefs() {
return [
"identity.fxaccounts.remote.profile.uri",
"identity.fxaccounts.auth.uri",
];
}
}