1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-18 17:23:37 +00:00

Merge branch 'main' into vault/pm-5273

# Conflicts:
#	libs/common/src/platform/state/state-definitions.ts
#	libs/common/src/state-migrations/migrate.ts
This commit is contained in:
Carlos Gonçalves
2024-03-07 15:44:21 +00:00
646 changed files with 21369 additions and 12700 deletions

View File

@@ -119,7 +119,7 @@ describe("OrganizationKeysMigrator", () => {
describe("rollback", () => {
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 10);
helper = mockMigrationHelper(rollbackJSON(), 11);
sut = new OrganizationKeyMigrator(10, 11);
});

View File

@@ -98,7 +98,7 @@ describe("ProviderKeysMigrator", () => {
describe("rollback", () => {
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 11);
helper = mockMigrationHelper(rollbackJSON(), 13);
sut = new ProviderKeyMigrator(12, 13);
});

View File

@@ -1,4 +1,4 @@
import { any, MockProxy } from "jest-mock-extended";
import { MockProxy, any } from "jest-mock-extended";
import { MigrationHelper } from "../migration-helper";
import { mockMigrationHelper } from "../migration-helper.spec";

View File

@@ -1,11 +1,16 @@
import { any, MockProxy } from "jest-mock-extended";
import { AutofillOverlayVisibility } from "../../../../../apps/browser/src/autofill/utils/autofill-overlay.enum";
import { StateDefinitionLike, MigrationHelper } from "../migration-helper";
import { mockMigrationHelper } from "../migration-helper.spec";
import { AutofillSettingsKeyMigrator } from "./18-move-autofill-settings-to-state-providers";
const AutofillOverlayVisibility = {
Off: 0,
OnButtonClick: 1,
OnFieldFocus: 2,
} as const;
function exampleJSON() {
return {
global: {
@@ -68,7 +73,7 @@ const autofillSettingsStateDefinition: {
},
};
describe("ProviderKeysMigrator", () => {
describe("AutofillSettingsKeyMigrator", () => {
let helper: MockProxy<MigrationHelper>;
let sut: AutofillSettingsKeyMigrator;
@@ -142,7 +147,7 @@ describe("ProviderKeysMigrator", () => {
describe("rollback", () => {
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 16);
helper = mockMigrationHelper(rollbackJSON(), 18);
sut = new AutofillSettingsKeyMigrator(17, 18);
});

View File

@@ -1,7 +1,15 @@
import { InlineMenuVisibilitySetting } from "../../../../../apps/browser/src/autofill/utils/autofill-overlay.enum";
import { StateDefinitionLike, MigrationHelper } from "../migration-helper";
import { Migrator } from "../migrator";
const AutofillOverlayVisibility = {
Off: 0,
OnButtonClick: 1,
OnFieldFocus: 2,
} as const;
type InlineMenuVisibilitySetting =
(typeof AutofillOverlayVisibility)[keyof typeof AutofillOverlayVisibility];
type ExpectedAccountState = {
settings?: {
autoFillOnPageLoadDefault?: boolean;

View File

@@ -0,0 +1,131 @@
import { MockProxy, any } from "jest-mock-extended";
import { MigrationHelper } from "../migration-helper";
import { mockMigrationHelper } from "../migration-helper.spec";
import {
MoveBiometricPromptsToStateProviders,
DISMISSED_BIOMETRIC_REQUIRE_PASSWORD_ON_START_CALLOUT,
PROMPT_AUTOMATICALLY,
} from "./23-move-biometric-prompts-to-state-providers";
function exampleJSON() {
return {
global: {
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["user-1", "user-2", "user-3"],
"user-1": {
settings: {
disableAutoBiometricsPrompt: false,
dismissedBiometricRequirePasswordOnStartCallout: true,
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
},
"user-2": {
otherStuff: "otherStuff4",
},
};
}
function rollbackJSON() {
return {
"user_user-1_biometricSettings_dismissedBiometricRequirePasswordOnStartCallout": true,
"user_user-1_biometricSettings_promptAutomatically": "false",
global: {
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["user-1", "user-2", "user-3"],
"user-1": {
settings: {
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
},
"user-2": {
otherStuff: "otherStuff4",
},
};
}
describe("MoveBiometricPromptsToStateProviders migrator", () => {
let helper: MockProxy<MigrationHelper>;
let sut: MoveBiometricPromptsToStateProviders;
describe("migrate", () => {
beforeEach(() => {
helper = mockMigrationHelper(exampleJSON(), 22);
sut = new MoveBiometricPromptsToStateProviders(22, 23);
});
it("should remove biometricUnlock, dismissedBiometricRequirePasswordOnStartCallout, and biometricEncryptionClientKeyHalf from all accounts", async () => {
await sut.migrate(helper);
expect(helper.set).toHaveBeenCalledTimes(2);
expect(helper.set).toHaveBeenCalledWith("user-1", {
settings: {
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
});
expect(helper.set).toHaveBeenCalledWith("user-2", {
otherStuff: "otherStuff4",
});
});
it("should set dismissedBiometricRequirePasswordOnStartCallout value for account that have it", async () => {
await sut.migrate(helper);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-1",
DISMISSED_BIOMETRIC_REQUIRE_PASSWORD_ON_START_CALLOUT,
true,
);
});
it("should not call extra setToUser", async () => {
await sut.migrate(helper);
expect(helper.setToUser).toHaveBeenCalledTimes(2);
});
});
describe("rollback", () => {
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 23);
sut = new MoveBiometricPromptsToStateProviders(22, 23);
});
it.each([DISMISSED_BIOMETRIC_REQUIRE_PASSWORD_ON_START_CALLOUT, PROMPT_AUTOMATICALLY])(
"should null out new values %s",
async (keyDefinition) => {
await sut.rollback(helper);
expect(helper.setToUser).toHaveBeenCalledWith("user-1", keyDefinition, null);
},
);
it("should add explicit value back to accounts", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledTimes(1);
expect(helper.set).toHaveBeenCalledWith("user-1", {
settings: {
disableAutoBiometricsPrompt: false,
dismissedBiometricRequirePasswordOnStartCallout: true,
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
});
});
it.each(["user-2", "user-3"])(
"should not try to restore values to missing accounts",
async (userId) => {
await sut.rollback(helper);
expect(helper.set).not.toHaveBeenCalledWith(userId, any());
},
);
});
});

View File

@@ -0,0 +1,99 @@
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
import { Migrator } from "../migrator";
type ExpectedAccountType = {
settings?: {
disableAutoBiometricsPrompt?: boolean;
dismissedBiometricRequirePasswordOnStartCallout?: boolean;
};
};
// prompt cancelled is refreshed on every app start/quit/unlock, so we don't need to migrate it
export const DISMISSED_BIOMETRIC_REQUIRE_PASSWORD_ON_START_CALLOUT: KeyDefinitionLike = {
key: "dismissedBiometricRequirePasswordOnStartCallout",
stateDefinition: { name: "biometricSettings" },
};
export const PROMPT_AUTOMATICALLY: KeyDefinitionLike = {
key: "promptAutomatically",
stateDefinition: { name: "biometricSettings" },
};
export class MoveBiometricPromptsToStateProviders extends Migrator<22, 23> {
async migrate(helper: MigrationHelper): Promise<void> {
const legacyAccounts = await helper.getAccounts<ExpectedAccountType>();
await Promise.all(
legacyAccounts.map(async ({ userId, account }) => {
if (account == null) {
return;
}
// Move account data
if (account?.settings?.dismissedBiometricRequirePasswordOnStartCallout != null) {
await helper.setToUser(
userId,
DISMISSED_BIOMETRIC_REQUIRE_PASSWORD_ON_START_CALLOUT,
account.settings.dismissedBiometricRequirePasswordOnStartCallout,
);
}
if (account?.settings?.disableAutoBiometricsPrompt != null) {
await helper.setToUser(
userId,
PROMPT_AUTOMATICALLY,
!account.settings.disableAutoBiometricsPrompt,
);
}
// Delete old account data
delete account?.settings?.dismissedBiometricRequirePasswordOnStartCallout;
delete account?.settings?.disableAutoBiometricsPrompt;
await helper.set(userId, account);
}),
);
}
async rollback(helper: MigrationHelper): Promise<void> {
async function rollbackUser(userId: string, account: ExpectedAccountType) {
let updatedAccount = false;
const userDismissed = await helper.getFromUser<boolean>(
userId,
DISMISSED_BIOMETRIC_REQUIRE_PASSWORD_ON_START_CALLOUT,
);
if (userDismissed) {
account ??= {};
account.settings ??= {};
updatedAccount = true;
account.settings.dismissedBiometricRequirePasswordOnStartCallout = userDismissed;
await helper.setToUser(userId, DISMISSED_BIOMETRIC_REQUIRE_PASSWORD_ON_START_CALLOUT, null);
}
const userPromptAutomatically = await helper.getFromUser<boolean>(
userId,
PROMPT_AUTOMATICALLY,
);
if (userPromptAutomatically != null) {
account ??= {};
account.settings ??= {};
updatedAccount = true;
account.settings.disableAutoBiometricsPrompt = !userPromptAutomatically;
await helper.setToUser(userId, PROMPT_AUTOMATICALLY, null);
}
if (updatedAccount) {
await helper.set(userId, account);
}
}
const accounts = await helper.getAccounts<ExpectedAccountType>();
await Promise.all(accounts.map(({ userId, account }) => rollbackUser(userId, account)));
}
}

View File

@@ -0,0 +1,200 @@
import { MockProxy, any } from "jest-mock-extended";
import { MigrationHelper } from "../migration-helper";
import { mockMigrationHelper } from "../migration-helper.spec";
import { SmOnboardingTasksMigrator } from "./24-move-sm-onboarding-key-to-state-providers";
function exampleJSON() {
return {
authenticatedAccounts: ["user-1", "user-2", "user-3"],
"user-1": {
settings: {
smOnboardingTasks: {
"0bd005de-c722-473b-a00c-b10101006fcd": {
createProject: true,
createSecret: true,
createServiceAccount: true,
importSecrets: true,
},
"2f0d26ec-493a-4ed7-9183-b10d013597c8": {
createProject: false,
createSecret: true,
createServiceAccount: false,
importSecrets: true,
},
},
someOtherProperty: "Some other value",
},
otherStuff: "otherStuff",
},
"user-2": {
settings: {
smOnboardingTasks: {
"000000-0000000-0000000-000000000": {
createProject: false,
createSecret: false,
createServiceAccount: false,
importSecrets: false,
},
},
someOtherProperty: "Some other value",
},
otherStuff: "otherStuff",
},
};
}
function rollbackJSON() {
return {
"user_user-1_smOnboarding_tasks": {
"0bd005de-c722-473b-a00c-b10101006fcd": {
createProject: true,
createSecret: true,
createServiceAccount: true,
importSecrets: true,
},
"2f0d26ec-493a-4ed7-9183-b10d013597c8": {
createProject: false,
createSecret: true,
createServiceAccount: false,
importSecrets: true,
},
},
"user_user-2_smOnboarding_tasks": {
"000000-0000000-0000000-000000000": {
createProject: false,
createSecret: false,
createServiceAccount: false,
importSecrets: false,
},
},
authenticatedAccounts: ["user-1", "user-2"],
"user-1": {
settings: {
someOtherProperty: "Some other value",
},
otherStuff: "otherStuff",
},
"user-2": {
settings: {
someOtherProperty: "Some other value",
},
otherStuff: "otherStuff",
},
};
}
describe("SmOnboardingTasksMigrator", () => {
let helper: MockProxy<MigrationHelper>;
let sut: SmOnboardingTasksMigrator;
const keyDefinitionLike = {
key: "tasks",
stateDefinition: { name: "smOnboarding" },
};
describe("migrate", () => {
beforeEach(() => {
helper = mockMigrationHelper(exampleJSON(), 23);
sut = new SmOnboardingTasksMigrator(23, 24);
});
it("should remove smOnboardingTasks from all accounts", async () => {
await sut.migrate(helper);
expect(helper.set).toHaveBeenCalledWith("user-1", {
settings: {
someOtherProperty: "Some other value",
},
otherStuff: "otherStuff",
});
});
it("should set smOnboardingTasks provider value for each account", async () => {
await sut.migrate(helper);
expect(helper.setToUser).toHaveBeenCalledWith("user-1", keyDefinitionLike, {
"0bd005de-c722-473b-a00c-b10101006fcd": {
createProject: true,
createSecret: true,
createServiceAccount: true,
importSecrets: true,
},
"2f0d26ec-493a-4ed7-9183-b10d013597c8": {
createProject: false,
createSecret: true,
createServiceAccount: false,
importSecrets: true,
},
});
expect(helper.setToUser).toHaveBeenCalledWith("user-2", keyDefinitionLike, {
"000000-0000000-0000000-000000000": {
createProject: false,
createSecret: false,
createServiceAccount: false,
importSecrets: false,
},
});
});
});
describe("rollback", () => {
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 24);
sut = new SmOnboardingTasksMigrator(23, 24);
});
it.each(["user-1", "user-2"])("should null out new values", async (userId) => {
await sut.rollback(helper);
expect(helper.setToUser).toHaveBeenCalledWith(userId, keyDefinitionLike, null);
});
it("should add smOnboardingTasks back to accounts", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledWith("user-1", {
settings: {
smOnboardingTasks: {
"0bd005de-c722-473b-a00c-b10101006fcd": {
createProject: true,
createSecret: true,
createServiceAccount: true,
importSecrets: true,
},
"2f0d26ec-493a-4ed7-9183-b10d013597c8": {
createProject: false,
createSecret: true,
createServiceAccount: false,
importSecrets: true,
},
},
someOtherProperty: "Some other value",
},
otherStuff: "otherStuff",
});
expect(helper.set).toHaveBeenCalledWith("user-2", {
settings: {
smOnboardingTasks: {
"000000-0000000-0000000-000000000": {
createProject: false,
createSecret: false,
createServiceAccount: false,
importSecrets: false,
},
},
someOtherProperty: "Some other value",
},
otherStuff: "otherStuff",
});
});
it("should not try to restore values to missing accounts", async () => {
await sut.rollback(helper);
expect(helper.set).not.toHaveBeenCalledWith("user-3", any());
});
});
});

View File

@@ -0,0 +1,53 @@
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
import { Migrator } from "../migrator";
type ExpectedAccountType = {
settings?: {
smOnboardingTasks?: Record<string, Record<string, boolean>>;
};
};
export const SM_ONBOARDING_TASKS: KeyDefinitionLike = {
key: "tasks",
stateDefinition: { name: "smOnboarding" },
};
export class SmOnboardingTasksMigrator extends Migrator<23, 24> {
async migrate(helper: MigrationHelper): Promise<void> {
const legacyAccounts = await helper.getAccounts<ExpectedAccountType>();
await Promise.all(
legacyAccounts.map(async ({ userId, account }) => {
// Move account data
if (account?.settings?.smOnboardingTasks != null) {
await helper.setToUser(userId, SM_ONBOARDING_TASKS, account.settings.smOnboardingTasks);
// Delete old account data
delete account.settings.smOnboardingTasks;
await helper.set(userId, account);
}
}),
);
}
async rollback(helper: MigrationHelper): Promise<void> {
async function rollbackUser(userId: string, account: ExpectedAccountType) {
const smOnboardingTasks = await helper.getFromUser<Record<string, Record<string, boolean>>>(
userId,
SM_ONBOARDING_TASKS,
);
if (smOnboardingTasks) {
account ??= {};
account.settings ??= {};
account.settings.smOnboardingTasks = smOnboardingTasks;
await helper.setToUser(userId, SM_ONBOARDING_TASKS, null);
await helper.set(userId, account);
}
}
const accounts = await helper.getAccounts<ExpectedAccountType>();
await Promise.all(accounts.map(({ userId, account }) => rollbackUser(userId, account)));
}
}

View File

@@ -0,0 +1,177 @@
import { any, MockProxy } from "jest-mock-extended";
import { StateDefinitionLike, MigrationHelper } from "../migration-helper";
import { mockMigrationHelper } from "../migration-helper.spec";
import { ClearClipboardDelayMigrator } from "./25-move-clear-clipboard-to-autofill-settings-state-provider";
export const ClearClipboardDelay = {
Never: null as null,
TenSeconds: 10,
TwentySeconds: 20,
ThirtySeconds: 30,
OneMinute: 60,
TwoMinutes: 120,
FiveMinutes: 300,
} as const;
const AutofillOverlayVisibility = {
Off: 0,
OnButtonClick: 1,
OnFieldFocus: 2,
} as const;
function exampleJSON() {
return {
global: {
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["user-1", "user-2", "user-3"],
"user-1": {
settings: {
clearClipboard: ClearClipboardDelay.TenSeconds,
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
},
"user-2": {
settings: {
clearClipboard: ClearClipboardDelay.Never,
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
},
"user-3": {
settings: {
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
},
};
}
function rollbackJSON() {
return {
global_autofillSettingsLocal_inlineMenuVisibility: AutofillOverlayVisibility.OnButtonClick,
"user_user-1_autofillSettingsLocal_clearClipboardDelay": ClearClipboardDelay.TenSeconds,
"user_user-2_autofillSettingsLocal_clearClipboardDelay": ClearClipboardDelay.Never,
global: {
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["user-1", "user-2", "user-3"],
"user-1": {
settings: {
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
},
"user-2": {
settings: {
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
},
};
}
const autofillSettingsLocalStateDefinition: {
stateDefinition: StateDefinitionLike;
} = {
stateDefinition: {
name: "autofillSettingsLocal",
},
};
describe("ClearClipboardDelayMigrator", () => {
let helper: MockProxy<MigrationHelper>;
let sut: ClearClipboardDelayMigrator;
describe("migrate", () => {
beforeEach(() => {
helper = mockMigrationHelper(exampleJSON(), 24);
sut = new ClearClipboardDelayMigrator(24, 25);
});
it("should remove clearClipboard setting from all accounts", async () => {
await sut.migrate(helper);
expect(helper.set).toHaveBeenCalledTimes(2);
expect(helper.set).toHaveBeenCalledWith("user-1", {
settings: {
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
});
expect(helper.set).toHaveBeenCalledWith("user-2", {
settings: {
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
});
});
it("should set autofill setting values for each account", async () => {
await sut.migrate(helper);
expect(helper.setToUser).toHaveBeenCalledTimes(2);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-1",
{ ...autofillSettingsLocalStateDefinition, key: "clearClipboardDelay" },
ClearClipboardDelay.TenSeconds,
);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-2",
{ ...autofillSettingsLocalStateDefinition, key: "clearClipboardDelay" },
ClearClipboardDelay.Never,
);
});
});
describe("rollback", () => {
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 25);
sut = new ClearClipboardDelayMigrator(24, 25);
});
it("should null out new values for each account", async () => {
await sut.rollback(helper);
expect(helper.setToUser).toHaveBeenCalledTimes(2);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-1",
{ ...autofillSettingsLocalStateDefinition, key: "clearClipboardDelay" },
null,
);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-2",
{ ...autofillSettingsLocalStateDefinition, key: "clearClipboardDelay" },
null,
);
});
it("should add explicit value back to accounts", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledTimes(2);
expect(helper.set).toHaveBeenCalledWith("user-1", {
settings: {
clearClipboard: ClearClipboardDelay.TenSeconds,
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
});
expect(helper.set).toHaveBeenCalledWith("user-2", {
settings: {
clearClipboard: ClearClipboardDelay.Never,
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
});
});
it("should not try to restore values to missing accounts", async () => {
await sut.rollback(helper);
expect(helper.set).not.toHaveBeenCalledWith("user-3", any());
});
});
});

View File

@@ -0,0 +1,88 @@
import { StateDefinitionLike, MigrationHelper } from "../migration-helper";
import { Migrator } from "../migrator";
const ClearClipboardDelay = {
Never: null as null,
TenSeconds: 10,
TwentySeconds: 20,
ThirtySeconds: 30,
OneMinute: 60,
TwoMinutes: 120,
FiveMinutes: 300,
} as const;
type ClearClipboardDelaySetting = (typeof ClearClipboardDelay)[keyof typeof ClearClipboardDelay];
type ExpectedAccountState = {
settings?: {
clearClipboard?: ClearClipboardDelaySetting;
};
};
const autofillSettingsLocalStateDefinition: {
stateDefinition: StateDefinitionLike;
} = {
stateDefinition: {
name: "autofillSettingsLocal",
},
};
export class ClearClipboardDelayMigrator extends Migrator<24, 25> {
async migrate(helper: MigrationHelper): Promise<void> {
// account state (e.g. account settings -> state provider framework keys)
const accounts = await helper.getAccounts<ExpectedAccountState>();
await Promise.all([...accounts.map(({ userId, account }) => migrateAccount(userId, account))]);
// migrate account state
async function migrateAccount(userId: string, account: ExpectedAccountState): Promise<void> {
const accountSettings = account?.settings;
if (accountSettings?.clearClipboard !== undefined) {
await helper.setToUser(
userId,
{ ...autofillSettingsLocalStateDefinition, key: "clearClipboardDelay" },
accountSettings.clearClipboard,
);
delete account.settings.clearClipboard;
// update the state account settings with the migrated values deleted
await helper.set(userId, account);
}
}
}
async rollback(helper: MigrationHelper): Promise<void> {
// account state (e.g. state provider framework keys -> account settings)
const accounts = await helper.getAccounts<ExpectedAccountState>();
await Promise.all([...accounts.map(({ userId, account }) => rollbackAccount(userId, account))]);
// rollback account state
async function rollbackAccount(userId: string, account: ExpectedAccountState): Promise<void> {
let settings = account?.settings || {};
const clearClipboardDelay: ClearClipboardDelaySetting = await helper.getFromUser(userId, {
...autofillSettingsLocalStateDefinition,
key: "clearClipboardDelay",
});
// update new settings and remove the account state provider framework keys for the rolled back values
if (clearClipboardDelay !== undefined) {
settings = { ...settings, clearClipboard: clearClipboardDelay };
await helper.setToUser(
userId,
{ ...autofillSettingsLocalStateDefinition, key: "clearClipboardDelay" },
null,
);
// commit updated settings to state
await helper.set(userId, {
...account,
settings,
});
}
}
}
}

View File

@@ -0,0 +1,112 @@
import { any, MockProxy } from "jest-mock-extended";
import { MigrationHelper } from "../migration-helper";
import { mockMigrationHelper } from "../migration-helper.spec";
import { RevertLastSyncMigrator } from "./26-revert-move-last-sync-to-state-provider";
function rollbackJSON() {
return {
global: {
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["user-1", "user-2"],
"user-1": {
profile: {
lastSync: "2024-01-24T00:00:00.000Z",
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
},
};
}
function exampleJSON() {
return {
"user_user-1_sync_lastSync": "2024-01-24T00:00:00.000Z",
"user_user-2_sync_lastSync": null as any,
global: {
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["user-1", "user-2"],
"user-1": {
profile: {
lastSync: "2024-01-24T00:00:00.000Z",
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
},
};
}
describe("LastSyncMigrator", () => {
let helper: MockProxy<MigrationHelper>;
let sut: RevertLastSyncMigrator;
const keyDefinitionLike = {
key: "lastSync",
stateDefinition: {
name: "sync",
},
};
describe("rollback", () => {
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 26);
sut = new RevertLastSyncMigrator(25, 26);
});
it("should remove lastSync from all accounts", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledWith("user-1", {
profile: {
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
});
});
it("should set lastSync provider value for each account", async () => {
await sut.rollback(helper);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-1",
keyDefinitionLike,
"2024-01-24T00:00:00.000Z",
);
expect(helper.setToUser).toHaveBeenCalledWith("user-2", keyDefinitionLike, null);
});
});
describe("migrate", () => {
beforeEach(() => {
helper = mockMigrationHelper(exampleJSON(), 25);
sut = new RevertLastSyncMigrator(25, 26);
});
it.each(["user-1", "user-2"])("should null out new values", async (userId) => {
await sut.migrate(helper);
expect(helper.setToUser).toHaveBeenCalledWith(userId, keyDefinitionLike, null);
});
it("should add lastSync back to accounts", async () => {
await sut.migrate(helper);
expect(helper.set).toHaveBeenCalledWith("user-1", {
profile: {
lastSync: "2024-01-24T00:00:00.000Z",
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
});
});
it("should not try to restore values to missing accounts", async () => {
await sut.rollback(helper);
expect(helper.set).not.toHaveBeenCalledWith("user-2", any());
});
});
});

View File

@@ -0,0 +1,47 @@
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
import { Migrator } from "../migrator";
type ExpectedAccountType = {
profile?: {
lastSync?: string;
};
};
const LAST_SYNC_KEY: KeyDefinitionLike = {
key: "lastSync",
stateDefinition: {
name: "sync",
},
};
export class RevertLastSyncMigrator extends Migrator<25, 26> {
async rollback(helper: MigrationHelper): Promise<void> {
const accounts = await helper.getAccounts<ExpectedAccountType>();
async function rollbackAccount(userId: string, account: ExpectedAccountType): Promise<void> {
const value = account?.profile?.lastSync;
await helper.setToUser(userId, LAST_SYNC_KEY, value ?? null);
if (value != null) {
delete account.profile.lastSync;
await helper.set(userId, account);
}
}
await Promise.all([...accounts.map(({ userId, account }) => rollbackAccount(userId, account))]);
}
async migrate(helper: MigrationHelper): Promise<void> {
const accounts = await helper.getAccounts<ExpectedAccountType>();
async function migrateAccount(userId: string, account: ExpectedAccountType): Promise<void> {
const value = await helper.getFromUser(userId, LAST_SYNC_KEY);
if (account) {
account.profile = Object.assign(account.profile ?? {}, {
lastSync: value,
});
await helper.set(userId, account);
}
await helper.setToUser(userId, LAST_SYNC_KEY, null);
}
await Promise.all([...accounts.map(({ userId, account }) => migrateAccount(userId, account))]);
}
}

View File

@@ -0,0 +1,166 @@
import { any, MockProxy } from "jest-mock-extended";
import { StateDefinitionLike, MigrationHelper } from "../migration-helper";
import { mockMigrationHelper } from "../migration-helper.spec";
import { BadgeSettingsMigrator } from "./27-move-badge-settings-to-state-providers";
function exampleJSON() {
return {
global: {
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["user-1", "user-2", "user-3"],
"user-1": {
settings: {
disableBadgeCounter: true,
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
},
"user-2": {
settings: {
disableBadgeCounter: false,
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
},
"user-3": {
settings: {
otherStuff: "otherStuff6",
},
otherStuff: "otherStuff7",
},
};
}
function rollbackJSON() {
return {
"user_user-1_badgeSettings_enableBadgeCounter": false,
"user_user-2_badgeSettings_enableBadgeCounter": true,
global: {
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["user-1", "user-2", "user-3"],
"user-1": {
settings: {
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
},
"user-2": {
settings: {
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
},
"user-3": {
settings: {
otherStuff: "otherStuff6",
},
otherStuff: "otherStuff7",
},
};
}
const badgeSettingsStateDefinition: {
stateDefinition: StateDefinitionLike;
} = {
stateDefinition: {
name: "badgeSettings",
},
};
describe("BadgeSettingsMigrator", () => {
let helper: MockProxy<MigrationHelper>;
let sut: BadgeSettingsMigrator;
describe("migrate", () => {
beforeEach(() => {
helper = mockMigrationHelper(exampleJSON(), 26);
sut = new BadgeSettingsMigrator(26, 27);
});
it("should remove disableBadgeCounter setting from all accounts", async () => {
await sut.migrate(helper);
expect(helper.set).toHaveBeenCalledTimes(2);
expect(helper.set).toHaveBeenCalledWith("user-1", {
settings: {
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
});
expect(helper.set).toHaveBeenCalledWith("user-2", {
settings: {
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
});
});
it("should set badge setting values for each account", async () => {
await sut.migrate(helper);
expect(helper.setToUser).toHaveBeenCalledTimes(2);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-1",
{ ...badgeSettingsStateDefinition, key: "enableBadgeCounter" },
false,
);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-2",
{ ...badgeSettingsStateDefinition, key: "enableBadgeCounter" },
true,
);
});
});
describe("rollback", () => {
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 27);
sut = new BadgeSettingsMigrator(26, 27);
});
it("should null out new values for each account", async () => {
await sut.rollback(helper);
expect(helper.setToUser).toHaveBeenCalledTimes(2);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-1",
{ ...badgeSettingsStateDefinition, key: "enableBadgeCounter" },
null,
);
expect(helper.setToUser).toHaveBeenCalledWith(
"user-2",
{ ...badgeSettingsStateDefinition, key: "enableBadgeCounter" },
null,
);
});
it("should add explicit value back to accounts", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledTimes(2);
expect(helper.set).toHaveBeenCalledWith("user-1", {
settings: {
disableBadgeCounter: true,
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
});
expect(helper.set).toHaveBeenCalledWith("user-2", {
settings: {
disableBadgeCounter: false,
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
});
});
it("should not try to restore values to missing accounts", async () => {
await sut.rollback(helper);
expect(helper.set).not.toHaveBeenCalledWith("user-3", any());
});
});
});

View File

@@ -0,0 +1,71 @@
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
import { Migrator } from "../migrator";
type ExpectedAccountState = {
settings?: {
disableBadgeCounter?: boolean;
};
};
const enableBadgeCounterKeyDefinition: KeyDefinitionLike = {
stateDefinition: {
name: "badgeSettings",
},
key: "enableBadgeCounter",
};
export class BadgeSettingsMigrator extends Migrator<26, 27> {
async migrate(helper: MigrationHelper): Promise<void> {
// account state (e.g. account settings -> state provider framework keys)
const accounts = await helper.getAccounts<ExpectedAccountState>();
await Promise.all([...accounts.map(({ userId, account }) => migrateAccount(userId, account))]);
// migrate account state
async function migrateAccount(userId: string, account: ExpectedAccountState): Promise<void> {
const accountSettings = account?.settings;
if (accountSettings?.disableBadgeCounter != undefined) {
await helper.setToUser(
userId,
enableBadgeCounterKeyDefinition,
!accountSettings.disableBadgeCounter,
);
delete account.settings.disableBadgeCounter;
// update the state account settings with the migrated values deleted
await helper.set(userId, account);
}
}
}
async rollback(helper: MigrationHelper): Promise<void> {
// account state (e.g. state provider framework keys -> account settings)
const accounts = await helper.getAccounts<ExpectedAccountState>();
await Promise.all([...accounts.map(({ userId, account }) => rollbackAccount(userId, account))]);
// rollback account state
async function rollbackAccount(userId: string, account: ExpectedAccountState): Promise<void> {
let settings = account?.settings || {};
const enableBadgeCounter: boolean = await helper.getFromUser(
userId,
enableBadgeCounterKeyDefinition,
);
// update new settings and remove the account state provider framework keys for the rolled back values
if (enableBadgeCounter != undefined) {
settings = { ...settings, disableBadgeCounter: !enableBadgeCounter };
await helper.setToUser(userId, enableBadgeCounterKeyDefinition, null);
// commit updated settings to state
await helper.set(userId, {
...account,
settings,
});
}
}
}
}

View File

@@ -0,0 +1,120 @@
import { MockProxy, any } from "jest-mock-extended";
import { MigrationHelper } from "../migration-helper";
import { mockMigrationHelper } from "../migration-helper.spec";
import {
BIOMETRIC_UNLOCK_ENABLED,
MoveBiometricUnlockToStateProviders,
} from "./28-move-biometric-unlock-to-state-providers";
function exampleJSON() {
return {
global: {
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["user-1", "user-2", "user-3"],
"user-1": {
settings: {
biometricUnlock: true,
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
},
"user-2": {
otherStuff: "otherStuff4",
},
};
}
function rollbackJSON() {
return {
"user_user-1_biometricSettings_biometricUnlockEnabled": true,
global: {
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["user-1", "user-2", "user-3"],
"user-1": {
settings: {
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
},
"user-2": {
otherStuff: "otherStuff4",
},
};
}
describe("MoveBiometricPromptsToStateProviders migrator", () => {
let helper: MockProxy<MigrationHelper>;
let sut: MoveBiometricUnlockToStateProviders;
describe("migrate", () => {
beforeEach(() => {
helper = mockMigrationHelper(exampleJSON(), 27);
sut = new MoveBiometricUnlockToStateProviders(27, 28);
});
it("removes biometricUnlock from all accounts", async () => {
await sut.migrate(helper);
expect(helper.set).toHaveBeenCalledTimes(2);
expect(helper.set).toHaveBeenCalledWith("user-1", {
settings: {
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
});
expect(helper.set).toHaveBeenCalledWith("user-2", {
otherStuff: "otherStuff4",
});
});
it("sets biometricUnlock value for account that have it", async () => {
await sut.migrate(helper);
expect(helper.setToUser).toHaveBeenCalledWith("user-1", BIOMETRIC_UNLOCK_ENABLED, true);
});
it("should not call extra setToUser", async () => {
await sut.migrate(helper);
expect(helper.setToUser).toHaveBeenCalledTimes(1);
});
});
describe("rollback", () => {
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 28);
sut = new MoveBiometricUnlockToStateProviders(27, 28);
});
it("nulls out new values", async () => {
await sut.rollback(helper);
expect(helper.setToUser).toHaveBeenCalledWith("user-1", BIOMETRIC_UNLOCK_ENABLED, null);
});
it("adds explicit value back to accounts", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledTimes(1);
expect(helper.set).toHaveBeenCalledWith("user-1", {
settings: {
biometricUnlock: true,
otherStuff: "otherStuff2",
},
otherStuff: "otherStuff3",
});
});
it.each(["user-2", "user-3"])(
"does not restore values when accounts are not present",
async (userId) => {
await sut.rollback(helper);
expect(helper.set).not.toHaveBeenCalledWith(userId, any());
},
);
});
});

View File

@@ -0,0 +1,58 @@
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
import { Migrator } from "../migrator";
type ExpectedAccountType = {
settings?: {
biometricUnlock?: boolean;
};
};
export const BIOMETRIC_UNLOCK_ENABLED: KeyDefinitionLike = {
key: "biometricUnlockEnabled",
stateDefinition: { name: "biometricSettings" },
};
export class MoveBiometricUnlockToStateProviders extends Migrator<27, 28> {
async migrate(helper: MigrationHelper): Promise<void> {
const legacyAccounts = await helper.getAccounts<ExpectedAccountType>();
await Promise.all(
legacyAccounts.map(async ({ userId, account }) => {
if (account == null) {
return;
}
// Move account data
if (account?.settings?.biometricUnlock != null) {
await helper.setToUser(
userId,
BIOMETRIC_UNLOCK_ENABLED,
account.settings.biometricUnlock,
);
}
// Delete old account data
delete account?.settings?.biometricUnlock;
await helper.set(userId, account);
}),
);
}
async rollback(helper: MigrationHelper): Promise<void> {
async function rollbackUser(userId: string, account: ExpectedAccountType) {
const biometricUnlock = await helper.getFromUser<boolean>(userId, BIOMETRIC_UNLOCK_ENABLED);
if (biometricUnlock != null) {
account ??= {};
account.settings ??= {};
account.settings.biometricUnlock = biometricUnlock;
await helper.setToUser(userId, BIOMETRIC_UNLOCK_ENABLED, null);
await helper.set(userId, account);
}
}
const accounts = await helper.getAccounts<ExpectedAccountType>();
await Promise.all(accounts.map(({ userId, account }) => rollbackUser(userId, account)));
}
}

View File

@@ -0,0 +1,145 @@
import { any, MockProxy } from "jest-mock-extended";
import { MigrationHelper } from "../migration-helper";
import { mockMigrationHelper } from "../migration-helper.spec";
import { ProviderMigrator } from "./28-move-provider-state-to-state-provider";
function exampleProvider1() {
return JSON.stringify({
id: "id",
name: "name",
status: 0,
type: 0,
enabled: true,
useEvents: true,
});
}
function exampleJSON() {
return {
global: {
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["user-1", "user-2"],
"user-1": {
data: {
providers: {
"provider-id-1": exampleProvider1(),
"provider-id-2": {
// ...
},
},
otherStuff: "overStuff2",
},
otherStuff: "otherStuff3",
},
"user-2": {
data: {
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
},
};
}
function rollbackJSON() {
return {
"user_user-1_providers_providers": {
"provider-id-1": exampleProvider1(),
"provider-id-2": {
// ...
},
},
"user_user-2_providers_providers": null as any,
global: {
otherStuff: "otherStuff1",
},
authenticatedAccounts: ["user-1", "user-2"],
"user-1": {
data: {
otherStuff: "overStuff2",
},
otherStuff: "otherStuff3",
},
"user-2": {
data: {
otherStuff: "otherStuff4",
},
otherStuff: "otherStuff5",
},
};
}
describe("ProviderMigrator", () => {
let helper: MockProxy<MigrationHelper>;
let sut: ProviderMigrator;
const keyDefinitionLike = {
key: "providers",
stateDefinition: {
name: "providers",
},
};
describe("migrate", () => {
beforeEach(() => {
helper = mockMigrationHelper(exampleJSON(), 28);
sut = new ProviderMigrator(27, 28);
});
it("should remove providers from all accounts", async () => {
await sut.migrate(helper);
expect(helper.set).toHaveBeenCalledWith("user-1", {
data: {
otherStuff: "overStuff2",
},
otherStuff: "otherStuff3",
});
});
it("should set providers value for each account", async () => {
await sut.migrate(helper);
expect(helper.setToUser).toHaveBeenCalledWith("user-1", keyDefinitionLike, {
"provider-id-1": exampleProvider1(),
"provider-id-2": {
// ...
},
});
});
});
describe("rollback", () => {
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 27);
sut = new ProviderMigrator(27, 28);
});
it.each(["user-1", "user-2"])("should null out new values", async (userId) => {
await sut.rollback(helper);
expect(helper.setToUser).toHaveBeenCalledWith(userId, keyDefinitionLike, null);
});
it("should add explicit value back to accounts", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledWith("user-1", {
data: {
providers: {
"provider-id-1": exampleProvider1(),
"provider-id-2": {
// ...
},
},
otherStuff: "overStuff2",
},
otherStuff: "otherStuff3",
});
});
it("should not try to restore values to missing accounts", async () => {
await sut.rollback(helper);
expect(helper.set).not.toHaveBeenCalledWith("user-3", any());
});
});
});

View File

@@ -0,0 +1,71 @@
import { Jsonify } from "type-fest";
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
import { Migrator } from "../migrator";
enum ProviderUserStatusType {
Invited = 0,
Accepted = 1,
Confirmed = 2,
Revoked = -1,
}
enum ProviderUserType {
ProviderAdmin = 0,
ServiceUser = 1,
}
type ProviderData = {
id: string;
name: string;
status: ProviderUserStatusType;
type: ProviderUserType;
enabled: boolean;
userId: string;
useEvents: boolean;
};
type ExpectedAccountType = {
data?: {
providers?: Record<string, Jsonify<ProviderData>>;
};
};
const USER_PROVIDERS: KeyDefinitionLike = {
key: "providers",
stateDefinition: {
name: "providers",
},
};
export class ProviderMigrator extends Migrator<27, 28> {
async migrate(helper: MigrationHelper): Promise<void> {
const accounts = await helper.getAccounts<ExpectedAccountType>();
async function migrateAccount(userId: string, account: ExpectedAccountType): Promise<void> {
const value = account?.data?.providers;
if (value != null) {
await helper.setToUser(userId, USER_PROVIDERS, value);
delete account.data.providers;
await helper.set(userId, account);
}
}
await Promise.all(accounts.map(({ userId, account }) => migrateAccount(userId, account)));
}
async rollback(helper: MigrationHelper): Promise<void> {
const accounts = await helper.getAccounts<ExpectedAccountType>();
async function rollbackAccount(userId: string, account: ExpectedAccountType): Promise<void> {
const value = await helper.getFromUser(userId, USER_PROVIDERS);
if (account) {
account.data = Object.assign(account.data ?? {}, {
providers: value,
});
await helper.set(userId, account);
}
await helper.setToUser(userId, USER_PROVIDERS, null);
}
await Promise.all(accounts.map(({ userId, account }) => rollbackAccount(userId, account)));
}
}

View File

@@ -0,0 +1,102 @@
import { MockProxy } from "jest-mock-extended";
import { StateDefinitionLike, MigrationHelper } from "../migration-helper";
import { mockMigrationHelper } from "../migration-helper.spec";
import { UserNotificationSettingsKeyMigrator } from "./29-move-user-notification-settings-to-state-provider";
function exampleJSON() {
return {
global: {
disableAddLoginNotification: false,
disableChangedPasswordNotification: false,
otherStuff: "otherStuff1",
},
};
}
function rollbackJSON() {
return {
global_userNotificationSettings_enableAddedLoginPrompt: true,
global_userNotificationSettings_enableChangedPasswordPrompt: true,
global: {
otherStuff: "otherStuff1",
},
};
}
const userNotificationSettingsLocalStateDefinition: {
stateDefinition: StateDefinitionLike;
} = {
stateDefinition: {
name: "userNotificationSettings",
},
};
describe("ProviderKeysMigrator", () => {
let helper: MockProxy<MigrationHelper>;
let sut: UserNotificationSettingsKeyMigrator;
describe("migrate", () => {
beforeEach(() => {
helper = mockMigrationHelper(exampleJSON(), 28);
sut = new UserNotificationSettingsKeyMigrator(28, 29);
});
it("should remove disableAddLoginNotification and disableChangedPasswordNotification global setting", async () => {
await sut.migrate(helper);
expect(helper.set).toHaveBeenCalledTimes(2);
expect(helper.set).toHaveBeenCalledWith("global", { otherStuff: "otherStuff1" });
expect(helper.set).toHaveBeenCalledWith("global", { otherStuff: "otherStuff1" });
});
it("should set global user notification setting values", async () => {
await sut.migrate(helper);
expect(helper.setToGlobal).toHaveBeenCalledTimes(2);
expect(helper.setToGlobal).toHaveBeenCalledWith(
{ ...userNotificationSettingsLocalStateDefinition, key: "enableAddedLoginPrompt" },
true,
);
expect(helper.setToGlobal).toHaveBeenCalledWith(
{ ...userNotificationSettingsLocalStateDefinition, key: "enableChangedPasswordPrompt" },
true,
);
});
});
describe("rollback", () => {
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 29);
sut = new UserNotificationSettingsKeyMigrator(28, 29);
});
it("should null out new global values", async () => {
await sut.rollback(helper);
expect(helper.setToGlobal).toHaveBeenCalledTimes(2);
expect(helper.setToGlobal).toHaveBeenCalledWith(
{ ...userNotificationSettingsLocalStateDefinition, key: "enableAddedLoginPrompt" },
null,
);
expect(helper.setToGlobal).toHaveBeenCalledWith(
{ ...userNotificationSettingsLocalStateDefinition, key: "enableChangedPasswordPrompt" },
null,
);
});
it("should add explicit global values back", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledTimes(2);
expect(helper.set).toHaveBeenCalledWith("global", {
disableAddLoginNotification: false,
otherStuff: "otherStuff1",
});
expect(helper.set).toHaveBeenCalledWith("global", {
disableChangedPasswordNotification: false,
otherStuff: "otherStuff1",
});
});
});
});

View File

@@ -0,0 +1,105 @@
import { MigrationHelper } from "../migration-helper";
import { Migrator } from "../migrator";
type ExpectedGlobalState = {
disableAddLoginNotification?: boolean;
disableChangedPasswordNotification?: boolean;
};
export class UserNotificationSettingsKeyMigrator extends Migrator<28, 29> {
async migrate(helper: MigrationHelper): Promise<void> {
const globalState = await helper.get<ExpectedGlobalState>("global");
// disableAddLoginNotification -> enableAddedLoginPrompt
if (globalState?.disableAddLoginNotification != null) {
await helper.setToGlobal(
{
stateDefinition: {
name: "userNotificationSettings",
},
key: "enableAddedLoginPrompt",
},
!globalState.disableAddLoginNotification,
);
// delete `disableAddLoginNotification` from state global
delete globalState.disableAddLoginNotification;
await helper.set<ExpectedGlobalState>("global", globalState);
}
// disableChangedPasswordNotification -> enableChangedPasswordPrompt
if (globalState?.disableChangedPasswordNotification != null) {
await helper.setToGlobal(
{
stateDefinition: {
name: "userNotificationSettings",
},
key: "enableChangedPasswordPrompt",
},
!globalState.disableChangedPasswordNotification,
);
// delete `disableChangedPasswordNotification` from state global
delete globalState.disableChangedPasswordNotification;
await helper.set<ExpectedGlobalState>("global", globalState);
}
}
async rollback(helper: MigrationHelper): Promise<void> {
const globalState = (await helper.get<ExpectedGlobalState>("global")) || {};
const enableAddedLoginPrompt: boolean = await helper.getFromGlobal({
stateDefinition: {
name: "userNotificationSettings",
},
key: "enableAddedLoginPrompt",
});
const enableChangedPasswordPrompt: boolean = await helper.getFromGlobal({
stateDefinition: {
name: "userNotificationSettings",
},
key: "enableChangedPasswordPrompt",
});
// enableAddedLoginPrompt -> disableAddLoginNotification
if (enableAddedLoginPrompt) {
await helper.set<ExpectedGlobalState>("global", {
...globalState,
disableAddLoginNotification: !enableAddedLoginPrompt,
});
// remove the global state provider framework key for `enableAddedLoginPrompt`
await helper.setToGlobal(
{
stateDefinition: {
name: "userNotificationSettings",
},
key: "enableAddedLoginPrompt",
},
null,
);
}
// enableChangedPasswordPrompt -> disableChangedPasswordNotification
if (enableChangedPasswordPrompt) {
await helper.set<ExpectedGlobalState>("global", {
...globalState,
disableChangedPasswordNotification: !enableChangedPasswordPrompt,
});
// remove the global state provider framework key for `enableChangedPasswordPrompt`
await helper.setToGlobal(
{
stateDefinition: {
name: "userNotificationSettings",
},
key: "enableChangedPasswordPrompt",
},
null,
);
}
}
}

View File

@@ -3,7 +3,7 @@ import { MockProxy } from "jest-mock-extended";
import { MigrationHelper } from "../migration-helper";
import { mockMigrationHelper } from "../migration-helper.spec";
import { LocalDataMigrator } from "./23-move-local-data-to-state-provider";
import { LocalDataMigrator } from "./30-move-local-data-to-state-provider";
function exampleJSON() {
return {

View File

@@ -17,7 +17,7 @@ const CIPHERS_DISK: KeyDefinitionLike = {
},
};
export class LocalDataMigrator extends Migrator<22, 23> {
export class LocalDataMigrator extends Migrator<29, 30> {
async migrate(helper: MigrationHelper): Promise<void> {
const accounts = await helper.getAccounts<ExpectedAccountType>();
async function migrateAccount(userId: string, account: ExpectedAccountType): Promise<void> {