2021-02-04 19:15:23 +01:00
|
|
|
import EventEmitter from 'events';
|
|
|
|
import { ObservableStore } from '@metamask/obs-store';
|
2021-02-09 00:22:30 +01:00
|
|
|
import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
|
2021-06-10 21:27:03 +02:00
|
|
|
import { MINUTE } from '../../../shared/constants/time';
|
2022-09-24 02:39:25 +02:00
|
|
|
import { AUTO_LOCK_TIMEOUT_ALARM } from '../../../shared/constants/alarms';
|
|
|
|
import { isManifestV3 } from '../../../shared/modules/mv3.utils';
|
2022-11-16 18:41:15 +01:00
|
|
|
import { isBeta } from '../../../ui/helpers/utils/build-types';
|
2019-05-13 18:16:09 +02:00
|
|
|
|
2020-05-06 00:19:38 +02:00
|
|
|
export default class AppStateController extends EventEmitter {
|
2019-05-13 18:16:09 +02:00
|
|
|
/**
|
2022-07-27 15:28:05 +02:00
|
|
|
* @param {object} opts
|
2019-05-13 18:16:09 +02:00
|
|
|
*/
|
2020-11-03 00:41:28 +01:00
|
|
|
constructor(opts = {}) {
|
2020-03-23 17:25:55 +01:00
|
|
|
const {
|
|
|
|
addUnlockListener,
|
|
|
|
isUnlocked,
|
|
|
|
initState,
|
|
|
|
onInactiveTimeout,
|
2020-05-05 16:03:21 +02:00
|
|
|
showUnlockRequest,
|
2020-03-23 17:25:55 +01:00
|
|
|
preferencesStore,
|
2021-11-23 18:28:39 +01:00
|
|
|
qrHardwareStore,
|
2021-02-04 19:15:23 +01:00
|
|
|
} = opts;
|
|
|
|
super();
|
2020-03-23 17:25:55 +01:00
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
this.onInactiveTimeout = onInactiveTimeout || (() => undefined);
|
2020-08-19 18:27:05 +02:00
|
|
|
this.store = new ObservableStore({
|
2019-05-13 18:16:09 +02:00
|
|
|
timeoutMinutes: 0,
|
2020-04-22 19:11:36 +02:00
|
|
|
connectedStatusPopoverHasBeenShown: true,
|
2020-11-03 00:41:28 +01:00
|
|
|
defaultHomeActiveTabName: null,
|
2021-04-30 17:28:07 +02:00
|
|
|
browserEnvironment: {},
|
2021-08-04 23:53:13 +02:00
|
|
|
popupGasPollTokens: [],
|
|
|
|
notificationGasPollTokens: [],
|
|
|
|
fullScreenGasPollTokens: [],
|
2021-06-05 08:33:58 +02:00
|
|
|
recoveryPhraseReminderHasBeenShown: false,
|
|
|
|
recoveryPhraseReminderLastShown: new Date().getTime(),
|
2021-12-14 00:41:10 +01:00
|
|
|
collectiblesDetectionNoticeDismissed: false,
|
2021-11-16 23:22:01 +01:00
|
|
|
showTestnetMessageInDropdown: true,
|
2022-09-13 15:41:58 +02:00
|
|
|
showPortfolioTooltip: true,
|
2022-11-16 18:41:15 +01:00
|
|
|
showBetaHeader: isBeta(),
|
2021-11-30 15:28:28 +01:00
|
|
|
trezorModel: null,
|
2020-11-03 00:41:28 +01:00
|
|
|
...initState,
|
2021-11-23 18:28:39 +01:00
|
|
|
qrHardware: {},
|
2022-01-27 18:26:33 +01:00
|
|
|
collectiblesDropdownState: {},
|
2022-08-23 17:04:07 +02:00
|
|
|
usedNetworks: {
|
|
|
|
'0x1': true,
|
|
|
|
'0x5': true,
|
|
|
|
'0x539': true,
|
|
|
|
},
|
2021-02-04 19:15:23 +01:00
|
|
|
});
|
|
|
|
this.timer = null;
|
2019-05-13 18:16:09 +02:00
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
this.isUnlocked = isUnlocked;
|
|
|
|
this.waitingForUnlock = [];
|
|
|
|
addUnlockListener(this.handleUnlock.bind(this));
|
2020-03-23 17:25:55 +01:00
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
this._showUnlockRequest = showUnlockRequest;
|
2020-05-05 16:03:21 +02:00
|
|
|
|
2020-05-06 01:30:50 +02:00
|
|
|
preferencesStore.subscribe(({ preferences }) => {
|
2021-02-04 19:15:23 +01:00
|
|
|
const currentState = this.store.getState();
|
2020-05-06 01:30:50 +02:00
|
|
|
if (currentState.timeoutMinutes !== preferences.autoLockTimeLimit) {
|
2021-02-04 19:15:23 +01:00
|
|
|
this._setInactiveTimeout(preferences.autoLockTimeLimit);
|
2020-05-06 01:30:50 +02:00
|
|
|
}
|
2021-02-04 19:15:23 +01:00
|
|
|
});
|
2019-05-13 18:16:09 +02:00
|
|
|
|
2021-11-23 18:28:39 +01:00
|
|
|
qrHardwareStore.subscribe((state) => {
|
|
|
|
this.store.updateState({ qrHardware: state });
|
|
|
|
});
|
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
const { preferences } = preferencesStore.getState();
|
|
|
|
this._setInactiveTimeout(preferences.autoLockTimeLimit);
|
2019-05-13 18:16:09 +02:00
|
|
|
}
|
|
|
|
|
2020-03-23 17:25:55 +01:00
|
|
|
/**
|
|
|
|
* Get a Promise that resolves when the extension is unlocked.
|
|
|
|
* This Promise will never reject.
|
|
|
|
*
|
2020-05-05 16:03:21 +02:00
|
|
|
* @param {boolean} shouldShowUnlockRequest - Whether the extension notification
|
|
|
|
* popup should be opened.
|
2020-03-23 17:25:55 +01:00
|
|
|
* @returns {Promise<void>} A promise that resolves when the extension is
|
|
|
|
* unlocked, or immediately if the extension is already unlocked.
|
|
|
|
*/
|
2020-11-03 00:41:28 +01:00
|
|
|
getUnlockPromise(shouldShowUnlockRequest) {
|
2020-03-23 17:25:55 +01:00
|
|
|
return new Promise((resolve) => {
|
|
|
|
if (this.isUnlocked()) {
|
2021-02-04 19:15:23 +01:00
|
|
|
resolve();
|
2020-03-23 17:25:55 +01:00
|
|
|
} else {
|
2021-02-04 19:15:23 +01:00
|
|
|
this.waitForUnlock(resolve, shouldShowUnlockRequest);
|
2020-03-23 17:25:55 +01:00
|
|
|
}
|
2021-02-04 19:15:23 +01:00
|
|
|
});
|
2020-03-23 17:25:55 +01:00
|
|
|
}
|
|
|
|
|
2020-05-05 16:03:21 +02:00
|
|
|
/**
|
|
|
|
* Adds a Promise's resolve function to the waitingForUnlock queue.
|
|
|
|
* Also opens the extension popup if specified.
|
|
|
|
*
|
|
|
|
* @param {Promise.resolve} resolve - A Promise's resolve function that will
|
|
|
|
* be called when the extension is unlocked.
|
|
|
|
* @param {boolean} shouldShowUnlockRequest - Whether the extension notification
|
|
|
|
* popup should be opened.
|
|
|
|
*/
|
2020-11-03 00:41:28 +01:00
|
|
|
waitForUnlock(resolve, shouldShowUnlockRequest) {
|
2021-02-04 19:15:23 +01:00
|
|
|
this.waitingForUnlock.push({ resolve });
|
2021-02-09 00:22:30 +01:00
|
|
|
this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE);
|
2020-05-05 16:03:21 +02:00
|
|
|
if (shouldShowUnlockRequest) {
|
2021-02-04 19:15:23 +01:00
|
|
|
this._showUnlockRequest();
|
2020-05-05 16:03:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-23 17:25:55 +01:00
|
|
|
/**
|
|
|
|
* Drains the waitingForUnlock queue, resolving all the related Promises.
|
|
|
|
*/
|
2020-11-03 00:41:28 +01:00
|
|
|
handleUnlock() {
|
2020-03-23 17:25:55 +01:00
|
|
|
if (this.waitingForUnlock.length > 0) {
|
|
|
|
while (this.waitingForUnlock.length > 0) {
|
2021-02-04 19:15:23 +01:00
|
|
|
this.waitingForUnlock.shift().resolve();
|
2020-03-23 17:25:55 +01:00
|
|
|
}
|
2021-02-09 00:22:30 +01:00
|
|
|
this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE);
|
2020-03-23 17:25:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-25 21:01:28 +02:00
|
|
|
/**
|
|
|
|
* Sets the default home tab
|
2022-01-07 16:57:33 +01:00
|
|
|
*
|
2020-05-25 21:01:28 +02:00
|
|
|
* @param {string} [defaultHomeActiveTabName] - the tab name
|
|
|
|
*/
|
2020-11-03 00:41:28 +01:00
|
|
|
setDefaultHomeActiveTabName(defaultHomeActiveTabName) {
|
2020-05-25 21:01:28 +02:00
|
|
|
this.store.updateState({
|
|
|
|
defaultHomeActiveTabName,
|
2021-02-04 19:15:23 +01:00
|
|
|
});
|
2020-05-25 21:01:28 +02:00
|
|
|
}
|
|
|
|
|
2020-04-22 19:11:36 +02:00
|
|
|
/**
|
|
|
|
* Record that the user has seen the connected status info popover
|
|
|
|
*/
|
2020-11-03 00:41:28 +01:00
|
|
|
setConnectedStatusPopoverHasBeenShown() {
|
2020-04-22 19:11:36 +02:00
|
|
|
this.store.updateState({
|
|
|
|
connectedStatusPopoverHasBeenShown: true,
|
2021-02-04 19:15:23 +01:00
|
|
|
});
|
2020-04-22 19:11:36 +02:00
|
|
|
}
|
|
|
|
|
2021-06-05 08:33:58 +02:00
|
|
|
/**
|
2022-01-07 16:57:33 +01:00
|
|
|
* Record that the user has been shown the recovery phrase reminder.
|
2021-06-05 08:33:58 +02:00
|
|
|
*/
|
|
|
|
setRecoveryPhraseReminderHasBeenShown() {
|
|
|
|
this.store.updateState({
|
|
|
|
recoveryPhraseReminderHasBeenShown: true,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Record the timestamp of the last time the user has seen the recovery phrase reminder
|
2022-01-07 16:57:33 +01:00
|
|
|
*
|
|
|
|
* @param {number} lastShown - timestamp when user was last shown the reminder.
|
2021-06-05 08:33:58 +02:00
|
|
|
*/
|
|
|
|
setRecoveryPhraseReminderLastShown(lastShown) {
|
|
|
|
this.store.updateState({
|
|
|
|
recoveryPhraseReminderLastShown: lastShown,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-05-13 18:16:09 +02:00
|
|
|
/**
|
2022-01-07 16:57:33 +01:00
|
|
|
* Sets the last active time to the current time.
|
2019-05-13 18:16:09 +02:00
|
|
|
*/
|
2020-11-03 00:41:28 +01:00
|
|
|
setLastActiveTime() {
|
2021-02-04 19:15:23 +01:00
|
|
|
this._resetTimer();
|
2019-05-13 18:16:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the inactive timeout for the app
|
2022-01-07 16:57:33 +01:00
|
|
|
*
|
2019-05-13 18:16:09 +02:00
|
|
|
* @private
|
2022-01-07 16:57:33 +01:00
|
|
|
* @param {number} timeoutMinutes - The inactive timeout in minutes.
|
2019-05-13 18:16:09 +02:00
|
|
|
*/
|
2020-11-03 00:41:28 +01:00
|
|
|
_setInactiveTimeout(timeoutMinutes) {
|
2019-11-26 21:44:29 +01:00
|
|
|
this.store.updateState({
|
2019-05-13 18:16:09 +02:00
|
|
|
timeoutMinutes,
|
2021-02-04 19:15:23 +01:00
|
|
|
});
|
2019-05-13 18:16:09 +02:00
|
|
|
|
2021-02-04 19:15:23 +01:00
|
|
|
this._resetTimer();
|
2019-05-13 18:16:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resets the internal inactive timer
|
|
|
|
*
|
|
|
|
* If the {@code timeoutMinutes} state is falsy (i.e., zero) then a new
|
|
|
|
* timer will not be created.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
2022-09-24 02:39:25 +02:00
|
|
|
/* eslint-disable no-undef */
|
2020-11-03 00:41:28 +01:00
|
|
|
_resetTimer() {
|
2021-02-04 19:15:23 +01:00
|
|
|
const { timeoutMinutes } = this.store.getState();
|
2019-05-13 18:16:09 +02:00
|
|
|
|
|
|
|
if (this.timer) {
|
2022-11-04 16:23:56 +01:00
|
|
|
clearTimeout(this.timer);
|
|
|
|
} else if (isManifestV3) {
|
|
|
|
chrome.alarms.clear(AUTO_LOCK_TIMEOUT_ALARM);
|
2019-05-13 18:16:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!timeoutMinutes) {
|
2021-02-04 19:15:23 +01:00
|
|
|
return;
|
2019-05-13 18:16:09 +02:00
|
|
|
}
|
|
|
|
|
2022-09-24 02:39:25 +02:00
|
|
|
if (isManifestV3) {
|
|
|
|
chrome.alarms.create(AUTO_LOCK_TIMEOUT_ALARM, {
|
|
|
|
delayInMinutes: timeoutMinutes,
|
|
|
|
periodInMinutes: timeoutMinutes,
|
|
|
|
});
|
2022-11-04 16:23:56 +01:00
|
|
|
chrome.alarms.onAlarm.addListener((alarmInfo) => {
|
|
|
|
if (alarmInfo.name === AUTO_LOCK_TIMEOUT_ALARM) {
|
|
|
|
this.onInactiveTimeout();
|
|
|
|
chrome.alarms.clear(AUTO_LOCK_TIMEOUT_ALARM);
|
|
|
|
}
|
2022-09-24 02:39:25 +02:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
this.timer = setTimeout(
|
|
|
|
() => this.onInactiveTimeout(),
|
|
|
|
timeoutMinutes * MINUTE,
|
|
|
|
);
|
|
|
|
}
|
2019-05-13 18:16:09 +02:00
|
|
|
}
|
2021-04-30 17:28:07 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the current browser and OS environment
|
2022-01-07 16:57:33 +01:00
|
|
|
*
|
|
|
|
* @param os
|
|
|
|
* @param browser
|
2021-04-30 17:28:07 +02:00
|
|
|
*/
|
|
|
|
setBrowserEnvironment(os, browser) {
|
|
|
|
this.store.updateState({ browserEnvironment: { os, browser } });
|
|
|
|
}
|
2021-08-04 23:53:13 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a pollingToken for a given environmentType
|
2022-01-07 16:57:33 +01:00
|
|
|
*
|
|
|
|
* @param pollingToken
|
|
|
|
* @param pollingTokenType
|
2021-08-04 23:53:13 +02:00
|
|
|
*/
|
|
|
|
addPollingToken(pollingToken, pollingTokenType) {
|
|
|
|
const prevState = this.store.getState()[pollingTokenType];
|
|
|
|
this.store.updateState({
|
|
|
|
[pollingTokenType]: [...prevState, pollingToken],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* removes a pollingToken for a given environmentType
|
2022-01-07 16:57:33 +01:00
|
|
|
*
|
|
|
|
* @param pollingToken
|
|
|
|
* @param pollingTokenType
|
2021-08-04 23:53:13 +02:00
|
|
|
*/
|
|
|
|
removePollingToken(pollingToken, pollingTokenType) {
|
|
|
|
const prevState = this.store.getState()[pollingTokenType];
|
|
|
|
this.store.updateState({
|
|
|
|
[pollingTokenType]: prevState.filter((token) => token !== pollingToken),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* clears all pollingTokens
|
|
|
|
*/
|
|
|
|
clearPollingTokens() {
|
|
|
|
this.store.updateState({
|
|
|
|
popupGasPollTokens: [],
|
|
|
|
notificationGasPollTokens: [],
|
|
|
|
fullScreenGasPollTokens: [],
|
|
|
|
});
|
|
|
|
}
|
2021-11-16 23:22:01 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets whether the testnet dismissal link should be shown in the network dropdown
|
2022-01-07 16:57:33 +01:00
|
|
|
*
|
|
|
|
* @param showTestnetMessageInDropdown
|
2021-11-16 23:22:01 +01:00
|
|
|
*/
|
|
|
|
setShowTestnetMessageInDropdown(showTestnetMessageInDropdown) {
|
|
|
|
this.store.updateState({ showTestnetMessageInDropdown });
|
|
|
|
}
|
2021-11-30 15:28:28 +01:00
|
|
|
|
2022-09-13 15:41:58 +02:00
|
|
|
/**
|
|
|
|
* Sets whether the portfolio site tooltip should be shown on the home page
|
|
|
|
*
|
|
|
|
* @param showPortfolioTooltip
|
|
|
|
*/
|
|
|
|
setShowPortfolioTooltip(showPortfolioTooltip) {
|
|
|
|
this.store.updateState({ showPortfolioTooltip });
|
|
|
|
}
|
|
|
|
|
2022-11-16 18:41:15 +01:00
|
|
|
/**
|
|
|
|
* Sets whether the beta notification heading on the home page
|
|
|
|
*
|
|
|
|
* @param showBetaHeader
|
|
|
|
*/
|
|
|
|
setShowBetaHeader(showBetaHeader) {
|
|
|
|
this.store.updateState({ showBetaHeader });
|
|
|
|
}
|
|
|
|
|
2021-11-30 15:28:28 +01:00
|
|
|
/**
|
|
|
|
* Sets a property indicating the model of the user's Trezor hardware wallet
|
2022-01-07 16:57:33 +01:00
|
|
|
*
|
|
|
|
* @param trezorModel - The Trezor model.
|
2021-11-30 15:28:28 +01:00
|
|
|
*/
|
|
|
|
setTrezorModel(trezorModel) {
|
|
|
|
this.store.updateState({ trezorModel });
|
|
|
|
}
|
2021-12-14 00:41:10 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A setter for the `collectiblesDetectionNoticeDismissed` property
|
2022-01-07 16:57:33 +01:00
|
|
|
*
|
|
|
|
* @param collectiblesDetectionNoticeDismissed
|
2021-12-14 00:41:10 +01:00
|
|
|
*/
|
|
|
|
setCollectiblesDetectionNoticeDismissed(
|
|
|
|
collectiblesDetectionNoticeDismissed,
|
|
|
|
) {
|
|
|
|
this.store.updateState({
|
|
|
|
collectiblesDetectionNoticeDismissed,
|
|
|
|
});
|
|
|
|
}
|
2022-01-11 20:17:56 +01:00
|
|
|
|
2022-01-27 18:26:33 +01:00
|
|
|
/**
|
|
|
|
* A setter for the `collectiblesDropdownState` property
|
|
|
|
*
|
|
|
|
* @param collectiblesDropdownState
|
|
|
|
*/
|
|
|
|
updateCollectibleDropDownState(collectiblesDropdownState) {
|
|
|
|
this.store.updateState({
|
|
|
|
collectiblesDropdownState,
|
|
|
|
});
|
|
|
|
}
|
2022-08-23 17:04:07 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the array of the first time used networks
|
|
|
|
*
|
|
|
|
* @param chainId
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
setFirstTimeUsedNetwork(chainId) {
|
|
|
|
const currentState = this.store.getState();
|
|
|
|
const { usedNetworks } = currentState;
|
|
|
|
usedNetworks[chainId] = true;
|
|
|
|
|
|
|
|
this.store.updateState({ usedNetworks });
|
|
|
|
}
|
2019-05-13 18:16:09 +02:00
|
|
|
}
|