Skip to content

Commit e242877

Browse files
committed
PR review
1 parent 7e28474 commit e242877

6 files changed

Lines changed: 62 additions & 45 deletions

File tree

src/common/ipc.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ export enum IPCMode {
1515
Both = 3,
1616
}
1717

18-
export const PROTOCOL_SCHEME = 'sentry-ipc';
19-
2018
export type Channel =
2119
/** IPC to check main process is listening */
2220
| 'start'
@@ -29,30 +27,29 @@ export type Channel =
2927
/** IPC to pass structured log messages */
3028
| 'structured-log';
3129

32-
/**
33-
* Utilities for creating namespaced IPC channels and protocol routes
34-
*/
35-
export function ipcChannelUtils(namespace: string | undefined): {
30+
export interface IpcUtils {
3631
createUrl: (channel: Channel) => string;
3732
urlMatches: (url: string, channel: Channel) => boolean;
3833
createKey: (channel: Channel) => string;
39-
readonly globalKey: string;
40-
} {
41-
const globalKey = `__${namespace?.replace('-', '_').toUpperCase() || 'SENTRY_IPC'}__`;
34+
readonly namespace: string;
35+
}
4236

37+
/**
38+
* Utility for creating namespaced IPC channels and protocol routes
39+
*/
40+
export function ipcChannelUtils(namespace: string): IpcUtils {
4341
return {
4442
createUrl: (channel: Channel) => {
45-
const scheme = namespace ? `${PROTOCOL_SCHEME}-${namespace}` : PROTOCOL_SCHEME;
4643
// sentry_key in the url stops these messages from being picked up by our HTTP instrumentations
47-
return `${scheme}://${channel}/sentry_key`;
44+
return `${namespace}://${channel}/sentry_key`;
4845
},
4946
urlMatches: function (url: string, channel: Channel): boolean {
5047
return url.startsWith(this.createUrl(channel));
5148
},
5249
createKey: (channel: Channel) => {
53-
return namespace ? `${PROTOCOL_SCHEME}-${namespace}.${channel}` : `${PROTOCOL_SCHEME}.${channel}`;
50+
return `${namespace}.${channel}`;
5451
},
55-
globalKey,
52+
namespace,
5653
};
5754
}
5855

@@ -109,7 +106,7 @@ export function getMagicMessage(): unknown {
109106
*/
110107
declare global {
111108
interface Window {
112-
[key: string]: unknown;
109+
__SENTRY_IPC__?: Record<string, IPCInterface>;
113110
__SENTRY__RENDERER_INIT__?: boolean;
114111
__SENTRY_RENDERER_ID__?: string;
115112
}

src/main/ipc.ts

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
import { captureEvent, getClient, getCurrentScope } from '@sentry/node';
1313
import { app, ipcMain, protocol, WebContents, webContents } from 'electron';
1414
import { eventFromEnvelope } from '../common/envelope.js';
15-
import { ipcChannelUtils, IPCMode, PROTOCOL_SCHEME, RendererStatus } from '../common/ipc.js';
15+
import { ipcChannelUtils, IPCMode, IpcUtils, RendererStatus } from '../common/ipc.js';
1616
import { registerProtocol } from './electron-normalize.js';
1717
import { createRendererEventLoopBlockStatusHandler } from './integrations/renderer-anr.js';
1818
import { rendererProfileFromIpc } from './integrations/renderer-profiling.js';
@@ -24,11 +24,6 @@ import { SDK_VERSION } from './version.js';
2424
let KNOWN_RENDERERS: Set<number> | undefined;
2525
let WINDOW_ID_TO_WEB_CONTENTS: Map<string, number> | undefined;
2626

27-
const SENTRY_CUSTOM_SCHEME = {
28-
scheme: PROTOCOL_SCHEME,
29-
privileges: { bypassCSP: true, corsEnabled: true, supportFetchAPI: true, secure: true },
30-
};
31-
3227
function newProtocolRenderer(): void {
3328
KNOWN_RENDERERS = KNOWN_RENDERERS || new Set();
3429
WINDOW_ID_TO_WEB_CONTENTS = WINDOW_ID_TO_WEB_CONTENTS || new Map();
@@ -183,20 +178,23 @@ function handleLogFromRenderer(client: Client, options: ElectronMainOptionsInter
183178
}
184179

185180
/** Enables Electron protocol handling */
186-
function configureProtocol(client: Client, options: ElectronMainOptionsInternal): void {
181+
function configureProtocol(client: Client, ipcUtil: IpcUtils, options: ElectronMainOptionsInternal): void {
187182
if (app.isReady()) {
188183
throw new Error("Sentry SDK should be initialized before the Electron app 'ready' event is fired");
189184
}
190185

191-
const ipcUtil = ipcChannelUtils(options.ipcNamespace);
186+
const scheme = {
187+
scheme: ipcUtil.namespace,
188+
privileges: { bypassCSP: true, corsEnabled: true, supportFetchAPI: true, secure: true },
189+
};
192190

193-
protocol.registerSchemesAsPrivileged([SENTRY_CUSTOM_SCHEME]);
191+
protocol.registerSchemesAsPrivileged([scheme]);
194192

195193
// We Proxy this function so that later user calls to registerSchemesAsPrivileged don't overwrite our custom scheme
196194
// eslint-disable-next-line @typescript-eslint/unbound-method
197195
protocol.registerSchemesAsPrivileged = new Proxy(protocol.registerSchemesAsPrivileged, {
198196
apply: (target, __, args: Parameters<typeof protocol.registerSchemesAsPrivileged>) => {
199-
target([...args[0], SENTRY_CUSTOM_SCHEME]);
197+
target([...args[0], scheme]);
200198
},
201199
});
202200

@@ -206,7 +204,7 @@ function configureProtocol(client: Client, options: ElectronMainOptionsInternal)
206204
.whenReady()
207205
.then(() => {
208206
for (const sesh of options.getSessions()) {
209-
registerProtocol(sesh.protocol, PROTOCOL_SCHEME, (request) => {
207+
registerProtocol(sesh.protocol, ipcUtil.namespace, (request) => {
210208
const getWebContents = (): WebContents | undefined => {
211209
const webContentsId = request.windowId ? WINDOW_ID_TO_WEB_CONTENTS?.get(request.windowId) : undefined;
212210
return webContentsId ? webContents.fromId(webContentsId) : undefined;
@@ -237,9 +235,7 @@ function configureProtocol(client: Client, options: ElectronMainOptionsInternal)
237235
/**
238236
* Hooks IPC for communication with the renderer processes
239237
*/
240-
function configureClassic(client: Client, options: ElectronMainOptionsInternal): void {
241-
const ipcUtil = ipcChannelUtils(options.ipcNamespace);
242-
238+
function configureClassic(client: Client, ipcUtil: IpcUtils, options: ElectronMainOptionsInternal): void {
243239
ipcMain.on(ipcUtil.createKey('start'), ({ sender }) => {
244240
const id = sender.id;
245241
// Keep track of renderers that are using IPC
@@ -276,13 +272,15 @@ function configureClassic(client: Client, options: ElectronMainOptionsInternal):
276272

277273
/** Sets up communication channels with the renderer */
278274
export function configureIPC(client: Client, options: ElectronMainOptionsInternal): void {
275+
const ipcUtil = ipcChannelUtils(options.ipcNamespace);
276+
279277
// eslint-disable-next-line no-bitwise
280278
if ((options.ipcMode & IPCMode.Protocol) > 0) {
281-
configureProtocol(client, options);
279+
configureProtocol(client, ipcUtil, options);
282280
}
283281

284282
// eslint-disable-next-line no-bitwise
285283
if ((options.ipcMode & IPCMode.Classic) > 0) {
286-
configureClassic(client, options);
284+
configureClassic(client, ipcUtil, options);
287285
}
288286
}

src/main/sdk.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,11 @@ export interface ElectronMainOptionsInternal
107107
* Custom namespace for IPC channels and protocol routes.
108108
*
109109
* Valid characters are a-z, 0-9, hyphen (-).
110-
* Should match `ipcNamespace` passed in renderer init.
110+
* Should match `ipcNamespace` passed in the renderer processes.
111+
*
112+
* @default 'sentry-ipc'
111113
*/
112-
ipcNamespace?: string;
114+
ipcNamespace: string;
113115

114116
/**
115117
* A function that returns an array of Electron session objects
@@ -145,8 +147,11 @@ export interface ElectronMainOptionsInternal
145147
}
146148

147149
// getSessions and ipcMode properties are optional because they have defaults
148-
export type ElectronMainOptions = Pick<Partial<ElectronMainOptionsInternal>, 'getSessions' | 'ipcMode'> &
149-
Omit<ElectronMainOptionsInternal, 'getSessions' | 'ipcMode'> &
150+
export type ElectronMainOptions = Pick<
151+
Partial<ElectronMainOptionsInternal>,
152+
'getSessions' | 'ipcMode' | 'ipcNamespace'
153+
> &
154+
Omit<ElectronMainOptionsInternal, 'getSessions' | 'ipcMode' | 'ipcNamespace'> &
150155
NodeOptions;
151156

152157
/**
@@ -162,6 +167,7 @@ export function init(userOptions: ElectronMainOptions): void {
162167
const optionsWithDefaults = {
163168
_metadata: { sdk: getSdkInfo(!!userOptions.sendDefaultPii) },
164169
ipcMode: IPCMode.Both,
170+
ipcNamespace: 'sentry-ipc',
165171
release: getDefaultReleaseName(),
166172
environment: getDefaultEnvironment(),
167173
defaultIntegrations: getDefaultIntegrations(userOptions),

src/preload/index.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ import { ipcChannelUtils, RendererStatus } from '../common/ipc.js';
1111
*
1212
* @param namespace An optional namespace to use for the IPC channels
1313
*/
14-
export function hookupIpc(namespace?: string): void {
14+
export function hookupIpc(namespace: string = 'sentry-ipc'): void {
1515
const ipcUtil = ipcChannelUtils(namespace);
1616

1717
// eslint-disable-next-line no-restricted-globals
18-
if (window[ipcUtil.globalKey]) {
18+
window.__SENTRY_IPC__ = window.__SENTRY_IPC__ || {};
19+
20+
// eslint-disable-next-line no-restricted-globals
21+
if (window.__SENTRY_IPC__[ipcUtil.namespace]) {
1922
// eslint-disable-next-line no-console
2023
console.log('Sentry Electron preload has already been run');
2124
} else {
@@ -28,13 +31,13 @@ export function hookupIpc(namespace?: string): void {
2831
};
2932

3033
// eslint-disable-next-line no-restricted-globals
31-
window[ipcUtil.globalKey] = ipcObject;
34+
window.__SENTRY_IPC__[ipcUtil.namespace] = ipcObject;
3235

3336
// We attempt to use contextBridge if it's available
3437
if (contextBridge) {
3538
// This will fail if contextIsolation is not enabled
3639
try {
37-
contextBridge.exposeInMainWorld(ipcUtil.globalKey, ipcObject);
40+
contextBridge.exposeInMainWorld(ipcUtil.namespace, ipcObject);
3841
} catch (e) {
3942
//
4043
}

src/renderer/ipc.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@
22
/* eslint-disable no-console */
33
import { Client, debug, getClient, SerializedLog, uuid4 } from '@sentry/core';
44
import { ipcChannelUtils, IPCInterface, RENDERER_ID_HEADER, RendererStatus } from '../common/ipc.js';
5-
import { ElectronRendererOptions } from './sdk.js';
5+
import { ElectronRendererOptionsInternal } from './sdk.js';
66

77
/** Gets the available IPC implementation */
8-
function getImplementation(ipcKey: string | undefined): IPCInterface {
8+
function getImplementation(ipcKey: string): IPCInterface {
99
const ipcUtil = ipcChannelUtils(ipcKey);
1010

11+
window.__SENTRY_IPC__ = window.__SENTRY_IPC__ || {};
12+
1113
// Favour IPC if it's been exposed by a preload script
12-
if (window[ipcUtil.globalKey]) {
13-
return window[ipcUtil.globalKey] as IPCInterface;
14+
if (window.__SENTRY_IPC__[ipcUtil.namespace]) {
15+
return window.__SENTRY_IPC__[ipcUtil.namespace] as IPCInterface;
1416
} else {
1517
debug.log('IPC was not configured in preload script, falling back to custom protocol and fetch');
1618

@@ -83,7 +85,7 @@ export function getIPC(client: Client | undefined = getClient()): IPCInterface {
8385
return found;
8486
}
8587

86-
const namespace = (client.getOptions() as ElectronRendererOptions).ipcNamespace;
88+
const namespace = (client.getOptions() as ElectronRendererOptionsInternal).ipcNamespace;
8789
const implementation = getImplementation(namespace);
8890
cachedInterfaces.set(client, implementation);
8991
implementation.sendRendererStart();

src/renderer/sdk.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function getDefaultIntegrations(options: ElectronRendererOptions): Integr
1818
];
1919
}
2020

21-
export interface ElectronRendererOptions extends Omit<BrowserOptions, 'dsn' | 'environment' | 'release'> {
21+
export interface ElectronRendererOptionsInternal extends Omit<BrowserOptions, 'dsn' | 'environment' | 'release'> {
2222
/** @deprecated `dsn` should only be passed to the main process `Sentry.init` call */
2323
dsn?: string;
2424
/** @deprecated `release` should only be passed to the main process `Sentry.init` call */
@@ -31,8 +31,15 @@ export interface ElectronRendererOptions extends Omit<BrowserOptions, 'dsn' | 'e
3131
*
3232
* Valid characters are a-z, 0-9, hyphen (-).
3333
* Should match `ipcNamespace` passed in the main process.
34+
*
35+
* @default 'sentry-ipc'
3436
*/
35-
ipcNamespace?: string;
37+
ipcNamespace: string;
38+
}
39+
40+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
41+
interface ElectronRendererOptions extends Partial<ElectronRendererOptionsInternal> {
42+
//
3643
}
3744

3845
/**
@@ -67,6 +74,10 @@ If init has been called in the preload and contextIsolation is disabled, is not
6774
options.stackParser = electronRendererStackParser;
6875
}
6976

77+
if (options.ipcNamespace === undefined) {
78+
options.ipcNamespace = 'sentry-ipc';
79+
}
80+
7081
// eslint-disable-next-line deprecation/deprecation
7182
if (options.dsn === undefined) {
7283
// Events are sent via the main process but browser SDK wont start without dsn

0 commit comments

Comments
 (0)