1. /* global chrome */
    
  2. 
    
  3. 'use strict';
    
  4. 
    
  5. import './dynamicallyInjectContentScripts';
    
  6. import './tabsManager';
    
  7. 
    
  8. import {
    
  9.   handleDevToolsPageMessage,
    
  10.   handleBackendManagerMessage,
    
  11.   handleReactDevToolsHookMessage,
    
  12.   handleFetchResourceContentScriptMessage,
    
  13. } from './messageHandlers';
    
  14. 
    
  15. /*
    
  16.   {
    
  17.     [tabId]: {
    
  18.       extension: ExtensionPort,
    
  19.       proxy: ProxyPort,
    
  20.       disconnectPipe: Function,
    
  21.     },
    
  22.     ...
    
  23.    }
    
  24.  */
    
  25. const ports = {};
    
  26. 
    
  27. function registerTab(tabId) {
    
  28.   if (!ports[tabId]) {
    
  29.     ports[tabId] = {
    
  30.       extension: null,
    
  31.       proxy: null,
    
  32.       disconnectPipe: null,
    
  33.     };
    
  34.   }
    
  35. }
    
  36. 
    
  37. function registerExtensionPort(port, tabId) {
    
  38.   ports[tabId].extension = port;
    
  39. 
    
  40.   port.onDisconnect.addListener(() => {
    
  41.     // This should delete disconnectPipe from ports dictionary
    
  42.     ports[tabId].disconnectPipe?.();
    
  43. 
    
  44.     delete ports[tabId].extension;
    
  45.   });
    
  46. }
    
  47. 
    
  48. function registerProxyPort(port, tabId) {
    
  49.   ports[tabId].proxy = port;
    
  50. 
    
  51.   // In case proxy port was disconnected from the other end, from content script
    
  52.   // This can happen if content script was detached, when user does in-tab navigation
    
  53.   // This listener should never be called when we call port.disconnect() from this (background/index.js) script
    
  54.   port.onDisconnect.addListener(() => {
    
  55.     ports[tabId].disconnectPipe?.();
    
  56. 
    
  57.     delete ports[tabId].proxy;
    
  58.   });
    
  59. }
    
  60. 
    
  61. function isNumeric(str: string): boolean {
    
  62.   return +str + '' === str;
    
  63. }
    
  64. 
    
  65. chrome.runtime.onConnect.addListener(port => {
    
  66.   if (port.name === 'proxy') {
    
  67.     // Might not be present for restricted pages in Firefox
    
  68.     if (port.sender?.tab?.id == null) {
    
  69.       // Not disconnecting it, so it would not reconnect
    
  70.       return;
    
  71.     }
    
  72. 
    
  73.     // Proxy content script is executed in tab, so it should have it specified.
    
  74.     const tabId = port.sender.tab.id;
    
  75. 
    
  76.     if (ports[tabId]?.proxy) {
    
  77.       ports[tabId].disconnectPipe?.();
    
  78.       ports[tabId].proxy.disconnect();
    
  79.     }
    
  80. 
    
  81.     registerTab(tabId);
    
  82.     registerProxyPort(port, tabId);
    
  83. 
    
  84.     if (ports[tabId].extension) {
    
  85.       connectExtensionAndProxyPorts(
    
  86.         ports[tabId].extension,
    
  87.         ports[tabId].proxy,
    
  88.         tabId,
    
  89.       );
    
  90.     }
    
  91. 
    
  92.     return;
    
  93.   }
    
  94. 
    
  95.   if (isNumeric(port.name)) {
    
  96.     // DevTools page port doesn't have tab id specified, because its sender is the extension.
    
  97.     const tabId = +port.name;
    
  98. 
    
  99.     registerTab(tabId);
    
  100.     registerExtensionPort(port, tabId);
    
  101. 
    
  102.     if (ports[tabId].proxy) {
    
  103.       connectExtensionAndProxyPorts(
    
  104.         ports[tabId].extension,
    
  105.         ports[tabId].proxy,
    
  106.         tabId,
    
  107.       );
    
  108.     }
    
  109. 
    
  110.     return;
    
  111.   }
    
  112. 
    
  113.   // I am not sure if we should throw here
    
  114.   console.warn(`Unknown port ${port.name} connected`);
    
  115. });
    
  116. 
    
  117. function connectExtensionAndProxyPorts(extensionPort, proxyPort, tabId) {
    
  118.   if (!extensionPort) {
    
  119.     throw new Error(
    
  120.       `Attempted to connect ports, when extension port is not present`,
    
  121.     );
    
  122.   }
    
  123. 
    
  124.   if (!proxyPort) {
    
  125.     throw new Error(
    
  126.       `Attempted to connect ports, when proxy port is not present`,
    
  127.     );
    
  128.   }
    
  129. 
    
  130.   if (ports[tabId].disconnectPipe) {
    
  131.     throw new Error(
    
  132.       `Attempted to connect already connected ports for tab with id ${tabId}`,
    
  133.     );
    
  134.   }
    
  135. 
    
  136.   function extensionPortMessageListener(message) {
    
  137.     try {
    
  138.       proxyPort.postMessage(message);
    
  139.     } catch (e) {
    
  140.       if (__DEV__) {
    
  141.         console.log(`Broken pipe ${tabId}: `, e);
    
  142.       }
    
  143. 
    
  144.       disconnectListener();
    
  145.     }
    
  146.   }
    
  147. 
    
  148.   function proxyPortMessageListener(message) {
    
  149.     try {
    
  150.       extensionPort.postMessage(message);
    
  151.     } catch (e) {
    
  152.       if (__DEV__) {
    
  153.         console.log(`Broken pipe ${tabId}: `, e);
    
  154.       }
    
  155. 
    
  156.       disconnectListener();
    
  157.     }
    
  158.   }
    
  159. 
    
  160.   function disconnectListener() {
    
  161.     extensionPort.onMessage.removeListener(extensionPortMessageListener);
    
  162.     proxyPort.onMessage.removeListener(proxyPortMessageListener);
    
  163. 
    
  164.     // We handle disconnect() calls manually, based on each specific case
    
  165.     // No need to disconnect other port here
    
  166. 
    
  167.     delete ports[tabId].disconnectPipe;
    
  168.   }
    
  169. 
    
  170.   ports[tabId].disconnectPipe = disconnectListener;
    
  171. 
    
  172.   extensionPort.onMessage.addListener(extensionPortMessageListener);
    
  173.   proxyPort.onMessage.addListener(proxyPortMessageListener);
    
  174. 
    
  175.   extensionPort.onDisconnect.addListener(disconnectListener);
    
  176.   proxyPort.onDisconnect.addListener(disconnectListener);
    
  177. }
    
  178. 
    
  179. chrome.runtime.onMessage.addListener((message, sender) => {
    
  180.   switch (message?.source) {
    
  181.     case 'devtools-page': {
    
  182.       handleDevToolsPageMessage(message);
    
  183.       break;
    
  184.     }
    
  185.     case 'react-devtools-fetch-resource-content-script': {
    
  186.       handleFetchResourceContentScriptMessage(message);
    
  187.       break;
    
  188.     }
    
  189.     case 'react-devtools-backend-manager': {
    
  190.       handleBackendManagerMessage(message, sender);
    
  191.       break;
    
  192.     }
    
  193.     case 'react-devtools-hook': {
    
  194.       handleReactDevToolsHookMessage(message, sender);
    
  195.     }
    
  196.   }
    
  197. });
    
  198. 
    
  199. chrome.tabs.onActivated.addListener(({tabId: activeTabId}) => {
    
  200.   for (const registeredTabId in ports) {
    
  201.     if (
    
  202.       ports[registeredTabId].proxy != null &&
    
  203.       ports[registeredTabId].extension != null
    
  204.     ) {
    
  205.       const numericRegisteredTabId = +registeredTabId;
    
  206.       const event =
    
  207.         activeTabId === numericRegisteredTabId
    
  208.           ? 'resumeElementPolling'
    
  209.           : 'pauseElementPolling';
    
  210. 
    
  211.       ports[registeredTabId].extension.postMessage({event});
    
  212.     }
    
  213.   }
    
  214. });