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
36interface 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