Skip to content

Commit

Permalink
Add locks
Browse files Browse the repository at this point in the history
  • Loading branch information
grod220 committed Aug 21, 2024
1 parent e83a83d commit a4030ae
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 4 deletions.
2 changes: 1 addition & 1 deletion apps/extension/src/state/password.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface PasswordSlice {

export const createPasswordSlice =
(
session: ExtensionStorage<Partial<SessionStorageState>>,
session: ExtensionStorage<SessionStorageState>,
local: ExtensionStorage<LocalStorageState>,
): SliceCreator<PasswordSlice> =>
set => {
Expand Down
26 changes: 23 additions & 3 deletions apps/extension/src/storage/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export type Migrations<T> = Partial<Record<Version, MigrationMap<any, T>>>;
export type VersionSteps = Record<Version, Version>;

export class ExtensionStorage<T> {
private migrationLocks: Record<string, Promise<T[keyof T]>> = {};

constructor(
private storage: IStorage,
private defaults: T,
Expand Down Expand Up @@ -97,9 +99,27 @@ export class ExtensionStorage<T> {
return item.value;
}

const migrationFn = this.migrations[item.version]?.[key];
// Check for an ongoing migration, if exists, return the result of that
const ongoingMigration = this.migrationLocks[String(key)] as Promise<T[K]> | undefined;
if (ongoingMigration) {
return ongoingMigration;
}

// Store the promise in the lock map. Ensures multiple callers don't run migrations multiple times.
const migrationPromise = this.migrateField(key, item);
this.migrationLocks[String(key)] = migrationPromise;

// Perform migration if available for version & field
try {
return await migrationPromise;
} finally {
// Release the lock once migration is complete
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete this.migrationLocks[String(key)];
}
}

private async migrateField<K extends keyof T>(key: K, item: StorageItem<T[K]>): Promise<T[K]> {
const migrationFn = this.migrations[item.version]?.[key];
const value = migrationFn
? await migrationFn(item.value, key => this.getRaw({ key, skipMigration: true }))
: item.value;
Expand All @@ -112,7 +132,7 @@ export class ExtensionStorage<T> {
}

// Recurse further if there are more migration steps
return await this.migrateIfNeeded(key, {
return await this.migrateField(key, {
version: nextVersion,
value,
});
Expand Down

0 comments on commit a4030ae

Please sign in to comment.