1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 00:03:56 +00:00

[PM-5266] Create Avatar Service (#7905)

* rename file, move, and update imports

* refactor and implement StateProvider

* remove comments

* add migration

* use 'disk-local' for web

* add JSDoc comments

* move AvatarService before SyncService

* create factory

* replace old method with observable in story

* fix tests

* add tests for migration

* receive most recent avatarColor emission

* move logic to component

* fix CLI dependency

* remove BehaviorSubject

* cleanup

* use UserKeyDefinition

* avoid extra write

* convert to observable

* fix tests
This commit is contained in:
rr-bw
2024-03-14 09:56:48 -07:00
committed by GitHub
parent 10d503c15f
commit 65b7ca7177
25 changed files with 403 additions and 165 deletions

View File

@@ -32,6 +32,7 @@ import { AppIdMigrator } from "./migrations/33-move-app-id-to-state-providers";
import { DomainSettingsMigrator } from "./migrations/34-move-domain-settings-to-state-providers";
import { MoveThemeToStateProviderMigrator } from "./migrations/35-move-theme-to-state-providers";
import { VaultSettingsKeyMigrator } from "./migrations/36-move-show-card-and-identity-to-state-provider";
import { AvatarColorMigrator } from "./migrations/37-move-avatar-color-to-state-providers";
import { RemoveEverBeenUnlockedMigrator } from "./migrations/4-remove-ever-been-unlocked";
import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys";
import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key";
@@ -41,7 +42,7 @@ import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-setting
import { MinVersionMigrator } from "./migrations/min-version";
export const MIN_VERSION = 2;
export const CURRENT_VERSION = 36;
export const CURRENT_VERSION = 37;
export type MinVersion = typeof MIN_VERSION;
export function createMigrationBuilder() {
@@ -80,7 +81,8 @@ export function createMigrationBuilder() {
.with(AppIdMigrator, 32, 33)
.with(DomainSettingsMigrator, 33, 34)
.with(MoveThemeToStateProviderMigrator, 34, 35)
.with(VaultSettingsKeyMigrator, 35, CURRENT_VERSION);
.with(VaultSettingsKeyMigrator, 35, 36)
.with(AvatarColorMigrator, 36, CURRENT_VERSION);
}
export async function currentVersion(

View File

@@ -0,0 +1,143 @@
import { MockProxy } from "jest-mock-extended";
import { MigrationHelper } from "../migration-helper";
import { mockMigrationHelper, runMigrator } from "../migration-helper.spec";
import { AvatarColorMigrator } from "./37-move-avatar-color-to-state-providers";
function rollbackJSON() {
return {
authenticatedAccounts: ["user-1", "user-2"],
"user_user-1_avatar_avatarColor": "#ff0000",
"user_user-2_avatar_avatarColor": "#cccccc",
"user-1": {
settings: {
extra: "data",
},
extra: "data",
},
"user-2": {
settings: {
extra: "data",
},
extra: "data",
},
};
}
describe("AvatarColorMigrator", () => {
const migrator = new AvatarColorMigrator(36, 37);
it("should migrate the avatarColor property from the account settings object to a user StorageKey", async () => {
const output = await runMigrator(migrator, {
authenticatedAccounts: ["user-1", "user-2"] as const,
"user-1": {
settings: {
avatarColor: "#ff0000",
extra: "data",
},
extra: "data",
},
"user-2": {
settings: {
avatarColor: "#cccccc",
extra: "data",
},
extra: "data",
},
});
expect(output).toEqual({
authenticatedAccounts: ["user-1", "user-2"],
"user_user-1_avatar_avatarColor": "#ff0000",
"user_user-2_avatar_avatarColor": "#cccccc",
"user-1": {
settings: {
extra: "data",
},
extra: "data",
},
"user-2": {
settings: {
extra: "data",
},
extra: "data",
},
});
});
it("should handle missing parts", async () => {
const output = await runMigrator(migrator, {
authenticatedAccounts: ["user-1", "user-2"],
global: {
extra: "data",
},
"user-1": {
extra: "data",
settings: {
extra: "data",
},
},
"user-2": null,
});
expect(output).toEqual({
authenticatedAccounts: ["user-1", "user-2"],
global: {
extra: "data",
},
"user-1": {
extra: "data",
settings: {
extra: "data",
},
},
"user-2": null,
});
});
describe("rollback", () => {
let helper: MockProxy<MigrationHelper>;
let sut: AvatarColorMigrator;
const keyDefinitionLike = {
key: "avatarColor",
stateDefinition: {
name: "avatar",
},
};
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 37);
sut = new AvatarColorMigrator(36, 37);
});
it("should null out the avatarColor user StorageKey for each account", async () => {
await sut.rollback(helper);
expect(helper.setToUser).toHaveBeenCalledTimes(2);
expect(helper.setToUser).toHaveBeenCalledWith("user-1", keyDefinitionLike, null);
expect(helper.setToUser).toHaveBeenCalledWith("user-2", keyDefinitionLike, null);
});
it("should add the avatarColor property back to the account settings object", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledTimes(2);
expect(helper.set).toHaveBeenCalledWith("user-1", {
settings: {
avatarColor: "#ff0000",
extra: "data",
},
extra: "data",
});
expect(helper.set).toHaveBeenCalledWith("user-2", {
settings: {
avatarColor: "#cccccc",
extra: "data",
},
extra: "data",
});
});
});
});

View File

@@ -0,0 +1,57 @@
import { KeyDefinitionLike, MigrationHelper, StateDefinitionLike } from "../migration-helper";
import { Migrator } from "../migrator";
type ExpectedAccountState = {
settings?: { avatarColor?: string };
};
const AVATAR_COLOR_STATE: StateDefinitionLike = { name: "avatar" };
const AVATAR_COLOR_KEY: KeyDefinitionLike = {
key: "avatarColor",
stateDefinition: AVATAR_COLOR_STATE,
};
export class AvatarColorMigrator extends Migrator<36, 37> {
async migrate(helper: MigrationHelper): Promise<void> {
const legacyAccounts = await helper.getAccounts<ExpectedAccountState>();
await Promise.all(
legacyAccounts.map(async ({ userId, account }) => {
// Move account avatarColor
if (account?.settings?.avatarColor != null) {
await helper.setToUser(userId, AVATAR_COLOR_KEY, account.settings.avatarColor);
// Delete old account avatarColor property
delete account?.settings?.avatarColor;
await helper.set(userId, account);
}
}),
);
}
async rollback(helper: MigrationHelper): Promise<void> {
async function rollbackUser(userId: string, account: ExpectedAccountState) {
let updatedAccount = false;
const userAvatarColor = await helper.getFromUser<string>(userId, AVATAR_COLOR_KEY);
if (userAvatarColor) {
if (!account) {
account = {};
}
updatedAccount = true;
account.settings.avatarColor = userAvatarColor;
await helper.setToUser(userId, AVATAR_COLOR_KEY, null);
}
if (updatedAccount) {
await helper.set(userId, account);
}
}
const accounts = await helper.getAccounts<ExpectedAccountState>();
await Promise.all(accounts.map(({ userId, account }) => rollbackUser(userId, account)));
}
}