Manifest V3:Websocket connection/debugging Extension Service worker 很痛苦

Manifest V3: Websocket connection/debugging Extension Service worker is a pain

我正在学习如何使用清单 v3 构建 chrome 扩展,我想做的是以下内容

在我的扩展 background.js(服务人员)中我想这样做:

我需要完成这些任务,而不依赖于使用弹出窗口或内容脚本打开端口。

我正在使用 Chrome 闹钟来唤醒 service worker

考虑到 chrome 在我关闭扩展开发工具后每隔 15 秒或更短时间关闭服务工作者(这让我哭了),每次服务工作者醒来时我都必须重新连接,这听起来很奇怪blood) 但它比使用 Chrome 警报定期发送 XHR 要好,后者会导致更多请求被发送到 API,因此重新连接到 Websocket 似乎问题不大。

我在调试我的 chrome 扩展中的服务工作者(后台脚本)时非常困难。问题是当我打开开发工具时,服务工作人员永远不会不活动,而我要做的是确定软件何时唤醒以执行任务,超级奇怪,因为我需要打开开发工具进行调试。 .

如何在没有打开 devtools 的情况下调试扩展软件?

you/anyone阅读这篇文章是否recommendations/thoughts了解我想用这个扩展做什么以及调试 SW 的痛苦过程?

这是 background.js

的代码

const extension = {
  count: 0,
  disconnected: false,
  port: {} as any,
  ws: null,
};

const fetchData = () => {
  return fetch(
    'https://api.coingecko.com/api/v3/coins/ethereum?localization=incididuntelit&tickers=false&market_data=true&community_data=true&developer_data=true&sparkline=true'
  ).then((res) => res.json());
};

// Chrome Alarms
const setupAlarms = () => {
  console.log('###ALARMS-SETUP');
  chrome.alarms.get('data-fetch', (alarm) => {
    if (!alarm) {
      chrome.alarms.create('data-fetch', { periodInMinutes: 0.1 });
    }
  });
  chrome.alarms.get('client-pinging-server', (alarm) => {
    if (!alarm) {
      chrome.alarms.create('client-pinging-server', { periodInMinutes: 0.1 });
    }
  });

  chrome.alarms.onAlarm.addListener((e) => {
    if (e.name === 'data-fetch') {
      fetchData()
        .then((res) => {
          // console.log('###ETHEREUM:', res.market_data.current_price.cad);
          chrome.action.setBadgeText({ text: `${Math.round(Math.random() * 100)}` });
        })
        .catch((error) => console.error('###ERROR', error));
    }

    if (e.name === 'client-pinging-server') {
      if (!extension.ws || !extension.ws.getInstance()) {
        console.log('\n');
        console.log('###reconnecting...', extension.ws);
        console.log('\n');
        extension.ws = WebSocketClient();
        extension.ws.connect();
      }
      if (extension.ws.getInstance()) {
        console.log('###sending-client-ping');
        extension.ws.getInstance().send(JSON.stringify({ message: 'client ping - keep alive' }));
      }
    }
  });
};

// ON INSTALL
chrome.runtime.onInstalled.addListener(async (e) => {
  const API_URL = 'ws://localhost:8080';
  chrome.storage.local.set({ apiUrl: API_URL, count: 0 });
  console.info('###Extension installed', e);

  chrome.action.setBadgeText({ text: '0' });
  chrome.action.setBadgeBackgroundColor({ color: '#FF9900' });
});

// ON SUSPEND
chrome.runtime.onSuspend.addListener(() => {
  console.log('Unloading.');
  chrome.action.setBadgeText({ text: `off` });
});

function WebSocketClient() {
  let instance = null;
  const connect = () => {
    return new Promise((resolve, reject) => {
      const ws = new WebSocket('ws://localhost:8080');

      const onOpen = () => {
        instance = ws;
        console.log('###websocket:connected', instance);
        return resolve(ws);
      };

      const onError = (event) => {
        console.log('###INIT-FAILED', event);
        ws.close(1000, 'closing due to unknown error');
        return reject('failed to connect to websocket');
      };

      const onClose = () => {
        console.log('###websocket:disconnected');
        instance = null;
        // reconnect is happening in the alarm callback
      };

      ws.onopen = onOpen;
      ws.onerror = onError;
      ws.onclose = onClose;
    });
  };

  const getInstance = () => {
    return instance;
  };

  return {
    connect,
    getInstance,
  };
}

self.addEventListener('install', async (event) => {
  console.log('====install', event);
  chrome.action.setBadgeBackgroundColor({ color: '#a6e22e' });
});

self.addEventListener('activate', async (event) => {
  console.log('====activate', event);
  chrome.action.setBadgeBackgroundColor({ color: '#FF9900' });
  extension.ws = WebSocketClient();
  extension.ws.connect();
  setupAlarms();
});

self.addEventListener('push', function (event) {
  // Keep the service worker alive until the notification is created.
  event.waitUntil(
    self.registration.showNotification('Testing PUSH API', {
      body: 'coming from push event',
    })
  );
});

由于 Devtools 可以同时附加到多个上下文,您可以为另一个上下文打开它,这样 SW 将是次要的,从而能够正常卸载。

  1. 打开扩展程序的任何可见页面,或者,如果有 none,它的 manifest.json URL:
    chrome-extension://ID/manifest.json 其中 ID 是扩展程序的 ID
  2. 打开 devtools 并切换到其 Application 选项卡,然后选择左侧的 Service worker
  3. 点击start(如果显示)启动service worker,点击后台脚本名称在Sources面板打开,设置断点等
  4. 单击 stop 停止服务工作者,可选择单击顶部的 Update 和中间的 skip waiting(如果显示)以强制更新。
  5. 再次单击 start - 您的断点将触发。

Service Worker 启动后,您可以在左侧的源面板(在文件面板中)、top-right(在线程面板中)、控制台工具栏(上下文选择器)。

一开始这可能看起来很笨拙,但是一旦你尝试掌握了它的诀窍,它就非常简单了,甚至可以击败点击 [=45= 中的“服务工作者”link 时显示的开发工具]://extensions 页面因为这个 a) 在 devtools 中显示扩展的 localStorage/IndexedDB,b) 提供对 service worker lifetime/execution 的控制,c) 支持 SW 启动的性能分析。

请注意,ManifestV3 文档中关于服务人员为扩展提供的好处的声明在很大程度上被夸大了或完全是错误的,例如在你的情况下,很明显重启服务工作者是不好的,所以你应该 尽可能多。