diff --git a/app/scripts/background.js b/app/scripts/background.js index ec2aff120..736e60603 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -494,7 +494,7 @@ function setupController(initState, initLangCode, remoteSourcePort) { // Message below if captured by UI code in app/scripts/ui.js which will trigger UI initialisation // This ensures that UI is initialised only after background is ready // It fixes the issue of blank screen coming when extension is loaded, the issue is very frequent in MV3 - remotePort.postMessage({ name: 'CONNECTION_READY' }); + remotePort.postMessage({ name: EXTENSION_MESSAGES.CONNECTION_READY }); // If we get a WORKER_KEEP_ALIVE message, we respond with an ACK remotePort.onMessage.addListener((message) => { diff --git a/app/scripts/lib/stream-utils.js b/app/scripts/lib/stream-utils.js index cac30c066..a9c42df7a 100644 --- a/app/scripts/lib/stream-utils.js +++ b/app/scripts/lib/stream-utils.js @@ -1,6 +1,8 @@ import ObjectMultiplex from 'obj-multiplex'; import pump from 'pump'; +import { EXTENSION_MESSAGES } from '../../../shared/constants/app'; + /** * Sets up stream multiplexing for the given stream * @@ -14,7 +16,7 @@ export function setupMultiplex(connectionStream) { * We need to tell the multiplexer to ignore them, else we get the " orphaned data for stream " warnings * https://github.com/MetaMask/object-multiplex/blob/280385401de84f57ef57054d92cfeb8361ef2680/src/ObjectMultiplex.ts#L63 */ - mux.ignoreStream('CONNECTION_READY'); + mux.ignoreStream(EXTENSION_MESSAGES.CONNECTION_READY); mux.ignoreStream('ACK_KEEP_ALIVE_MESSAGE'); mux.ignoreStream('WORKER_KEEP_ALIVE_MESSAGE'); pump(connectionStream, mux, connectionStream, (err) => { diff --git a/app/scripts/ui.js b/app/scripts/ui.js index 13516becd..2852519a9 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -15,6 +15,7 @@ import launchMetaMaskUi, { updateBackgroundConnection } from '../../ui'; import { ENVIRONMENT_TYPE_FULLSCREEN, ENVIRONMENT_TYPE_POPUP, + EXTENSION_MESSAGES, PLATFORM_FIREFOX, } from '../../shared/constants/app'; import { isManifestV3 } from '../../shared/modules/mv3.utils'; @@ -29,8 +30,8 @@ const container = document.getElementById('app-content'); const ONE_SECOND_IN_MILLISECONDS = 1_000; -const WORKER_KEEP_ALIVE_INTERVAL = ONE_SECOND_IN_MILLISECONDS; // Service Worker Keep Alive Message Constants +const WORKER_KEEP_ALIVE_INTERVAL = ONE_SECOND_IN_MILLISECONDS; const WORKER_KEEP_ALIVE_MESSAGE = 'WORKER_KEEP_ALIVE_MESSAGE'; const ACK_KEEP_ALIVE_WAIT_TIME = 60_000; // 1 minute const ACK_KEEP_ALIVE_MESSAGE = 'ACK_KEEP_ALIVE_MESSAGE'; @@ -40,33 +41,35 @@ const PHISHING_WARNING_PAGE_TIMEOUT = ONE_SECOND_IN_MILLISECONDS; const PHISHING_WARNING_SW_STORAGE_KEY = 'phishing-warning-sw-registered'; -let lastMessageRecievedTimestamp = Date.now(); +let lastMessageReceivedTimestamp = Date.now(); + +let extensionPort; +let ackTimeoutToDisplayError; + /* * As long as UI is open it will keep sending messages to service worker * In service worker as this message is received * if service worker is inactive it is reactivated and script re-loaded * Time has been kept to 1000ms but can be reduced for even faster re-activation of service worker */ -let extensionPort; -let timeoutHandle; - if (isManifestV3) { // Checking for SW aliveness (or stuckness) flow // 1. Check if we have an extensionPort, if yes // 2a. Send a keep alive message to the background via extensionPort // 2b. Add a listener to it (if not already added) // 3a. Set a timeout to check if we have received an ACK from background - // 3b. If we have not received an ACK within Xs, we know the background is stuck or dead + // 3b. If we have not received an ACK within ACK_KEEP_ALIVE_WAIT_TIME, + // we know the background is stuck or dead // 4. If we recieve an ACK_KEEP_ALIVE_MESSAGE from the service worker, we know it is alive const ackKeepAliveListener = (message) => { if (message.name === ACK_KEEP_ALIVE_MESSAGE) { - lastMessageRecievedTimestamp = Date.now(); - clearTimeout(timeoutHandle); + lastMessageReceivedTimestamp = Date.now(); + clearTimeout(ackTimeoutToDisplayError); } }; - const handle = setInterval(() => { + const keepAliveInterval = setInterval(() => { browser.runtime.sendMessage({ name: WORKER_KEEP_ALIVE_MESSAGE }); if (extensionPort !== null && extensionPort !== undefined) { @@ -77,12 +80,12 @@ if (isManifestV3) { } } - timeoutHandle = setTimeout(() => { + ackTimeoutToDisplayError = setTimeout(() => { if ( - Date.now() - lastMessageRecievedTimestamp > + Date.now() - lastMessageReceivedTimestamp > ACK_KEEP_ALIVE_WAIT_TIME ) { - clearInterval(handle); + clearInterval(keepAliveInterval); displayCriticalError( 'somethingIsWrong', new Error("Something's gone wrong. Try reloading the page."), @@ -110,10 +113,7 @@ async function start() { const activeTab = await queryCurrentActiveTab(windowType); let loadPhishingWarningPage; - /** - * In case of MV3 the issue of blank screen was very frequent, it is caused by UI initialising before background is ready to send state. - * Code below ensures that UI is rendered only after background is ready. - */ + if (isManifestV3) { /* * In case of MV3 the issue of blank screen was very frequent, it is caused by UI initialising before background is ready to send state. @@ -121,7 +121,7 @@ async function start() { * In case the UI is already rendered, only update the streams. */ const messageListener = async (message) => { - if (message?.name === 'CONNECTION_READY') { + if (message?.name === EXTENSION_MESSAGES.CONNECTION_READY) { if (isUIInitialised) { // Currently when service worker is revived we create new streams // in later version we might try to improve it by reviving same streams. @@ -147,10 +147,8 @@ async function start() { * worker has been registered, so that the warning page works offline. */ loadPhishingWarningPage = async function () { - const currentPlatform = getPlatform(); - - // Check session storage for whether we've already initalized the phishing warning - // service worker this browser session and do not attempt to re-initialize if so. + // Check session storage for whether we've already initialized the phishing warning + // service worker in this browser session and do not attempt to re-initialize if so. const phishingSWMemoryFetch = await browser.storage.session.get( PHISHING_WARNING_SW_STORAGE_KEY, ); @@ -159,7 +157,9 @@ async function start() { return; } + const currentPlatform = getPlatform(); let iframe; + try { const extensionStartupPhishingPageUrl = new URL( process.env.PHISHING_WARNING_PAGE_URL, @@ -195,7 +195,9 @@ async function start() { () => deferredReject(new PhishingWarningPageTimeoutError()), PHISHING_WARNING_PAGE_TIMEOUT, ); + await loadComplete; + // store a flag in sessions storage that we've already loaded the service worker // and don't need to try again if (currentPlatform === PLATFORM_FIREFOX) { @@ -225,12 +227,13 @@ async function start() { }; // resetExtensionStreamAndListeners takes care to remove listeners from closed streams - // it also creates new streams and attach event listeners to them + // it also creates new streams and attaches event listeners to them const resetExtensionStreamAndListeners = () => { extensionPort.onMessage.removeListener(messageListener); extensionPort.onDisconnect.removeListener( resetExtensionStreamAndListeners, ); + // message below will try to activate service worker // in MV3 is likely that reason of stream closing is service worker going in-active browser.runtime.sendMessage({ name: WORKER_KEEP_ALIVE_MESSAGE }); diff --git a/shared/constants/app.ts b/shared/constants/app.ts index 8ebf53cba..017e4a347 100644 --- a/shared/constants/app.ts +++ b/shared/constants/app.ts @@ -61,6 +61,7 @@ export const MESSAGE_TYPE = { * Custom messages to send and be received by the extension */ export const EXTENSION_MESSAGES = { + CONNECTION_READY: 'CONNECTION_READY', READY: 'METAMASK_EXTENSION_READY', } as const;