Skip to content

Commit 44b78ea

Browse files
authored
feat: track IPC events sent from main to service worker (#273)
* feat: main-to-service-worker IPC tracking * refactor: rename `serviceWorker` to `devtronSW` * feat: track SW scope and versionID * fix: avoid redundant patching of service worker `.send`
1 parent 7f4374d commit 44b78ea

File tree

6 files changed

+142
-16
lines changed

6 files changed

+142
-16
lines changed

src/extension/react/components/DetailPanel.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ function DetailPanel({ selectedRow, onClose, direction = 'right' }: Props) {
1515
const { theme } = useDevtronContext();
1616
if (!selectedRow) return null;
1717

18+
const { serviceWorkerVersionId, serviceWorkerScope } = selectedRow.serviceWorkerDetails || {};
19+
1820
const timestamp = formatTimestamp(selectedRow.timestamp);
1921

2022
const isBottomDocked = direction === 'bottom';
@@ -43,7 +45,7 @@ function DetailPanel({ selectedRow, onClose, direction = 'right' }: Props) {
4345
<div className="flex items-center gap-x-1">
4446
<span className="font-medium"> Channel: </span>
4547
{selectedRow.channel && (
46-
<span className="block max-w-72 break-all rounded bg-gray-200 px-1 py-0.5 dark:bg-charcoal-500">
48+
<span className="block max-w-96 break-all rounded bg-gray-200 px-1 py-0.5 dark:bg-charcoal-500">
4749
{selectedRow.channel}
4850
</span>
4951
)}
@@ -64,12 +66,32 @@ function DetailPanel({ selectedRow, onClose, direction = 'right' }: Props) {
6466
{/* Method */}
6567
{selectedRow.method && (
6668
<div className="flex w-fit items-center gap-x-1">
67-
<span className="font-medium">Method: </span>
68-
<span className="block max-w-72 break-all rounded bg-gray-200 px-1 py-0.5 dark:bg-charcoal-500">
69+
<span className="text-nowrap font-medium">Method: </span>
70+
<span className="block max-w-96 break-all rounded bg-gray-200 px-1 py-0.5 dark:bg-charcoal-500">
6971
{selectedRow.method}
7072
</span>
7173
</div>
7274
)}
75+
76+
{/* Service Worker Scope */}
77+
{serviceWorkerScope && (
78+
<div className="flex w-fit items-center gap-x-1">
79+
<span className="text-nowrap font-medium">SW Scope: </span>
80+
<span className="block max-w-96 break-all rounded bg-gray-200 px-1 py-0.5 dark:bg-charcoal-500">
81+
{serviceWorkerScope}
82+
</span>
83+
</div>
84+
)}
85+
86+
{/* Service Worker Version ID */}
87+
{serviceWorkerVersionId && (
88+
<div className="flex w-fit items-center gap-x-1">
89+
<span className="text-nowrap font-medium">SW Version ID: </span>
90+
<span className="block max-w-96 break-all rounded bg-gray-200 px-1 py-0.5 dark:bg-charcoal-500">
91+
{serviceWorkerVersionId}
92+
</span>
93+
</div>
94+
)}
7395
</div>
7496

7597
{/* Args */}

src/extension/react/components/DirectionBadge.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,21 @@ export default function DirectionBadge({ direction }: Props) {
3030
case 'service-worker-to-main':
3131
return {
3232
colorClass:
33-
'dark:bg-dark-orange dark:text-light-orange bg-yellow-100 text-yellow-800 border-yellow-300 dark:border-light-orange',
33+
'dark:bg-dark-yellow dark:text-light-yellow bg-yellow-100 text-yellow-800 border-yellow-300 dark:border-light-yellow',
3434
Icon: ArrowRight,
3535
labelLeft: 'SW',
3636
labelRight: 'Main',
3737
tooltip: 'Service Worker to Main',
3838
};
39+
case 'main-to-service-worker':
40+
return {
41+
colorClass:
42+
'dark:bg-dark-orange dark:text-light-orange bg-orange-100 text-orange-800 border-orange-300 dark:border-light-orange',
43+
Icon: ArrowRight,
44+
labelLeft: 'Main',
45+
labelRight: 'SW',
46+
tooltip: 'Main to Service Worker',
47+
};
3948
case 'renderer':
4049
return {
4150
colorClass:

src/extension/react/test_data/test_data.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,15 @@ export const events: IpcEventDataIndexed[] = [
9292
},
9393
],
9494
},
95+
{
96+
serialNumber: 129,
97+
direction: 'main-to-service-worker',
98+
channel: 'sw-check-2',
99+
args: ['Hello from main to service worker!'],
100+
timestamp: 1749114387250,
101+
serviceWorkerDetails: {
102+
serviceWorkerScope: 'chrome-extension://floigfkhicjhinimocdoblflbmefpjeg/',
103+
serviceWorkerVersionId: 32,
104+
},
105+
},
95106
];

src/index.ts

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { app, session } from 'electron';
22
import path from 'node:path';
33
import { createRequire } from 'node:module';
4-
import type { Direction, IpcEventData } from './types/shared';
4+
import type { Direction, IpcEventData, ServiceWorkerDetails } from './types/shared';
55

66
let isInstalled = false;
77
let isInstalledToDefaultSession = false;
@@ -13,23 +13,25 @@ function trackIpcEvent(
1313
direction: Direction,
1414
channel: string,
1515
args: any[],
16-
serviceWorker: Electron.ServiceWorkerMain,
16+
devtronSW: Electron.ServiceWorkerMain,
17+
serviceWorkerDetails?: ServiceWorkerDetails,
1718
) {
1819
const eventData: IpcEventData = {
1920
direction,
2021
channel,
2122
args,
2223
timestamp: Date.now(),
24+
serviceWorkerDetails,
2325
};
2426

25-
if (serviceWorker === null) {
27+
if (devtronSW === null) {
2628
console.error('The service-worker for Devtron is not registered yet. Cannot track IPC event.');
2729
return;
2830
}
29-
serviceWorker.send('devtron-render-event', eventData);
31+
devtronSW.send('devtron-render-event', eventData);
3032
}
3133

32-
function registerIpcListeners(ses: Electron.Session, serviceWorker: Electron.ServiceWorkerMain) {
34+
function registerIpcListeners(ses: Electron.Session, devtronSW: Electron.ServiceWorkerMain) {
3335
ses.on(
3436
// @ts-expect-error: '-ipc-message' is an internal event
3537
'-ipc-message',
@@ -38,9 +40,9 @@ function registerIpcListeners(ses: Electron.Session, serviceWorker: Electron.Ser
3840
channel: string,
3941
args: any[],
4042
) => {
41-
if (event.type === 'frame') trackIpcEvent('renderer-to-main', channel, args, serviceWorker);
43+
if (event.type === 'frame') trackIpcEvent('renderer-to-main', channel, args, devtronSW);
4244
else if (event.type === 'service-worker')
43-
trackIpcEvent('service-worker-to-main', channel, args, serviceWorker);
45+
trackIpcEvent('service-worker-to-main', channel, args, devtronSW);
4446
},
4547
);
4648

@@ -52,9 +54,9 @@ function registerIpcListeners(ses: Electron.Session, serviceWorker: Electron.Ser
5254
channel: string,
5355
args: any[],
5456
) => {
55-
if (event.type === 'frame') trackIpcEvent('renderer-to-main', channel, args, serviceWorker);
57+
if (event.type === 'frame') trackIpcEvent('renderer-to-main', channel, args, devtronSW);
5658
else if (event.type === 'service-worker')
57-
trackIpcEvent('service-worker-to-main', channel, args, serviceWorker);
59+
trackIpcEvent('service-worker-to-main', channel, args, devtronSW);
5860
},
5961
);
6062
ses.on(
@@ -65,18 +67,87 @@ function registerIpcListeners(ses: Electron.Session, serviceWorker: Electron.Ser
6567
channel: string,
6668
args: any[],
6769
) => {
68-
if (event.type === 'frame') trackIpcEvent('renderer-to-main', channel, args, serviceWorker);
70+
if (event.type === 'frame') trackIpcEvent('renderer-to-main', channel, args, devtronSW);
6971
else if (event.type === 'service-worker')
70-
trackIpcEvent('service-worker-to-main', channel, args, serviceWorker);
72+
trackIpcEvent('service-worker-to-main', channel, args, devtronSW);
7173
},
7274
);
7375
}
7476

77+
/**
78+
* Registers a listener for the service worker's send method to track IPC events
79+
* sent from the main process to the service worker.
80+
*/
81+
function registerServiceWorkerSendListener(
82+
ses: Electron.Session,
83+
devtronSW: Electron.ServiceWorkerMain,
84+
): void {
85+
const isInstalledSet = new Set<number>(); // stores version IDs of patched service workers
86+
87+
// register listener for existing service workers
88+
const allRunning = ses.serviceWorkers.getAllRunning();
89+
for (const vid in allRunning) {
90+
const swInfo = allRunning[vid];
91+
92+
const sw = ses.serviceWorkers.getWorkerFromVersionID(Number(vid));
93+
94+
if (typeof sw === 'undefined' || sw.scope === devtronSW.scope) continue;
95+
isInstalledSet.add(swInfo.versionId);
96+
97+
const originalSend = sw.send;
98+
sw.send = function (...args) {
99+
trackIpcEvent(
100+
'main-to-service-worker',
101+
args[0], // channel
102+
args.slice(1), // args
103+
devtronSW,
104+
{
105+
serviceWorkerScope: sw.scope,
106+
serviceWorkerVersionId: sw.versionId,
107+
},
108+
);
109+
return originalSend.apply(this, args);
110+
};
111+
}
112+
113+
// register listener for new service workers
114+
ses.serviceWorkers.on('running-status-changed', (details) => {
115+
if (details.runningStatus === 'running' || details.runningStatus === 'starting') {
116+
const sw = ses.serviceWorkers.getWorkerFromVersionID(details.versionId);
117+
118+
if (
119+
typeof sw === 'undefined' ||
120+
sw.scope === devtronSW.scope ||
121+
isInstalledSet.has(sw.versionId)
122+
)
123+
return;
124+
125+
isInstalledSet.add(details.versionId);
126+
127+
const originalSend = sw.send;
128+
sw.send = function (...args) {
129+
trackIpcEvent(
130+
'main-to-service-worker',
131+
args[0], // channel
132+
args.slice(1), // args
133+
devtronSW,
134+
{
135+
serviceWorkerScope: sw.scope,
136+
serviceWorkerVersionId: sw.versionId,
137+
},
138+
);
139+
return originalSend.apply(this, args);
140+
};
141+
}
142+
});
143+
}
144+
75145
async function startServiceWorker(ses: Electron.Session, extension: Electron.Extension) {
76146
try {
77147
const sw = await ses.serviceWorkers.startWorkerForScope(extension.url);
78148
sw.startTask();
79149
registerIpcListeners(ses, sw);
150+
registerServiceWorkerSendListener(ses, sw);
80151
} catch (error) {
81152
console.warn(`Failed to start Devtron service-worker (${error}), trying again...`);
82153
/**
@@ -93,6 +164,7 @@ async function startServiceWorker(ses: Electron.Session, extension: Electron.Ext
93164
const sw = await ses.serviceWorkers.startWorkerForScope(extension.url);
94165
sw.startTask();
95166
registerIpcListeners(ses, sw);
167+
registerServiceWorkerSendListener(ses, sw);
96168
ses.serviceWorkers.removeListener('registration-completed', handleDetails);
97169
console.log(`Devtron service-worker started successfully`);
98170
}

src/types/shared.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,30 @@ export type Direction =
44
| 'renderer-to-main'
55
| 'main-to-renderer'
66
| 'service-worker-to-main'
7+
| 'main-to-service-worker'
78
| 'renderer';
9+
10+
export type ServiceWorkerDetails = {
11+
serviceWorkerVersionId: number;
12+
serviceWorkerScope: string;
13+
};
14+
815
export interface IpcEventData {
916
direction: Direction;
1017
channel: string;
1118
args: any[];
1219
timestamp: number;
1320
method?: string;
21+
serviceWorkerDetails?: ServiceWorkerDetails;
1422
}
23+
1524
/* ------------------------------------------------------ */
1625

1726
/* ---------------------- EXTENSION --------------------- */
1827
export interface IpcEventDataIndexed extends IpcEventData {
1928
serialNumber: number;
2029
}
30+
2131
export type MessagePanel =
2232
| { type: typeof MSG_TYPE.PONG }
2333
| { type: typeof MSG_TYPE.PING }

tailwind.config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ export default {
1919
'charcoal-700': '#171717',
2020
'charcoal-800': '#111113',
2121

22-
'light-orange': '#ffa057',
22+
'light-yellow': '#ffc53d',
23+
'light-orange': '#ff8a30 ',
2324
'light-blue': '#70b8ff',
2425
'light-green': '#3ddb8f',
2526
'light-purple': '#c96cff',
2627

28+
'dark-yellow': '#362b00',
2729
'dark-orange': '#331e10',
2830
'dark-blue': '#0d2849',
2931
'dark-green': '#132d23',

0 commit comments

Comments
 (0)