title: Manifest V3: Websocket connection/debugging Extension Service worker is a pain
body: <p>I'm learning how to build chrome extensions with manifest v3, what I'm trying to do is the following</p>
  <p>In my extension background.js (service worker) I want to do this:</p>
  <ul>
  <li>connect to WebSocket to get data updates</li>
  <li>reconnect to the Websocket when service-worker wake up</li>
  <li>those are tasks to get data updates from a WebSocket and update the badge text and send notifications.</li>
  </ul>
  <p>I need to do these tasks while not relying on having a port open with the popup or a content script.</p>
  <p>I'm using Chrome Alarms to wake up the service worker</p>
  <p>it may sound weird that I have to reconnect every time the service worker wakes up considering chrome is shutting the service worker down like every 15s or less once I close the extensions dev tools (which makes me cry blood) but it is better than sending XHR periodically using Chrome alarms, which would result in a lot more requests being sent to an API, so reconnecting to the Websocket seems less problematic.</p>
  <p>I'm having a super hard time debugging my service worker (background script) in my chrome extension. The problem is when I have dev tools open the service worker will NEVER go inactive, and what I'm trying to do is identify when the SW wakes up to perform tasks, super-duper weird because I need dev tools open to debugging...</p>
  <p>how do you debug an extension SW without devtools open?</p>
  <p>do you/anyone reading this have any recommendations/thoughts on what I want to do with this extension and the pain process for debugging the SW?</p>
  <p>here is the code I have for the background.js</p>
  <pre class="lang-js prettyprint-override"><code>
  const extension = {
    count: 0,
    disconnected: false,
    port: {} as any,
    ws: null,
  };
  
  const fetchData = () =&gt; {
    return fetch(
      'https://api.coingecko.com/api/v3/coins/ethereum?localization=incididuntelit&amp;tickers=false&amp;market_data=true&amp;community_data=true&amp;developer_data=true&amp;sparkline=true'
    ).then((res) =&gt; res.json());
  };
  
  // Chrome Alarms
  const setupAlarms = () =&gt; {
    console.log('###ALARMS-SETUP');
    chrome.alarms.get('data-fetch', (alarm) =&gt; {
      if (!alarm) {
        chrome.alarms.create('data-fetch', { periodInMinutes: 0.1 });
      }
    });
    chrome.alarms.get('client-pinging-server', (alarm) =&gt; {
      if (!alarm) {
        chrome.alarms.create('client-pinging-server', { periodInMinutes: 0.1 });
      }
    });
  
    chrome.alarms.onAlarm.addListener((e) =&gt; {
      if (e.name === 'data-fetch') {
        fetchData()
          .then((res) =&gt; {
            // console.log('###ETHEREUM:', res.market_data.current_price.cad);
            chrome.action.setBadgeText({ text: `${Math.round(Math.random() * 100)}` });
          })
          .catch((error) =&gt; 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) =&gt; {
    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(() =&gt; {
    console.log('Unloading.');
    chrome.action.setBadgeText({ text: `off` });
  });
  
  function WebSocketClient() {
    let instance = null;
    const connect = () =&gt; {
      return new Promise((resolve, reject) =&gt; {
        const ws = new WebSocket('ws://localhost:8080');
  
        const onOpen = () =&gt; {
          instance = ws;
          console.log('###websocket:connected', instance);
          return resolve(ws);
        };
  
        const onError = (event) =&gt; {
          console.log('###INIT-FAILED', event);
          ws.close(1000, 'closing due to unknown error');
          return reject('failed to connect to websocket');
        };
  
        const onClose = () =&gt; {
          console.log('###websocket:disconnected');
          instance = null;
          // reconnect is happening in the alarm callback
        };
  
        ws.onopen = onOpen;
        ws.onerror = onError;
        ws.onclose = onClose;
      });
    };
  
    const getInstance = () =&gt; {
      return instance;
    };
  
    return {
      connect,
      getInstance,
    };
  }
  
  self.addEventListener('install', async (event) =&gt; {
    console.log('====install', event);
    chrome.action.setBadgeBackgroundColor({ color: '#a6e22e' });
  });
  
  self.addEventListener('activate', async (event) =&gt; {
    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',
      })
    );
  });
  </code></pre>