-
-
Notifications
You must be signed in to change notification settings - Fork 224
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes storage reactivity by reimplementing useAsyncStorage from vueuse and replacing the storage listener on window by a custom one using the polyfill webextension storage API.
- Loading branch information
Showing
3 changed files
with
151 additions
and
32 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import { StorageSerializers } from '@vueuse/core' | ||
import { toValue, tryOnScopeDispose, watchWithFilter } from '@vueuse/shared' | ||
import { ref, shallowRef } from 'vue-demi' | ||
import { storage } from 'webextension-polyfill' | ||
|
||
import type { | ||
StorageLikeAsync, | ||
UseStorageAsyncOptions, | ||
} from '@vueuse/core' | ||
import type { MaybeRefOrGetter, RemovableRef } from '@vueuse/shared' | ||
import type { Ref } from 'vue-demi' | ||
import type { Storage } from 'webextension-polyfill' | ||
|
||
export type WebExtensionStorageOptions<T> = UseStorageAsyncOptions<T> | ||
|
||
// https://github.com/vueuse/vueuse/blob/658444bf9f8b96118dbd06eba411bb6639e24e88/packages/core/useStorage/guess.ts | ||
export function guessSerializerType<T extends(string | number | boolean | object | null)>(rawInit: T) { | ||
return rawInit == null | ||
? 'any' | ||
: rawInit instanceof Set | ||
? 'set' | ||
: rawInit instanceof Map | ||
? 'map' | ||
: rawInit instanceof Date | ||
? 'date' | ||
: typeof rawInit === 'boolean' | ||
? 'boolean' | ||
: typeof rawInit === 'string' | ||
? 'string' | ||
: typeof rawInit === 'object' | ||
? 'object' | ||
: Number.isNaN(rawInit) | ||
? 'any' | ||
: 'number' | ||
} | ||
|
||
const storageInterface: StorageLikeAsync = { | ||
removeItem(key: string) { | ||
return storage.local.remove(key) | ||
}, | ||
|
||
setItem(key: string, value: string) { | ||
return storage.local.set({ [key]: value }) | ||
}, | ||
|
||
async getItem(key: string) { | ||
const storedData = await storage.local.get(key) | ||
|
||
return storedData[key] | ||
}, | ||
} | ||
|
||
/** | ||
* https://github.com/vueuse/vueuse/blob/658444bf9f8b96118dbd06eba411bb6639e24e88/packages/core/useStorageAsync/index.ts | ||
* | ||
* @param key | ||
* @param initialValue | ||
* @param options | ||
*/ | ||
export function useWebExtensionStorage<T extends(string | number | boolean | object | null)>( | ||
key: string, | ||
initialValue: MaybeRefOrGetter<T>, | ||
options: WebExtensionStorageOptions<T> = {}, | ||
): RemovableRef<T> { | ||
const { | ||
flush = 'pre', | ||
deep = true, | ||
listenToStorageChanges = true, | ||
writeDefaults = true, | ||
mergeDefaults = false, | ||
shallow, | ||
eventFilter, | ||
onError = (e) => { | ||
console.error(e) | ||
}, | ||
} = options | ||
|
||
const rawInit: T = toValue(initialValue) | ||
const type = guessSerializerType<T>(rawInit) | ||
|
||
const data = (shallow ? shallowRef : ref)(initialValue) as Ref<T> | ||
const serializer = options.serializer ?? StorageSerializers[type] | ||
|
||
async function read(event?: { key: string; newValue: string | null }) { | ||
if (event && event.key !== key) | ||
return | ||
|
||
try { | ||
const rawValue = event ? event.newValue : await storageInterface.getItem(key) | ||
if (rawValue == null) { | ||
data.value = rawInit | ||
if (writeDefaults && rawInit !== null) | ||
await storageInterface.setItem(key, await serializer.write(rawInit)) | ||
} | ||
else if (mergeDefaults) { | ||
const value = await serializer.read(rawValue) as T | ||
if (typeof mergeDefaults === 'function') | ||
data.value = mergeDefaults(value, rawInit) | ||
else if (type === 'object' && !Array.isArray(value)) | ||
data.value = { ...(rawInit as Record<keyof unknown, unknown>), ...(value as Record<keyof unknown, unknown>) } as T | ||
else data.value = value | ||
} | ||
else { | ||
data.value = await serializer.read(rawValue) as T | ||
} | ||
} | ||
catch (error) { | ||
onError(error) | ||
} | ||
} | ||
|
||
void read() | ||
|
||
if (listenToStorageChanges) { | ||
const listener = async (changes: Record<string, Storage.StorageChange>) => { | ||
for (const [key, change] of Object.entries(changes)) { | ||
await read({ | ||
key, | ||
newValue: change.newValue as string | null, | ||
}) | ||
} | ||
} | ||
|
||
storage.onChanged.addListener(listener) | ||
|
||
tryOnScopeDispose(() => { | ||
storage.onChanged.removeListener(listener) | ||
}) | ||
} | ||
|
||
watchWithFilter( | ||
data, | ||
async () => { | ||
try { | ||
await (data.value == null ? storageInterface.removeItem(key) : storageInterface.setItem(key, await serializer.write(data.value))) | ||
} | ||
catch (error) { | ||
onError(error) | ||
} | ||
}, | ||
{ | ||
flush, | ||
deep, | ||
eventFilter, | ||
}, | ||
) | ||
|
||
return data as RemovableRef<T> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
import { useStorageLocal } from '~/composables/useStorageLocal' | ||
import { useWebExtensionStorage } from '~/composables/useWebExtensionStorage' | ||
|
||
export const storageDemo = useStorageLocal('webext-demo', 'Storage Demo') | ||
export const storageDemo = useWebExtensionStorage('webext-demo', 'Storage Demo') |