mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-08 08:39:59 +00:00
146 lines
4.3 KiB
TypeScript
146 lines
4.3 KiB
TypeScript
import defaultOptions, { type Options } from "../defaultOptions";
|
|
export type { Options };
|
|
|
|
import logger from "./logger";
|
|
|
|
import { TypedEventTarget } from "./TypedEventTarget";
|
|
import { TypedStorageArea } from "./TypedStorageArea";
|
|
|
|
const storageArea = new TypedStorageArea<{
|
|
options: Options;
|
|
}>(browser.storage.sync);
|
|
|
|
interface EventMap {
|
|
changed: Array<keyof Options>;
|
|
}
|
|
|
|
export default new (class extends TypedEventTarget<EventMap> {
|
|
constructor() {
|
|
super();
|
|
this.onStorageChanged = this.onStorageChanged.bind(this);
|
|
browser.storage.onChanged.addListener(this.onStorageChanged);
|
|
|
|
// Supresses sendRemoveListener closed conduit error
|
|
window.addEventListener("unload", () => {
|
|
browser.storage.onChanged.removeListener(this.onStorageChanged);
|
|
});
|
|
}
|
|
|
|
private onStorageChanged(
|
|
changes: { [key: string]: browser.storage.StorageChange },
|
|
areaName: string
|
|
) {
|
|
if (areaName !== "sync") {
|
|
return;
|
|
}
|
|
|
|
if ("options" in changes) {
|
|
const { oldValue, newValue } = changes.options;
|
|
const changedKeys = [];
|
|
|
|
for (const key of Object.keys(newValue)) {
|
|
if (oldValue) {
|
|
// Don't track added keys
|
|
if (!(key in oldValue)) {
|
|
continue;
|
|
}
|
|
|
|
const oldKeyValue = oldValue[key];
|
|
const newKeyValue = newValue[key];
|
|
|
|
// Equality comparison
|
|
if (oldKeyValue === newKeyValue) {
|
|
continue;
|
|
}
|
|
|
|
// Array comparison
|
|
if (
|
|
oldKeyValue instanceof Array &&
|
|
newKeyValue instanceof Array
|
|
) {
|
|
if (
|
|
oldKeyValue.length === newKeyValue.length &&
|
|
oldKeyValue.every(
|
|
(value, index) => value === newKeyValue[index]
|
|
)
|
|
) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
changedKeys.push(key);
|
|
}
|
|
|
|
this.dispatchEvent(
|
|
new CustomEvent("changed", {
|
|
detail: changedKeys as Array<keyof Options>
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetches `options` key from storage and returns it as
|
|
* Options interface type.
|
|
*/
|
|
public async getAll(): Promise<Options> {
|
|
const { options } = await storageArea.get("options");
|
|
return options;
|
|
}
|
|
|
|
/**
|
|
* Takes Options object and sets to `options` storage key.
|
|
* Returns storage promise.
|
|
*/
|
|
public async setAll(options: Options): Promise<void> {
|
|
return storageArea.set({ options });
|
|
}
|
|
|
|
/**
|
|
* Gets specific option from storage and returns it as its
|
|
* type from Options interface type.
|
|
*/
|
|
public async get<T extends keyof Options>(name: T): Promise<Options[T]> {
|
|
const options = await this.getAll();
|
|
|
|
if (options.hasOwnProperty(name)) {
|
|
return options[name];
|
|
} else {
|
|
throw logger.error(`Failed to find option ${name} in storage.`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets specific option to storage. Returns storage
|
|
* promise.
|
|
*/
|
|
public async set<T extends keyof Options>(
|
|
name: T,
|
|
value: Options[T]
|
|
): Promise<void> {
|
|
const options = await this.getAll();
|
|
options[name] = value;
|
|
return this.setAll(options);
|
|
}
|
|
|
|
/**
|
|
* Gets existing options from storage and compares it
|
|
* against defaults. Any options in defaults and not in
|
|
* storage are set. Does not override any existing options.
|
|
*/
|
|
public async update(defaults = defaultOptions): Promise<void> {
|
|
const newOpts = await this.getAll();
|
|
|
|
// Find options not already in storage
|
|
for (const [optName, optVal] of Object.entries(defaults)) {
|
|
if (!newOpts.hasOwnProperty(optName)) {
|
|
newOpts[optName] = optVal;
|
|
}
|
|
}
|
|
|
|
// Update storage with default values of new options
|
|
return this.setAll(newOpts);
|
|
}
|
|
})();
|