Files
wxt/src/storage.ts
T
2023-11-14 23:07:45 -06:00

119 lines
3.5 KiB
TypeScript

/**
* @module wxt/storage
*/
import {
Driver,
WatchCallback,
createStorage,
defineDriver,
Storage,
} from 'unstorage';
import browser, { Storage as BrowserStorage } from 'webextension-polyfill';
export interface WebExtensionDriverOptions {
storageArea: 'sync' | 'local' | 'managed' | 'session';
}
export const webExtensionDriver: (opts: WebExtensionDriverOptions) => Driver =
defineDriver((opts) => {
const checkPermission = () => {
if (browser.storage == null)
throw Error(
"You must request the 'storage' permission to use webExtensionDriver",
);
};
const _storageListener: (
changes: BrowserStorage.StorageAreaSyncOnChangedChangesType,
) => void = (changes) => {
Object.entries(changes).forEach(([key, { newValue }]) => {
_listeners.forEach((callback) => {
callback(newValue ? 'update' : 'remove', key);
});
});
};
const _listeners = new Set<WatchCallback>();
return {
name: 'web-extension:' + opts.storageArea,
async hasItem(key) {
checkPermission();
const res = await browser.storage[opts.storageArea].get(key);
return res[key] != null;
},
async getItem(key) {
checkPermission();
const res = await browser.storage[opts.storageArea].get(key);
return res[key] ?? null;
},
async getItems(items) {
checkPermission();
const res = await browser.storage[opts.storageArea].get(
items.map((item) => item.key),
);
return items.map((item) => ({
key: item.key,
value: res[item.key] ?? null,
}));
},
async setItem(key, value) {
checkPermission();
await browser.storage[opts.storageArea].set({ [key]: value ?? null });
},
async setItems(items) {
checkPermission();
const map = items.reduce<Record<string, any>>((map, item) => {
map[item.key] = item.value ?? null;
return map;
}, {});
await browser.storage[opts.storageArea].set(map);
},
async removeItem(key) {
checkPermission();
await browser.storage[opts.storageArea].remove(key);
},
async getKeys() {
checkPermission();
const all = await browser.storage[opts.storageArea].get();
return Object.keys(all);
},
async clear() {
checkPermission();
await browser.storage[opts.storageArea].clear();
},
watch(callback) {
checkPermission();
_listeners.add(callback);
if (_listeners.size === 1) {
browser.storage[opts.storageArea].onChanged.addListener(
_storageListener,
);
}
return () => {
_listeners.delete(callback);
if (_listeners.size === 0) {
browser.storage[opts.storageArea].onChanged.removeListener(
_storageListener,
);
}
};
},
};
});
function createWebExtensionStorage() {
const storage = createStorage();
storage.mount('local', webExtensionDriver({ storageArea: 'local' }));
storage.mount('session', webExtensionDriver({ storageArea: 'session' }));
storage.mount('sync', webExtensionDriver({ storageArea: 'sync' }));
storage.mount('managed', webExtensionDriver({ storageArea: 'managed' }));
return storage;
}
export type StorageValue = null | string | number | boolean | object;
export const storage: Storage<StorageValue> = createWebExtensionStorage();
export * from 'unstorage';