8

I'm trying to switch a Chrome extension to using Manifest v3.

I've gotten everything except where I'm using localStorage:

if (localStorage.getItem(lastchecked[0]) < Date.now() - 2500000000) {
    localStorage.setItem(lastchecked[0], Date.now());
} else {
    const remover = Date.now() - 2500000000;
    Object.entries(localStorage).forEach(([k, v]) => {
        if (v < remover) {
            delete localStorage[k];
        }
    });
}

This is the error I'm getting:

ReferenceError: localStorage is not defined

From what I can tell, it's because I switched the extension from using a background script to using a service worker, which doesn't appear to give access to localStorage.

Is there any simple way to switch this to using something else besides localStorage since it's not available?

xowim46108
  • 97
  • 1
  • 4

1 Answers1

13

localStorage is not available in a service worker per the specification. The reason is that since it provides synchronous access it has to be read in its entirety before starting the JS environment, which may take some time that's comparable to the startup time (~50ms) of the environment itself in case the storage contains several megabytes (the maximum is 5MB).

Only asynchronous storage API are available in a service worker.
Extensions can use these:

  • chrome.storage

    • good: small amount of simple data
    • good: directly available in a content script
    • meh: only JSON-compatible types (string, number, boolean, null, object/array consisting of these types recursively), so attempting to store complex things like Set or Map will end up as an empty object {} and you'll have to serialize them e.g. [...mySet] when writing, new Set(result.foo) when reading.
    • bad: very slow for big/nested data
  • IndexedDB

    • good: very fast for any amount/complexity of data
    • good: more data types like ArrayBuffer, File, Blob, typed arrays, Set, Map,
      see the structured clone algorithm
    • meh: the data is not available in a content script so you'll have to use messaging
    • bad: its API is obsolete and clunky, but there are several libraries that fix it

These API are asynchronous, so you will have to rework your code.
Since Chrome 95 promisified chrome.storage, we can use async/await for your example.
And don't forget to add "storage" to "permissions" in manifest.json.

const LS = chrome.storage.local;

async function pruneStorage() {
  const remover = Date.now() - 2500000000;
  const key = lastchecked[0];
  if ((await LS.get(key))[key] < remover) {
    await LS.set({[key]: Date.now()});
  } else {
    const toRemove = Object.entries(await LS.get())
      .map(([k, v]) => v < remover && k)
      .filter(Boolean);
    if (toRemove.length) {
      await LS.remove(toRemove);
    }
  }
}

Alternatively, mimic localStorage:

const LS = {
  getAllItems: () => chrome.storage.local.get(),
  getItem: async key => (await chrome.storage.local.get(key))[key],
  setItem: (key, val) => chrome.storage.local.set({[key]: val}),
  removeItems: keys => chrome.storage.local.remove(keys),
};

async function pruneStorage() {
  const remover = Date.now() - 2500000000;
  const key = lastchecked[0];
  if (await LS.getItem(key) < remover) {
    await LS.setItem(key, Date.now());
  } else {
    const toRemove = Object.entries(await LS.getAllItems())
      .map(([k, v]) => v < remover && k)
      .filter(Boolean);
    if (toRemove.length) {
      await LS.removeItems(toRemove);
    }
  }
}

Warning: don't make your chrome.runtime.onMessage listener async if you want to send the response asynchronously, use an async IIFE or a separate function instead, more info.

wOxxOm
  • 65,848
  • 11
  • 132
  • 136
  • Is it possible to access data saved (in previous version) to locaStorage, from bg SW? – erosman Oct 13 '22 at 07:50
  • 2
    No, it's not possible. In the future Chrome will have chrome.offscreen API which starts a hidden DOM page that can access localStorage and then send it via a message to the background script. Meanwhile, you'll have to open an *onboarding* page on update that will welcome the user and convert the storage. You'll need to set a `beforeunload` listener to prevent accidental closing of the page until the work is done. – wOxxOm Oct 13 '22 at 15:39