Skip to content

Commit b355291

Browse files
committed
feat(browser): Add ContextLines integration for html-embedded JS stack
frames
1 parent 4e4ed70 commit b355291

File tree

1 file changed

+69
-6
lines changed

1 file changed

+69
-6
lines changed

packages/browser/src/integrations/contextlines.ts

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import type { Event, EventProcessor, Integration } from '@sentry/types';
1+
import type { Event, EventProcessor, Integration, StackFrame } from '@sentry/types';
2+
import { stripUrlQueryAndFragment } from '@sentry/utils';
3+
4+
import { WINDOW } from '../helpers';
25

36
interface ContextLinesOptions {
47
/**
@@ -29,11 +32,7 @@ export class ContextLines implements Integration {
2932
*/
3033
public name: string = ContextLines.id;
3134

32-
public constructor(
33-
private readonly _options: ContextLinesOptions = {
34-
frameContextLines: 0,
35-
},
36-
) {}
35+
public constructor(private readonly _options: ContextLinesOptions = {}) {}
3736

3837
/**
3938
* @inheritDoc
@@ -44,6 +43,70 @@ export class ContextLines implements Integration {
4443

4544
/** Processes an event and adds context lines */
4645
public addSourceContext(event: Event): Event {
46+
const doc = WINDOW.document;
47+
const htmlFilename = WINDOW.location && stripUrlQueryAndFragment(WINDOW.location.href);
48+
if (!doc || !htmlFilename) {
49+
return event;
50+
}
51+
52+
const exceptions = event.exception && event.exception.values;
53+
if (!exceptions || !exceptions.length) {
54+
return event;
55+
}
56+
57+
const html = doc.documentElement.innerHTML;
58+
59+
const htmlLines = ['<!DOCTYPE html>', '<html>', ...html.split('\n'), '</html>'];
60+
if (!htmlLines.length) {
61+
return event;
62+
}
63+
64+
exceptions.forEach(exception => {
65+
const stacktrace = exception.stacktrace;
66+
if (stacktrace && stacktrace.frames) {
67+
stacktrace.frames = stacktrace.frames.map(frame =>
68+
applySourceContextToFrame(frame, htmlLines, htmlFilename, this._options.frameContextLines || 7),
69+
);
70+
}
71+
});
72+
4773
return event;
4874
}
4975
}
76+
77+
/**
78+
* Only exported for testing
79+
*/
80+
export function applySourceContextToFrame(
81+
frame: StackFrame,
82+
htmlLines: string[],
83+
htmlFilename: string,
84+
contextRange: number,
85+
): StackFrame {
86+
if (frame.filename !== htmlFilename || !frame.lineno || !htmlLines.length) {
87+
return frame;
88+
}
89+
90+
const sourroundingRange = Math.floor(contextRange / 2);
91+
const contextLineIndex = frame.lineno - 1;
92+
const preStartIndex = Math.max(contextLineIndex - sourroundingRange, 0);
93+
const postEndIndex = Math.min(contextLineIndex + sourroundingRange, htmlLines.length - 1);
94+
95+
const preLines = htmlLines.slice(preStartIndex, contextLineIndex);
96+
const contextLine = htmlLines[contextLineIndex];
97+
const postLines = htmlLines.slice(contextLineIndex + 1, postEndIndex + 1);
98+
99+
if (preLines.length) {
100+
frame.pre_context = preLines;
101+
}
102+
103+
if (contextLine) {
104+
frame.context_line = contextLine;
105+
}
106+
107+
if (postLines.length) {
108+
frame.post_context = postLines || undefined;
109+
}
110+
111+
return frame;
112+
}

0 commit comments

Comments
 (0)