mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
PM-3585 Improve state migrations (#5009)
* WIP: safer state migrations Co-authored-by: Justin Baur <justindbaur@users.noreply.github.com> * Add min version check and remove old migrations Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> * Add rollback and version checking * Add state version move migration * Expand tests and improve typing for Migrations * Remove StateMigration Service * Rewrite version 5 and 6 migrations * Add all but initial migration to supported migrations * Handle stateVersion location in migrator update versions * Move to unique migrations directory * Disallow imports outside of state-migrations * Lint and test fixes * Do not run migrations if we cannot determine state * Fix desktop background StateService build * Document Migration builder class * Add debug logging to migrations * Comment on migrator overrides * Use specific property names * `npm run prettier` 🤖 * Insert new migration * Set stateVersion when creating new globals object * PR comments * Fix migrate imports * Move migration building into `migrate` function * Export current version from migration definitions * Move file version concerns to migrator * Update migrate spec to reflect new version requirements * Fix import paths * Prefer unique state data * Remove unnecessary async * Prefer to not use `any` --------- Co-authored-by: Justin Baur <justindbaur@users.noreply.github.com> Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
This commit is contained in:
60
libs/common/src/state-migrations/migrate.ts
Normal file
60
libs/common/src/state-migrations/migrate.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
// eslint-disable-next-line import/no-restricted-paths -- Needed to print log messages
|
||||
import { LogService } from "../platform/abstractions/log.service";
|
||||
// eslint-disable-next-line import/no-restricted-paths -- Needed to interface with storage locations
|
||||
import { AbstractStorageService } from "../platform/abstractions/storage.service";
|
||||
|
||||
import { MigrationBuilder } from "./migration-builder";
|
||||
import { MigrationHelper } from "./migration-helper";
|
||||
import { FixPremiumMigrator } from "./migrations/3-fix-premium";
|
||||
import { RemoveEverBeenUnlockedMigrator } from "./migrations/4-remove-ever-been-unlocked";
|
||||
import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys";
|
||||
import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key";
|
||||
import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account";
|
||||
import { MoveStateVersionMigrator } from "./migrations/8-move-state-version";
|
||||
import { MinVersionMigrator } from "./migrations/min-version";
|
||||
|
||||
export const MIN_VERSION = 2;
|
||||
export const CURRENT_VERSION = 8;
|
||||
export type MinVersion = typeof MIN_VERSION;
|
||||
|
||||
export async function migrate(
|
||||
storageService: AbstractStorageService,
|
||||
logService: LogService
|
||||
): Promise<void> {
|
||||
const migrationHelper = new MigrationHelper(
|
||||
await currentVersion(storageService, logService),
|
||||
storageService,
|
||||
logService
|
||||
);
|
||||
if (migrationHelper.currentVersion < 0) {
|
||||
// Cannot determine state, assuming empty so we don't repeatedly apply a migration.
|
||||
await storageService.save("stateVersion", CURRENT_VERSION);
|
||||
return;
|
||||
}
|
||||
MigrationBuilder.create()
|
||||
.with(MinVersionMigrator)
|
||||
.with(FixPremiumMigrator, 2, 3)
|
||||
.with(RemoveEverBeenUnlockedMigrator, 3, 4)
|
||||
.with(AddKeyTypeToOrgKeysMigrator, 4, 5)
|
||||
.with(RemoveLegacyEtmKeyMigrator, 5, 6)
|
||||
.with(MoveBiometricAutoPromptToAccount, 6, 7)
|
||||
.with(MoveStateVersionMigrator, 7, CURRENT_VERSION)
|
||||
.migrate(migrationHelper);
|
||||
}
|
||||
|
||||
export async function currentVersion(
|
||||
storageService: AbstractStorageService,
|
||||
logService: LogService
|
||||
) {
|
||||
let state = await storageService.get<number>("stateVersion");
|
||||
if (state == null) {
|
||||
// Pre v8
|
||||
state = (await storageService.get<{ stateVersion: number }>("global"))?.stateVersion;
|
||||
}
|
||||
if (state == null) {
|
||||
logService.info("No state version found, assuming empty state.");
|
||||
return -1;
|
||||
}
|
||||
logService.info(`State version: ${state}`);
|
||||
return state;
|
||||
}
|
||||
Reference in New Issue
Block a user