JavaScript浏览器Webextension API清单V3-服务工作者和Chrome.Storage.Local API问题

发布于 2025-01-18 11:13:38 字数 4619 浏览 2 评论 0原文

我正在为 Copy as cURL 功能实现一个小扩展(通过 DevTools 的 Network 选项卡完成),并且我想使用 Manifest v3。根据文档和社区的贡献,Service Worker 在某个时间停止运行,因此某些变量无法从活动选项卡中检索所需的信息。

为了管理这个问题,我使用 chrome.storage.local.set 和 .get 函数,以便在 Service Worker 停止运行后也保留所需的信息。当我运行扩展测试时,我没有收到任何错误,但是,尽管我通过 chrome.storage API 检索了存储的变量,但有时当 Service Worker 应该处于活动状态时,我仍然无法再检索这些值。例如:

  • 当我连接到一个网站时,我也可以在 1 分钟内检索并复制正确的数据,然后,如果我继续复制(不刷新页面),我不会获取参数(即 GET 标头) 。
  • 有时,如果我打开一个新选项卡,插入一个地址,然后快速按我的扩展程序的“复制为 cURL”,标题不会被复制,并且我需要刷新页面(不是通过单击浏览器的刷新按钮,而是单击 URL 然后输入)为了得到它们。

也许这个问题与 Service Worker 的生存时间无关,因为我可以让页面打开很长时间,并且它为我提供了正确的参数。我不知道我的方法失败在哪里。这个小实现的代码如下:

background.js

"use strict";

/*
Called when the item has been created, or when creation failed due to an error.
We'll just log success/failure here.
*/
function onCreated() {
  if (chrome.runtime.lastError) {
    console.log(`Error: ${chrome.runtime.lastError}`);
  } else {
    console.log("Item created successfully");
  }
}

/*
Called when the item has been removed.
We'll just log success here.
*/
function onRemoved() {
  console.log("Item removed successfully");
}

/*
Called when there was an error.
We'll just log the error here.
*/
function onError(error) {
  console.log(`Error: ${error}`);
}

/*
Create all the context menu items.
*/

chrome.contextMenus.create({
  id: "tools-copy",
  //title: chrome.i18n.getMessage("menuItemToolsCopy"),
  title: "Copy",
  contexts: ["all"],
}, onCreated);

chrome.contextMenus.create({
  id: "tools-copy-curl",
  parentId: "tools-copy",
  //title: chrome.i18n.getMessage("menuItemToolsCopyAsFFUF"),
  title: "Copy as cURL",
  contexts: ["all"],
}, onCreated);

const tabData = {};
const getProp = (obj, key) => (obj[key] || (obj[key] = {}));
const encodeBody = body => {
  var data = '';
  // Read key
  for (var key in body.formData) { //body is a JSON object
    data += `${key}=${body.formData[key]}&`;
  }
  data = data.replace(/.$/,"");
  var body_data = `'${data}'`;
  return body_data;
}

const FILTER = {
  types: ['main_frame', 'sub_frame'],
  urls: ['<all_urls>'],
};

const TOOLS = {
  CURL: 'tools-copy-curl',
};

chrome.webRequest.onBeforeRequest.addListener(e => {
    getProp(getProp(tabData, e.tabId), e.frameId).body = e.requestBody;
    chrome.storage.local.set({tabData: tabData}, function() {
      console.log('HTTP request saved');
    });
  }, FILTER, ['requestBody']);

chrome.webRequest.onBeforeSendHeaders.addListener(e => {
  getProp(getProp(tabData, e.tabId), e.frameId).headers = e.requestHeaders;
  chrome.storage.local.set({tabData: tabData}, function() {
    console.log('HTTP request saved');
  });
}, FILTER, ['requestHeaders']);

chrome.tabs.onRemoved.addListener(tabId => delete tabData[tabId]);

chrome.tabs.onReplaced.addListener((addId, delId) => delete tabData[delId]);

chrome.contextMenus.onClicked.addListener((info, tab) => {
  
  chrome.storage.local.get(["tabData"], function(items) {
    const data = items.tabData[tab.id]?.[info.frameId || 0] || {};
    if (info.menuItemId === TOOLS.CURL) {
      var txt_clip = `curl -u '${info.frameUrl || tab.url}'` +
      (data.headers?.map(h => ` -H '${h.name}: ${h.value}'`).join('') || '') +
      (data.body? ' --data_raw ' + encodeBody(data.body) : '');
    }
    
    chrome.tabs.sendMessage(tab.id, 
      {
          message: "copyText",
          textToCopy: txt_clip
      }, function(response) {})
  });

  
});

content.js

chrome.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
        if (request.message === "copyText") {
            navigator.clipboard.writeText(request.textToCopy);
            sendResponse({status: true});
        }
    }
);

ma​​nifest.json

{

  "manifest_version": 3,
  "name": "CopyAsCURL",
  "description": "Copy as cURL test example.",
  "version": "1.0",
  "default_locale": "en",

  "background": {
    "service_worker": "background.js"
  },
  
  "permissions": [
    "contextMenus",
    "activeTab",
    "cookies",
    "webRequest",
    "tabs",
    "clipboardWrite",
    "storage"
  ],

  "host_permissions": [
    "<all_urls>"
  ],

  "content_scripts": [
    {
       "matches": [
          "<all_urls>"
       ],
       "js": ["content.js"]
    }
 ],
  
  "icons": {
    "16": "icons/menu-16.png",
    "32": "icons/menu-32.png",
    "48": "icons/menu-48.png"
  }

}

我还要感谢@wOxxOm 的支持关于类似的话题。

I'm implementing a small extension for Copy as cURL feature (as done by the Network tab of DevTools) and I would like to use Manifest v3. According to the documentation and to the contribution of the community, Service Worker at a certain time stops to live so some variables cannot retrieve the needed information from the active tab.

For managing this, I'm using chrome.storage.local.set and .get functions in order to keep the needed information also after the Service Worker stops to live. When I run the extension test, I don't receive any error, but, despite I retrieve the stored variables by the chrome.storage API, sometimes I continue to not retrieve the values anymore also when the Service Worker should be alive. For example:

  • when I connect to a website, I can retrieve and copy the correct data also in 1 min, then, if I continue to Copy (without refreshing the page), I don't get the parameters (i.e., GET headers).
  • sometimes, if I open a new tab, insert an address and quickly press Copy as cURL, of my extension, headers are not copied, and I need to refresh the page (not by clicking refresh button of browser but click on URL then ENTER) for getting them.

Maybe the issue is not related to the Time-to-live of the Service Worker because I can keep a page opened for a lot of minutes and it gives me the right parameters. I don't know where my approach is failing. The code of this small implementation is the following:

background.js

"use strict";

/*
Called when the item has been created, or when creation failed due to an error.
We'll just log success/failure here.
*/
function onCreated() {
  if (chrome.runtime.lastError) {
    console.log(`Error: ${chrome.runtime.lastError}`);
  } else {
    console.log("Item created successfully");
  }
}

/*
Called when the item has been removed.
We'll just log success here.
*/
function onRemoved() {
  console.log("Item removed successfully");
}

/*
Called when there was an error.
We'll just log the error here.
*/
function onError(error) {
  console.log(`Error: ${error}`);
}

/*
Create all the context menu items.
*/

chrome.contextMenus.create({
  id: "tools-copy",
  //title: chrome.i18n.getMessage("menuItemToolsCopy"),
  title: "Copy",
  contexts: ["all"],
}, onCreated);

chrome.contextMenus.create({
  id: "tools-copy-curl",
  parentId: "tools-copy",
  //title: chrome.i18n.getMessage("menuItemToolsCopyAsFFUF"),
  title: "Copy as cURL",
  contexts: ["all"],
}, onCreated);

const tabData = {};
const getProp = (obj, key) => (obj[key] || (obj[key] = {}));
const encodeBody = body => {
  var data = '';
  // Read key
  for (var key in body.formData) { //body is a JSON object
    data += `${key}=${body.formData[key]}&`;
  }
  data = data.replace(/.$/,"");
  var body_data = `'${data}'`;
  return body_data;
}

const FILTER = {
  types: ['main_frame', 'sub_frame'],
  urls: ['<all_urls>'],
};

const TOOLS = {
  CURL: 'tools-copy-curl',
};

chrome.webRequest.onBeforeRequest.addListener(e => {
    getProp(getProp(tabData, e.tabId), e.frameId).body = e.requestBody;
    chrome.storage.local.set({tabData: tabData}, function() {
      console.log('HTTP request saved');
    });
  }, FILTER, ['requestBody']);

chrome.webRequest.onBeforeSendHeaders.addListener(e => {
  getProp(getProp(tabData, e.tabId), e.frameId).headers = e.requestHeaders;
  chrome.storage.local.set({tabData: tabData}, function() {
    console.log('HTTP request saved');
  });
}, FILTER, ['requestHeaders']);

chrome.tabs.onRemoved.addListener(tabId => delete tabData[tabId]);

chrome.tabs.onReplaced.addListener((addId, delId) => delete tabData[delId]);

chrome.contextMenus.onClicked.addListener((info, tab) => {
  
  chrome.storage.local.get(["tabData"], function(items) {
    const data = items.tabData[tab.id]?.[info.frameId || 0] || {};
    if (info.menuItemId === TOOLS.CURL) {
      var txt_clip = `curl -u '${info.frameUrl || tab.url}'` +
      (data.headers?.map(h => ` -H '${h.name}: ${h.value}'`).join('') || '') +
      (data.body? ' --data_raw ' + encodeBody(data.body) : '');
    }
    
    chrome.tabs.sendMessage(tab.id, 
      {
          message: "copyText",
          textToCopy: txt_clip
      }, function(response) {})
  });

  
});

content.js

chrome.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
        if (request.message === "copyText") {
            navigator.clipboard.writeText(request.textToCopy);
            sendResponse({status: true});
        }
    }
);

manifest.json

{

  "manifest_version": 3,
  "name": "CopyAsCURL",
  "description": "Copy as cURL test example.",
  "version": "1.0",
  "default_locale": "en",

  "background": {
    "service_worker": "background.js"
  },
  
  "permissions": [
    "contextMenus",
    "activeTab",
    "cookies",
    "webRequest",
    "tabs",
    "clipboardWrite",
    "storage"
  ],

  "host_permissions": [
    "<all_urls>"
  ],

  "content_scripts": [
    {
       "matches": [
          "<all_urls>"
       ],
       "js": ["content.js"]
    }
 ],
  
  "icons": {
    "16": "icons/menu-16.png",
    "32": "icons/menu-32.png",
    "48": "icons/menu-48.png"
  }

}

I want also to thank @wOxxOm for the support on similar topic.

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

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

发布评论

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