import browser from "webextension-polyfill"; export type StorageArea = browser.Storage.StorageArea; type ValueOptions = { /** A function to convert a string to the type `T`. */ deserialize: (input: string) => T; /** The key to use for storage. */ key: string; /** A function convert the type `T` to a string, defaults to `JSON.stringify`. */ serialize?: (input: T) => string; /** The storage area to use, defaults to local. */ storage?: StorageArea; /** The default value to use if none exists in storage. */ value: T; }; export async function createValue( options: ValueOptions, ): Promise> { const storage = options.storage ?? browser.storage.local; const value = await storage.get(options.key); const stored = value[options.key] as string | undefined; return new Value({ key: options.key, deserialize: options.deserialize, serialize: options.serialize ?? JSON.stringify, storage, value: stored === undefined ? options.value : options.deserialize(stored), }); } type Props = Required>; export class Value implements Props { public readonly deserialize: Props["deserialize"]; public readonly key: Props["key"]; public readonly serialize: Props["serialize"]; public readonly storage: Props["storage"]; private inner: Props["value"]; constructor(options: Required>) { this.deserialize = options.deserialize; this.key = options.key; this.serialize = options.serialize; this.storage = options.storage; this.inner = options.value; } get value(): T { return this.inner; } set value(value: T) { this.inner = value; } /** Remove the value from storage. */ public async remove(): Promise { await this.storage.remove(this.key); } /** Save the value to storage. */ public async save(): Promise { await this.storage.set({ [this.key]: this.serialize(this.inner), }); } }