Skip to content
Merged
35 changes: 30 additions & 5 deletions app/scripts/app-init.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
72 changes: 35 additions & 37 deletions app/scripts/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

/**
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Copy link
Member Author

@Gudahtt Gudahtt Mar 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why this function was here, it's not really related to the install event. This was being called irrespective of whether this session was the first after install as well, which is confusing.

I've moved it down to initBackground, so it should get called as part of initialization the same way it always was before.

}

function onNavigateToTab() {
Expand Down Expand Up @@ -1334,7 +1332,7 @@ function setupSentryGetStateGlobal(store) {
}

async function initBackground() {
await onInstall();
onNavigateToTab();
try {
await initialize();
if (process.env.IN_TEST) {
Expand Down
10 changes: 10 additions & 0 deletions types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,16 @@ type StateHooks = {
metamaskGetState?: () => Promise<any>;
throwTestBackgroundError?: (msg?: string) => Promise<void>;
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 {
Expand Down
Loading