UNPKG

@hitarth-gg/devtron

Version:

Electron DevTools Extension to track IPC events

278 lines (266 loc) 11.6 kB
import * as __WEBPACK_EXTERNAL_MODULE_electron__ from "electron"; import { createRequire as __WEBPACK_EXTERNAL_createRequire } from "node:module"; /******/ var __webpack_modules__ = ([ /* 0 */, /* 1 */ /***/ ((module) => { module.exports = __WEBPACK_EXTERNAL_MODULE_electron__; /***/ }), /* 2 */ /***/ ((module) => { module.exports = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("node:path"); /***/ }), /* 3 */ /***/ ((module) => { module.exports = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("node:module"); /***/ }) /******/ ]); /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ var cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed /******/ // no module.loaded needed /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /************************************************************************/ /******/ /* webpack/runtime/compat get default export */ /******/ (() => { /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = (module) => { /******/ var getter = module && module.__esModule ? /******/ () => (module['default']) : /******/ () => (module); /******/ __webpack_require__.d(getter, { a: getter }); /******/ return getter; /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /******/ /* webpack/runtime/make namespace object */ /******/ (() => { /******/ // define __esModule on exports /******/ __webpack_require__.r = (exports) => { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ })(); /******/ /************************************************************************/ var __webpack_exports__ = {}; // This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk. (() => { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ devtron: () => (/* binding */ devtron) /* harmony export */ }); /* harmony import */ var electron__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2); /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(node_path__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var node_module__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3); /* harmony import */ var node_module__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(node_module__WEBPACK_IMPORTED_MODULE_2__); let isInstalled = false; let isInstalledToDefaultSession = false; /** * sends captured IPC events to the service-worker preload script */ function trackIpcEvent(direction, channel, args, devtronSW, serviceWorkerDetails) { const eventData = { direction, channel, args, timestamp: Date.now(), serviceWorkerDetails, }; if (devtronSW === null) { console.error('The service-worker for Devtron is not registered yet. Cannot track IPC event.'); return; } devtronSW.send('devtron-render-event', eventData); } function registerIpcListeners(ses, devtronSW) { ses.on( // @ts-expect-error: '-ipc-message' is an internal event '-ipc-message', (event, channel, args) => { if (event.type === 'frame') trackIpcEvent('renderer-to-main', channel, args, devtronSW); else if (event.type === 'service-worker') trackIpcEvent('service-worker-to-main', channel, args, devtronSW); }); ses.on( // @ts-expect-error: '-ipc-invoke' is an internal event '-ipc-invoke', (event, channel, args) => { if (event.type === 'frame') trackIpcEvent('renderer-to-main', channel, args, devtronSW); else if (event.type === 'service-worker') trackIpcEvent('service-worker-to-main', channel, args, devtronSW); }); ses.on( // @ts-expect-error: '-ipc-message-sync' is an internal event '-ipc-message-sync', (event, channel, args) => { if (event.type === 'frame') trackIpcEvent('renderer-to-main', channel, args, devtronSW); else if (event.type === 'service-worker') trackIpcEvent('service-worker-to-main', channel, args, devtronSW); }); } /** * Registers a listener for the service worker's send method to track IPC events * sent from the main process to the service worker. */ function registerServiceWorkerSendListener(ses, devtronSW) { const isInstalledSet = new Set(); // stores version IDs of patched service workers // register listener for existing service workers const allRunning = ses.serviceWorkers.getAllRunning(); for (const vid in allRunning) { const swInfo = allRunning[vid]; const sw = ses.serviceWorkers.getWorkerFromVersionID(Number(vid)); if (typeof sw === 'undefined' || sw.scope === devtronSW.scope) continue; isInstalledSet.add(swInfo.versionId); const originalSend = sw.send; sw.send = function (...args) { trackIpcEvent('main-to-service-worker', args[0], // channel args.slice(1), // args devtronSW, { serviceWorkerScope: sw.scope, serviceWorkerVersionId: sw.versionId, }); return originalSend.apply(this, args); }; } // register listener for new service workers ses.serviceWorkers.on('running-status-changed', (details) => { if (details.runningStatus === 'running' || details.runningStatus === 'starting') { const sw = ses.serviceWorkers.getWorkerFromVersionID(details.versionId); if (typeof sw === 'undefined' || sw.scope === devtronSW.scope || isInstalledSet.has(sw.versionId)) return; isInstalledSet.add(details.versionId); const originalSend = sw.send; sw.send = function (...args) { trackIpcEvent('main-to-service-worker', args[0], // channel args.slice(1), // args devtronSW, { serviceWorkerScope: sw.scope, serviceWorkerVersionId: sw.versionId, }); return originalSend.apply(this, args); }; } }); } async function startServiceWorker(ses, extension) { try { const sw = await ses.serviceWorkers.startWorkerForScope(extension.url); sw.startTask(); registerIpcListeners(ses, sw); registerServiceWorkerSendListener(ses, sw); } catch (error) { console.warn(`Failed to start Devtron service-worker (${error}), trying again...`); /** * This is a workaround for the issue where the Devtron service-worker fails to start * when the Electron app is launched for the first time, or when the service worker * hasn't been cached yet. */ try { const handleDetails = async (event, details) => { if (details.scope === extension.url) { const sw = await ses.serviceWorkers.startWorkerForScope(extension.url); sw.startTask(); registerIpcListeners(ses, sw); registerServiceWorkerSendListener(ses, sw); ses.serviceWorkers.removeListener('registration-completed', handleDetails); console.log(`Devtron service-worker started successfully`); } }; ses.serviceWorkers.on('registration-completed', handleDetails); } catch (error) { console.error('Failed to start Devtron service-worker:', error); } } } async function install() { if (isInstalled) return; isInstalled = true; const installToSession = async (ses) => { if (ses === electron__WEBPACK_IMPORTED_MODULE_0__.session.defaultSession && isInstalledToDefaultSession) return; if (ses === electron__WEBPACK_IMPORTED_MODULE_0__.session.defaultSession) isInstalledToDefaultSession = true; let devtron; try { // register service worker preload script const dirname = import.meta.url; // __dirname is replaced with import.meta.url in ESM builds using webpack const serviceWorkerPreloadPath = (0,node_module__WEBPACK_IMPORTED_MODULE_2__.createRequire)(dirname).resolve('@hitarth-gg/devtron/service-worker-preload'); const rendererPreloadPath = (0,node_module__WEBPACK_IMPORTED_MODULE_2__.createRequire)(dirname).resolve('@hitarth-gg/devtron/renderer-preload'); ses.registerPreloadScript({ filePath: serviceWorkerPreloadPath, type: 'service-worker', id: 'devtron-sw-preload', }); ses.registerPreloadScript({ filePath: rendererPreloadPath, type: 'frame', id: 'devtron-renderer-preload', }); // load extension const extensionPath = node_path__WEBPACK_IMPORTED_MODULE_1___default().resolve(serviceWorkerPreloadPath, '..', '..', 'extension'); devtron = await ses.extensions.loadExtension(extensionPath, { allowFileAccess: true }); await startServiceWorker(ses, devtron); console.log('Devtron loaded successfully'); } catch (error) { console.error('Failed to load Devtron:', error); } }; electron__WEBPACK_IMPORTED_MODULE_0__.app.on('session-created', installToSession); // explicitly install Devtron to the defaultSession in case the app is already ready if (!isInstalledToDefaultSession && electron__WEBPACK_IMPORTED_MODULE_0__.app.isReady()) await installToSession(electron__WEBPACK_IMPORTED_MODULE_0__.session.defaultSession); } const devtron = { install, }; })(); const __webpack_exports__devtron = __webpack_exports__.devtron; export { __webpack_exports__devtron as devtron };