@hitarth-gg/devtron
Version:
Electron DevTools Extension to track IPC events
278 lines (266 loc) • 11.6 kB
JavaScript
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 };