diff --git a/app/scripts/app-init.js b/app/scripts/app-init.js index 9da5414ff015..7abad9ad9957 100644 --- a/app/scripts/app-init.js +++ b/app/scripts/app-init.js @@ -6,6 +6,11 @@ let scriptsLoadInitiated = false; const { chrome } = globalThis; const testMode = process.env.IN_TEST; +/** + * @type {globalThis.stateHooks} + */ +globalThis.stateHooks = globalThis.stateHooks || {}; + const loadTimeLogs = []; // eslint-disable-next-line import/unambiguous function tryImport(...fileNames) { @@ -184,12 +189,32 @@ const registerInPageContentScript = async () => { } }; -chrome.runtime.onInstalled.addListener(function (details) { +/** + * `onInstalled` event handler. + * + * On MV3 builds we must listen for this event in `app-init`, otherwise we found that the listener + * is never called. + * For MV2 builds, the listener is added in `background.js` instead. + * + * @param {chrome.runtime.InstalledDetails} details - Event details. + */ +function onInstalledListener(details) { if (details.reason === 'install') { - chrome.storage.session.set({ isFirstTimeInstall: true }); - } else if (details.reason === 'update') { - chrome.storage.session.set({ isFirstTimeInstall: false }); + // This condition is for when `background.js` was loaded before the `onInstalled` listener was + // called. + if (globalThis.stateHooks.metamaskTriggerOnInstall) { + globalThis.stateHooks.metamaskTriggerOnInstall(); + // Delete just to clean up global namespace + delete globalThis.stateHooks.metamaskTriggerOnInstall; + // This condition is for when the `onInstalled` listener in `app-init` was called before + // `background.js` was loaded. + } else { + globalThis.stateHooks.metamaskWasJustInstalled = true; + } + chrome.runtime.onInstalled.removeListener(onInstalledListener); } -}); +} + +chrome.runtime.onInstalled.addListener(onInstalledListener); registerInPageContentScript(); diff --git a/app/scripts/background.js b/app/scripts/background.js index 96f16cd95a4e..4ab0b6ff486f 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -141,22 +141,35 @@ const PHISHING_WARNING_PAGE_TIMEOUT = ONE_SECOND_IN_MILLISECONDS; // Event emitter for state persistence export const statePersistenceEvents = new EventEmitter(); -if (isFirefox) { - browser.runtime.onInstalled.addListener(function (details) { - if (details.reason === 'install') { - browser.storage.session.set({ isFirstTimeInstall: true }); - } else if (details.reason === 'update') { - browser.storage.session.set({ isFirstTimeInstall: false }); - } - }); -} else if (!isManifestV3) { - browser.runtime.onInstalled.addListener(function (details) { +if (!isManifestV3) { + /** + * `onInstalled` event handler. + * + * On MV3 builds we must listen for this event in `app-init`, otherwise we found that the listener + * is never called. + * There is no `app-init` file on MV2 builds, so we add a listener here instead. + * + * @param {import('webextension-polyfill').Runtime.OnInstalledDetailsType} details - Event details. + */ + const onInstalledListener = (details) => { if (details.reason === 'install') { - global.sessionStorage.setItem('isFirstTimeInstall', true); - } else if (details.reason === 'update') { - global.sessionStorage.setItem('isFirstTimeInstall', false); + onInstall(); + browser.runtime.onInstalled.removeListener(onInstalledListener); } - }); + }; + + browser.runtime.onInstalled.addListener(onInstalledListener); + + // This condition is for when the `onInstalled` listener in `app-init` was called before + // `background.js` was loaded. +} else if (globalThis.stateHooks.metamaskWasJustInstalled) { + onInstall(); + // Delete just to clean up global namespace + delete globalThis.stateHooks.metamaskWasJustInstalled; + // This condition is for when `background.js` was loaded before the `onInstalled` listener was + // called. +} else { + globalThis.stateHooks.metamaskTriggerOnInstall = () => onInstall(); } /** @@ -505,12 +518,6 @@ async function initialize() { await sendReadyMessageToTabs(); log.info('MetaMask initialization complete.'); - if (isManifestV3 || isFirefox) { - browser.storage.session.set({ isFirstTimeInstall: false }); - } else { - global.sessionStorage.setItem('isFirstTimeInstall', false); - } - resolveInitialization(); } catch (error) { rejectInitialization(error); @@ -1286,24 +1293,15 @@ const addAppInstalledEvent = () => { }, 500); }; -// On first install, open a new tab with MetaMask -async function onInstall() { - const sessionData = - isManifestV3 || isFirefox - ? await browser.storage.session.get(['isFirstTimeInstall']) - : await global.sessionStorage.getItem('isFirstTimeInstall'); - - const isFirstTimeInstall = sessionData?.isFirstTimeInstall; - - if (process.env.IN_TEST) { - addAppInstalledEvent(); - } else if (!isFirstTimeInstall && !process.env.METAMASK_DEBUG) { - // If storeAlreadyExisted is true then this is a fresh installation - // and an app installed event should be tracked. - addAppInstalledEvent(); +/** + * Trigger actions that should happen only upon initial install (e.g. open tab for onboarding). + */ +function onInstall() { + log.debug('First install detected'); + addAppInstalledEvent(); + if (!process.env.IN_TEST && !process.env.METAMASK_DEBUG) { platform.openExtensionInBrowser(); } - onNavigateToTab(); } function onNavigateToTab() { @@ -1334,7 +1332,7 @@ function setupSentryGetStateGlobal(store) { } async function initBackground() { - await onInstall(); + onNavigateToTab(); try { await initialize(); if (process.env.IN_TEST) { diff --git a/types/global.d.ts b/types/global.d.ts index f18db580d6eb..dfdadbe8fc65 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -250,6 +250,16 @@ type StateHooks = { metamaskGetState?: () => Promise; throwTestBackgroundError?: (msg?: string) => Promise; throwTestError?: (msg?: string) => void; + /** + * This is set in `app-init.js` to communicate that MetaMask was just installed, and is read in + * `background.js`. + */ + metamaskWasJustInstalled?: boolean; + /** + * This is set in `background.js` so that `app-init.js` can trigger "on install" actions when + * the `onInstalled` listener is called. + */ + metamaskTriggerOnInstall?: () => void; }; export declare global {