useWebExtensionStorage.ts
1 import { StorageSerializers } from '@vueuse/core' 2 import { pausableWatch, toValue, tryOnScopeDispose } from '@vueuse/shared' 3 import { ref, shallowRef } from 'vue-demi' 4 import { storage } from 'webextension-polyfill' 5 6 import type { 7 StorageLikeAsync, 8 UseStorageAsyncOptions, 9 } from '@vueuse/core' 10 import type { MaybeRefOrGetter, RemovableRef } from '@vueuse/shared' 11 import type { Ref } from 'vue-demi' 12 import type { Storage } from 'webextension-polyfill' 13 14 export type WebExtensionStorageOptions<T> = UseStorageAsyncOptions<T> 15 16 // https://github.com/vueuse/vueuse/blob/658444bf9f8b96118dbd06eba411bb6639e24e88/packages/core/useStorage/guess.ts 17 export function guessSerializerType(rawInit: unknown) { 18 return rawInit == null 19 ? 'any' 20 : rawInit instanceof Set 21 ? 'set' 22 : rawInit instanceof Map 23 ? 'map' 24 : rawInit instanceof Date 25 ? 'date' 26 : typeof rawInit === 'boolean' 27 ? 'boolean' 28 : typeof rawInit === 'string' 29 ? 'string' 30 : typeof rawInit === 'object' 31 ? 'object' 32 : Number.isNaN(rawInit) 33 ? 'any' 34 : 'number' 35 } 36 37 const storageInterface: StorageLikeAsync = { 38 removeItem(key: string) { 39 return storage.local.remove(key) 40 }, 41 42 setItem(key: string, value: string) { 43 return storage.local.set({ [key]: value }) 44 }, 45 46 async getItem(key: string) { 47 const storedData = await storage.local.get(key) 48 49 return storedData[key] as string 50 }, 51 } 52 53 /** 54 * https://github.com/vueuse/vueuse/blob/658444bf9f8b96118dbd06eba411bb6639e24e88/packages/core/useStorageAsync/index.ts 55 * 56 * @param key 57 * @param initialValue 58 * @param options 59 */ 60 export function useWebExtensionStorage<T>( 61 key: string, 62 initialValue: MaybeRefOrGetter<T>, 63 options: WebExtensionStorageOptions<T> = {}, 64 ): RemovableRef<T> { 65 const { 66 flush = 'pre', 67 deep = true, 68 listenToStorageChanges = true, 69 writeDefaults = true, 70 mergeDefaults = false, 71 shallow, 72 eventFilter, 73 onError = (e) => { 74 console.error(e) 75 }, 76 } = options 77 78 const rawInit: T = toValue(initialValue) 79 const type = guessSerializerType(rawInit) 80 81 const data = (shallow ? shallowRef : ref)(initialValue) as Ref<T> 82 const serializer = options.serializer ?? StorageSerializers[type] 83 84 async function read(event?: { key: string, newValue: string | null }) { 85 if (event && event.key !== key) 86 return 87 88 try { 89 const rawValue = event ? event.newValue : await storageInterface.getItem(key) 90 if (rawValue == null) { 91 data.value = rawInit 92 if (writeDefaults && rawInit !== null) 93 await storageInterface.setItem(key, await serializer.write(rawInit)) 94 } 95 else if (mergeDefaults) { 96 const value = await serializer.read(rawValue) as T 97 if (typeof mergeDefaults === 'function') 98 data.value = mergeDefaults(value, rawInit) 99 else if (type === 'object' && !Array.isArray(value)) 100 data.value = { ...(rawInit as Record<keyof unknown, unknown>), ...(value as Record<keyof unknown, unknown>) } as T 101 else data.value = value 102 } 103 else { 104 data.value = await serializer.read(rawValue) as T 105 } 106 } 107 catch (error) { 108 onError(error) 109 } 110 } 111 112 void read() 113 114 async function write() { 115 try { 116 await ( 117 data.value == null 118 ? storageInterface.removeItem(key) 119 : storageInterface.setItem(key, await serializer.write(data.value)) 120 ) 121 } 122 catch (error) { 123 onError(error) 124 } 125 } 126 127 const { pause: pauseWatch, resume: resumeWatch } = pausableWatch( 128 data, 129 write, 130 { 131 flush, 132 deep, 133 eventFilter, 134 }, 135 ) 136 137 if (listenToStorageChanges) { 138 const listener = async (changes: Record<string, Storage.StorageChange>) => { 139 try { 140 pauseWatch() 141 for (const [key, change] of Object.entries(changes)) { 142 await read({ 143 key, 144 newValue: change.newValue as string | null, 145 }) 146 } 147 } 148 finally { 149 resumeWatch() 150 } 151 } 152 153 storage.onChanged.addListener(listener) 154 155 tryOnScopeDispose(() => { 156 storage.onChanged.removeListener(listener) 157 }) 158 } 159 160 return data as RemovableRef<T> 161 }