Skip to content

Latest commit

 

History

History
201 lines (135 loc) · 13.1 KB

File metadata and controls

201 lines (135 loc) · 13.1 KB

Product: Restore Electron Devtron Extension

Preamble

I am deeply grateful to my mentors @David Sanders, @Erick Zhao, @Samuel Maddock, @Anny Yang and the entire Electron team for their patience, guidance, and thoughtful feedback on my pull requests.

Your encouragement and support throughout Google Summer of Code has been invaluable, and I truly appreciate the time and care you’ve invested in helping me grow.

Contact Information

Social Media

Overview

Electron is an open-source framework that lets developers build desktop applications with web technologies. By embedding Chromium and Node.js into its binary, Electron allows you to maintain one JavaScript codebase and create cross-platform apps that work on Windows, macOS, and Linux.

Many widely used desktop applications such as VS Code, Slack, Discord, and Notion, are built with Electron.

During Google Summer of Code 2025, I rewrote Devtron, a decade old developer tool for Electron, from scratch using modern frameworks and libraries, while accounting for the significant changes in Electron APIs over the years and the transition to Chrome’s Manifest Version 3.

The original Devtron extension had become non-functional over the past ten years due to evolving APIs in both Chrome and Electron, as well as the release of newer manifest versions.

This work was especially meaningful to me because it provides newcomers with a clearer understanding of how IPC events flow between different processes in an Electron app. At the same time, it serves as a developer tool that helps developers keep track of all the IPC events occurring within the applications they are building.

An issue tracking progress, known issues and next steps can be found here: electron/devtron#272

Details

This section highlights the development process and some of the challenges encountered.

I had submitted my GSoC proposal with a prototype that tracked IPC events sent and received by ipcRenderer and ipcMain . This served as a good starting point, and the prototype was later refined and converted into a pull request.

However, the prototype was initially written entirely in JavaScript and later converted to TypeScript in a follow-up pull request to improve long-term maintainability.

How IPC events are tracked

My initial approach to tracking IPC events was to patch the ipcMain and ipcRenderer methods, attaching a tracker that intercepted the requests they received.

However, my mentor @Samuel Maddock pointed out in a comment that this approach would track ipcMain but miss several other channels, such as webContents.ipc, webFrameMain.ipc, and serviceWorkerMain.ipc.

The solution was to attach listeners to the Session object, which allowed capturing IPC messages from multiple sources. For example:

// ses = defaultSession or a newly created session
ses.on(
  "-ipc-message",
  (
    event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent,
    channel: Channel,
    args: any[]
  ) => {
    if (event.type === "frame")
      trackIpcEvent({
        direction: "renderer-to-main",
        channel,
        args,
        devtronSW,
      });
    else if (event.type === "service-worker")
      trackIpcEvent({
        direction: "service-worker-to-main",
        channel,
        args,
        devtronSW,
      });
  }
);

These .on listeners are attached to three channels: -ipc-message, -ipc-invoke, and -ipc-message-sync. This approach also makes it possible to track IPC events sent by service workers to Electron’s main process.

To track IPC events flowing through ipcRenderer, Devtron still patches its various methods to attach trackers.

Initially, this was achieved by manually importing and executing monitorRenderer() from Devtron in the renderer preload scripts where IPC tracking was needed. With sandbox: false, this worked fine. However, when sandbox: true was set in the webPreferences of an Electron app (the default setting), preload scripts were restricted in what they could import or require, which caused issues when trying to load the Devtron module.

This issue was resolved by registering the file that patched ipcRenderer as a preload script in the main process of electron using ses.registerPreloadScript().

With this change, Devtron can now be installed with just a few lines of code:

// main.js
const { devtron } = require("@electron/devtron");
// or import { devtron } from '@electron/devtron'

devtron.install(); // call this function at the top of your file

Devtron can also be installed conditionally. For example:

const { app } = require("electron");

const isDev = !app.isPackaged;

async function installDevtron() {
  const { devtron } = await import("@electron/devtron");
  await devtron.install();
}

if (isDev) {
  installDevtron().catch((error) => {
    console.error("Failed to install Devtron:", error);
  });
}

Messages sent from the main process to service workers using serviceWorker.send(channel, ...args) had to be tracked separately by patching the .send method.

