Add source code.
This commit is contained in:
parent
ca592b6b26
commit
408291e229
|
@ -0,0 +1 @@
|
||||||
|
export * from "./value.js";
|
|
@ -0,0 +1,73 @@
|
||||||
|
import browser from "webextension-polyfill";
|
||||||
|
|
||||||
|
export type StorageArea = browser.Storage.StorageArea;
|
||||||
|
|
||||||
|
type ValueOptions<T> = {
|
||||||
|
/** 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<T>(
|
||||||
|
options: ValueOptions<T>,
|
||||||
|
): Promise<Value<T>> {
|
||||||
|
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<T> = Required<ValueOptions<T>>;
|
||||||
|
|
||||||
|
export class Value<T> implements Props<T> {
|
||||||
|
public readonly deserialize: Props<T>["deserialize"];
|
||||||
|
public readonly key: Props<T>["key"];
|
||||||
|
public readonly serialize: Props<T>["serialize"];
|
||||||
|
public readonly storage: Props<T>["storage"];
|
||||||
|
|
||||||
|
private inner: Props<T>["value"];
|
||||||
|
|
||||||
|
constructor(options: Required<ValueOptions<T>>) {
|
||||||
|
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<void> {
|
||||||
|
await this.storage.remove(this.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Save the value to storage. */
|
||||||
|
public async save(): Promise<void> {
|
||||||
|
await this.storage.set({
|
||||||
|
[this.key]: this.serialize(this.inner),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import browser from "webextension-polyfill";
|
||||||
|
|
||||||
|
browser.browserAction.onClicked.addListener(async () => {
|
||||||
|
await browser.runtime.openOptionsPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.runtime.onInstalled.addListener(async () => {
|
||||||
|
await browser.runtime.openOptionsPage();
|
||||||
|
});
|
|
@ -0,0 +1,33 @@
|
||||||
|
import browser from "webextension-polyfill";
|
||||||
|
|
||||||
|
import {createValue} from "../build/index.js";
|
||||||
|
|
||||||
|
const updatedDate = await createValue<Date>({
|
||||||
|
// A function that deserializes a string from storage to convert to the wanted
|
||||||
|
// type.
|
||||||
|
deserialize: (value) => new Date(value),
|
||||||
|
|
||||||
|
// A function that serializes the type to a string to be set in storage.
|
||||||
|
serialize: (date) => date.toISOString(),
|
||||||
|
|
||||||
|
// The key to get from storage.
|
||||||
|
key: "updatedDate",
|
||||||
|
|
||||||
|
// The StorageArea to use, defaults to local.
|
||||||
|
storage: browser.storage.sync,
|
||||||
|
|
||||||
|
// The value to use if there is none in storage.
|
||||||
|
value: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the inner value.
|
||||||
|
console.log(updatedDate.value);
|
||||||
|
|
||||||
|
// Set the inner value.
|
||||||
|
updatedDate.value = new Date();
|
||||||
|
|
||||||
|
// Save the value to storage.
|
||||||
|
await updatedDate.save();
|
||||||
|
|
||||||
|
// Remove the value from storage.
|
||||||
|
await updatedDate.remove();
|
|
@ -0,0 +1,78 @@
|
||||||
|
import {setup, type TestContext} from "@holllo/test";
|
||||||
|
import browser from "webextension-polyfill";
|
||||||
|
|
||||||
|
import {createValue, type Value} from "../source/index.js";
|
||||||
|
|
||||||
|
const create = async <T>(
|
||||||
|
key: string,
|
||||||
|
expected: T,
|
||||||
|
): Promise<[string, T, Value<T>]> => {
|
||||||
|
return [
|
||||||
|
key,
|
||||||
|
expected,
|
||||||
|
await createValue<T>({
|
||||||
|
deserialize: JSON.parse,
|
||||||
|
key,
|
||||||
|
serialize: JSON.stringify,
|
||||||
|
value: expected,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const isStored = async (test: TestContext, key: string, exist: boolean) => {
|
||||||
|
const stored = await browser.storage.local.get(key);
|
||||||
|
test.equals(typeof stored[key], exist ? "string" : "undefined");
|
||||||
|
};
|
||||||
|
|
||||||
|
type SampleObject = {
|
||||||
|
name: string;
|
||||||
|
status: "failed" | "passed";
|
||||||
|
};
|
||||||
|
|
||||||
|
const sampleObject: SampleObject = {
|
||||||
|
name: "Sample Object",
|
||||||
|
status: "passed",
|
||||||
|
};
|
||||||
|
|
||||||
|
const group = await setup("Value<T>", async (group) => {
|
||||||
|
const samples = [
|
||||||
|
["number", "testNumber", Math.PI],
|
||||||
|
["string", "testString", "A string to test with!" as string],
|
||||||
|
["SampleObject", "testSampleObject", sampleObject],
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
for (const sample of samples) {
|
||||||
|
group.test(`T = ${sample[0]}`, async (test) => {
|
||||||
|
const [key, expected, value] = await create(sample[1], sample[2]);
|
||||||
|
if (sample[0] === "SampleObject") {
|
||||||
|
const _expected = expected as SampleObject;
|
||||||
|
const _value = value.value as SampleObject;
|
||||||
|
test.equals(_value.name, _expected.name);
|
||||||
|
test.equals(_value.status, _expected.status);
|
||||||
|
} else {
|
||||||
|
test.equals(value.value, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
await isStored(test, key, false);
|
||||||
|
await value.save();
|
||||||
|
await isStored(test, key, true);
|
||||||
|
await value.remove();
|
||||||
|
await isStored(test, key, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.test(`T = Date`, async (test) => {
|
||||||
|
const expectedString = "2022-12-31T12:34:56.789Z";
|
||||||
|
const expected = new Date(expectedString);
|
||||||
|
const value = await createValue<Date>({
|
||||||
|
deserialize: (input) => new Date(input),
|
||||||
|
key: "testDate",
|
||||||
|
serialize: (input) => input.toISOString(),
|
||||||
|
value: expected,
|
||||||
|
});
|
||||||
|
test.equals(value.value instanceof Date, true);
|
||||||
|
await value.save();
|
||||||
|
const stored = await browser.storage.local.get(value.key);
|
||||||
|
test.equals(stored[value.key], expectedString);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>WebExtension Storage Tests</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script src="tests.js" type="module"></script>
|
||||||
|
<script src="example.js" type="module"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json.schemastore.org/webextension",
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "WebExtension Storage Tests",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"applications": {
|
||||||
|
"gecko": {
|
||||||
|
"id": "webextension-storage-tests@holllo.org"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"scripts": [
|
||||||
|
"background.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browser_action": {},
|
||||||
|
"options_ui": {
|
||||||
|
"page": "index.html",
|
||||||
|
"open_in_tab": true
|
||||||
|
},
|
||||||
|
"permissions": [
|
||||||
|
"storage"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue