mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-25 20:02:58 +01:00
20e16d41be
* Improve Sentry state pre-initialization Previously the masked state snapshot sent to Sentry would be blank for errors that occured during initialization. Instead we'll now include some basic information in all cases, and a masked copy of the persisted state if it happens after the first time the persisted state is read. * Add test * Fix crash when persisted state not yet fetched * Add descriptions for initial state hooks * Update comments to reflect recent changes * Re-order imports to follow conventions * Move initial state hooks back to module-level The initial state hooks are now setup at the top-level of their module. This ensures that they're setup prior to later imports. Calling a function to setup these hooks in the entrypoint module wouldn't accomplish this even if it was run "before" the imports because ES6 imports always get hoisted to the top of the file. The `localStore` instance wasn't available statically, so a new state hook was introduced for retrieving the most recent retrieved persisted state. * Fix error e2e tests
128 lines
3.4 KiB
JavaScript
128 lines
3.4 KiB
JavaScript
import browser from 'webextension-polyfill';
|
|
import log from 'loglevel';
|
|
import { captureException } from '@sentry/browser';
|
|
import { checkForLastError } from '../../../shared/modules/browser-runtime.utils';
|
|
|
|
/**
|
|
* A wrapper around the extension's storage local API
|
|
*/
|
|
export default class ExtensionStore {
|
|
constructor() {
|
|
this.isSupported = Boolean(browser.storage.local);
|
|
if (!this.isSupported) {
|
|
log.error('Storage local API not available.');
|
|
}
|
|
// we use this flag to avoid flooding sentry with a ton of errors:
|
|
// once data persistence fails once and it flips true we don't send further
|
|
// data persistence errors to sentry
|
|
this.dataPersistenceFailing = false;
|
|
this.mostRecentRetrievedState = null;
|
|
}
|
|
|
|
setMetadata(initMetaData) {
|
|
this.metadata = initMetaData;
|
|
}
|
|
|
|
async set(state) {
|
|
if (!this.isSupported) {
|
|
throw new Error(
|
|
'Metamask- cannot persist state to local store as this browser does not support this action',
|
|
);
|
|
}
|
|
if (!state) {
|
|
throw new Error('MetaMask - updated state is missing');
|
|
}
|
|
if (!this.metadata) {
|
|
throw new Error(
|
|
'MetaMask - metadata must be set on instance of ExtensionStore before calling "set"',
|
|
);
|
|
}
|
|
try {
|
|
// we format the data for storage as an object with the "data" key for the controller state object
|
|
// and the "meta" key for a metadata object containing a version number that tracks how the data shape
|
|
// has changed using migrations to adapt to backwards incompatible changes
|
|
await this._set({ data: state, meta: this.metadata });
|
|
if (this.dataPersistenceFailing) {
|
|
this.dataPersistenceFailing = false;
|
|
}
|
|
} catch (err) {
|
|
if (!this.dataPersistenceFailing) {
|
|
this.dataPersistenceFailing = true;
|
|
captureException(err);
|
|
}
|
|
log.error('error setting state in local store:', err);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns all of the keys currently saved
|
|
*
|
|
* @returns {Promise<*>}
|
|
*/
|
|
async get() {
|
|
if (!this.isSupported) {
|
|
return undefined;
|
|
}
|
|
const result = await this._get();
|
|
// extension.storage.local always returns an obj
|
|
// if the object is empty, treat it as undefined
|
|
if (isEmpty(result)) {
|
|
this.mostRecentRetrievedState = null;
|
|
return undefined;
|
|
}
|
|
this.mostRecentRetrievedState = result;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Returns all of the keys currently saved
|
|
*
|
|
* @private
|
|
* @returns {object} the key-value map from local storage
|
|
*/
|
|
_get() {
|
|
const { local } = browser.storage;
|
|
return new Promise((resolve, reject) => {
|
|
local.get(null).then((/** @type {any} */ result) => {
|
|
const err = checkForLastError();
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve(result);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Sets the key in local state
|
|
*
|
|
* @param {object} obj - The key to set
|
|
* @returns {Promise<void>}
|
|
* @private
|
|
*/
|
|
_set(obj) {
|
|
const { local } = browser.storage;
|
|
return new Promise((resolve, reject) => {
|
|
local.set(obj).then(() => {
|
|
const err = checkForLastError();
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns whether or not the given object contains no keys
|
|
*
|
|
* @param {object} obj - The object to check
|
|
* @returns {boolean}
|
|
*/
|
|
function isEmpty(obj) {
|
|
return Object.keys(obj).length === 0;
|
|
}
|