1- import { Event , EventProcessor , Exception , Hub , Integration , StackFrame , StackParser } from '@sentry/types' ;
1+ import {
2+ ClientOptions ,
3+ Event ,
4+ EventProcessor ,
5+ Exception ,
6+ Hub ,
7+ Integration ,
8+ StackFrame ,
9+ StackParser ,
10+ } from '@sentry/types' ;
211import { Debugger , InspectorNotification , Runtime , Session } from 'inspector' ;
312import { LRUMap } from 'lru_map' ;
413
14+ export interface DebugSession {
15+ /** Configures and connects to the debug session */
16+ configureAndConnect ( onPause : ( message : InspectorNotification < Debugger . PausedEventDataType > ) => void ) : void ;
17+ /** Gets local variables for an objectId */
18+ getLocalVariables ( objectId : string ) : Promise < Record < string , unknown > > ;
19+ }
20+
521/**
622 * Promise API is available as `Experimental` and in Node 19 only.
723 *
@@ -11,8 +27,38 @@ import { LRUMap } from 'lru_map';
1127 * https://nodejs.org/docs/latest-v19.x/api/inspector.html#promises-api
1228 * https://nodejs.org/docs/latest-v14.x/api/inspector.html
1329 */
14- class AsyncSession extends Session {
15- public getProperties ( objectId : string ) : Promise < Runtime . PropertyDescriptor [ ] > {
30+ class AsyncSession extends Session implements DebugSession {
31+ /** @inheritdoc */
32+ public configureAndConnect ( onPause : ( message : InspectorNotification < Debugger . PausedEventDataType > ) => void ) : void {
33+ this . connect ( ) ;
34+ this . on ( 'Debugger.paused' , onPause ) ;
35+ this . post ( 'Debugger.enable' ) ;
36+ // We only want to pause on uncaught exceptions
37+ this . post ( 'Debugger.setPauseOnExceptions' , { state : 'uncaught' } ) ;
38+ }
39+
40+ /** @inheritdoc */
41+ public async getLocalVariables ( objectId : string ) : Promise < Record < string , unknown > > {
42+ const props = await this . _getProperties ( objectId ) ;
43+ const unrolled : Record < string , unknown > = { } ;
44+
45+ for ( const prop of props ) {
46+ if ( prop ?. value ?. objectId && prop ?. value . className === 'Array' ) {
47+ unrolled [ prop . name ] = await this . _unrollArray ( prop . value . objectId ) ;
48+ } else if ( prop ?. value ?. objectId && prop ?. value ?. className === 'Object' ) {
49+ unrolled [ prop . name ] = await this . _unrollObject ( prop . value . objectId ) ;
50+ } else if ( prop ?. value ?. value || prop ?. value ?. description ) {
51+ unrolled [ prop . name ] = prop . value . value || `<${ prop . value . description } >` ;
52+ }
53+ }
54+
55+ return unrolled ;
56+ }
57+
58+ /**
59+ * Gets all the PropertyDescriptors of an object
60+ */
61+ private _getProperties ( objectId : string ) : Promise < Runtime . PropertyDescriptor [ ] > {
1662 return new Promise ( ( resolve , reject ) => {
1763 this . post (
1864 'Runtime.getProperties' ,
@@ -30,6 +76,30 @@ class AsyncSession extends Session {
3076 ) ;
3177 } ) ;
3278 }
79+
80+ /**
81+ * Unrolls an array property
82+ */
83+ private async _unrollArray ( objectId : string ) : Promise < unknown > {
84+ const props = await this . _getProperties ( objectId ) ;
85+ return props
86+ . filter ( v => v . name !== 'length' && ! isNaN ( parseInt ( v . name , 10 ) ) )
87+ . sort ( ( a , b ) => parseInt ( a . name , 10 ) - parseInt ( b . name , 10 ) )
88+ . map ( v => v ?. value ?. value ) ;
89+ }
90+
91+ /**
92+ * Unrolls an object property
93+ */
94+ private async _unrollObject ( objectId : string ) : Promise < Record < string , unknown > > {
95+ const props = await this . _getProperties ( objectId ) ;
96+ return props
97+ . map < [ string , unknown ] > ( v => [ v . name , v ?. value ?. value ] )
98+ . reduce ( ( obj , [ key , val ] ) => {
99+ obj [ key ] = val ;
100+ return obj ;
101+ } , { } as Record < string , unknown > ) ;
102+ }
33103}
34104
35105// Add types for the exception event data
@@ -60,7 +130,7 @@ function hashFrames(frames: StackFrame[] | undefined): string | undefined {
60130 return frames . slice ( - 10 ) . reduce ( ( acc , frame ) => `${ acc } ,${ frame . function } ,${ frame . lineno } ,${ frame . colno } ` , '' ) ;
61131}
62132
63- interface FrameVariables {
133+ export interface FrameVariables {
64134 function : string ;
65135 vars ?: Record < string , unknown > ;
66136}
@@ -73,24 +143,27 @@ export class LocalVariables implements Integration {
73143
74144 public readonly name : string = LocalVariables . id ;
75145
76- private readonly _session : AsyncSession = new AsyncSession ( ) ;
77146 private readonly _cachedFrames : LRUMap < string , Promise < FrameVariables [ ] > > = new LRUMap ( 20 ) ;
78147 private _stackParser : StackParser | undefined ;
79148
149+ public constructor ( private readonly _session : DebugSession = new AsyncSession ( ) ) { }
150+
80151 /**
81152 * @inheritDoc
82153 */
83154 public setupOnce ( addGlobalEventProcessor : ( callback : EventProcessor ) => void , getCurrentHub : ( ) => Hub ) : void {
84- const options = getCurrentHub ( ) . getClient ( ) ?. getOptions ( ) ;
155+ this . _setup ( addGlobalEventProcessor , getCurrentHub ( ) . getClient ( ) ?. getOptions ( ) ) ;
156+ }
85157
86- if ( options ?. _experiments ?. includeStackLocals ) {
87- this . _stackParser = options . stackParser ;
158+ /** Setup in a way that's easier to call from tests */
159+ private _setup (
160+ addGlobalEventProcessor : ( callback : EventProcessor ) => void ,
161+ clientOptions : ClientOptions | undefined ,
162+ ) : void {
163+ if ( clientOptions ?. _experiments ?. includeStackLocals ) {
164+ this . _stackParser = clientOptions . stackParser ;
88165
89- this . _session . connect ( ) ;
90- this . _session . on ( 'Debugger.paused' , this . _handlePaused . bind ( this ) ) ;
91- this . _session . post ( 'Debugger.enable' ) ;
92- // We only want to pause on uncaught exceptions
93- this . _session . post ( 'Debugger.setPauseOnExceptions' , { state : 'uncaught' } ) ;
166+ this . _session . configureAndConnect ( this . _handlePaused . bind ( this ) ) ;
94167
95168 addGlobalEventProcessor ( async event => this . _addLocalVariables ( event ) ) ;
96169 }
@@ -134,7 +207,7 @@ export class LocalVariables implements Integration {
134207 return { function : fn } ;
135208 }
136209
137- const vars = await this . _unrollProps ( await this . _session . getProperties ( localScope . object . objectId ) ) ;
210+ const vars = await this . _session . getLocalVariables ( localScope . object . objectId ) ;
138211
139212 return { function : fn , vars } ;
140213 } ) ;
@@ -144,49 +217,6 @@ export class LocalVariables implements Integration {
144217 this . _cachedFrames . set ( exceptionHash , Promise . all ( framePromises ) ) ;
145218 }
146219
147- /**
148- * Unrolls all the properties
149- */
150- private async _unrollProps ( props : Runtime . PropertyDescriptor [ ] ) : Promise < Record < string , unknown > > {
151- const unrolled : Record < string , unknown > = { } ;
152-
153- for ( const prop of props ) {
154- if ( prop ?. value ?. objectId && prop ?. value . className === 'Array' ) {
155- unrolled [ prop . name ] = await this . _unrollArray ( prop . value . objectId ) ;
156- } else if ( prop ?. value ?. objectId && prop ?. value ?. className === 'Object' ) {
157- unrolled [ prop . name ] = await this . _unrollObject ( prop . value . objectId ) ;
158- } else if ( prop ?. value ?. value || prop ?. value ?. description ) {
159- unrolled [ prop . name ] = prop . value . value || `<${ prop . value . description } >` ;
160- }
161- }
162-
163- return unrolled ;
164- }
165-
166- /**
167- * Unrolls an array property
168- */
169- private async _unrollArray ( objectId : string ) : Promise < unknown > {
170- const props = await this . _session . getProperties ( objectId ) ;
171- return props
172- . filter ( v => v . name !== 'length' && ! isNaN ( parseInt ( v . name , 10 ) ) )
173- . sort ( ( a , b ) => parseInt ( a . name , 10 ) - parseInt ( b . name , 10 ) )
174- . map ( v => v ?. value ?. value ) ;
175- }
176-
177- /**
178- * Unrolls an object property
179- */
180- private async _unrollObject ( objectId : string ) : Promise < Record < string , unknown > > {
181- const props = await this . _session . getProperties ( objectId ) ;
182- return props
183- . map < [ string , unknown ] > ( v => [ v . name , v ?. value ?. value ] )
184- . reduce ( ( obj , [ key , val ] ) => {
185- obj [ key ] = val ;
186- return obj ;
187- } , { } as Record < string , unknown > ) ;
188- }
189-
190220 /**
191221 * Adds local variables event stack frames.
192222 */
@@ -209,14 +239,13 @@ export class LocalVariables implements Integration {
209239 }
210240
211241 // Check if we have local variables for an exception that matches the hash
212- const cachedFrames = await this . _cachedFrames . get ( hash ) ;
242+ // delete is identical to get but also removes the entry from the cache
243+ const cachedFrames = await this . _cachedFrames . delete ( hash ) ;
213244
214245 if ( cachedFrames === undefined ) {
215246 return ;
216247 }
217248
218- await this . _cachedFrames . delete ( hash ) ;
219-
220249 const frameCount = exception . stacktrace ?. frames ?. length || 0 ;
221250
222251 for ( let i = 0 ; i < frameCount ; i ++ ) {
0 commit comments