title: Http requests being dropped in Chrome Extension
body: <h3>Summary:</h3>
  <p>I've built a chrome extension that reaches out to external API to fetch some data. Sometimes that data returns quickly, sometimes it takes 4 seconds or so. I'm often doing about 5-10 in rapid succession (this is a scraping tool).</p>
  <p>Previously, a lot of requests were dropped because the service worker in V3 of Manifest randomly shuts down. I thought I had resolved that. Then I realized there was a race condition because local storage doesn't have a proper queue.</p>
  <p><strong>Current Error</strong> - Even with all these fixes, requests are still being dropped. The external API returns the correct data successfully, but it seems like the extension never gets it. Hoping someone can point me in the right direction.</p>
  <p>Relevant code attached, I imagine it will help someone dealing with these queue and service worker issues.</p>
  <h3>Local Storage queue</h3>
  <pre class="lang-js prettyprint-override"><code>let writing: Map&lt;string, Promise&lt;any&gt;&gt; = new Map();
  
  let updateUnsynchronized = async (ks: string[], f: Function) =&gt; {
    let m = await new Promise((resolve, reject) =&gt; {
      chrome.storage.local.get(ks, res =&gt; {
        let m = {};
        for (let k of ks) {
          m[k] = res[k];
        }
        maybeResolveLocalStorage(resolve, reject, m);
      });
    });
    // Guaranteed to have not changed in the meantime
    let updated = await new Promise((resolve, reject) =&gt; {
      let updateMap = f(m);
      chrome.storage.local.set(updateMap, () =&gt; {
        maybeResolveLocalStorage(resolve, reject, updateMap);
      });
    });
    console.log(ks, 'Updated', updated);
    return updated;
  };
  
  export async function update(ks: string[], f: Function) {
    let ret = null;
  
    // Global lock for now
    await navigator.locks.request('global-storage-lock', async lock =&gt; {
      ret = await updateUnsynchronized(ks, f);
    });
  
    return ret;
  }
  </code></pre>
  <h3>Here's the main function</h3>
  <pre class="lang-js prettyprint-override"><code>export async function appendStoredScrapes(
    scrape: any,
    fromHTTPResponse: boolean
  ) {
    let updated = await update(['urlType', 'scrapes'], storage =&gt; {
      const urlType = storage.urlType;
      const scrapes = storage.scrapes;
      const {url} = scrape;
  
      if (fromHTTPResponse) {
        // We want to make sure that the url type at time of scrape, not time of return, is used
        scrapes[url] = {...scrapes[url], ...scrape};
      } else {
        scrapes[url] = {...scrapes[url], ...scrape, urlType};
      }
  
      return {scrapes};
    });
  
    chrome.action.setBadgeText({text: `${Object.keys(updated['scrapes']).length}`});
  }
  </code></pre>
  <h3>Keeping the service worker alive</h3>
  <pre class="lang-js prettyprint-override"><code>let defaultKeepAliveInterval = 20000;
  
  // To avoid GC
  let channel;
  
  // To be run in content scripts
  export function contentKeepAlive(name : string) {
    channel = chrome.runtime.connect({ name });
  
    channel.onDisconnect.addListener(() =&gt; contentKeepAlive(name));
  
    channel.onMessage.addListener(msg =&gt; { });
  }
  
  let deleteTimer = (chan : any) =&gt; {
    if (chan._timer) {
      clearTimeout(chan._timer);
      delete chan._timer;
    }
  }
  
  let backgroundForceReconnect = (chan : chrome.runtime.Port) =&gt; {
    deleteTimer(chan);
    chan.disconnect();
  }
  
  // To be run in background scripts
  export function backgroundKeepAlive(name : string) {
    chrome.runtime.onConnect.addListener(chan =&gt; {
      if (chan.name === name) {
        channel = chan;
        channel.onMessage.addListener((msg, chan) =&gt; { });
        channel.onDisconnect.addListener(deleteTimer);
        channel._timer = setTimeout(backgroundForceReconnect, defaultKeepAliveInterval, channel);
      }
    });
  }
  
  // &quot;Always call sendResponse() in your chrome.runtime.onMessage listener even if you don't need
  // the response. This is a bug in MV3.&quot; — https://stackoverflow.com/questions/66618136/persistent-service-worker-in-chrome-extension
  export function defaultSendResponse (sendResponse : Function) {
    sendResponse({ farewell: 'goodbye' });
  }
  </code></pre>
  <h3>Relevant parts of background.ts</h3>
  <pre class="lang-js prettyprint-override"><code>backgroundKeepAlive('extension-background');
  
  let listen = async (request, sender, sendResponse) =&gt; {
    try {
      if (request.message === 'SEND_URL_DETAIL') {
        const {url, website, urlType} = request;
        await appendStoredScrapes({url}, false);
        let data = await fetchPageData(url, website, urlType);
        console.log(data, url, 'fetch data returned background');
        await appendStoredScrapes(data, true);
        defaultSendResponse(sendResponse);
      } else if (request.message === 'KEEPALIVE') {
        sendResponse({isAlive: true});
      } else {
        defaultSendResponse(sendResponse);
      }
    } catch (e) {
      console.error('background listener error', e);
    }
  };
  
  chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
    listen(request, sender, sendResponse);
  });
  </code></pre>