HTTP请求被删除在Chrome扩展中

发布于 2025-02-08 02:50:43 字数 4323 浏览 1 评论 0原文

摘要:

我已经建立了一个镀铬扩展名,该扩展可以与外部API接触以获取一些数据。有时数据会很快返回,有时需要4秒钟左右。我经常快速连续地进行大约5-10(这是一个刮擦工具)。

以前,由于V3中的服务工作者随机关闭,因此删除了许多请求。我以为我已经解决了。然后我意识到有种族条件,因为本地存储没有适当的队列。

当前错误 - 即使使用了所有这些修复程序,请求仍在删除。外部API成功返回了正确的数据,但似乎扩展名似乎永远不会得到。希望有人可以将我指向正确的方向。

附带的相关代码,我想这将有助于处理这些队列和服务工作者问题的人。

本地存储队列

let writing: Map<string, Promise<any>> = new Map();

let updateUnsynchronized = async (ks: string[], f: Function) => {
  let m = await new Promise((resolve, reject) => {
    chrome.storage.local.get(ks, res => {
      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) => {
    let updateMap = f(m);
    chrome.storage.local.set(updateMap, () => {
      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 => {
    ret = await updateUnsynchronized(ks, f);
  });

  return ret;
}

主要功能。

export async function appendStoredScrapes(
  scrape: any,
  fromHTTPResponse: boolean
) {
  let updated = await update(['urlType', 'scrapes'], storage => {
    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}`});
}

这是使服务工作者活着

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(() => contentKeepAlive(name));

  channel.onMessage.addListener(msg => { });
}

let deleteTimer = (chan : any) => {
  if (chan._timer) {
    clearTimeout(chan._timer);
    delete chan._timer;
  }
}

let backgroundForceReconnect = (chan : chrome.runtime.Port) => {
  deleteTimer(chan);
  chan.disconnect();
}

// To be run in background scripts
export function backgroundKeepAlive(name : string) {
  chrome.runtime.onConnect.addListener(chan => {
    if (chan.name === name) {
      channel = chan;
      channel.onMessage.addListener((msg, chan) => { });
      channel.onDisconnect.addListener(deleteTimer);
      channel._timer = setTimeout(backgroundForceReconnect, defaultKeepAliveInterval, channel);
    }
  });
}

// "Always call sendResponse() in your chrome.runtime.onMessage listener even if you don't need
// the response. This is a bug in MV3." — https://stackoverflow.com/questions/66618136/persistent-service-worker-in-chrome-extension
export function defaultSendResponse (sendResponse : Function) {
  sendResponse({ farewell: 'goodbye' });
}

的背景相关部分的

backgroundKeepAlive('extension-background');

let listen = async (request, sender, sendResponse) => {
  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);
});

Summary:

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).

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.

Current Error - 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.

Relevant code attached, I imagine it will help someone dealing with these queue and service worker issues.

Local Storage queue

let writing: Map<string, Promise<any>> = new Map();

let updateUnsynchronized = async (ks: string[], f: Function) => {
  let m = await new Promise((resolve, reject) => {
    chrome.storage.local.get(ks, res => {
      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) => {
    let updateMap = f(m);
    chrome.storage.local.set(updateMap, () => {
      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 => {
    ret = await updateUnsynchronized(ks, f);
  });

  return ret;
}

Here's the main function

export async function appendStoredScrapes(
  scrape: any,
  fromHTTPResponse: boolean
) {
  let updated = await update(['urlType', 'scrapes'], storage => {
    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}`});
}

Keeping the service worker alive

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(() => contentKeepAlive(name));

  channel.onMessage.addListener(msg => { });
}

let deleteTimer = (chan : any) => {
  if (chan._timer) {
    clearTimeout(chan._timer);
    delete chan._timer;
  }
}

let backgroundForceReconnect = (chan : chrome.runtime.Port) => {
  deleteTimer(chan);
  chan.disconnect();
}

// To be run in background scripts
export function backgroundKeepAlive(name : string) {
  chrome.runtime.onConnect.addListener(chan => {
    if (chan.name === name) {
      channel = chan;
      channel.onMessage.addListener((msg, chan) => { });
      channel.onDisconnect.addListener(deleteTimer);
      channel._timer = setTimeout(backgroundForceReconnect, defaultKeepAliveInterval, channel);
    }
  });
}

// "Always call sendResponse() in your chrome.runtime.onMessage listener even if you don't need
// the response. This is a bug in MV3." — https://stackoverflow.com/questions/66618136/persistent-service-worker-in-chrome-extension
export function defaultSendResponse (sendResponse : Function) {
  sendResponse({ farewell: 'goodbye' });
}

Relevant parts of background.ts

backgroundKeepAlive('extension-background');

let listen = async (request, sender, sendResponse) => {
  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);
});

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文