0

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.

Develobeer
  • 425
  • 1
  • 8
  • 19
  • 1
    There's a bug in ManifestV3: service worker won't wake up for webRequest events, so you'll need to use [workarounds in this topic](/a/66618269). – wOxxOm Apr 01 '22 at 15:45
  • Thank you @wOxxOm . It is sad that, despite the v3 has a lot of relevant bugs, in the Chrome Web Store, v2 based extensions are not allowed to be uploaded anymore... At least Google could also wait the end of this year or fixing the most relevant bugs before to allow only v3-based extensions. – Develobeer Apr 01 '22 at 16:33

0 Answers0