Skip to content

Commit 81ddea1

Browse files
committed
Simplify by sending raw module data from worker to main-thread
1 parent 862a096 commit 81ddea1

File tree

5 files changed

+56
-142
lines changed

5 files changed

+56
-142
lines changed

packages/browser/src/integrations/webWorker.ts

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,8 @@
11
import type { Integration, IntegrationFn } from '@sentry/core';
2-
import {
3-
captureEvent,
4-
debug,
5-
defineIntegration,
6-
getClient,
7-
getFilenameToMetadataMap,
8-
isPlainObject,
9-
isPrimitive,
10-
mergeMetadataMap,
11-
} from '@sentry/core';
2+
import { captureEvent, debug, defineIntegration, getClient, isPlainObject, isPrimitive } from '@sentry/core';
123
import { DEBUG_BUILD } from '../debug-build';
134
import { eventFromUnknownInput } from '../eventbuilder';
145
import { WINDOW } from '../helpers';
15-
import { defaultStackParser } from '../stack-parsers';
166
import { _eventFromRejectionWithPrimitive, _getUnhandledRejectionError } from './globalhandlers';
177

188
export const INTEGRATION_NAME = 'WebWorker';
@@ -136,8 +126,13 @@ function listenForSentryMessages(worker: Worker): void {
136126
// Handle module metadata
137127
if (event.data._sentryModuleMetadata) {
138128
DEBUG_BUILD && debug.log('Sentry module metadata web worker message received', event.data);
139-
// Merge worker metadata into the main thread's metadata cache
140-
mergeMetadataMap(event.data._sentryModuleMetadata);
129+
// Merge worker's raw metadata into the global object
130+
// It will be parsed lazily when needed by getMetadataForUrl
131+
WINDOW._sentryModuleMetadata = {
132+
...event.data._sentryModuleMetadata,
133+
// Module metadata of the main thread have precedence over the worker's in case of a collision.
134+
...WINDOW._sentryModuleMetadata,
135+
};
141136
}
142137

143138
// Handle unhandled rejections forwarded from worker
@@ -237,13 +232,12 @@ interface RegisterWebWorkerOptions {
237232
* - `self`: The worker instance you're calling this function from (self).
238233
*/
239234
export function registerWebWorker({ self }: RegisterWebWorkerOptions): void {
240-
const moduleMetadata = self._sentryModuleMetadata ? getFilenameToMetadataMap(defaultStackParser) : undefined;
241-
242-
// Send debug IDs and module metadata to parent thread
235+
// Send debug IDs and raw module metadata to parent thread
236+
// The metadata will be parsed lazily on the main thread when needed
243237
self.postMessage({
244238
_sentryMessage: true,
245239
_sentryDebugIds: self._sentryDebugIds ?? undefined,
246-
_sentryModuleMetadata: moduleMetadata,
240+
_sentryModuleMetadata: self._sentryModuleMetadata ?? undefined,
247241
});
248242

249243
// Set up unhandledrejection handler inside the worker

packages/browser/test/integrations/webWorker.test.ts

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ vi.mock('@sentry/core', async importActual => {
1414
debug: {
1515
log: vi.fn(),
1616
},
17-
mergeMetadataMap: vi.fn(),
18-
getFilenameToMetadataMap: vi.fn(),
1917
};
2018
});
2119

@@ -213,10 +211,10 @@ describe('webWorkerIntegration', () => {
213211
});
214212

215213
it('processes module metadata from worker', () => {
216-
const mockMergeMetadataMap = SentryCore.mergeMetadataMap as any;
214+
(helpers.WINDOW as any)._sentryModuleMetadata = undefined;
217215
const moduleMetadata = {
218-
'worker-file1.js': { '_sentryBundlerPluginAppKey:my-app': true },
219-
'worker-file2.js': { '_sentryBundlerPluginAppKey:my-app': true },
216+
'Error\n at worker-file1.js:1:1': { '_sentryBundlerPluginAppKey:my-app': true },
217+
'Error\n at worker-file2.js:2:2': { '_sentryBundlerPluginAppKey:my-app': true },
220218
};
221219

222220
mockEvent.data = {
@@ -228,13 +226,13 @@ describe('webWorkerIntegration', () => {
228226

229227
expect(mockEvent.stopImmediatePropagation).toHaveBeenCalled();
230228
expect(mockDebugLog).toHaveBeenCalledWith('Sentry module metadata web worker message received', mockEvent.data);
231-
expect(mockMergeMetadataMap).toHaveBeenCalledWith(moduleMetadata);
229+
expect((helpers.WINDOW as any)._sentryModuleMetadata).toEqual(moduleMetadata);
232230
});
233231

234232
it('handles message with both debug IDs and module metadata', () => {
235-
const mockMergeMetadataMap = SentryCore.mergeMetadataMap as any;
233+
(helpers.WINDOW as any)._sentryModuleMetadata = undefined;
236234
const moduleMetadata = {
237-
'worker-file.js': { '_sentryBundlerPluginAppKey:my-app': true },
235+
'Error\n at worker-file.js:1:1': { '_sentryBundlerPluginAppKey:my-app': true },
238236
};
239237

240238
mockEvent.data = {
@@ -246,16 +244,16 @@ describe('webWorkerIntegration', () => {
246244
messageHandler(mockEvent);
247245

248246
expect(mockEvent.stopImmediatePropagation).toHaveBeenCalled();
249-
expect(mockMergeMetadataMap).toHaveBeenCalledWith(moduleMetadata);
247+
expect((helpers.WINDOW as any)._sentryModuleMetadata).toEqual(moduleMetadata);
250248
expect((helpers.WINDOW as any)._sentryDebugIds).toEqual({
251249
'worker-file.js': 'debug-id-1',
252250
});
253251
});
254252

255253
it('accepts message with only module metadata', () => {
256-
const mockMergeMetadataMap = SentryCore.mergeMetadataMap as any;
254+
(helpers.WINDOW as any)._sentryModuleMetadata = undefined;
257255
const moduleMetadata = {
258-
'worker-file.js': { '_sentryBundlerPluginAppKey:my-app': true },
256+
'Error\n at worker-file.js:1:1': { '_sentryBundlerPluginAppKey:my-app': true },
259257
};
260258

261259
mockEvent.data = {
@@ -266,7 +264,7 @@ describe('webWorkerIntegration', () => {
266264
messageHandler(mockEvent);
267265

268266
expect(mockEvent.stopImmediatePropagation).toHaveBeenCalled();
269-
expect(mockMergeMetadataMap).toHaveBeenCalledWith(moduleMetadata);
267+
expect((helpers.WINDOW as any)._sentryModuleMetadata).toEqual(moduleMetadata);
270268
});
271269

272270
it('ignores invalid module metadata', () => {
@@ -279,6 +277,29 @@ describe('webWorkerIntegration', () => {
279277

280278
expect(mockEvent.stopImmediatePropagation).not.toHaveBeenCalled();
281279
});
280+
281+
it('gives main thread precedence over worker for conflicting module metadata', () => {
282+
(helpers.WINDOW as any)._sentryModuleMetadata = {
283+
'Error\n at shared-file.js:1:1': { '_sentryBundlerPluginAppKey:main-app': true, source: 'main' },
284+
'Error\n at main-only.js:1:1': { '_sentryBundlerPluginAppKey:main-app': true },
285+
};
286+
287+
mockEvent.data = {
288+
_sentryMessage: true,
289+
_sentryModuleMetadata: {
290+
'Error\n at shared-file.js:1:1': { '_sentryBundlerPluginAppKey:worker-app': true, source: 'worker' },
291+
'Error\n at worker-only.js:1:1': { '_sentryBundlerPluginAppKey:worker-app': true },
292+
},
293+
};
294+
295+
messageHandler(mockEvent);
296+
297+
expect((helpers.WINDOW as any)._sentryModuleMetadata).toEqual({
298+
'Error\n at shared-file.js:1:1': { '_sentryBundlerPluginAppKey:main-app': true, source: 'main' }, // Main thread wins
299+
'Error\n at main-only.js:1:1': { '_sentryBundlerPluginAppKey:main-app': true }, // Main thread preserved
300+
'Error\n at worker-only.js:1:1': { '_sentryBundlerPluginAppKey:worker-app': true }, // Worker added
301+
});
302+
});
282303
});
283304
});
284305
});
@@ -343,38 +364,28 @@ describe('registerWebWorker', () => {
343364
});
344365
});
345366

346-
it('calls getFilenameToMetadataMap when module metadata is available', () => {
347-
const mockGetFilenameToMetadataMap = SentryCore.getFilenameToMetadataMap as any;
348-
const extractedMetadata = {
349-
'worker-file1.js': { '_sentryBundlerPluginAppKey:my-app': true },
350-
'worker-file2.js': { '_sentryBundlerPluginAppKey:my-app': true },
351-
};
352-
353-
mockWorkerSelf._sentryModuleMetadata = {
367+
it('includes raw module metadata when available', () => {
368+
const rawMetadata = {
354369
'Error\n at worker-file1.js:1:1': { '_sentryBundlerPluginAppKey:my-app': true },
355370
'Error\n at worker-file2.js:1:1': { '_sentryBundlerPluginAppKey:my-app': true },
356371
};
357372

358-
mockGetFilenameToMetadataMap.mockReturnValue(extractedMetadata);
373+
mockWorkerSelf._sentryModuleMetadata = rawMetadata;
359374

360375
registerWebWorker({ self: mockWorkerSelf as any });
361376

362-
expect(mockGetFilenameToMetadataMap).toHaveBeenCalledWith(expect.any(Function));
363377
expect(mockWorkerSelf.postMessage).toHaveBeenCalledWith({
364378
_sentryMessage: true,
365379
_sentryDebugIds: undefined,
366-
_sentryModuleMetadata: extractedMetadata,
380+
_sentryModuleMetadata: rawMetadata,
367381
});
368382
});
369383

370-
it('does not call getFilenameToMetadataMap when module metadata is not available', () => {
371-
const mockGetFilenameToMetadataMap = SentryCore.getFilenameToMetadataMap as any;
372-
384+
it('sends undefined module metadata when not available', () => {
373385
mockWorkerSelf._sentryModuleMetadata = undefined;
374386

375387
registerWebWorker({ self: mockWorkerSelf as any });
376388

377-
expect(mockGetFilenameToMetadataMap).not.toHaveBeenCalled();
378389
expect(mockWorkerSelf.postMessage).toHaveBeenCalledWith({
379390
_sentryMessage: true,
380391
_sentryDebugIds: undefined,
@@ -383,19 +394,14 @@ describe('registerWebWorker', () => {
383394
});
384395

385396
it('includes both debug IDs and module metadata when both available', () => {
386-
const mockGetFilenameToMetadataMap = SentryCore.getFilenameToMetadataMap as any;
387-
const extractedMetadata = {
388-
'worker-file.js': { '_sentryBundlerPluginAppKey:my-app': true },
397+
const rawMetadata = {
398+
'Error\n at worker-file.js:1:1': { '_sentryBundlerPluginAppKey:my-app': true },
389399
};
390400

391401
mockWorkerSelf._sentryDebugIds = {
392402
'worker-file.js': 'debug-id-1',
393403
};
394-
mockWorkerSelf._sentryModuleMetadata = {
395-
'Error\n at worker-file.js:1:1': { '_sentryBundlerPluginAppKey:my-app': true },
396-
};
397-
398-
mockGetFilenameToMetadataMap.mockReturnValue(extractedMetadata);
404+
mockWorkerSelf._sentryModuleMetadata = rawMetadata;
399405

400406
registerWebWorker({ self: mockWorkerSelf as any });
401407

@@ -404,7 +410,7 @@ describe('registerWebWorker', () => {
404410
_sentryDebugIds: {
405411
'worker-file.js': 'debug-id-1',
406412
},
407-
_sentryModuleMetadata: extractedMetadata,
413+
_sentryModuleMetadata: rawMetadata,
408414
});
409415
});
410416
});
@@ -474,6 +480,7 @@ describe('registerWebWorker and webWorkerIntegration', () => {
474480
expect(mockWorker.postMessage).toHaveBeenCalledWith({
475481
_sentryMessage: true,
476482
_sentryDebugIds: mockWorker._sentryDebugIds,
483+
_sentryModuleMetadata: undefined,
477484
});
478485

479486
expect((helpers.WINDOW as any)._sentryDebugIds).toEqual({
@@ -494,6 +501,7 @@ describe('registerWebWorker and webWorkerIntegration', () => {
494501
expect(mockWorker3.postMessage).toHaveBeenCalledWith({
495502
_sentryMessage: true,
496503
_sentryDebugIds: mockWorker3._sentryDebugIds,
504+
_sentryModuleMetadata: undefined,
497505
});
498506

499507
expect((helpers.WINDOW as any)._sentryDebugIds).toEqual({

packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ export { vercelWaitUntil } from './utils/vercelWaitUntil';
322322
export { flushIfServerless } from './utils/flushIfServerless';
323323
export { SDK_VERSION } from './utils/version';
324324
export { getDebugImagesForResources, getFilenameToDebugIdMap } from './utils/debug-ids';
325-
export { getFilenameToMetadataMap, mergeMetadataMap } from './metadata';
325+
export { getFilenameToMetadataMap } from './metadata';
326326
export { escapeStringForRegex } from './vendor/escapeStringForRegex';
327327

328328
export type { Attachment } from './types-hoist/attachment';

packages/core/src/metadata.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,6 @@ function ensureMetadataStacksAreParsed(parser: StackParser): void {
6767
}
6868
}
6969

70-
/**
71-
* Merges a filename-to-metadata map into the internal metadata cache.
72-
* This is used to integrate metadata from web workers into the main thread.
73-
*
74-
* @param metadataMap - A map of filename to metadata object
75-
*/
76-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
77-
export function mergeMetadataMap(metadataMap: Record<string, any>): void {
78-
for (const filename of Object.keys(metadataMap)) {
79-
if (!filenameMetadataMap.has(filename)) {
80-
filenameMetadataMap.set(filename, metadataMap[filename]);
81-
}
82-
}
83-
}
84-
8570
/**
8671
* Retrieve metadata for a specific JavaScript file URL.
8772
*

packages/core/test/lib/metadata.test.ts

Lines changed: 0 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
addMetadataToStackFrames,
44
getFilenameToMetadataMap,
55
getMetadataForUrl,
6-
mergeMetadataMap,
76
stripMetadataFromStackFrames,
87
} from '../../src/metadata';
98
import type { Event } from '../../src/types-hoist/event';
@@ -175,75 +174,3 @@ describe('getFilenameToMetadataMap', () => {
175174
expect(result['/path/to/same-file.js']).toBeDefined();
176175
});
177176
});
178-
179-
describe('mergeMetadataMap', () => {
180-
beforeEach(() => {
181-
delete GLOBAL_OBJ._sentryModuleMetadata;
182-
});
183-
184-
it('merges metadata from a map into internal cache', () => {
185-
const workerMetadata = {
186-
'worker-file1.js': { '_sentryBundlerPluginAppKey:my-app': true },
187-
'worker-file2.js': { '_sentryBundlerPluginAppKey:my-app': true },
188-
};
189-
190-
mergeMetadataMap(workerMetadata);
191-
192-
const metadata1 = getMetadataForUrl(parser, 'worker-file1.js');
193-
const metadata2 = getMetadataForUrl(parser, 'worker-file2.js');
194-
195-
expect(metadata1).toEqual({ '_sentryBundlerPluginAppKey:my-app': true });
196-
expect(metadata2).toEqual({ '_sentryBundlerPluginAppKey:my-app': true });
197-
});
198-
199-
it('does not overwrite existing metadata', () => {
200-
const stack = `Error
201-
at Object.<anonymous> (/existing-file.js:10:15)`;
202-
203-
GLOBAL_OBJ._sentryModuleMetadata = {
204-
[stack]: { '_sentryBundlerPluginAppKey:main-app': true, existing: true },
205-
};
206-
207-
const existingMetadata = getMetadataForUrl(parser, '/existing-file.js');
208-
expect(existingMetadata).toEqual({ '_sentryBundlerPluginAppKey:main-app': true, existing: true });
209-
210-
const workerMetadata = {
211-
'/existing-file.js': { '_sentryBundlerPluginAppKey:worker-app': true, worker: true },
212-
};
213-
214-
mergeMetadataMap(workerMetadata);
215-
216-
const metadataAfterMerge = getMetadataForUrl(parser, '/existing-file.js');
217-
expect(metadataAfterMerge).toEqual({ '_sentryBundlerPluginAppKey:main-app': true, existing: true });
218-
});
219-
220-
it('handles empty metadata map', () => {
221-
mergeMetadataMap({});
222-
223-
const metadata = getMetadataForUrl(parser, 'nonexistent-file.js');
224-
expect(metadata).toBeUndefined();
225-
});
226-
227-
it('adds new files without affecting existing ones', () => {
228-
const stack = `Error
229-
at Object.<anonymous> (/main-file.js:10:15)`;
230-
231-
GLOBAL_OBJ._sentryModuleMetadata = {
232-
[stack]: { '_sentryBundlerPluginAppKey:main-app': true },
233-
};
234-
235-
getMetadataForUrl(parser, '/main-file.js');
236-
237-
const workerMetadata = {
238-
'/worker-file.js': { '_sentryBundlerPluginAppKey:worker-app': true },
239-
};
240-
241-
mergeMetadataMap(workerMetadata);
242-
243-
const mainMetadata = getMetadataForUrl(parser, '/main-file.js');
244-
const workerMetadataResult = getMetadataForUrl(parser, '/worker-file.js');
245-
246-
expect(mainMetadata).toEqual({ '_sentryBundlerPluginAppKey:main-app': true });
247-
expect(workerMetadataResult).toEqual({ '_sentryBundlerPluginAppKey:worker-app': true });
248-
});
249-
});

0 commit comments

Comments
 (0)