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 { AuthProviderImpl } from "moz-src:///browser/components/aiwindow/models/auth/AuthProviderImpl.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
const PREF_TOKEN = "browser.smartwindow.auth.guest.token";
const PREF_MODE = "browser.smartwindow.auth.guest.mode";
const VERIFY_PATH = "/verify/v1/guest";
const SUCCESS_PATH = "/verify/v1/guest/success";
const lazy = XPCOMUtils.declareLazy({
AuthProvider:
"moz-src:///browser/components/aiwindow/models/AuthProvider.sys.mjs",
openAIEngine:
"moz-src:///browser/components/aiwindow/models/openAIEngine.sys.mjs",
ToUSpotlight:
"moz-src:///browser/components/aiwindow/ui/modules/ToUSpotlight.sys.mjs",
token: {
pref: PREF_TOKEN,
default: "",
},
mode: {
pref: PREF_MODE,
default: "fetch",
},
});
/**
* AuthProviderImpl that obtains a guest token from MLPA, without an account.
*/
export class GuestAuthProviderImpl extends AuthProviderImpl {
static cleanup() {
Services.prefs.clearUserPref(PREF_TOKEN);
}
get providerName() {
return "guest";
}
async getToken() {
return lazy.token || null;
}
async invalidateToken(token) {
if (token === lazy.token) {
Services.prefs.clearUserPref(PREF_TOKEN);
this.dispatchEvent(
new CustomEvent("AuthProviderImpl:FallbackNeeded", {
detail: { reason: "token_rejected" },
})
);
}
}
async isSignedIn() {
return !!lazy.token;
}
async canSignIn() {
return true;
}
async signIn(browser) {
if (
!lazy.AuthProvider.hasToUConsent &&
!(await lazy.ToUSpotlight.show(browser))
) {
return false;
}
const origin = new URL(lazy.openAIEngine.endpoint).origin;
const state = crypto.randomUUID();
const url = `${origin}${VERIFY_PATH}?state=${encodeURIComponent(state)}`;
if (lazy.mode === "tab") {
return this.#signInWithTab(browser, url, origin, state);
}
try {
return await this.#signInWithFetch(url);
} catch (error) {
await lazy.ToUSpotlight.showUnavailable(browser).catch(e =>
console.error("Error showing unavailable modal:", e)
);
throw error;
}
}
async #signInWithFetch(url) {
const response = await fetch(url, {
credentials: "omit",
headers: { Accept: "application/json" },
});
if (!response.ok) {
throw new Error(`Guest token request failed: ${response.status}`);
}
const { access_token: token, token_type: tokenType } =
await response.json();
if (typeof token !== "string" || !token || tokenType !== "Bearer") {
throw new Error("Invalid guest token response");
}
Services.prefs.setStringPref(PREF_TOKEN, token);
return true;
}
async #signInWithTab(browser, url, origin, state) {
const guestBrowser = await new Promise(resolve => {
browser.documentGlobal.openLinkIn(url, "tab", {
private: false,
triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal(
{}
),
resolveOnContentBrowserCreated: resolve,
forceForeground: true,
});
});
const gBrowser = guestBrowser.getTabBrowser();
const guestTab = gBrowser.getTabForBrowser(guestBrowser);
const token = await new Promise(resolve => {
const controller = new AbortController();
const { signal } = controller;
const finish = value => {
controller.abort();
resolve(value);
};
const progressListener = {
QueryInterface: ChromeUtils.generateQI([
Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference,
]),
onLocationChange(webProgress, _request, location) {
if (
!webProgress.isTopLevel ||
location.prePath !== origin ||
location.filePath !== SUCCESS_PATH
) {
return;
}
const params = new URLSearchParams(location.ref);
if (params.get("state") !== state) {
return;
}
finish(params.get("access_token"));
},
};
guestTab.addEventListener("TabClose", () => finish(null), {
once: true,
signal,
});
guestTab.documentGlobal.addEventListener("unload", () => finish(null), {
once: true,
signal,
});
guestBrowser.addProgressListener(
progressListener,
Ci.nsIWebProgress.NOTIFY_LOCATION
);
signal.addEventListener(
"abort",
() => guestBrowser.removeProgressListener(progressListener),
{ once: true }
);
});
if (!token) {
return false;
}
Services.prefs.setStringPref(PREF_TOKEN, token);
if (
Services.prefs.getBoolPref(
"browser.smartwindow.firstrun.hasCompleted",
false
)
) {
gBrowser.removeTab(guestTab);
}
return true;
}
}