mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
MV3: Re-activate service worker and reconnect UI streams (#14781)
This commit is contained in:
parent
5d828be611
commit
bca9a61d6b
@ -1,8 +1,9 @@
|
|||||||
|
/* global chrome */
|
||||||
// This file is used only for manifest version 3
|
// This file is used only for manifest version 3
|
||||||
|
|
||||||
// Represents if importAllScripts has been run
|
// Represents if importAllScripts has been run
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
let scriptsLoaded = false;
|
let scriptsLoadInitiated = false;
|
||||||
|
|
||||||
// Variable testMode is set to true when preparing test build.
|
// Variable testMode is set to true when preparing test build.
|
||||||
// This helps in changing service worker execution in test environment.
|
// This helps in changing service worker execution in test environment.
|
||||||
@ -35,10 +36,10 @@ function tryImport(...fileNames) {
|
|||||||
|
|
||||||
function importAllScripts() {
|
function importAllScripts() {
|
||||||
// Bail if we've already imported scripts
|
// Bail if we've already imported scripts
|
||||||
if (scriptsLoaded) {
|
if (scriptsLoadInitiated) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
scriptsLoadInitiated = true;
|
||||||
const files = [];
|
const files = [];
|
||||||
|
|
||||||
// In testMode individual files are imported, this is to help capture load time stats
|
// In testMode individual files are imported, this is to help capture load time stats
|
||||||
@ -69,9 +70,6 @@ function importAllScripts() {
|
|||||||
loadFile('./runtime-cjs.js');
|
loadFile('./runtime-cjs.js');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark scripts as loaded
|
|
||||||
scriptsLoaded = true;
|
|
||||||
|
|
||||||
const fileList = [
|
const fileList = [
|
||||||
// The list of files is injected at build time by replacing comment below with comma separated strings of file names
|
// The list of files is injected at build time by replacing comment below with comma separated strings of file names
|
||||||
// https://github.com/MetaMask/metamask-extension/blob/496d9d81c3367931031edc11402552690c771acf/development/build/scripts.js#L406
|
// https://github.com/MetaMask/metamask-extension/blob/496d9d81c3367931031edc11402552690c771acf/development/build/scripts.js#L406
|
||||||
@ -111,14 +109,20 @@ function importAllScripts() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ref: https://stackoverflow.com/questions/66406672/chrome-extension-mv3-modularize-service-worker-js-file
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
self.addEventListener('install', importAllScripts);
|
self.addEventListener('install', importAllScripts);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Message event listener below loads script if they are no longer available.
|
* A keepalive message listener to prevent Service Worker getting shut down due to inactivity.
|
||||||
|
* UI sends the message periodically, in a setInterval.
|
||||||
|
* Chrome will revive the service worker if it was shut down, whenever a new message is sent, but only if a listener was defined here.
|
||||||
|
*
|
||||||
* chrome below needs to be replaced by cross-browser object,
|
* chrome below needs to be replaced by cross-browser object,
|
||||||
* but there is issue in importing webextension-polyfill into service worker.
|
* but there is issue in importing webextension-polyfill into service worker.
|
||||||
* chrome does seems to work in at-least all chromium based browsers
|
* chrome does seems to work in at-least all chromium based browsers
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line no-undef
|
chrome.runtime.onMessage.addListener(() => {
|
||||||
chrome.runtime.onMessage.addListener(importAllScripts);
|
importAllScripts();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
@ -11,7 +11,7 @@ import Eth from 'ethjs';
|
|||||||
import EthQuery from 'eth-query';
|
import EthQuery from 'eth-query';
|
||||||
import StreamProvider from 'web3-stream-provider';
|
import StreamProvider from 'web3-stream-provider';
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
import launchMetaMaskUi from '../../ui';
|
import launchMetaMaskUi, { updateBackgroundConnection } from '../../ui';
|
||||||
import {
|
import {
|
||||||
ENVIRONMENT_TYPE_FULLSCREEN,
|
ENVIRONMENT_TYPE_FULLSCREEN,
|
||||||
ENVIRONMENT_TYPE_POPUP,
|
ENVIRONMENT_TYPE_POPUP,
|
||||||
@ -24,34 +24,37 @@ import { setupMultiplex } from './lib/stream-utils';
|
|||||||
import { getEnvironmentType } from './lib/util';
|
import { getEnvironmentType } from './lib/util';
|
||||||
import metaRPCClientFactory from './lib/metaRPCClientFactory';
|
import metaRPCClientFactory from './lib/metaRPCClientFactory';
|
||||||
|
|
||||||
|
const container = document.getElementById('app-content');
|
||||||
|
|
||||||
|
const WORKER_KEEP_ALIVE_INTERVAL = 1000;
|
||||||
|
const WORKER_KEEP_ALIVE_MESSAGE = 'WORKER_KEEP_ALIVE_MESSAGE';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
if (isManifestV3()) {
|
||||||
|
setInterval(() => {
|
||||||
|
browser.runtime.sendMessage({ name: WORKER_KEEP_ALIVE_MESSAGE });
|
||||||
|
}, WORKER_KEEP_ALIVE_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
start().catch(log.error);
|
start().catch(log.error);
|
||||||
|
|
||||||
async function start() {
|
async function start() {
|
||||||
async function displayCriticalError(container, err, metamaskState) {
|
|
||||||
const html = await getErrorHtml(SUPPORT_LINK, metamaskState);
|
|
||||||
|
|
||||||
container.innerHTML = html;
|
|
||||||
|
|
||||||
const button = document.getElementById('critical-error-button');
|
|
||||||
|
|
||||||
button.addEventListener('click', (_) => {
|
|
||||||
browser.runtime.reload();
|
|
||||||
});
|
|
||||||
|
|
||||||
log.error(err.stack);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create platform global
|
// create platform global
|
||||||
global.platform = new ExtensionPlatform();
|
global.platform = new ExtensionPlatform();
|
||||||
|
|
||||||
// identify window type (popup, notification)
|
// identify window type (popup, notification)
|
||||||
const windowType = getEnvironmentType();
|
const windowType = getEnvironmentType();
|
||||||
|
|
||||||
// setup stream to background
|
let isUIInitialised = false;
|
||||||
const extensionPort = browser.runtime.connect({ name: windowType });
|
|
||||||
|
|
||||||
const connectionStream = new PortStream(extensionPort);
|
// setup stream to background
|
||||||
|
let extensionPort = browser.runtime.connect({ name: windowType });
|
||||||
|
let connectionStream = new PortStream(extensionPort);
|
||||||
|
|
||||||
const activeTab = await queryCurrentActiveTab(windowType);
|
const activeTab = await queryCurrentActiveTab(windowType);
|
||||||
|
|
||||||
@ -60,23 +63,52 @@ async function start() {
|
|||||||
* Code below ensures that UI is rendered only after background is ready.
|
* Code below ensures that UI is rendered only after background is ready.
|
||||||
*/
|
*/
|
||||||
if (isManifestV3()) {
|
if (isManifestV3()) {
|
||||||
extensionPort.onMessage.addListener((message) => {
|
/*
|
||||||
|
* 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 CONNECTION_READY message is received thus background is ready.
|
||||||
|
* In case the UI is already rendered, only update the streams.
|
||||||
|
*/
|
||||||
|
const messageListener = (message) => {
|
||||||
if (message?.name === 'CONNECTION_READY') {
|
if (message?.name === 'CONNECTION_READY') {
|
||||||
initializeUiWithTab(activeTab);
|
if (isUIInitialised) {
|
||||||
|
updateUiStreams();
|
||||||
|
} else {
|
||||||
|
initializeUiWithTab(activeTab);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
// resetExtensionStreamAndListeners takes care to remove listeners from closed streams
|
||||||
|
// it also creates new streams and attach 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 });
|
||||||
|
|
||||||
|
extensionPort = browser.runtime.connect({ name: windowType });
|
||||||
|
connectionStream = new PortStream(extensionPort);
|
||||||
|
extensionPort.onMessage.addListener(messageListener);
|
||||||
|
extensionPort.onDisconnect.addListener(resetExtensionStreamAndListeners);
|
||||||
|
};
|
||||||
|
|
||||||
|
extensionPort.onMessage.addListener(messageListener);
|
||||||
|
extensionPort.onDisconnect.addListener(resetExtensionStreamAndListeners);
|
||||||
} else {
|
} else {
|
||||||
initializeUiWithTab(activeTab);
|
initializeUiWithTab(activeTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeUiWithTab(tab) {
|
function initializeUiWithTab(tab) {
|
||||||
const container = document.getElementById('app-content');
|
initializeUi(tab, connectionStream, (err, store) => {
|
||||||
initializeUi(tab, container, connectionStream, (err, store) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
// if there's an error, store will be = metamaskState
|
// if there's an error, store will be = metamaskState
|
||||||
displayCriticalError(container, err, store);
|
displayCriticalError(err, store);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
isUIInitialised = true;
|
||||||
|
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
const { metamask: { completedOnboarding } = {} } = state;
|
const { metamask: { completedOnboarding } = {} } = state;
|
||||||
@ -86,6 +118,18 @@ async function start() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to update new backgroundConnection in the UI
|
||||||
|
function updateUiStreams() {
|
||||||
|
connectToAccountManager(connectionStream, (err, backgroundConnection) => {
|
||||||
|
if (err) {
|
||||||
|
displayCriticalError(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBackgroundConnection(backgroundConnection);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function queryCurrentActiveTab(windowType) {
|
async function queryCurrentActiveTab(windowType) {
|
||||||
@ -112,7 +156,7 @@ async function queryCurrentActiveTab(windowType) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeUi(activeTab, container, connectionStream, cb) {
|
function initializeUi(activeTab, connectionStream, cb) {
|
||||||
connectToAccountManager(connectionStream, (err, backgroundConnection) => {
|
connectToAccountManager(connectionStream, (err, backgroundConnection) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
cb(err, null);
|
cb(err, null);
|
||||||
@ -130,6 +174,21 @@ function initializeUi(activeTab, container, connectionStream, cb) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function displayCriticalError(err, metamaskState) {
|
||||||
|
const html = await getErrorHtml(SUPPORT_LINK, metamaskState);
|
||||||
|
|
||||||
|
container.innerHTML = html;
|
||||||
|
|
||||||
|
const button = document.getElementById('critical-error-button');
|
||||||
|
|
||||||
|
button.addEventListener('click', (_) => {
|
||||||
|
browser.runtime.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
log.error(err.stack);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establishes a connection to the background and a Web3 provider
|
* Establishes a connection to the background and a Web3 provider
|
||||||
*
|
*
|
||||||
|
36
ui/index.js
36
ui/index.js
@ -31,9 +31,30 @@ import txHelper from './helpers/utils/tx-helper';
|
|||||||
|
|
||||||
log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn');
|
log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn');
|
||||||
|
|
||||||
|
let reduxStore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to update backgroundConnection object use by UI
|
||||||
|
*
|
||||||
|
* @param backgroundConnection - connection object to background
|
||||||
|
*/
|
||||||
|
export const updateBackgroundConnection = (backgroundConnection) => {
|
||||||
|
actions._setBackgroundConnection(backgroundConnection);
|
||||||
|
backgroundConnection.onNotification((data) => {
|
||||||
|
if (data.method === 'sendUpdate') {
|
||||||
|
reduxStore.dispatch(actions.updateMetamaskState(data.params[0]));
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`Internal JSON-RPC Notification Not Handled:\n\n ${JSON.stringify(
|
||||||
|
data,
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export default function launchMetamaskUi(opts, cb) {
|
export default function launchMetamaskUi(opts, cb) {
|
||||||
const { backgroundConnection } = opts;
|
const { backgroundConnection } = opts;
|
||||||
actions._setBackgroundConnection(backgroundConnection);
|
|
||||||
// check if we are unlocked first
|
// check if we are unlocked first
|
||||||
backgroundConnection.getState(function (err, metamaskState) {
|
backgroundConnection.getState(function (err, metamaskState) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -117,6 +138,7 @@ async function startApp(metamaskState, backgroundConnection, opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const store = configureStore(draftInitialState);
|
const store = configureStore(draftInitialState);
|
||||||
|
reduxStore = store;
|
||||||
|
|
||||||
// if unconfirmed txs, start on txConf page
|
// if unconfirmed txs, start on txConf page
|
||||||
const unapprovedTxsAll = txHelper(
|
const unapprovedTxsAll = txHelper(
|
||||||
@ -138,17 +160,7 @@ async function startApp(metamaskState, backgroundConnection, opts) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
backgroundConnection.onNotification((data) => {
|
updateBackgroundConnection(backgroundConnection);
|
||||||
if (data.method === 'sendUpdate') {
|
|
||||||
store.dispatch(actions.updateMetamaskState(data.params[0]));
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
`Internal JSON-RPC Notification Not Handled:\n\n ${JSON.stringify(
|
|
||||||
data,
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// global metamask api - used by tooling
|
// global metamask api - used by tooling
|
||||||
global.metamask = {
|
global.metamask = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user