1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-13 14:53:33 +00:00

[PM-5568] Implement Badge Settings state provider (#8112)

* create badge settings state provider

* replace state service get/set disableBadgeCounter with badge settings service equivalent

* migrate disableBadgeCounter account setting to badge settings state provider

* cleanup and address PR suggestions
This commit is contained in:
Jonathan Prusik
2024-02-27 16:03:12 -05:00
committed by GitHub
parent e833e93b3b
commit 929b5ebec3
15 changed files with 334 additions and 35 deletions

View File

@@ -0,0 +1,25 @@
import { BadgeSettingsService } from "@bitwarden/common/autofill/services/badge-settings.service";
import {
CachedServices,
factory,
FactoryOptions,
} from "../../../platform/background/service-factories/factory-options";
import {
stateProviderFactory,
StateProviderInitOptions,
} from "../../../platform/background/service-factories/state-provider.factory";
export type BadgeSettingsServiceInitOptions = FactoryOptions & StateProviderInitOptions;
export function badgeSettingsServiceFactory(
cache: { badgeSettingsService?: BadgeSettingsService } & CachedServices,
opts: BadgeSettingsServiceInitOptions,
): Promise<BadgeSettingsService> {
return factory(
cache,
"badgeSettingsService",
opts,
async () => new BadgeSettingsService(await stateProviderFactory(cache, opts)),
);
}

View File

@@ -49,6 +49,10 @@ import {
AutofillSettingsServiceAbstraction, AutofillSettingsServiceAbstraction,
AutofillSettingsService, AutofillSettingsService,
} from "@bitwarden/common/autofill/services/autofill-settings.service"; } from "@bitwarden/common/autofill/services/autofill-settings.service";
import {
BadgeSettingsServiceAbstraction,
BadgeSettingsService,
} from "@bitwarden/common/autofill/services/badge-settings.service";
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service";
import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service";
@@ -243,6 +247,7 @@ export default class MainBackground {
notificationsService: NotificationsServiceAbstraction; notificationsService: NotificationsServiceAbstraction;
stateService: StateServiceAbstraction; stateService: StateServiceAbstraction;
autofillSettingsService: AutofillSettingsServiceAbstraction; autofillSettingsService: AutofillSettingsServiceAbstraction;
badgeSettingsService: BadgeSettingsServiceAbstraction;
systemService: SystemServiceAbstraction; systemService: SystemServiceAbstraction;
eventCollectionService: EventCollectionServiceAbstraction; eventCollectionService: EventCollectionServiceAbstraction;
eventUploadService: EventUploadServiceAbstraction; eventUploadService: EventUploadServiceAbstraction;
@@ -482,6 +487,7 @@ export default class MainBackground {
this.stateProvider, this.stateProvider,
this.policyService, this.policyService,
); );
this.badgeSettingsService = new BadgeSettingsService(this.stateProvider);
this.policyApiService = new PolicyApiService( this.policyApiService = new PolicyApiService(
this.policyService, this.policyService,
this.apiService, this.apiService,
@@ -1079,7 +1085,10 @@ export default class MainBackground {
this.keyConnectorService.clear(), this.keyConnectorService.clear(),
this.vaultFilterService.clear(), this.vaultFilterService.clear(),
this.biometricStateService.logout(userId), this.biometricStateService.logout(userId),
// We intentionally do not clear the autofillSettingsService /* We intentionally do not clear:
* - autofillSettingsService
* - badgeSettingsService
*/
]); ]);
//Needs to be checked before state is cleaned //Needs to be checked before state is cleaned

View File

