mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-21 16:18:28 +01:00
[PM-6032] Run migrations in main process (#7795)
* Run Migrations in Desktop Main Process * Add `waitForMigrations` method * Add `InitOptions` * Fix Destructuring
This commit is contained in:
parent
166269520c
commit
f64092cc90
@ -43,7 +43,7 @@ export class InitService {
|
||||
init() {
|
||||
return async () => {
|
||||
this.nativeMessagingService.init();
|
||||
await this.stateService.init();
|
||||
await this.stateService.init({ runMigrations: false }); // Desktop will run them in main process
|
||||
await this.environmentService.setUrlsFromStorage();
|
||||
// Workaround to ignore stateService.activeAccount until URLs are set
|
||||
// TODO: Remove this when implementing ticket PM-2637
|
||||
|
@ -16,7 +16,8 @@ import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/imp
|
||||
import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider";
|
||||
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
|
||||
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
||||
/*/ eslint-enable import/no-restricted-paths */
|
||||
/* eslint-enable import/no-restricted-paths */
|
||||
import { migrate } from "@bitwarden/common/state-migrations";
|
||||
|
||||
import { MenuMain } from "./main/menu/menu.main";
|
||||
import { MessagingMain } from "./main/messaging.main";
|
||||
@ -191,8 +192,10 @@ export class Main {
|
||||
|
||||
bootstrap() {
|
||||
this.desktopCredentialStorageListener.init();
|
||||
this.windowMain.init().then(
|
||||
// Run migrations first, then other things
|
||||
migrate(this.storageService, this.logService).then(
|
||||
async () => {
|
||||
await this.windowMain.init();
|
||||
const locale = await this.stateService.getLocale();
|
||||
await this.i18nService.init(locale != null ? locale : app.getLocale());
|
||||
this.messagingMain.init();
|
||||
|
@ -36,6 +36,20 @@ import { EncString } from "../models/domain/enc-string";
|
||||
import { StorageOptions } from "../models/domain/storage-options";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
||||
|
||||
/**
|
||||
* Options for customizing the initiation behavior.
|
||||
*/
|
||||
export type InitOptions = {
|
||||
/**
|
||||
* Whether or not to run state migrations as part of the init process. Defaults to true.
|
||||
*
|
||||
* If false, the init method will instead wait for migrations to complete before doing its
|
||||
* other init operations. Make sure migrations have either already completed, or will complete
|
||||
* before calling {@link StateService.init} with `runMigrations: false`.
|
||||
*/
|
||||
runMigrations?: boolean;
|
||||
};
|
||||
|
||||
export abstract class StateService<T extends Account = Account> {
|
||||
accounts$: Observable<{ [userId: string]: T }>;
|
||||
activeAccount$: Observable<string>;
|
||||
@ -44,7 +58,7 @@ export abstract class StateService<T extends Account = Account> {
|
||||
addAccount: (account: T) => Promise<void>;
|
||||
setActiveUser: (userId: string) => Promise<void>;
|
||||
clean: (options?: StorageOptions) => Promise<UserId>;
|
||||
init: () => Promise<void>;
|
||||
init: (initOptions?: InitOptions) => Promise<void>;
|
||||
|
||||
getAccessToken: (options?: StorageOptions) => Promise<string>;
|
||||
setAccessToken: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
|
@ -16,6 +16,7 @@ import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum";
|
||||
import { EventData } from "../../models/data/event.data";
|
||||
import { WindowState } from "../../models/domain/window-state";
|
||||
import { migrate } from "../../state-migrations";
|
||||
import { waitForMigrations } from "../../state-migrations/migrate";
|
||||
import { GeneratorOptions } from "../../tools/generator/generator-options";
|
||||
import { GeneratedPasswordHistory, PasswordGeneratorOptions } from "../../tools/generator/password";
|
||||
import { UsernameGeneratorOptions } from "../../tools/generator/username";
|
||||
@ -33,7 +34,10 @@ import { CollectionView } from "../../vault/models/view/collection.view";
|
||||
import { AddEditCipherInfo } from "../../vault/types/add-edit-cipher-info";
|
||||
import { EnvironmentService } from "../abstractions/environment.service";
|
||||
import { LogService } from "../abstractions/log.service";
|
||||
import { StateService as StateServiceAbstraction } from "../abstractions/state.service";
|
||||
import {
|
||||
InitOptions,
|
||||
StateService as StateServiceAbstraction,
|
||||
} from "../abstractions/state.service";
|
||||
import {
|
||||
AbstractMemoryStorageService,
|
||||
AbstractStorageService,
|
||||
@ -126,12 +130,20 @@ export class StateService<
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
async init(initOptions: InitOptions = {}): Promise<void> {
|
||||
// Deconstruct and apply defaults
|
||||
const { runMigrations = true } = initOptions;
|
||||
if (this.hasBeenInited) {
|
||||
return;
|
||||
}
|
||||
|
||||
await migrate(this.storageService, this.logService);
|
||||
if (runMigrations) {
|
||||
await migrate(this.storageService, this.logService);
|
||||
} else {
|
||||
// It may have been requested to not run the migrations but we should defensively not
|
||||
// continue this method until migrations have a chance to be completed elsewhere.
|
||||
await waitForMigrations(this.storageService, this.logService);
|
||||
}
|
||||
|
||||
await this.state().then(async (state) => {
|
||||
if (state == null) {
|
||||
|
@ -71,3 +71,46 @@ export async function currentVersion(
|
||||
logService.info(`State version: ${state}`);
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for migrations to have a chance to run and will resolve the promise once they are.
|
||||
*
|
||||
* @param storageService Disk storage where the `stateVersion` will or is already saved in.
|
||||
* @param logService Log service
|
||||
*/
|
||||
export async function waitForMigrations(
|
||||
storageService: AbstractStorageService,
|
||||
logService: LogService,
|
||||
) {
|
||||
const isReady = async () => {
|
||||
const version = await currentVersion(storageService, logService);
|
||||
// The saved version is what we consider the latest
|
||||
// migrations should be complete
|
||||
return version === CURRENT_VERSION;
|
||||
};
|
||||
|
||||
const wait = async (time: number) => {
|
||||
// Wait exponentially
|
||||
const nextTime = time * 2;
|
||||
if (nextTime > 8192) {
|
||||
// Don't wait longer than ~8 seconds in a single wait,
|
||||
// if the migrations still haven't happened. They aren't
|
||||
// likely to.
|
||||
return;
|
||||
}
|
||||
return new Promise<void>((resolve) => {
|
||||
setTimeout(async () => {
|
||||
if (!(await isReady())) {
|
||||
logService.info(`Waiting for migrations to finish, waiting for ${nextTime}ms`);
|
||||
await wait(nextTime);
|
||||
}
|
||||
resolve();
|
||||
}, time);
|
||||
});
|
||||
};
|
||||
|
||||
if (!(await isReady())) {
|
||||
// Wait for 2ms to start with
|
||||
await wait(2);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user