1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-23 10:30:04 +01:00
metamask-extension/app/scripts/controllers/app-state.js
Alex Donesky d359429f04
Stop GasFeeController polling when pop closes (#11746)
* Stop GasFeeController polling when pop closes

* Stop estimate gas polling on window unload

* lint + comments

* Improve client closed logic

* lint

* Add back _beforeUnload on unmount in gas-modal-page-container

* Add full check and call onClientClosed method for notifcation environment

* Add gas pollingToken tracking to appStateController and use to disconnect polling for each environment type

* remove unused method

* move controller manipulation logic from background.js to metamask-controller, disaggregate methods

* add beforeunload handling to reset gas polling tokens from root of send page

* cleanup, lint and address feedback

* clear appState gasPollingTokens when all instances of all env types are closed, fix pollingTokenType arg from onEnvironmentTypeClosed call in metamask-controller

* mock new methods to fix tests

* final bit of cleanup + comments

Co-authored-by: Dan Miller <danjm.com@gmail.com>
2021-08-04 16:53:13 -05:00

232 lines
6.0 KiB
JavaScript

import EventEmitter from 'events';
import { ObservableStore } from '@metamask/obs-store';
import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
import { MINUTE } from '../../../shared/constants/time';
export default class AppStateController extends EventEmitter {
/**
* @constructor
* @param {Object} opts
*/
constructor(opts = {}) {
const {
addUnlockListener,
isUnlocked,
initState,
onInactiveTimeout,
showUnlockRequest,
preferencesStore,
} = opts;
super();
this.onInactiveTimeout = onInactiveTimeout || (() => undefined);
this.store = new ObservableStore({
timeoutMinutes: 0,
connectedStatusPopoverHasBeenShown: true,
defaultHomeActiveTabName: null,
browserEnvironment: {},
popupGasPollTokens: [],
notificationGasPollTokens: [],
fullScreenGasPollTokens: [],
recoveryPhraseReminderHasBeenShown: false,
recoveryPhraseReminderLastShown: new Date().getTime(),
...initState,
});
this.timer = null;
this.isUnlocked = isUnlocked;
this.waitingForUnlock = [];
addUnlockListener(this.handleUnlock.bind(this));
this._showUnlockRequest = showUnlockRequest;
preferencesStore.subscribe(({ preferences }) => {
const currentState = this.store.getState();
if (currentState.timeoutMinutes !== preferences.autoLockTimeLimit) {
this._setInactiveTimeout(preferences.autoLockTimeLimit);
}
});
const { preferences } = preferencesStore.getState();
this._setInactiveTimeout(preferences.autoLockTimeLimit);
}
/**
* Get a Promise that resolves when the extension is unlocked.
* This Promise will never reject.
*
* @param {boolean} shouldShowUnlockRequest - Whether the extension notification
* popup should be opened.
* @returns {Promise<void>} A promise that resolves when the extension is
* unlocked, or immediately if the extension is already unlocked.
*/
getUnlockPromise(shouldShowUnlockRequest) {
return new Promise((resolve) => {
if (this.isUnlocked()) {
resolve();
} else {
this.waitForUnlock(resolve, shouldShowUnlockRequest);
}
});
}
/**
* 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.
*/
waitForUnlock(resolve, shouldShowUnlockRequest) {
this.waitingForUnlock.push({ resolve });
this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE);
if (shouldShowUnlockRequest) {
this._showUnlockRequest();
}
}
/**
* Drains the waitingForUnlock queue, resolving all the related Promises.
*/
handleUnlock() {
if (this.waitingForUnlock.length > 0) {
while (this.waitingForUnlock.length > 0) {
this.waitingForUnlock.shift().resolve();
}
this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE);
}
}
/**
* Sets the default home tab
* @param {string} [defaultHomeActiveTabName] - the tab name
*/
setDefaultHomeActiveTabName(defaultHomeActiveTabName) {
this.store.updateState({
defaultHomeActiveTabName,
});
}
/**
* Record that the user has seen the connected status info popover
*/
setConnectedStatusPopoverHasBeenShown() {
this.store.updateState({
connectedStatusPopoverHasBeenShown: true,
});
}
/**
* Record that the user has been shown the recovery phrase reminder
* @returns {void}
*/
setRecoveryPhraseReminderHasBeenShown() {
this.store.updateState({
recoveryPhraseReminderHasBeenShown: true,
});
}
/**
* Record the timestamp of the last time the user has seen the recovery phrase reminder
* @param {number} lastShown - timestamp when user was last shown the reminder
* @returns {void}
*/
setRecoveryPhraseReminderLastShown(lastShown) {
this.store.updateState({
recoveryPhraseReminderLastShown: lastShown,
});
}
/**
* Sets the last active time to the current time
* @returns {void}
*/
setLastActiveTime() {
this._resetTimer();
}
/**
* Sets the inactive timeout for the app
* @param {number} timeoutMinutes - the inactive timeout in minutes
* @returns {void}
* @private
*/
_setInactiveTimeout(timeoutMinutes) {
this.store.updateState({
timeoutMinutes,
});
this._resetTimer();
}
/**
* Resets the internal inactive timer
*
* If the {@code timeoutMinutes} state is falsy (i.e., zero) then a new
* timer will not be created.
*
* @returns {void}
* @private
*/
_resetTimer() {
const { timeoutMinutes } = this.store.getState();
if (this.timer) {
clearTimeout(this.timer);
}
if (!timeoutMinutes) {
return;
}
this.timer = setTimeout(
() => this.onInactiveTimeout(),
timeoutMinutes * MINUTE,
);
}
/**
* Sets the current browser and OS environment
* @returns {void}
*/
setBrowserEnvironment(os, browser) {
this.store.updateState({ browserEnvironment: { os, browser } });
}
/**
* Adds a pollingToken for a given environmentType
* @returns {void}
*/
addPollingToken(pollingToken, pollingTokenType) {
const prevState = this.store.getState()[pollingTokenType];
this.store.updateState({
[pollingTokenType]: [...prevState, pollingToken],
});
}
/**
* removes a pollingToken for a given environmentType
* @returns {void}
*/
removePollingToken(pollingToken, pollingTokenType) {
const prevState = this.store.getState()[pollingTokenType];
this.store.updateState({
[pollingTokenType]: prevState.filter((token) => token !== pollingToken),
});
}
/**
* clears all pollingTokens
* @returns {void}
*/
clearPollingTokens() {
this.store.updateState({
popupGasPollTokens: [],
notificationGasPollTokens: [],
fullScreenGasPollTokens: [],
});
}
}