diff --git a/src/migrations/index.js b/src/migrations/index.js index 350221c5..b470d313 100644 --- a/src/migrations/index.js +++ b/src/migrations/index.js @@ -8,14 +8,16 @@ import migrations from './migrations' // Logic to run all the migrations based on what was not yet run: export const runMigrations = async (): Promise => { - const current = await db.getNamespace('migrations') + // Legacy: the migration nonce was previously stored in separate file + // it can happen, so we have to check it from here also. + const current = (await db.getKey('app', 'migrations')) || (await db.getNamespace('migrations')) let { nonce } = current || { nonce: migrations.length } const outdated = migrations.length - nonce if (!outdated) { if (!current) { - await db.setNamespace('migrations', { nonce }) + await db.setKey('app', 'migrations.nonce', nonce) } return } @@ -31,6 +33,6 @@ export const runMigrations = async (): Promise => { } logger.log(`${outdated} migration(s) performed.`) } finally { - await db.setNamespace('migrations', { nonce }) + await db.setKey('app', 'migrations.nonce', nonce) } } diff --git a/src/migrations/migrations.js b/src/migrations/migrations.js index ed7fc04d..e67767d2 100644 --- a/src/migrations/migrations.js +++ b/src/migrations/migrations.js @@ -18,7 +18,7 @@ const migrations: Migration[] = [ const dbPath = db.getDBPath() const legacyKeys = ['accounts', 'countervalues', 'settings', 'user'] const [accounts, countervalues, settings, user] = await Promise.all( - legacyKeys.map(key => getLegacyData(path.join(dbPath, `${key}.json`))), + legacyKeys.map(key => getFileData(dbPath, key)), ) const appData = { user, settings, accounts, countervalues } await db.setNamespace('app', appData) @@ -28,15 +28,33 @@ const migrations: Migration[] = [ const windowParams = await db.getKey('app', 'settings.window') await db.setKey('app', 'settings.window', undefined) await db.setNamespace('windowParams', windowParams) - await Promise.all(legacyKeys.map(key => fsUnlink(path.join(dbPath, `${key}.json`)))) + await Promise.all( + legacyKeys.map(async key => { + try { + await fsUnlink(path.join(dbPath, `${key}.json`)) + } catch (err) {} // eslint-disable-line + }), + ) + }, + }, + { + doc: 'merging migrations into app.json', + run: async () => { + const migrations = await db.getNamespace('migrations') + await db.setKey('app', 'migrations', migrations) + const dbPath = db.getDBPath() + try { + await fsUnlink(path.resolve(dbPath, 'migrations.json')) + } catch (err) {} // eslint-disable-line }, }, ] -async function getLegacyData(filePath) { +async function getFileData(dbPath, fileName) { + const filePath = path.join(dbPath, `${fileName}.json`) let finalData - const fileContent = await fsReadfile(filePath, 'utf-8') try { + const fileContent = await fsReadfile(filePath, 'utf-8') const { data } = JSON.parse(fileContent) finalData = data } catch (err) { @@ -45,7 +63,8 @@ async function getLegacyData(filePath) { const buf = await fsReadfile(filePath) return buf.toString('base64') } - throw err + // will be stripped down by JSON.stringify + return undefined } return finalData } diff --git a/src/migrations/migrations.spec.js b/src/migrations/migrations.spec.js index 5898cab8..18af426e 100644 --- a/src/migrations/migrations.spec.js +++ b/src/migrations/migrations.spec.js @@ -12,6 +12,7 @@ import db from 'helpers/db' const rimraf = promisify(rimrafModule) const fsReaddir = promisify(fs.readdir) +const fsReadFile = promisify(fs.readFile) const tmpDir = os.tmpdir() @@ -20,14 +21,12 @@ const accountsTransform = { set: encodeAccountsModel, } -describe('migration 1', () => { +describe('from nonce 0', () => { describe('without encryption', () => { test('merging db files', async () => { const dir = await extractMock('userdata_v1.0.5_mock-01') - let files db.init(dir) - files = await fsReaddir(dir) - expect(files).toEqual([ + await expectFiles(dir, [ 'accounts.json', 'countervalues.json', 'migrations.json', @@ -35,8 +34,7 @@ describe('migration 1', () => { 'user.json', ]) await runMigrations() - files = await fsReaddir(dir) - expect(files).toEqual(['app.json', 'migrations.json', 'windowParams.json']) + await expectFiles(dir, ['app.json', 'windowParams.json']) db.init(dir) db.registerTransform('app', 'accounts', accountsTransform) const accounts = await db.getKey('app', 'accounts') @@ -50,6 +48,40 @@ describe('migration 1', () => { }, }) }) + + test('handle missing file without crash', async () => { + const dir = await extractMock('userdata_v1.0.5_mock-03-missing-file') + await expectFiles(dir, [ + 'countervalues.json', + 'migrations.json', + 'settings.json', + 'user.json', + ]) + db.init(dir) + let err + try { + await runMigrations() + } catch (e) { + err = e + } + expect(err).toBeUndefined() + await expectFiles(dir, ['app.json', 'windowParams.json']) + }) + + test('handle where app.json is already present', async () => { + const dir = await extractMock('userdata_v1.0.5_mock-04-app-json-present') + await expectFiles(dir, [ + 'accounts.json', + 'app.json', + 'countervalues.json', + 'migrations.json', + 'settings.json', + 'user.json', + ]) + db.init(dir) + await runMigrations() + await expectFiles(dir, ['app.json', 'windowParams.json']) + }) }) describe('with encryption', () => { @@ -59,8 +91,7 @@ describe('migration 1', () => { db.registerTransform('app', 'accounts', accountsTransform) await runMigrations() await db.setEncryptionKey('app', 'accounts', 'passw0rd') - const files = await fsReaddir(dir) - expect(files).toEqual(['app.json', 'migrations.json', 'windowParams.json']) + await expectFiles(dir, ['app.json', 'windowParams.json']) const accounts = await db.getKey('app', 'accounts') expect(accounts.length).toBe(6) expect(accounts[0].balance).toBeInstanceOf(BigNumber) @@ -79,6 +110,21 @@ describe('migration 1', () => { }) }) +describe('from nonce 1', () => { + test('merging migration file into app file', async () => { + const dir = await extractMock('userdata_v1.1.1_mock-01') + await expectFiles(dir, ['app.json', 'migrations.json', 'windowParams.json']) + const migrationsBefore = await fsReadFile(path.resolve(dir, 'migrations.json'), 'utf-8') + expect(migrationsBefore).toBe('{"data":{"nonce":1}}') + db.init(dir) + db.registerTransform('app', 'accounts', accountsTransform) + await runMigrations() + await expectFiles(dir, ['app.json', 'windowParams.json']) + const migrations = await db.getKey('app', 'migrations') + expect(migrations).toEqual({ nonce: 2 }) + }) +}) + async function extractMock(mockName) { const destDirectory = path.resolve(tmpDir, mockName) const zipFilePath = path.resolve(__dirname, 'mocks', `${mockName}.zip`) @@ -94,3 +140,8 @@ function extractZip(zipFilePath, destDirectory) { childProcess.on('error', reject) }) } + +async function expectFiles(dir, expectedFiles) { + const files = await fsReaddir(dir) + expect(files).toEqual(expectedFiles) +} diff --git a/src/migrations/mocks/userdata_v1.0.5_mock-03-missing-file.zip b/src/migrations/mocks/userdata_v1.0.5_mock-03-missing-file.zip new file mode 100644 index 00000000..b390d1b0 Binary files /dev/null and b/src/migrations/mocks/userdata_v1.0.5_mock-03-missing-file.zip differ diff --git a/src/migrations/mocks/userdata_v1.0.5_mock-04-app-json-present.zip b/src/migrations/mocks/userdata_v1.0.5_mock-04-app-json-present.zip new file mode 100644 index 00000000..eebdc20c Binary files /dev/null and b/src/migrations/mocks/userdata_v1.0.5_mock-04-app-json-present.zip differ diff --git a/src/migrations/mocks/userdata_v1.1.1_mock-01.zip b/src/migrations/mocks/userdata_v1.1.1_mock-01.zip new file mode 100644 index 00000000..c309f8b2 Binary files /dev/null and b/src/migrations/mocks/userdata_v1.1.1_mock-01.zip differ diff --git a/src/sentry/install.js b/src/sentry/install.js index 5b78ddf8..5d75ed47 100644 --- a/src/sentry/install.js +++ b/src/sentry/install.js @@ -16,6 +16,18 @@ export default (Raven: any, shouldSendCallback: () => boolean, userId: string) = }, environment: __DEV__ ? 'development' : 'production', shouldSendCallback, + ignoreErrors: [ + 'status code 404', + 'timeout', + 'socket hang up', + 'getaddrinfo ', + 'ETIMEDOUT', + 'ECONNRESET', + 'ENETUNREACH', + 'request timed out', + 'NetworkDown', + 'ERR_CONNECTION_TIMED_OUT', + ], autoBreadcrumbs: { xhr: false, // it is track anonymously from logger console: false, // we don't track because not anonymized