Merge pull request #3900 from MetaMask/migration-error-report

migrations - report migrations errors to sentry with vault structure
feature/default_network_editable
Frankie 7 years ago committed by GitHub
commit 2dfa8a2e50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      app/scripts/background.js
  2. 33
      app/scripts/lib/getObjStructure.js
  3. 27
      app/scripts/lib/migrator/index.js
  4. 8
      app/scripts/migrations/024.js

@ -20,6 +20,7 @@ const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry')
const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics') const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
const EdgeEncryptor = require('./edge-encryptor') const EdgeEncryptor = require('./edge-encryptor')
const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code') const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code')
const getObjStructure = require('./lib/getObjStructure')
const STORAGE_KEY = 'metamask-config' const STORAGE_KEY = 'metamask-config'
const METAMASK_DEBUG = process.env.METAMASK_DEBUG const METAMASK_DEBUG = process.env.METAMASK_DEBUG
@ -77,6 +78,16 @@ async function loadStateFromPersistence () {
diskStore.getState() || diskStore.getState() ||
migrator.generateInitialState(firstTimeState) migrator.generateInitialState(firstTimeState)
// report migration errors to sentry
migrator.on('error', (err) => {
// get vault structure without secrets
const vaultStructure = getObjStructure(versionedData)
raven.captureException(err, {
// "extra" key is required by Sentry
extra: { vaultStructure },
})
})
// migrate data // migrate data
versionedData = await migrator.migrateData(versionedData) versionedData = await migrator.migrateData(versionedData)
if (!versionedData) { if (!versionedData) {
@ -84,7 +95,14 @@ async function loadStateFromPersistence () {
} }
// write to disk // write to disk
if (localStore.isSupported) localStore.set(versionedData) if (localStore.isSupported) {
localStore.set(versionedData)
} else {
// throw in setTimeout so as to not block boot
setTimeout(() => {
throw new Error('MetaMask - Localstore not supported')
})
}
// return just the data // return just the data
return versionedData.data return versionedData.data

@ -0,0 +1,33 @@
const clone = require('clone')
module.exports = getObjStructure
// This will create an object that represents the structure of the given object
// it replaces all values with the result of their type
// {
// "data": {
// "CurrencyController": {
// "conversionDate": "number",
// "conversionRate": "number",
// "currentCurrency": "string"
// }
// }
function getObjStructure(obj) {
const structure = clone(obj)
return deepMap(structure, (value) => {
return value === null ? 'null' : typeof value
})
}
function deepMap(target = {}, visit) {
Object.entries(target).forEach(([key, value]) => {
if (typeof value === 'object' && value !== null) {
target[key] = deepMap(value, visit)
} else {
target[key] = visit(value)
}
})
return target
}

@ -1,6 +1,9 @@
class Migrator { const EventEmitter = require('events')
class Migrator extends EventEmitter {
constructor (opts = {}) { constructor (opts = {}) {
super()
const migrations = opts.migrations || [] const migrations = opts.migrations || []
// sort migrations by version // sort migrations by version
this.migrations = migrations.sort((a, b) => a.version - b.version) this.migrations = migrations.sort((a, b) => a.version - b.version)
@ -12,13 +15,29 @@ class Migrator {
// run all pending migrations on meta in place // run all pending migrations on meta in place
async migrateData (versionedData = this.generateInitialState()) { async migrateData (versionedData = this.generateInitialState()) {
// get all migrations that have not yet been run
const pendingMigrations = this.migrations.filter(migrationIsPending) const pendingMigrations = this.migrations.filter(migrationIsPending)
// perform each migration
for (const index in pendingMigrations) { for (const index in pendingMigrations) {
const migration = pendingMigrations[index] const migration = pendingMigrations[index]
versionedData = await migration.migrate(versionedData) try {
if (!versionedData.data) throw new Error('Migrator - migration returned empty data') // attempt migration and validate
if (versionedData.version !== undefined && versionedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly') const migratedData = await migration.migrate(versionedData)
if (!migratedData.data) throw new Error('Migrator - migration returned empty data')
if (migratedData.version !== undefined && migratedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly')
// accept the migration as good
versionedData = migratedData
} catch (err) {
// rewrite error message to add context without clobbering stack
const originalErrorMessage = err.message
err.message = `MetaMask Migration Error #${migration.version}: ${originalErrorMessage}`
console.warn(err.stack)
// emit error instead of throw so as to not break the run (gracefully fail)
this.emit('error', err)
// stop migrating and use state as is
return versionedData
}
} }
return versionedData return versionedData

@ -13,17 +13,13 @@ const clone = require('clone')
module.exports = { module.exports = {
version, version,
migrate: function (originalVersionedData) { migrate: async function (originalVersionedData) {
const versionedData = clone(originalVersionedData) const versionedData = clone(originalVersionedData)
versionedData.meta.version = version versionedData.meta.version = version
try {
const state = versionedData.data const state = versionedData.data
const newState = transformState(state) const newState = transformState(state)
versionedData.data = newState versionedData.data = newState
} catch (err) { return versionedData
console.warn(`MetaMask Migration #${version}` + err.stack)
}
return Promise.resolve(versionedData)
}, },
} }

Loading…
Cancel
Save