mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
[PM-5540] DesktopSettingsService (#8369)
* WIP: First Try at making DesktopSettingsService Does not work, migrations are ran in renderer but the values are read in main. * Update window$ retrieval * Fix DesktopSettings * Rename Migration * Add Migration to Builder * Cleanup * Update Comments * Update `migrate.ts` * Catch Unawaited Promises * Remove Comments * Update Tests * Rename Migration * Add `alwaysOnTop` * Make `init` async * Fix Desktop Build
This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
export class WindowState {
|
||||
width?: number;
|
||||
height?: number;
|
||||
isMaximized?: boolean;
|
||||
// TODO: displayBounds is an Electron.Rectangle.
|
||||
// We need to establish some kind of client-specific global state, similar to the way we already extend a base Account.
|
||||
displayBounds: any;
|
||||
x?: number;
|
||||
y?: number;
|
||||
zoomFactor?: number;
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import { AdminAuthRequestStorable } from "../../auth/models/domain/admin-auth-re
|
||||
import { ForceSetPasswordReason } from "../../auth/models/domain/force-set-password-reason";
|
||||
import { KdfConfig } from "../../auth/models/domain/kdf-config";
|
||||
import { BiometricKey } from "../../auth/types/biometric-key";
|
||||
import { WindowState } from "../../models/domain/window-state";
|
||||
import { GeneratorOptions } from "../../tools/generator/generator-options";
|
||||
import { GeneratedPasswordHistory, PasswordGeneratorOptions } from "../../tools/generator/password";
|
||||
import { UsernameGeneratorOptions } from "../../tools/generator/username";
|
||||
@@ -52,8 +51,6 @@ export abstract class StateService<T extends Account = Account> {
|
||||
|
||||
getAddEditCipherInfo: (options?: StorageOptions) => Promise<AddEditCipherInfo>;
|
||||
setAddEditCipherInfo: (value: AddEditCipherInfo, options?: StorageOptions) => Promise<void>;
|
||||
getAlwaysShowDock: (options?: StorageOptions) => Promise<boolean>;
|
||||
setAlwaysShowDock: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getBiometricFingerprintValidated: (options?: StorageOptions) => Promise<boolean>;
|
||||
setBiometricFingerprintValidated: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getConvertAccountToKeyConnector: (options?: StorageOptions) => Promise<boolean>;
|
||||
@@ -184,8 +181,6 @@ export abstract class StateService<T extends Account = Account> {
|
||||
setEmail: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getEmailVerified: (options?: StorageOptions) => Promise<boolean>;
|
||||
setEmailVerified: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getEnableAlwaysOnTop: (options?: StorageOptions) => Promise<boolean>;
|
||||
setEnableAlwaysOnTop: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getEnableBrowserIntegration: (options?: StorageOptions) => Promise<boolean>;
|
||||
setEnableBrowserIntegration: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getEnableBrowserIntegrationFingerprint: (options?: StorageOptions) => Promise<boolean>;
|
||||
@@ -193,19 +188,11 @@ export abstract class StateService<T extends Account = Account> {
|
||||
value: boolean,
|
||||
options?: StorageOptions,
|
||||
) => Promise<void>;
|
||||
getEnableCloseToTray: (options?: StorageOptions) => Promise<boolean>;
|
||||
setEnableCloseToTray: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getEnableDuckDuckGoBrowserIntegration: (options?: StorageOptions) => Promise<boolean>;
|
||||
setEnableDuckDuckGoBrowserIntegration: (
|
||||
value: boolean,
|
||||
options?: StorageOptions,
|
||||
) => Promise<void>;
|
||||
getEnableMinimizeToTray: (options?: StorageOptions) => Promise<boolean>;
|
||||
setEnableMinimizeToTray: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getEnableStartToTray: (options?: StorageOptions) => Promise<boolean>;
|
||||
setEnableStartToTray: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getEnableTray: (options?: StorageOptions) => Promise<boolean>;
|
||||
setEnableTray: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getEncryptedCiphers: (options?: StorageOptions) => Promise<{ [id: string]: CipherData }>;
|
||||
setEncryptedCiphers: (
|
||||
value: { [id: string]: CipherData },
|
||||
@@ -261,12 +248,8 @@ export abstract class StateService<T extends Account = Account> {
|
||||
) => Promise<void>;
|
||||
getLocale: (options?: StorageOptions) => Promise<string>;
|
||||
setLocale: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getMainWindowSize: (options?: StorageOptions) => Promise<number>;
|
||||
setMainWindowSize: (value: number, options?: StorageOptions) => Promise<void>;
|
||||
getMinimizeOnCopyToClipboard: (options?: StorageOptions) => Promise<boolean>;
|
||||
setMinimizeOnCopyToClipboard: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getOpenAtLogin: (options?: StorageOptions) => Promise<boolean>;
|
||||
setOpenAtLogin: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getOrganizationInvitation: (options?: StorageOptions) => Promise<any>;
|
||||
setOrganizationInvitation: (value: any, options?: StorageOptions) => Promise<void>;
|
||||
getPasswordGenerationOptions: (options?: StorageOptions) => Promise<PasswordGeneratorOptions>;
|
||||
@@ -302,8 +285,6 @@ export abstract class StateService<T extends Account = Account> {
|
||||
setVaultTimeoutAction: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getApproveLoginRequests: (options?: StorageOptions) => Promise<boolean>;
|
||||
setApproveLoginRequests: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getWindow: () => Promise<WindowState>;
|
||||
setWindow: (value: WindowState) => Promise<void>;
|
||||
/**
|
||||
* @deprecated Do not call this directly, use ConfigService
|
||||
*/
|
||||
|
||||
@@ -186,7 +186,6 @@ export class AccountProfile {
|
||||
export class AccountSettings {
|
||||
defaultUriMatch?: UriMatchStrategySetting;
|
||||
disableGa?: boolean;
|
||||
enableAlwaysOnTop?: boolean;
|
||||
enableBiometric?: boolean;
|
||||
minimizeOnCopyToClipboard?: boolean;
|
||||
passwordGenerationOptions?: PasswordGeneratorOptions;
|
||||
|
||||
@@ -1,26 +1,17 @@
|
||||
import { WindowState } from "../../../models/domain/window-state";
|
||||
import { ThemeType } from "../../enums";
|
||||
|
||||
export class GlobalState {
|
||||
enableAlwaysOnTop?: boolean;
|
||||
installedVersion?: string;
|
||||
locale?: string;
|
||||
organizationInvitation?: any;
|
||||
rememberedEmail?: string;
|
||||
theme?: ThemeType = ThemeType.System;
|
||||
window?: WindowState = new WindowState();
|
||||
twoFactorToken?: string;
|
||||
biometricFingerprintValidated?: boolean;
|
||||
vaultTimeout?: number;
|
||||
vaultTimeoutAction?: string;
|
||||
loginRedirect?: any;
|
||||
mainWindowSize?: number;
|
||||
enableTray?: boolean;
|
||||
enableMinimizeToTray?: boolean;
|
||||
enableCloseToTray?: boolean;
|
||||
enableStartToTray?: boolean;
|
||||
openAtLogin?: boolean;
|
||||
alwaysShowDock?: boolean;
|
||||
enableBrowserIntegration?: boolean;
|
||||
enableBrowserIntegrationFingerprint?: boolean;
|
||||
enableDuckDuckGoBrowserIntegration?: boolean;
|
||||
|
||||
@@ -8,7 +8,6 @@ import { AdminAuthRequestStorable } from "../../auth/models/domain/admin-auth-re
|
||||
import { ForceSetPasswordReason } from "../../auth/models/domain/force-set-password-reason";
|
||||
import { KdfConfig } from "../../auth/models/domain/kdf-config";
|
||||
import { BiometricKey } from "../../auth/types/biometric-key";
|
||||
import { WindowState } from "../../models/domain/window-state";
|
||||
import { GeneratorOptions } from "../../tools/generator/generator-options";
|
||||
import { GeneratedPasswordHistory, PasswordGeneratorOptions } from "../../tools/generator/password";
|
||||
import { UsernameGeneratorOptions } from "../../tools/generator/username";
|
||||
@@ -277,24 +276,6 @@ export class StateService<
|
||||
);
|
||||
}
|
||||
|
||||
async getAlwaysShowDock(options?: StorageOptions): Promise<boolean> {
|
||||
return (
|
||||
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
|
||||
?.alwaysShowDock ?? false
|
||||
);
|
||||
}
|
||||
|
||||
async setAlwaysShowDock(value: boolean, options?: StorageOptions): Promise<void> {
|
||||
const globals = await this.getGlobals(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
globals.alwaysShowDock = value;
|
||||
await this.saveGlobals(
|
||||
globals,
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
}
|
||||
|
||||
async getBiometricFingerprintValidated(options?: StorageOptions): Promise<boolean> {
|
||||
return (
|
||||
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
|
||||
@@ -847,36 +828,6 @@ export class StateService<
|
||||
);
|
||||
}
|
||||
|
||||
async getEnableAlwaysOnTop(options?: StorageOptions): Promise<boolean> {
|
||||
const accountPreference = (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||
)?.settings?.enableAlwaysOnTop;
|
||||
const globalPreference = (
|
||||
await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||
)?.enableAlwaysOnTop;
|
||||
return accountPreference ?? globalPreference ?? false;
|
||||
}
|
||||
|
||||
async setEnableAlwaysOnTop(value: boolean, options?: StorageOptions): Promise<void> {
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
account.settings.enableAlwaysOnTop = value;
|
||||
await this.saveAccount(
|
||||
account,
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
|
||||
const globals = await this.getGlobals(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
globals.enableAlwaysOnTop = value;
|
||||
await this.saveGlobals(
|
||||
globals,
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
}
|
||||
|
||||
async getEnableBrowserIntegration(options?: StorageOptions): Promise<boolean> {
|
||||
return (
|
||||
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
|
||||
@@ -916,24 +867,6 @@ export class StateService<
|
||||
);
|
||||
}
|
||||
|
||||
async getEnableCloseToTray(options?: StorageOptions): Promise<boolean> {
|
||||
return (
|
||||
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
|
||||
?.enableCloseToTray ?? false
|
||||
);
|
||||
}
|
||||
|
||||
async setEnableCloseToTray(value: boolean, options?: StorageOptions): Promise<void> {
|
||||
const globals = await this.getGlobals(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
globals.enableCloseToTray = value;
|
||||
await this.saveGlobals(
|
||||
globals,
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
}
|
||||
|
||||
async getEnableDuckDuckGoBrowserIntegration(options?: StorageOptions): Promise<boolean> {
|
||||
return (
|
||||
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
|
||||
@@ -955,60 +888,6 @@ export class StateService<
|
||||
);
|
||||
}
|
||||
|
||||
async getEnableMinimizeToTray(options?: StorageOptions): Promise<boolean> {
|
||||
return (
|
||||
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
|
||||
?.enableMinimizeToTray ?? false
|
||||
);
|
||||
}
|
||||
|
||||
async setEnableMinimizeToTray(value: boolean, options?: StorageOptions): Promise<void> {
|
||||
const globals = await this.getGlobals(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
globals.enableMinimizeToTray = value;
|
||||
await this.saveGlobals(
|
||||
globals,
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
}
|
||||
|
||||
async getEnableStartToTray(options?: StorageOptions): Promise<boolean> {
|
||||
return (
|
||||
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
|
||||
?.enableStartToTray ?? false
|
||||
);
|
||||
}
|
||||
|
||||
async setEnableStartToTray(value: boolean, options?: StorageOptions): Promise<void> {
|
||||
const globals = await this.getGlobals(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
globals.enableStartToTray = value;
|
||||
await this.saveGlobals(
|
||||
globals,
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
}
|
||||
|
||||
async getEnableTray(options?: StorageOptions): Promise<boolean> {
|
||||
return (
|
||||
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
|
||||
?.enableTray ?? false
|
||||
);
|
||||
}
|
||||
|
||||
async setEnableTray(value: boolean, options?: StorageOptions): Promise<void> {
|
||||
const globals = await this.getGlobals(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
globals.enableTray = value;
|
||||
await this.saveGlobals(
|
||||
globals,
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
}
|
||||
|
||||
@withPrototypeForObjectValues(CipherData)
|
||||
async getEncryptedCiphers(options?: StorageOptions): Promise<{ [id: string]: CipherData }> {
|
||||
return (
|
||||
@@ -1309,23 +1188,6 @@ export class StateService<
|
||||
);
|
||||
}
|
||||
|
||||
async getMainWindowSize(options?: StorageOptions): Promise<number> {
|
||||
return (
|
||||
await this.getGlobals(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
||||
)?.mainWindowSize;
|
||||
}
|
||||
|
||||
async setMainWindowSize(value: number, options?: StorageOptions): Promise<void> {
|
||||
const globals = await this.getGlobals(
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions()),
|
||||
);
|
||||
globals.mainWindowSize = value;
|
||||
await this.saveGlobals(
|
||||
globals,
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions()),
|
||||
);
|
||||
}
|
||||
|
||||
async getMinimizeOnCopyToClipboard(options?: StorageOptions): Promise<boolean> {
|
||||
return (
|
||||
(await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
|
||||
@@ -1344,24 +1206,6 @@ export class StateService<
|
||||
);
|
||||
}
|
||||
|
||||
async getOpenAtLogin(options?: StorageOptions): Promise<boolean> {
|
||||
return (
|
||||
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
|
||||
?.openAtLogin ?? false
|
||||
);
|
||||
}
|
||||
|
||||
async setOpenAtLogin(value: boolean, options?: StorageOptions): Promise<void> {
|
||||
const globals = await this.getGlobals(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
globals.openAtLogin = value;
|
||||
await this.saveGlobals(
|
||||
globals,
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
}
|
||||
|
||||
async getOrganizationInvitation(options?: StorageOptions): Promise<any> {
|
||||
return (
|
||||
await this.getGlobals(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
||||
@@ -1571,24 +1415,6 @@ export class StateService<
|
||||
);
|
||||
}
|
||||
|
||||
async getWindow(): Promise<WindowState> {
|
||||
const globals = await this.getGlobals(await this.defaultOnDiskOptions());
|
||||
return globals?.window != null && Object.keys(globals.window).length > 0
|
||||
? globals.window
|
||||
: new WindowState();
|
||||
}
|
||||
|
||||
async setWindow(value: WindowState, options?: StorageOptions): Promise<void> {
|
||||
const globals = await this.getGlobals(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
globals.window = value;
|
||||
return await this.saveGlobals(
|
||||
globals,
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
|
||||
);
|
||||
}
|
||||
|
||||
async setServerConfig(value: ServerConfigData, options?: StorageOptions): Promise<void> {
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()),
|
||||
|
||||
@@ -42,6 +42,7 @@ import { AutoConfirmFingerPrintsMigrator } from "./migrations/43-move-auto-confi
|
||||
import { UserDecryptionOptionsMigrator } from "./migrations/44-move-user-decryption-options-to-state-provider";
|
||||
import { MergeEnvironmentState } from "./migrations/45-merge-environment-state";
|
||||
import { DeleteBiometricPromptCancelledData } from "./migrations/46-delete-orphaned-biometric-prompt-data";
|
||||
import { MoveDesktopSettingsMigrator } from "./migrations/47-move-desktop-settings";
|
||||
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";
|
||||
@@ -50,7 +51,7 @@ import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-setting
|
||||
import { MinVersionMigrator } from "./migrations/min-version";
|
||||
|
||||
export const MIN_VERSION = 3;
|
||||
export const CURRENT_VERSION = 46;
|
||||
export const CURRENT_VERSION = 47;
|
||||
export type MinVersion = typeof MIN_VERSION;
|
||||
|
||||
export function createMigrationBuilder() {
|
||||
@@ -98,7 +99,8 @@ export function createMigrationBuilder() {
|
||||
.with(AutoConfirmFingerPrintsMigrator, 42, 43)
|
||||
.with(UserDecryptionOptionsMigrator, 43, 44)
|
||||
.with(MergeEnvironmentState, 44, 45)
|
||||
.with(DeleteBiometricPromptCancelledData, 45, CURRENT_VERSION);
|
||||
.with(DeleteBiometricPromptCancelledData, 45, 46)
|
||||
.with(MoveDesktopSettingsMigrator, 46, CURRENT_VERSION);
|
||||
}
|
||||
|
||||
export async function currentVersion(
|
||||
|
||||
@@ -178,12 +178,9 @@ export function mockMigrationHelper(
|
||||
return mockHelper;
|
||||
}
|
||||
|
||||
// TODO: Use const generic for TUsers in TypeScript 5.0 so consumers don't have to `as const` themselves
|
||||
export type InitialDataHint<TUsers extends readonly string[]> = {
|
||||
/**
|
||||
* A string array of the users id who are authenticated
|
||||
*
|
||||
* NOTE: It's recommended to as const this string array so you get type help defining the users data
|
||||
*/
|
||||
authenticatedAccounts?: TUsers;
|
||||
/**
|
||||
@@ -282,10 +279,9 @@ function expectInjectedData(
|
||||
* @param initalData The data to start with
|
||||
* @returns State after your migration has ran.
|
||||
*/
|
||||
// TODO: Use const generic for TUsers in TypeScript 5.0 so consumers don't have to `as const` themselves
|
||||
export async function runMigrator<
|
||||
TMigrator extends Migrator<number, number>,
|
||||
TUsers extends readonly string[] = string[],
|
||||
const TUsers extends readonly string[],
|
||||
>(
|
||||
migrator: TMigrator,
|
||||
initalData?: InitialDataHint<TUsers>,
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import { runMigrator } from "../migration-helper.spec";
|
||||
|
||||
import { MoveDesktopSettingsMigrator } from "./47-move-desktop-settings";
|
||||
|
||||
describe("MoveDesktopSettings", () => {
|
||||
const sut = new MoveDesktopSettingsMigrator(46, 47);
|
||||
|
||||
it("can migrate truthy values", async () => {
|
||||
const output = await runMigrator(sut, {
|
||||
authenticatedAccounts: ["user1"],
|
||||
global: {
|
||||
window: {
|
||||
width: 400,
|
||||
height: 400,
|
||||
displayBounds: {
|
||||
height: 200,
|
||||
width: 200,
|
||||
x: 200,
|
||||
y: 200,
|
||||
},
|
||||
},
|
||||
enableAlwaysOnTop: true,
|
||||
enableCloseToTray: true,
|
||||
enableMinimizeToTray: true,
|
||||
enableStartToTray: true,
|
||||
enableTray: true,
|
||||
openAtLogin: true,
|
||||
alwaysShowDock: true,
|
||||
},
|
||||
user1: {
|
||||
settings: {
|
||||
enableAlwaysOnTop: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(output).toEqual({
|
||||
authenticatedAccounts: ["user1"],
|
||||
global: {},
|
||||
global_desktopSettings_window: {
|
||||
width: 400,
|
||||
height: 400,
|
||||
displayBounds: {
|
||||
height: 200,
|
||||
width: 200,
|
||||
x: 200,
|
||||
y: 200,
|
||||
},
|
||||
},
|
||||
global_desktopSettings_closeToTray: true,
|
||||
global_desktopSettings_minimizeToTray: true,
|
||||
global_desktopSettings_startToTray: true,
|
||||
global_desktopSettings_trayEnabled: true,
|
||||
global_desktopSettings_openAtLogin: true,
|
||||
global_desktopSettings_alwaysShowDock: true,
|
||||
global_desktopSettings_alwaysOnTop: true,
|
||||
user1: {
|
||||
settings: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("can migrate falsey values", async () => {
|
||||
const output = await runMigrator(sut, {
|
||||
authenticatedAccounts: ["user1"],
|
||||
global: {
|
||||
window: null,
|
||||
enableCloseToTray: false,
|
||||
enableMinimizeToTray: false,
|
||||
enableStartToTray: false,
|
||||
enableTray: false,
|
||||
openAtLogin: false,
|
||||
alwaysShowDock: false,
|
||||
enableAlwaysOnTop: false,
|
||||
},
|
||||
user1: {
|
||||
settings: {
|
||||
enableAlwaysOnTop: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(output).toEqual({
|
||||
authenticatedAccounts: ["user1"],
|
||||
global: {},
|
||||
global_desktopSettings_window: null,
|
||||
global_desktopSettings_closeToTray: false,
|
||||
global_desktopSettings_minimizeToTray: false,
|
||||
global_desktopSettings_startToTray: false,
|
||||
global_desktopSettings_trayEnabled: false,
|
||||
global_desktopSettings_openAtLogin: false,
|
||||
global_desktopSettings_alwaysShowDock: false,
|
||||
global_desktopSettings_alwaysOnTop: false,
|
||||
user1: {
|
||||
settings: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("can migrate even if none of our values are found", async () => {
|
||||
//
|
||||
const output = await runMigrator(sut, {
|
||||
authenticatedAccounts: ["user1"] as const,
|
||||
global: {
|
||||
anotherSetting: "",
|
||||
},
|
||||
});
|
||||
|
||||
expect(output).toEqual({
|
||||
authenticatedAccounts: ["user1"] as const,
|
||||
global: {
|
||||
anotherSetting: "",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,128 @@
|
||||
import { KeyDefinitionLike, MigrationHelper, StateDefinitionLike } from "../migration-helper";
|
||||
import { IRREVERSIBLE, Migrator } from "../migrator";
|
||||
|
||||
type ExpectedGlobalType = {
|
||||
window?: object;
|
||||
enableTray?: boolean;
|
||||
enableMinimizeToTray?: boolean;
|
||||
enableCloseToTray?: boolean;
|
||||
enableStartToTray?: boolean;
|
||||
openAtLogin?: boolean;
|
||||
alwaysShowDock?: boolean;
|
||||
enableAlwaysOnTop?: boolean;
|
||||
};
|
||||
|
||||
type ExpectedAccountType = {
|
||||
settings?: {
|
||||
enableAlwaysOnTop?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
const DESKTOP_SETTINGS_STATE: StateDefinitionLike = { name: "desktopSettings" };
|
||||
|
||||
const WINDOW_KEY: KeyDefinitionLike = { key: "window", stateDefinition: DESKTOP_SETTINGS_STATE };
|
||||
|
||||
const CLOSE_TO_TRAY_KEY: KeyDefinitionLike = {
|
||||
key: "closeToTray",
|
||||
stateDefinition: DESKTOP_SETTINGS_STATE,
|
||||
};
|
||||
const MINIMIZE_TO_TRAY_KEY: KeyDefinitionLike = {
|
||||
key: "minimizeToTray",
|
||||
stateDefinition: DESKTOP_SETTINGS_STATE,
|
||||
};
|
||||
const START_TO_TRAY_KEY: KeyDefinitionLike = {
|
||||
key: "startToTray",
|
||||
stateDefinition: DESKTOP_SETTINGS_STATE,
|
||||
};
|
||||
const TRAY_ENABLED_KEY: KeyDefinitionLike = {
|
||||
key: "trayEnabled",
|
||||
stateDefinition: DESKTOP_SETTINGS_STATE,
|
||||
};
|
||||
const OPEN_AT_LOGIN_KEY: KeyDefinitionLike = {
|
||||
key: "openAtLogin",
|
||||
stateDefinition: DESKTOP_SETTINGS_STATE,
|
||||
};
|
||||
const ALWAYS_SHOW_DOCK_KEY: KeyDefinitionLike = {
|
||||
key: "alwaysShowDock",
|
||||
stateDefinition: DESKTOP_SETTINGS_STATE,
|
||||
};
|
||||
|
||||
const ALWAYS_ON_TOP_KEY: KeyDefinitionLike = {
|
||||
key: "alwaysOnTop",
|
||||
stateDefinition: DESKTOP_SETTINGS_STATE,
|
||||
};
|
||||
|
||||
export class MoveDesktopSettingsMigrator extends Migrator<46, 47> {
|
||||
async migrate(helper: MigrationHelper): Promise<void> {
|
||||
const legacyGlobal = await helper.get<ExpectedGlobalType>("global");
|
||||
|
||||
let updatedGlobal = false;
|
||||
if (legacyGlobal?.window !== undefined) {
|
||||
await helper.setToGlobal(WINDOW_KEY, legacyGlobal.window);
|
||||
updatedGlobal = true;
|
||||
delete legacyGlobal.window;
|
||||
}
|
||||
|
||||
if (legacyGlobal?.enableCloseToTray != null) {
|
||||
await helper.setToGlobal(CLOSE_TO_TRAY_KEY, legacyGlobal.enableCloseToTray);
|
||||
updatedGlobal = true;
|
||||
delete legacyGlobal.enableCloseToTray;
|
||||
}
|
||||
|
||||
if (legacyGlobal?.enableMinimizeToTray != null) {
|
||||
await helper.setToGlobal(MINIMIZE_TO_TRAY_KEY, legacyGlobal.enableMinimizeToTray);
|
||||
updatedGlobal = true;
|
||||
delete legacyGlobal.enableMinimizeToTray;
|
||||
}
|
||||
|
||||
if (legacyGlobal?.enableStartToTray != null) {
|
||||
await helper.setToGlobal(START_TO_TRAY_KEY, legacyGlobal.enableStartToTray);
|
||||
updatedGlobal = true;
|
||||
delete legacyGlobal.enableStartToTray;
|
||||
}
|
||||
|
||||
if (legacyGlobal?.enableTray != null) {
|
||||
await helper.setToGlobal(TRAY_ENABLED_KEY, legacyGlobal.enableTray);
|
||||
updatedGlobal = true;
|
||||
delete legacyGlobal.enableTray;
|
||||
}
|
||||
|
||||
if (legacyGlobal?.openAtLogin != null) {
|
||||
await helper.setToGlobal(OPEN_AT_LOGIN_KEY, legacyGlobal.openAtLogin);
|
||||
updatedGlobal = true;
|
||||
delete legacyGlobal.openAtLogin;
|
||||
}
|
||||
|
||||
if (legacyGlobal?.alwaysShowDock != null) {
|
||||
await helper.setToGlobal(ALWAYS_SHOW_DOCK_KEY, legacyGlobal.alwaysShowDock);
|
||||
updatedGlobal = true;
|
||||
delete legacyGlobal.alwaysShowDock;
|
||||
}
|
||||
|
||||
if (legacyGlobal?.enableAlwaysOnTop != null) {
|
||||
await helper.setToGlobal(ALWAYS_ON_TOP_KEY, legacyGlobal.enableAlwaysOnTop);
|
||||
updatedGlobal = true;
|
||||
delete legacyGlobal.enableAlwaysOnTop;
|
||||
}
|
||||
|
||||
if (updatedGlobal) {
|
||||
await helper.set("global", legacyGlobal);
|
||||
}
|
||||
|
||||
async function migrateAccount(userId: string, account: ExpectedAccountType) {
|
||||
// We only migrate the global setting for this, if we find it on the account object
|
||||
// just delete it.
|
||||
if (account?.settings?.enableAlwaysOnTop != null) {
|
||||
delete account.settings.enableAlwaysOnTop;
|
||||
await helper.set(userId, account);
|
||||
}
|
||||
}
|
||||
|
||||
const accounts = await helper.getAccounts<ExpectedAccountType>();
|
||||
await Promise.all(accounts.map(({ userId, account }) => migrateAccount(userId, account)));
|
||||
}
|
||||
|
||||
rollback(helper: MigrationHelper): Promise<void> {
|
||||
throw IRREVERSIBLE;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user