In one of the weekly Slack huddles, @Samuel Maddock suggested that it’d be nice to track the time it takes for the ipcRenderer.invoke(channel, ...args) and ipcRenderer.sendSync(channel, ...args) to receive back a response from the main process.

This was a great addition to Devtron, implemented using UUIDs. The renderer process generates a UUID and attaches it to .invoke and .sendSync requests. When these requests reach the main process, the UUID is extracted from the arguments. Since both the renderer and main processes are now using the same identifier, events can be reliably linked together.

On the frontend, Devtron links request and response events using the same UUID, allowing users to easily navigate back and forth between them.

devtron-1

Devtron Extension

The Devtron extension is built with Manifest Version 3 in mind. The frontend for Devtron’s DevTools Panel is built using React and TailwindCSS. To display the tracked events in the panel, I decided to use the community version of AG Grid. Since it comes with virtualization, thousands of rows can be stored and displayed efficiently by rendering only the rows visible to the user.

Some additional features were also added, such as light and dark theme support, the ability to dock the Details Panel to the bottom or the right, and a “lock to bottom” option that automatically scrolls to newly added events.

devtron-2 devtron-3

One challenge encountered during development was that Devtron’s Service Worker would terminate after a period of inactivity. This was resolved by keeping it alive using serviceWorker.startTask() .

Building the package

For packaging Devtron, I chose Webpack, enabling bundling into both ESM and CommonJS formats to ensure compatibility across different module systems.
Webpack’s DefinePlugin() is also used to replace __dirname with import.meta.url, allowing correct resolution of file paths in the ESM context.

// src/index.ts
const dirname = __dirname; // __dirname is replaced with import.meta.url in ESM builds using webpack
const serviceWorkerPreloadPath = createRequire(dirname).resolve(
  // resolves the path as defined in Devtron's package.json "exports"
  "@electron/devtron/service-worker-preload"
);

Contributions

Below is a list of all contributions made to electron/devtron:

Merged Pull Requests

#265: chore: initial setup with prototype
#266: chore: migrate to TypeScript
#268: feat: use Session to track IPCs and improve UI
#269: fix: commonjs compatibility
#270: feat: raise event display limit to 20k
#271: feat: remove need to manually call monitorRenderer
#273: feat: track IPC events sent from main to service worker
#274: feat: add tooltips to header buttons
#275: feat: track response time of invoke and sendSync methods on ipcRenderer
#276: fix: type declarations not found and invalid require usage in ESM build
#277: feat: default to system color scheme in UI
#279: test: set up testing environment
#280: feat: add getEvents API to retrieve tracked IPC events in main process
#281: fix: correct return type of getEvents() function
#284: feat: add filtering support to event grid
#285: fix: move "types" field above "import" in package exports
#290: test: extend wait time to capture devtron SW reliably
#291: ci: run tests on Ubuntu and Windows

Open Pull Requests (at the time of writing)

#282: test: set up Vitest and add tests for src/index.ts

  • Initially, I opened two separate PRs to explore different testing approaches for Devtron. One used mocha and chai with a real Electron app to verify IPC events with Devtron’s Service Worker. This one (#282) uses vitest, mocking various Electron APIs to enable testing in a lighter environment.
  • During a weekly Slack huddle, we decided to move forward with #279. The vitest PR may now be closed.

#283: feat: add configurable logLevel to install options

Future Work

Add more tests to make sure everything’s covered and all features work reliably.

Since Devtron is primarily a developer tool for Electron, some features from the legacy Devtron, such as an Event Inspector for core Electron objects and tracking require calls for different modules could also be reintroduced in future updates.

Closing Remarks

I’m really grateful for the chance to be a GSoC Contributor and work closely with the Electron team. Huge thanks to my mentors for their guidance, code reviews, and for patiently answering all my questions about the codebase and design approaches.

The last few months have been super productive. Building Devtron from scratch gave me a ton of hands-on experience and a much deeper understanding of Electron’s architecture.

I’ve also improved a lot as a developer, especially when it comes to writing clean, maintainable code.

Overall, this has been an amazing learning experience :)

Now that the GSoC coding phase is over, I’ll keep contributing to Devtron unofficially, squashing bugs and making it better as more people start using it.