should use storage.local over window.localStorage
privatenumber opened this issue · comments
Problem
This template currently uses localStorage(1, 2).
MDN advises against using local storage because of persistence issues:
Although this API is similar to Window.localStorage it is recommended that you don't use Window.localStorage in extension code. Firefox will clear data stored by extensions using the localStorage API in various scenarios where users clear their browsing history and data for privacy reasons, while data saved using the
storage.local
API will be correctly persisted in these scenarios.
Proposal
Use storage.local
via webextension-polyfill
.
FYI VueUse useStorage
accepts a 3rd parameter to accept a StoreLike object, so perhaps a wrapper around storage.local
could have been passed in. However, storage.local
APIs return promises so it cannot.
Because of storage.local
's async nature, it might be best to create another composable function that returns an isLoaded
state.
I'm also facing that issue, for now I will continue to use my custom helpers for storage.local
.
If anyone's interested, here's the composition "use" function I ended up with to work with storage.local
:
import { storage } from 'webextension-polyfill';
const get = async (key: string) => {
const data = await storage.local.get(key);
return data[key];
};
export function useBrowserStorage<T>(
key: string,
initialValue: T,
) {
const data = reactive({
value: initialValue,
isLoaded: false,
});
// First data read from storage
get(key).then(
(keyDataRaw) => {
if (keyDataRaw) {
data.value = JSON.parse(keyDataRaw);
}
data.isLoaded = true;
},
(error) => {
throw error;
},
);
// Update local reactive state
storage.onChanged.addListener((changes) => {
data.value = JSON.parse(changes[key].newValue);
});
// Update storage
watch(
data,
async () => {
const { value } = data;
if (value === null || value === undefined) {
await storage.local.remove(key);
} else {
const serializedValue = JSON.stringify(value);
const currentValue = await get(key);
if (serializedValue !== currentValue) {
await storage.local.set({
[key]: serializedValue,
});
}
}
},
{
deep: true,
},
);
return data;
}
It returns an object { value: T, isLoaded: boolean }
. The return type is similar to a ref, but with an isLoaded
property because storage.local
API is async.
@antfu I wasn't sure if this belongs in VueUse because it relies on webextension-polyfill
, but happy to PR this in somewhere if you'd like.
I wasn't sure if this belongs in VueUse
@privatenumber I guess we are missing a composable to support async storage. I imagine we could have useAsyncStorage
in VueUse, and convert the storage interface from webextension-polyfill
in this template.
I ended up using this:
import { useStorageAsync, StorageLikeAsync } from '@vueuse/core';
import { storage } from 'webextension-polyfill';
const browserStorageLocal: StorageLikeAsync = {
removeItem(key: string) {
return storage.local.remove(key);
},
setItem(key: string, value: string) {
return storage.local.set({ [key]: value });
},
async getItem(key: string) {
return (await storage.local.get(key))[key];
}
};
const useBrowserStorageLocal = <T>(key: string, initialValue: T) => {
return useStorageAsync(key, initialValue, browserStorageLocal);
};
export default useBrowserStorageLocal;
Nice! Looks like @antfu added useStorageAsync
shortly after: vueuse/vueuse@169b02b
Do you want to PR that in @hankolsen ?