diff --git a/package.json b/package.json index 00c2034..9fe0f52 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "caret-pos": "^2.0.0", "debounce": "^1.2.1", "htm": "^3.1.0", + "migration-helper": "^0.1.2", "modern-normalize": "^1.1.0", "platform": "^1.3.6", "preact": "^10.6.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9963fa4..9c0d261 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,7 @@ specifiers: caret-pos: ^2.0.0 debounce: ^1.2.1 htm: ^3.1.0 + migration-helper: ^0.1.2 modern-normalize: ^1.1.0 platform: ^1.3.6 postcss: ^8.4.8 @@ -27,6 +28,7 @@ dependencies: caret-pos: 2.0.0 debounce: 1.2.1 htm: 3.1.0 + migration-helper: 0.1.2 modern-normalize: 1.1.0 platform: 1.3.6 preact: 10.6.6 @@ -3795,6 +3797,10 @@ packages: picomatch: 2.3.1 dev: true + /migration-helper/0.1.2: + resolution: {integrity: sha512-2ZiOVWqKwGL/hx5xCOXKqfNc7aB6Kz/YC4W/vlVd3v1mmnAl4wQb9jGfBZYnKS8Yc+EBCMYsbAhZJFsqcmqyfg==} + dev: false + /mime-db/1.51.0: resolution: {integrity: sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==} engines: {node: '>= 0.6'} diff --git a/source/migrations.ts b/source/migrations.ts new file mode 100644 index 0000000..1d0902f --- /dev/null +++ b/source/migrations.ts @@ -0,0 +1,50 @@ +import {Migration} from 'migration-helper'; + +export const migrations: Array> = [ + { + version: '1.1.2', + async migrate(data: Record) { + const migrated: Record = { + data: { + hideVotes: data.data.hideVotes as Record, + knownGroups: data.data.knownGroups as string[], + latestActiveFeatureTab: data.data.latestActiveFeatureTab as string, + }, + features: (data.features as Record) ?? {}, + version: '1.1.2', + }; + + const userLabels = data.data.userLabels as UserLabel[]; + for (const label of userLabels) { + migrated[`userLabel${label.id}`] = label; + } + + const usernameColors = data.data.usernameColors as UsernameColor[]; + for (const color of usernameColors) { + migrated[`usernameColor${color.id}`] = color; + } + + return migrated; + }, + }, +]; + +export function deserializeData(data: Record): { + userLabels: UserLabel[]; + usernameColors: UsernameColor[]; +} { + const deserialized: ReturnType = { + userLabels: [], + usernameColors: [], + }; + + for (const [key, value] of Object.entries(data)) { + if (key.startsWith('userLabel')) { + deserialized.userLabels.push(value); + } else if (key.startsWith('usernameColor')) { + deserialized.usernameColors.push(value); + } + } + + return deserialized; +} diff --git a/source/settings.ts b/source/settings.ts index dd0cefd..5e8140f 100644 --- a/source/settings.ts +++ b/source/settings.ts @@ -1,20 +1,35 @@ +import {migrate} from 'migration-helper'; import browser from 'webextension-polyfill'; +import {migrations, deserializeData} from './migrations.js'; import {log} from './utilities/exports.js'; export default class Settings { public static async fromSyncStorage(): Promise { const settings = new Settings(); - const defaultsObject = { - data: settings.data, - features: settings.features, + + const sync = { + ...settings, + ...(await browser.storage.sync.get(null)), }; - const sync = (await browser.storage.sync.get( - defaultsObject, - )) as typeof defaultsObject; - settings.data = {...settings.data, ...sync.data}; - settings.features = {...settings.features, ...sync.features}; + const migrated = (await migrate( + sync, + sync.version ?? settings.version, + migrations, + )) as Record; + + const deserialized = deserializeData(migrated); + + settings.data = migrated.data as Settings['data']; + settings.data.userLabels = deserialized.userLabels; + settings.data.usernameColors = deserialized.usernameColors; + settings.features = migrated.features as Settings['features']; + settings.version = migrated.version as Settings['version']; + + if (sync.version !== settings.version) { + await settings.save(); + } return settings; } @@ -73,6 +88,8 @@ export default class Settings { usernameColors: boolean; }; + public version: string; + private constructor() { this.data = { hideVotes: { @@ -136,6 +153,8 @@ export default class Settings { userLabels: true, usernameColors: false, }; + + this.version = '0.0.0'; } public manifest(): TRXManifest { @@ -147,6 +166,24 @@ export default class Settings { } public async save(): Promise { - await browser.storage.sync.set(this); + const sync: Record = { + data: { + hideVotes: this.data.hideVotes, + knownGroups: this.data.knownGroups, + latestActiveFeatureTab: this.data.latestActiveFeatureTab, + }, + features: this.features, + version: this.version, + }; + + for (const label of this.data.userLabels) { + sync[`userLabel${label.id}`] = {...label}; + } + + for (const color of this.data.usernameColors) { + sync[`usernameColor${color.id}`] = {...color}; + } + + await browser.storage.sync.set(sync); } }