Skip to content

Commit 869c575

Browse files
fix: filter Sentry Replay iframe prototype TypeError (EPICSHOP-HF) (#652)
Session Replay's iframe load / attachShadow patches throw TypeError reading 'prototype' on exercise preview iframes. Stack is entirely @sentry-internal/replay — drop via client beforeSend filter. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent e26e7d9 commit 869c575

2 files changed

Lines changed: 129 additions & 1 deletion

File tree

packages/workshop-app/app/utils/sentry-filters.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,43 @@ export function isUnexpectedServerErrorNoise(event: SentryEventWithException) {
277277
)
278278
}
279279

280+
function frameLooksLikeSentryReplay(frame: {
281+
filename?: string
282+
module?: string
283+
}) {
284+
const filename = frame.filename ?? ''
285+
const module = frame.module ?? ''
286+
return (
287+
filename.includes('@sentry-internal/replay') ||
288+
module.includes('@sentry-internal/replay')
289+
)
290+
}
291+
292+
/**
293+
* Sentry Session Replay patches iframe load / attachShadow and can throw
294+
* TypeError "Cannot read properties of undefined (reading 'prototype')" when
295+
* exercise preview iframes load. Entire stack is SDK frames — not product.
296+
*/
297+
export function isSentryReplayIframeNoise(event: SentryEventWithException) {
298+
return getExceptionValues(event).some((value) => {
299+
if (value.type !== 'TypeError') return false
300+
const text = exceptionValueText(value)
301+
if (!/reading ['"]prototype['"]/i.test(text)) return false
302+
303+
const frames = value.stacktrace?.frames ?? []
304+
if (!frames.length) return false
305+
306+
return frames.some((frame) => {
307+
if (!frameLooksLikeSentryReplay(frame)) return false
308+
const fn = frame.function ?? ''
309+
return (
310+
/onIframeLoad|observeAttachShadow|patchAttachShadow/i.test(fn) ||
311+
/HTMLIFrameElement/i.test(fn)
312+
)
313+
})
314+
})
315+
}
316+
280317
export function isClientSentryNoise(event: SentryEventWithException) {
281318
return (
282319
isProcessingPictureInPictureRequest(event) ||
@@ -289,6 +326,7 @@ export function isClientSentryNoise(event: SentryEventWithException) {
289326
isDomMutationNoise(event) ||
290327
isPlaygroundClientNoise(event) ||
291328
isReactExtensionRenderLoopNoise(event) ||
292-
isUnexpectedServerErrorNoise(event)
329+
isUnexpectedServerErrorNoise(event) ||
330+
isSentryReplayIframeNoise(event)
293331
)
294332
}

packages/workshop-app/tests/sentry-filters.test.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
isPlaygroundClientNoise,
1010
isProcessingPictureInPictureRequest,
1111
isReactExtensionRenderLoopNoise,
12+
isSentryReplayIframeNoise,
1213
isSessionStorageAccessDenied,
1314
isStaleRouteResultNoise,
1415
isUnexpectedServerErrorNoise,
@@ -433,6 +434,95 @@ test('drops React Router opaque Unexpected Server Error client mirrors (aha)', (
433434
).toBe(false)
434435
})
435436

437+
test('drops Sentry Replay iframe/attachShadow prototype TypeErrors (aha)', () => {
438+
const replayIframeEvent = {
439+
exception: {
440+
values: [
441+
{
442+
type: 'TypeError',
443+
value: "Cannot read properties of undefined (reading 'prototype')",
444+
stacktrace: {
445+
frames: [
446+
{
447+
filename:
448+
'../../../../../node_modules/@sentry/browser/build/npm/esm/prod/helpers.js',
449+
function: 'r',
450+
inApp: false,
451+
},
452+
{
453+
filename:
454+
'../../../../../node_modules/@sentry-internal/replay/build/npm/esm/index.js',
455+
function: 'HTMLIFrameElement.<anonymous>',
456+
module: '@sentry-internal/replay/build/npm/esm/index',
457+
inApp: false,
458+
},
459+
{
460+
filename:
461+
'../../../../../node_modules/@sentry-internal/replay/build/npm/esm/index.js',
462+
function: 's.onIframeLoad',
463+
module: '@sentry-internal/replay/build/npm/esm/index',
464+
inApp: false,
465+
},
466+
{
467+
filename:
468+
'../../../../../node_modules/@sentry-internal/replay/build/npm/esm/index.js',
469+
function: 'TT.observeAttachShadow',
470+
module: '@sentry-internal/replay/build/npm/esm/index',
471+
inApp: false,
472+
},
473+
{
474+
filename:
475+
'../../../../../node_modules/@sentry-internal/replay/build/npm/esm/index.js',
476+
function: 'TT.patchAttachShadow',
477+
module: '@sentry-internal/replay/build/npm/esm/index',
478+
inApp: false,
479+
},
480+
],
481+
},
482+
},
483+
],
484+
},
485+
}
486+
487+
expect(isSentryReplayIframeNoise(replayIframeEvent)).toBe(true)
488+
expect(isClientSentryNoise(replayIframeEvent)).toBe(true)
489+
490+
// Same message without Replay iframe/shadow frames must still alert.
491+
expect(
492+
isSentryReplayIframeNoise({
493+
exception: {
494+
values: [
495+
{
496+
type: 'TypeError',
497+
value: "Cannot read properties of undefined (reading 'prototype')",
498+
stacktrace: {
499+
frames: [
500+
{
501+
filename: '/app/utils/something.ts',
502+
function: 'extendThing',
503+
inApp: true,
504+
},
505+
],
506+
},
507+
},
508+
],
509+
},
510+
}),
511+
).toBe(false)
512+
expect(
513+
isSentryReplayIframeNoise({
514+
exception: {
515+
values: [
516+
{
517+
type: 'TypeError',
518+
value: "Cannot read properties of undefined (reading 'prototype')",
519+
},
520+
],
521+
},
522+
}),
523+
).toBe(false)
524+
})
525+
436526
test('isClientSentryNoise aggregates the client predicates', () => {
437527
expect(
438528
isClientSentryNoise({

0 commit comments

Comments
 (0)