diff --git a/extension/chrome/elements/compose-modules/compose-recipients-module.ts b/extension/chrome/elements/compose-modules/compose-recipients-module.ts index 5158b78c1f8..55bd1a601cf 100644 --- a/extension/chrome/elements/compose-modules/compose-recipients-module.ts +++ b/extension/chrome/elements/compose-modules/compose-recipients-module.ts @@ -878,7 +878,6 @@ export class ComposeRecipientsModule extends ViewModule { const authResult = await BrowserMsg.send.bg.await.reconnectAcctAuthPopup({ acctEmail: this.view.acctEmail, scopes: GoogleOAuth.defaultScopes('contacts'), - screenDimensions: Ui.getScreenDimensions(), }); if (authResult.result === 'Success') { this.googleContactsSearchEnabled = true; diff --git a/extension/chrome/elements/oauth2.htm b/extension/chrome/elements/oauth2.htm deleted file mode 100644 index 4f504a22f9e..00000000000 --- a/extension/chrome/elements/oauth2.htm +++ /dev/null @@ -1,8 +0,0 @@ - - - - OAuth 2.0 Finish Page - - - - diff --git a/extension/js/common/api/authentication/configured-idp-oauth.ts b/extension/js/common/api/authentication/configured-idp-oauth.ts index 0283970ddfd..0913d2e5417 100644 --- a/extension/js/common/api/authentication/configured-idp-oauth.ts +++ b/extension/js/common/api/authentication/configured-idp-oauth.ts @@ -101,7 +101,7 @@ export class ConfiguredIdpOAuth extends OAuth { refreshToken, client_id: authConf.oauth.clientId, - redirect_uri: chrome.identity.getRedirectURL('oauth'), + redirect_uri: this.getRedirectUri(), }, dataType: 'JSON', /* eslint-enable @typescript-eslint/naming-convention */ @@ -121,7 +121,7 @@ export class ConfiguredIdpOAuth extends OAuth { prompt: 'login', state, - redirect_uri: chrome.identity.getRedirectURL('oauth'), + redirect_uri: this.getRedirectUri(), scope: this.OAUTH_REQUEST_SCOPES.join(' '), login_hint: acctEmail, }); @@ -207,7 +207,7 @@ export class ConfiguredIdpOAuth extends OAuth { code, client_id: authConf.oauth.clientId, - redirect_uri: chrome.identity.getRedirectURL('oauth'), + redirect_uri: this.getRedirectUri(), }, dataType: 'JSON', /* eslint-enable @typescript-eslint/naming-convention */ diff --git a/extension/js/common/api/authentication/generic/oauth.ts b/extension/js/common/api/authentication/generic/oauth.ts index e6d92bf0e60..97af5d91f96 100644 --- a/extension/js/common/api/authentication/generic/oauth.ts +++ b/extension/js/common/api/authentication/generic/oauth.ts @@ -37,27 +37,42 @@ export type AuthorizationHeader = { export class OAuth { /* eslint-disable @typescript-eslint/naming-convention */ - public static GOOGLE_OAUTH_CONFIG = { - client_id: '717284730244-5oejn54f10gnrektjdc4fv4rbic1bj1p.apps.googleusercontent.com', - client_secret: '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', - redirect_uri: 'https://www.google.com/robots.txt', - url_code: `${GOOGLE_OAUTH_SCREEN_HOST}/o/oauth2/auth`, - url_tokens: `${OAUTH_GOOGLE_API_HOST}/token`, - state_header: 'CRYPTUP_STATE_', - scopes: { - email: 'email', - openid: 'openid', - profile: 'https://www.googleapis.com/auth/userinfo.profile', // needed so that `name` is present in `id_token`, which is required for key-server auth when in use - compose: 'https://www.googleapis.com/auth/gmail.compose', - modify: 'https://www.googleapis.com/auth/gmail.modify', - readContacts: 'https://www.googleapis.com/auth/contacts.readonly', - readOtherContacts: 'https://www.googleapis.com/auth/contacts.other.readonly', - }, - legacy_scopes: { - gmail: 'https://mail.google.com/', // causes a freakish oauth warn: "can permannently delete all your email" ... - }, - }; public static OAUTH_REQUEST_SCOPES = ['offline_access', 'openid', 'profile', 'email']; + + public static get GOOGLE_OAUTH_CONFIG() { + return { + client_id: '717284730244-5oejn54f10gnrektjdc4fv4rbic1bj1p.apps.googleusercontent.com', + client_secret: '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', + url_code: `${GOOGLE_OAUTH_SCREEN_HOST}/o/oauth2/auth`, + url_tokens: `${OAUTH_GOOGLE_API_HOST}/token`, + state_header: 'CRYPTUP_STATE_', + scopes: { + email: 'email', + openid: 'openid', + profile: 'https://www.googleapis.com/auth/userinfo.profile', // needed so that `name` is present in `id_token`, which is required for key-server auth when in use + compose: 'https://www.googleapis.com/auth/gmail.compose', + modify: 'https://www.googleapis.com/auth/gmail.modify', + readContacts: 'https://www.googleapis.com/auth/contacts.readonly', + readOtherContacts: 'https://www.googleapis.com/auth/contacts.other.readonly', + }, + legacy_scopes: { + gmail: 'https://mail.google.com/', // causes a freakish oauth warn: "can permannently delete all your email" ... + }, + }; + } + + public static getRedirectUri(): string { + if (!chrome?.identity?.getRedirectURL) { + throw new Error('chrome.identity.getRedirectURL is not available in this context'); + } + const redirectUri = chrome.identity.getRedirectURL('oauth'); + if (navigator.userAgent.includes('Firefox')) { + const url = new URL(redirectUri); + const subdomain = url.hostname.split('.')[0]; + return `http://127.0.0.1/mozoauth2/${subdomain}`; + } + return redirectUri; + } /* eslint-enable @typescript-eslint/naming-convention */ /** * Happens on enterprise builds diff --git a/extension/js/common/api/authentication/google/google-oauth.ts b/extension/js/common/api/authentication/google/google-oauth.ts index 2c33fbb168e..784786bdf5c 100644 --- a/extension/js/common/api/authentication/google/google-oauth.ts +++ b/extension/js/common/api/authentication/google/google-oauth.ts @@ -6,10 +6,7 @@ import { Url } from '../../../core/common.js'; import { FLAVOR, OAUTH_GOOGLE_API_HOST } from '../../../core/const.js'; import { ApiErr } from '../../shared/api-error.js'; import { Ajax, Api } from '../../shared/api.js'; - -import { Bm, ScreenDimensions } from '../../../browser/browser-msg.js'; import { InMemoryStoreKeys } from '../../../core/const.js'; -import { OAuth2 } from '../../../oauth2/oauth2.js'; import { CatchHelper } from '../../../platform/catch-helper.js'; import { AcctStore, AcctStoreDict } from '../../../platform/store/acct-store.js'; import { InMemoryStore } from '../../../platform/store/in-memory-store.js'; @@ -18,7 +15,6 @@ import { AuthorizationHeader, AuthReq, AuthRes, OAuth, OAuthTokensResponse } fro import { ExternalService } from '../../account-servers/external-service.js'; import { GoogleAuthErr } from '../../shared/api-error.js'; import { Assert, AssertError } from '../../../assert.js'; -import { Ui } from '../../../browser/ui.js'; import { ConfiguredIdpOAuth } from '../configured-idp-oauth.js'; // eslint-disable-next-line @typescript-eslint/naming-convention @@ -110,17 +106,7 @@ export class GoogleOAuth extends OAuth { } } - public static async newAuthPopup({ - acctEmail, - scopes, - save, - screenDimensions, - }: { - acctEmail?: string; - scopes?: string[]; - save?: boolean; - screenDimensions?: ScreenDimensions; - }): Promise { + public static async newAuthPopup({ acctEmail, scopes, save }: { acctEmail?: string; scopes?: string[]; save?: boolean }): Promise { if (acctEmail) { acctEmail = acctEmail.toLowerCase(); } @@ -133,18 +119,12 @@ export class GoogleOAuth extends OAuth { } const authRequest = GoogleOAuth.newAuthRequest(acctEmail, scopes); const authUrl = GoogleOAuth.apiGoogleAuthCodeUrl(authRequest); - // Added below logic because in service worker, it's not possible to access window object. - // Therefore need to retrieve screenDimensions when calling service worker and pass it to OAuth2 - if (!screenDimensions) { - screenDimensions = Ui.getScreenDimensions(); - } - const authWindowResult = await OAuth2.webAuthFlow(authUrl, screenDimensions); const authRes = await GoogleOAuth.getAuthRes({ acctEmail, save, requestedScopes: scopes, expectedState: authRequest.expectedState, - authWindowResult, + authUrl, }); if (authRes.result === 'Success') { if (!authRes.id_token) { @@ -211,24 +191,27 @@ export class GoogleOAuth extends OAuth { save, requestedScopes, expectedState, - authWindowResult, + authUrl, }: { acctEmail?: string; save: boolean; requestedScopes: string[]; expectedState: string; - authWindowResult: Bm.AuthWindowResult; + authUrl: string; }): Promise { /* eslint-disable @typescript-eslint/naming-convention */ try { - if (!authWindowResult.url) { - return { acctEmail, result: 'Denied', error: 'Invalid response url', id_token: undefined }; - } - if (authWindowResult.error) { - return { acctEmail, result: 'Denied', error: authWindowResult.error, id_token: undefined }; + const redirectUri = await chrome.identity.launchWebAuthFlow({ url: authUrl, interactive: true }); + if (chrome.runtime.lastError || !redirectUri || redirectUri?.includes('access_denied')) { + const errorMsg = chrome.runtime.lastError?.message || 'access_denied'; + const normalizedErrorMsg = errorMsg.toLowerCase(); + const userCancelled = ['user', 'cancel', 'deny', 'denied', 'close'].some(keyword => normalizedErrorMsg.includes(keyword)); + if (userCancelled) { + return { acctEmail, result: 'Closed', error: errorMsg, id_token: undefined }; + } + return { acctEmail, result: 'Denied', error: `Failed to launch web auth flow: ${errorMsg}`, id_token: undefined }; } - - const uncheckedUrlParams = Url.parse(['scope', 'code', 'state'], authWindowResult.url); + const uncheckedUrlParams = Url.parse(['scope', 'code', 'state'], redirectUri); const allowedScopes = Assert.urlParamRequire.string(uncheckedUrlParams, 'scope'); const code = Assert.urlParamRequire.optionalString(uncheckedUrlParams, 'code'); const receivedState = Assert.urlParamRequire.string(uncheckedUrlParams, 'state'); @@ -277,7 +260,7 @@ export class GoogleOAuth extends OAuth { access_type: 'offline', prompt: 'consent', state: authReq.expectedState, - redirect_uri: this.GOOGLE_OAUTH_CONFIG.redirect_uri, + redirect_uri: this.getRedirectUri(), scope: (authReq.scopes || []).join(' '), login_hint: authReq.acctEmail, }); @@ -309,7 +292,7 @@ export class GoogleOAuth extends OAuth { code, client_id: this.GOOGLE_OAUTH_CONFIG.client_id, client_secret: this.GOOGLE_OAUTH_CONFIG.client_secret, - redirect_uri: this.GOOGLE_OAUTH_CONFIG.redirect_uri, + redirect_uri: this.getRedirectUri(), }), /* eslint-enable @typescript-eslint/naming-convention */ method: 'POST', diff --git a/extension/js/common/browser/browser-msg.ts b/extension/js/common/browser/browser-msg.ts index 3321888a884..e133cf65e02 100644 --- a/extension/js/common/browser/browser-msg.ts +++ b/extension/js/common/browser/browser-msg.ts @@ -85,7 +85,7 @@ export namespace Bm { }; export type InMemoryStoreGet = { acctEmail: string; key: string }; export type GetApiAuthorization = { idToken: string }; - export type ReconnectAcctAuthPopup = { acctEmail: string; scopes?: string[]; screenDimensions: ScreenDimensions }; + export type ReconnectAcctAuthPopup = { acctEmail: string; scopes?: string[] }; export type ReconnectCustomIDPAcctAuthPopup = { acctEmail: string }; export type Ajax = { req: ApiAjax; resFmt: ResFmt }; export type AjaxProgress = { operationId: string; percent?: number; loaded: number; total: number; expectedTransferSize: number }; diff --git a/extension/js/common/notifications.ts b/extension/js/common/notifications.ts index a02c5092321..43b88c12dbe 100644 --- a/extension/js/common/notifications.ts +++ b/extension/js/common/notifications.ts @@ -81,7 +81,6 @@ export class Notifications { private reconnectAcctAuthPopup = async (acctEmail: string) => { const authRes = await BrowserMsg.send.bg.await.reconnectAcctAuthPopup({ acctEmail, - screenDimensions: Ui.getScreenDimensions(), }); if (authRes.result === 'Success') { this.show(`Connected successfully. You may need to reload the tab. Close`); diff --git a/extension/js/common/oauth2/oauth2.ts b/extension/js/common/oauth2/oauth2.ts deleted file mode 100644 index e34fe4d9934..00000000000 --- a/extension/js/common/oauth2/oauth2.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ - -import { Bm, BrowserMsg, ScreenDimensions } from '../browser/browser-msg.js'; -import { windowsCreate } from '../browser/chrome.js'; - -export class OAuth2 { - public static webAuthFlow = async (url: string, screenDimensions: ScreenDimensions): Promise => { - let adaptiveWidth = Math.floor(screenDimensions.width * 0.4); - if (adaptiveWidth < 550) { - adaptiveWidth = Math.min(550, Math.floor(screenDimensions.width * 0.9)); - } - const adaptiveHeight = Math.floor(screenDimensions.height * 0.9); - const leftOffset = Math.floor(screenDimensions.width / 2 - adaptiveWidth / 2 + screenDimensions.availLeft); - const topOffset = Math.floor(screenDimensions.height / 2 - adaptiveHeight / 2 + screenDimensions.availTop); - - const oauthWin = await windowsCreate({ - url, - left: leftOffset, - top: topOffset, - height: adaptiveHeight, - width: adaptiveWidth, - type: 'popup', - }); - - if (!oauthWin?.tabs?.length || !oauthWin.id) { - return { error: 'No oauth window returned after initiating it' }; - } - const tabId = oauthWin?.tabs?.[0].id; - return await new Promise(resolve => { - // need to use chrome.runtime.onMessage because BrowserMsg.addListener doesn't work - // In gmail page reconnect auth popup, it sends event to background page (BrowserMsg.send.bg.await.reconnectAcctAuthPopup) - // thefore BrowserMsg.addListener doesn't work - - chrome.runtime.onMessage.addListener((message: Bm.Raw) => { - if (message.name === 'auth_window_result') { - void chrome.tabs.remove(tabId!); // eslint-disable-line @typescript-eslint/no-non-null-assertion - resolve(message.data.bm as Bm.AuthWindowResult); - } - }); - - chrome.tabs.onRemoved.addListener(removedTabId => { - // Only reject error when auth result not successful - if (removedTabId === tabId) { - resolve({ error: 'Canceled by user' }); - } - }); - }); - }; - - public static finishAuth = (url: string) => { - BrowserMsg.send.authWindowResult('broadcast', { url }); - }; -} diff --git a/extension/js/common/oauth2/oauth2_finish.ts b/extension/js/common/oauth2/oauth2_finish.ts deleted file mode 100644 index d4b28087b40..00000000000 --- a/extension/js/common/oauth2/oauth2_finish.ts +++ /dev/null @@ -1,4 +0,0 @@ -/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ -import { OAuth2 } from './oauth2.js'; - -OAuth2.finishAuth(window.location.href); diff --git a/extension/js/common/oauth2/oauth2_inject.ts b/extension/js/common/oauth2/oauth2_inject.ts deleted file mode 100644 index b757ceb72a2..00000000000 --- a/extension/js/common/oauth2/oauth2_inject.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ - -// Declared win variable to avoid `Type 'string' is not assignable to type 'Location | (string & Location)'.ts(2322)` error -const win: Window = window; - -// Redirect back to the extension itself so that we have priveledged access again -// Need to send BrowserMsg event back to GoogleAuth - -const redirect = chrome.runtime.getURL('/chrome/elements/oauth2.htm'); - -win.location = redirect + win.location.search; diff --git a/extension/manifest.json b/extension/manifest.json index 1dacb7dea2a..3bde29d83cd 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -56,11 +56,6 @@ "/lib/emailjs/emailjs-mime-parser.js", "/js/content_scripts/webmail_bundle.js" ] - }, - { - "matches": ["https://www.google.com/robots.txt*"], - "js": ["/js/common/oauth2/oauth2_inject.js"], - "run_at": "document_start" } ], "background": { @@ -90,7 +85,6 @@ "/chrome/elements/add_pubkey.htm", "/chrome/elements/pgp_pubkey.htm", "/chrome/elements/backup.htm", - "/chrome/elements/oauth2.htm", "/js/common/core/feature-config-injector.js" ], "matches": ["https://mail.google.com/*", "https://accounts.google.com/*", "https://www.google.com/*"] diff --git a/test/source/mock/google/google-endpoints.ts b/test/source/mock/google/google-endpoints.ts index 0d9d69e5780..9493ba3b3ae 100644 --- a/test/source/mock/google/google-endpoints.ts +++ b/test/source/mock/google/google-endpoints.ts @@ -133,13 +133,13 @@ export const getMockGoogleEndpoints = (oauth: OauthMock, config: GoogleConfig | } else if (!proceed) { return oauth.renderText('redirect with proceed=true to continue'); } else { - return oauth.successResult(parsePort(req), login_hint, state, scope); + return oauth.successResult(login_hint, state, scope, redirect_uri); } } else if (client_id === OauthMock.customIDPClientId) { if (!proceed) { return oauth.renderText('redirect with proceed=true to continue'); } - return oauth.successResult(parsePort(req), login_hint, state, scope, redirect_uri); + return oauth.successResult(login_hint, state, scope, redirect_uri); } } throw new HttpClientErr(`Method not implemented for ${req.url}: ${req.method}`); diff --git a/test/source/mock/lib/api.ts b/test/source/mock/lib/api.ts index ee864d3a1eb..c44d31d8f6d 100644 --- a/test/source/mock/lib/api.ts +++ b/test/source/mock/lib/api.ts @@ -308,7 +308,7 @@ export class Api { private throttledResponse = async (response: http2.Http2ServerResponse, data: Buffer) => { // If google oauth2 or custom oauth login, then redirect to url - if (/^https:\/\/(google\.localhost:[0-9]+\/robots\.txt|[a-zA-Z0-9]+\.chromiumapp\.org)/.test(data.toString())) { + if (/^(https:\/\/[a-zA-Z0-9-]+\.(chromiumapp\.org|extensions\.mozilla\.org)|http:\/\/127\.0\.0\.1\/mozoauth2\/)/.test(data.toString())) { response.writeHead(302, { Location: data.toString() }); // eslint-disable-line @typescript-eslint/naming-convention } else { const chunkSize = 100 * 1024; diff --git a/test/source/mock/lib/oauth.ts b/test/source/mock/lib/oauth.ts index 491c5aef05d..9430c89a41b 100644 --- a/test/source/mock/lib/oauth.ts +++ b/test/source/mock/lib/oauth.ts @@ -37,7 +37,7 @@ export class OauthMock { }; // eslint-disable-next-line @typescript-eslint/naming-convention - public successResult = (port: string, acct: string, state: string, scope: string, redirect_uri?: string) => { + public successResult = (acct: string, state: string, scope: string, redirect_uri: string) => { const authCode = `mock-auth-code-${Str.sloppyRandom(4)}-${acct.replace(/[^a-z0-9]+/g, '')}`; const refreshToken = `mock-refresh-token-${Str.sloppyRandom(4)}-${acct.replace(/[^a-z0-9]+/g, '')}`; const accessToken = `mock-access-token-${Str.sloppyRandom(4)}-${acct.replace(/[^a-z0-9]+/g, '')}`; @@ -46,7 +46,7 @@ export class OauthMock { this.accessTokenByRefreshToken[refreshToken] = accessToken; this.acctByAccessToken[accessToken] = acct; this.scopesByAccessToken[accessToken] = `${this.scopesByAccessToken[accessToken] ?? ''} ${scope}`; - const url = new URL(redirect_uri ?? `https://google.localhost:${port}/robots.txt`); + const url = new URL(redirect_uri); url.searchParams.set('code', authCode); url.searchParams.set('scope', scope); // return invalid state for test.invalid.csrf@gmail.com to check invalid csrf login diff --git a/test/source/tests/setup.ts b/test/source/tests/setup.ts index f571256f370..5979d3b5143 100644 --- a/test/source/tests/setup.ts +++ b/test/source/tests/setup.ts @@ -100,6 +100,9 @@ export const defineSetupTests = (testVariant: TestVariant, testWithBrowser: Test test( 'settings > login > close oauth window > close popup', testWithBrowser(async (t, browser) => { + t.context.mockApi!.configProvider = new ConfigurationProvider({ + attester: { pubkeyLookup: {} }, + }); const settingsPage = await BrowserRecipe.openSettingsLoginButCloseOauthWindowBeforeGrantingPermission( t, browser, diff --git a/tooling/build-types-and-manifests.ts b/tooling/build-types-and-manifests.ts index b68d68aed61..97054225ffa 100644 --- a/tooling/build-types-and-manifests.ts +++ b/tooling/build-types-and-manifests.ts @@ -1,4 +1,6 @@ /* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ +/// + import { copySync } from 'fs-extra'; import { readFileSync, writeFileSync } from 'fs'; const MOCK_PORT = '[TEST_REPLACEABLE_MOCK_PORT]'; @@ -100,7 +102,7 @@ addManifest('chrome-enterprise', manifest => { 'https://flowcrypt.com/*', ]; for (const csDef of manifest.content_scripts ?? []) { - csDef.matches = csDef.matches?.filter(host => host === 'https://mail.google.com/*' || host === 'https://www.google.com/robots.txt*'); + csDef.matches = csDef.matches?.filter(host => host === 'https://mail.google.com/*'); } manifest.content_scripts = (manifest.content_scripts ?? []).filter(csDef => csDef.matches?.length); // remove empty defs if (!manifest.content_scripts.length) { @@ -172,10 +174,7 @@ const makeMockBuild = (sourceBuildType: string) => { edit(`${buildDir(mockBuildType)}/js/common/platform/catch.js`, editor); edit(`${buildDir(mockBuildType)}/js/content_scripts/webmail_bundle.js`, editor); edit(`${buildDir(mockBuildType)}/manifest.json`, code => - code - .replace(/https:\/\/mail\.google\.com/g, mockGmailPage) - .replace(/https:\/\/www\.google\.com/g, `https://google.localhost:${MOCK_PORT}`) - .replace(/https:\/\/\*\.google.com\/\*/, 'https://google.localhost/*') + code.replace(/https:\/\/mail\.google\.com/g, mockGmailPage).replace(/https:\/\/\*\.google.com\/\*/, 'https://google.localhost/*') ); }; @@ -198,9 +197,22 @@ const makeContentScriptTestsBuild = (sourceBuildType: string) => { ); }; +const makeConsumerLocalBuild = () => { + const localBuildType = 'chrome-consumer-local'; + const publicKey = + 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArV4mhxGkdt2FcJoWJhZrzNUftI0S7i55jMooL+FLjRSyK1hh6G4so7KLYhY/Tc327luMwWFkCAcdsamjbhOfJneBMZ0IT7swAS3zsC87vLE5YeWO2CX02FvHjgXm60T1Fk4gJh/zqCp4OLjyawoJRyuovvVN0LwH4j6DjHn3nodl2YeY+4K7jzFGHj6+68tlok9BtI6k8tntIbnFToRr9gVR85UT+W8rKXqx20Kne14k2my5fjrGZjEpK74YU6QNlKRzprVqpNEE989sxNk1tL6xKYgoDO9m8JZtuFKFsoQY/fV8xhNkXB19KLVJEW8aqPG/0ZTTMg9llZI8c6Yx+QIDAQAB'; + copySync(buildDir(CHROME_CONSUMER), buildDir(localBuildType)); + edit(`${buildDir(localBuildType)}/manifest.json`, code => { + const manifest = JSON.parse(code) as chrome.runtime.ManifestV3; + manifest.key = publicKey; + return JSON.stringify(manifest, undefined, 2); + }); +}; + updateEnterpriseBuild(); makeMockBuild(CHROME_CONSUMER); makeMockBuild(CHROME_ENTERPRISE); makeLocalFesBuild(CHROME_ENTERPRISE); +makeConsumerLocalBuild(); makeContentScriptTestsBuild('chrome-consumer-mock'); // makeContentScriptTestsBuild('firefox-consumer'); // for manual testing of content script in Firefox