@@ -1,5 +1,8 @@
import { firstValueFrom } from "rxjs";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { BadgeSettingsService } from "@bitwarden/common/autofill/services/badge-settings.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
@@ -9,9 +12,8 @@ import { ContainerService } from "@bitwarden/common/platform/services/container.
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { authServiceFactory } from "../../auth/background/service-factories/auth-service.factory"; import { authServiceFactory } from "../../auth/background/service-factories/auth-service.factory";
import { badgeSettingsServiceFactory } from "../../autofill/background/service_factories/badge-settings-service.factory";
import { Account } from "../../models/account"; import { Account } from "../../models/account";
import { stateServiceFactory } from "../../platform/background/service-factories/state-service.factory";
import { BrowserStateService } from "../../platform/services/abstractions/browser-state.service";
import IconDetails from "../../vault/background/models/icon-details"; import IconDetails from "../../vault/background/models/icon-details";
import { cipherServiceFactory } from "../../vault/background/service_factories/cipher-service.factory"; import { cipherServiceFactory } from "../../vault/background/service_factories/cipher-service.factory";
import { BrowserApi } from "../browser/browser-api"; import { BrowserApi } from "../browser/browser-api";
@@ -24,7 +26,7 @@ export type BadgeOptions = {
export class UpdateBadge { export class UpdateBadge {
private authService: AuthService; private authService: AuthService;
private stateService: BrowserStateService; private badgeSettingsService: BadgeSettingsService;
private cipherService: CipherService; private cipherService: CipherService;
private badgeAction: typeof chrome.action | typeof chrome.browserAction; private badgeAction: typeof chrome.action | typeof chrome.browserAction;
private sidebarAction: OperaSidebarAction | FirefoxSidebarAction; private sidebarAction: OperaSidebarAction | FirefoxSidebarAction;
@@ -152,8 +154,8 @@ export class UpdateBadge {
await this.setBadgeIcon(""); await this.setBadgeIcon("");
const disableBadgeCounter = await this.stateService.getDisableBadgeCounter(); const enableBadgeCounter = await firstValueFrom(this.badgeSettingsService.enableBadgeCounter$);
if (disableBadgeCounter) { if (!enableBadgeCounter) {
return; return;
} }
@@ -290,7 +292,7 @@ export class UpdateBadge {
systemLanguage: BrowserApi.getUILanguage(), systemLanguage: BrowserApi.getUILanguage(),
}, },
}; };
this.stateService = await stateServiceFactory(serviceCache, opts); this.badgeSettingsService = await badgeSettingsServiceFactory(serviceCache, opts);
this.authService = await authServiceFactory(serviceCache, opts); this.authService = await authServiceFactory(serviceCache, opts);
this.cipherService = await cipherServiceFactory(serviceCache, opts); this.cipherService = await cipherServiceFactory(serviceCache, opts);

View File

@@ -4,6 +4,7 @@ import { firstValueFrom } from "rxjs";
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction"; import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service"; import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
@@ -20,7 +21,7 @@ import { enableAccountSwitching } from "../../platform/flags";
}) })
export class OptionsComponent implements OnInit { export class OptionsComponent implements OnInit {
enableFavicon = false; enableFavicon = false;
enableBadgeCounter = false; enableBadgeCounter = true;
enableAutoFillOnPageLoad = false; enableAutoFillOnPageLoad = false;
autoFillOnPageLoadDefault = false; autoFillOnPageLoadDefault = false;
autoFillOnPageLoadOptions: any[]; autoFillOnPageLoadOptions: any[];
@@ -47,6 +48,7 @@ export class OptionsComponent implements OnInit {
private messagingService: MessagingService, private messagingService: MessagingService,
private stateService: StateService, private stateService: StateService,
private autofillSettingsService: AutofillSettingsServiceAbstraction, private autofillSettingsService: AutofillSettingsServiceAbstraction,
private badgeSettingsService: BadgeSettingsServiceAbstraction,
i18nService: I18nService, i18nService: I18nService,
private themingService: AbstractThemingService, private themingService: AbstractThemingService,
private settingsService: SettingsService, private settingsService: SettingsService,
@@ -107,7 +109,7 @@ export class OptionsComponent implements OnInit {
this.enableFavicon = !this.settingsService.getDisableFavicon(); this.enableFavicon = !this.settingsService.getDisableFavicon();
this.enableBadgeCounter = !(await this.stateService.getDisableBadgeCounter()); this.enableBadgeCounter = await firstValueFrom(this.badgeSettingsService.enableBadgeCounter$);
this.enablePasskeys = await firstValueFrom(this.vaultSettingsService.enablePasskeys$); this.enablePasskeys = await firstValueFrom(this.vaultSettingsService.enablePasskeys$);
@@ -155,7 +157,7 @@ export class OptionsComponent implements OnInit {
} }
async updateBadgeCounter() { async updateBadgeCounter() {
await this.stateService.setDisableBadgeCounter(!this.enableBadgeCounter); await this.badgeSettingsService.setEnableBadgeCounter(this.enableBadgeCounter);
this.messagingService.send("bgUpdateContextMenu"); this.messagingService.send("bgUpdateContextMenu");
} }

View File

@@ -86,6 +86,10 @@ import {
AutofillSettingsServiceAbstraction, AutofillSettingsServiceAbstraction,
AutofillSettingsService, AutofillSettingsService,
} from "@bitwarden/common/autofill/services/autofill-settings.service"; } from "@bitwarden/common/autofill/services/autofill-settings.service";
import {
BadgeSettingsServiceAbstraction,
BadgeSettingsService,
} from "@bitwarden/common/autofill/services/badge-settings.service";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
import { BillingBannerServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-banner.service.abstraction"; import { BillingBannerServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-banner.service.abstraction";
import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions/organization-billing.service"; import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions/organization-billing.service";
@@ -936,6 +940,11 @@ import { ModalService } from "./modal.service";
useClass: AutofillSettingsService, useClass: AutofillSettingsService,
deps: [StateProvider, PolicyServiceAbstraction], deps: [StateProvider, PolicyServiceAbstraction],
}, },
{
provide: BadgeSettingsServiceAbstraction,
useClass: BadgeSettingsService,
deps: [StateProvider],
},
{ {
provide: BiometricStateService, provide: BiometricStateService,
useClass: DefaultBiometricStateService, useClass: DefaultBiometricStateService,

View File

@@ -0,0 +1,31 @@
import { map, Observable } from "rxjs";
import {
BADGE_SETTINGS_DISK,
ActiveUserState,
KeyDefinition,
StateProvider,
} from "../../platform/state";
const ENABLE_BADGE_COUNTER = new KeyDefinition(BADGE_SETTINGS_DISK, "enableBadgeCounter", {
deserializer: (value: boolean) => value ?? true,
});
export abstract class BadgeSettingsServiceAbstraction {
enableBadgeCounter$: Observable<boolean>;
setEnableBadgeCounter: (newValue: boolean) => Promise<void>;
}
export class BadgeSettingsService implements BadgeSettingsServiceAbstraction {
private enableBadgeCounterState: ActiveUserState<boolean>;
readonly enableBadgeCounter$: Observable<boolean>;
constructor(private stateProvider: StateProvider) {
this.enableBadgeCounterState = this.stateProvider.getActive(ENABLE_BADGE_COUNTER);
this.enableBadgeCounter$ = this.enableBadgeCounterState.state$.pipe(map((x) => x ?? true));
}
async setEnableBadgeCounter(newValue: boolean): Promise<void> {
await this.enableBadgeCounterState.update(() => newValue);
}
}

View File

@@ -204,8 +204,6 @@ export abstract class StateService<T extends Account = Account> {
setDefaultUriMatch: (value: UriMatchType, options?: StorageOptions) => Promise<void>; setDefaultUriMatch: (value: UriMatchType, options?: StorageOptions) => Promise<void>;
getDisableAddLoginNotification: (options?: StorageOptions) => Promise<boolean>; getDisableAddLoginNotification: (options?: StorageOptions) => Promise<boolean>;
setDisableAddLoginNotification: (value: boolean, options?: StorageOptions) => Promise<void>; setDisableAddLoginNotification: (value: boolean, options?: StorageOptions) => Promise<void>;
getDisableBadgeCounter: (options?: StorageOptions) => Promise<boolean>;
setDisableBadgeCounter: (value: boolean, options?: StorageOptions) => Promise<void>;
getDisableChangedPasswordNotification: (options?: StorageOptions) => Promise<boolean>; getDisableChangedPasswordNotification: (options?: StorageOptions) => Promise<boolean>;
setDisableChangedPasswordNotification: ( setDisableChangedPasswordNotification: (
value: boolean, value: boolean,

View File

@@ -202,7 +202,6 @@ export class AccountSettings {
autoConfirmFingerPrints?: boolean; autoConfirmFingerPrints?: boolean;
biometricUnlock?: boolean; biometricUnlock?: boolean;
defaultUriMatch?: UriMatchType; defaultUriMatch?: UriMatchType;
disableBadgeCounter?: boolean;
disableGa?: boolean; disableGa?: boolean;
dontShowCardsCurrentTab?: boolean; dontShowCardsCurrentTab?: boolean;
dontShowIdentitiesCurrentTab?: boolean; dontShowIdentitiesCurrentTab?: boolean;

View File

@@ -889,24 +889,6 @@ export class StateService<
); );
} }
async getDisableBadgeCounter(options?: StorageOptions): Promise<boolean> {
return (
(await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
?.settings?.disableBadgeCounter ?? false
);
}
async setDisableBadgeCounter(value: boolean, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
account.settings.disableBadgeCounter = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
}
async getDisableChangedPasswordNotification(options?: StorageOptions): Promise<boolean> { async getDisableChangedPasswordNotification(options?: StorageOptions): Promise<boolean> {
return ( return (
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))

View File

@@ -53,6 +53,9 @@ export const VAULT_SETTINGS_DISK = new StateDefinition("vaultSettings", "disk",
export const COLLECTION_DATA = new StateDefinition("collection", "disk", { export const COLLECTION_DATA = new StateDefinition("collection", "disk", {
web: "memory", web: "memory",
}); });
export const BADGE_SETTINGS_DISK = new StateDefinition("badgeSettings", "disk");
export const AUTOFILL_SETTINGS_DISK = new StateDefinition("autofillSettings", "disk"); export const AUTOFILL_SETTINGS_DISK = new StateDefinition("autofillSettings", "disk");
export const AUTOFILL_SETTINGS_DISK_LOCAL = new StateDefinition("autofillSettingsLocal", "disk", { export const AUTOFILL_SETTINGS_DISK_LOCAL = new StateDefinition("autofillSettingsLocal", "disk", {
web: "disk-local", web: "disk-local",

View File

@@ -20,6 +20,7 @@ import { CollapsedGroupingsMigrator } from "./migrations/22-move-collapsed-group
import { MoveBiometricPromptsToStateProviders } from "./migrations/23-move-biometric-prompts-to-state-providers"; import { MoveBiometricPromptsToStateProviders } from "./migrations/23-move-biometric-prompts-to-state-providers";
import { SmOnboardingTasksMigrator } from "./migrations/24-move-sm-onboarding-key-to-state-providers"; import { SmOnboardingTasksMigrator } from "./migrations/24-move-sm-onboarding-key-to-state-providers";
import { ClearClipboardDelayMigrator } from "./migrations/25-move-clear-clipboard-to-autofill-settings-state-provider"; import { ClearClipboardDelayMigrator } from "./migrations/25-move-clear-clipboard-to-autofill-settings-state-provider";
import { BadgeSettingsMigrator } from "./migrations/26-move-badge-settings-to-state-providers";
import { FixPremiumMigrator } from "./migrations/3-fix-premium"; import { FixPremiumMigrator } from "./migrations/3-fix-premium";
import { RemoveEverBeenUnlockedMigrator } from "./migrations/4-remove-ever-been-unlocked"; import { RemoveEverBeenUnlockedMigrator } from "./migrations/4-remove-ever-been-unlocked";
import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys"; import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys";
@@ -30,7 +31,7 @@ import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-setting
import { MinVersionMigrator } from "./migrations/min-version"; import { MinVersionMigrator } from "./migrations/min-version";
export const MIN_VERSION = 2; export const MIN_VERSION = 2;
export const CURRENT_VERSION = 25; export const CURRENT_VERSION = 26;
export type MinVersion = typeof MIN_VERSION; export type MinVersion = typeof MIN_VERSION;
export function createMigrationBuilder() { export function createMigrationBuilder() {
@@ -58,7 +59,8 @@ export function createMigrationBuilder() {
.with(CollapsedGroupingsMigrator, 21, 22) .with(CollapsedGroupingsMigrator, 21, 22)
.with(MoveBiometricPromptsToStateProviders, 22, 23) .with(MoveBiometricPromptsToStateProviders, 22, 23)
.with(SmOnboardingTasksMigrator, 23, 24) .with(SmOnboardingTasksMigrator, 23, 24)
.with(ClearClipboardDelayMigrator, 24, CURRENT_VERSION); .with(ClearClipboardDelayMigrator, 24, 25)
.with(BadgeSettingsMigrator, 25, CURRENT_VERSION);
} }
export async function currentVersion( export async function currentVersion(

View File

@@ -73,7 +73,7 @@ const autofillSettingsStateDefinition: {
}, },
}; };
describe("ProviderKeysMigrator", () => { describe("AutofillSettingsKeyMigrator", () => {
let helper: MockProxy<MigrationHelper>; let helper: MockProxy<MigrationHelper>;
let sut: AutofillSettingsKeyMigrator; let sut: AutofillSettingsKeyMigrator;

View File

@@ -82,7 +82,7 @@ const autofillSettingsLocalStateDefinition: {
}, },
}; };
describe("ProviderKeysMigrator", () => { describe("ClearClipboardDelayMigrator", () => {
let helper: MockProxy<MigrationHelper>; let helper: MockProxy<MigrationHelper>;
let sut: ClearClipboardDelayMigrator; let sut: ClearClipboardDelayMigrator;

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 "./26-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(), 25);
sut = new BadgeSettingsMigrator(25, 26);
});
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(), 24);
sut = new BadgeSettingsMigrator(25, 26);
});
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<25, 26> {
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,
});
}
}
}
}