@@ -64,80 +64,106 @@ const LogTimestamp = ({ timestamp }) => {
6464 return < span className = "log-timestamp" > { time } </ span > ;
6565} ;
6666
67- const LogMessage = ( { message, args } ) => {
68- const { displayedTheme } = useTheme ( ) ;
67+ // Helper function to check if an object is a plain object (not a class instance)
68+ const isPlainObject = ( obj ) => {
69+ if ( typeof obj !== 'object' || obj === null ) return false ;
70+ const proto = Object . getPrototypeOf ( obj ) ;
71+ return proto === null || proto === Object . prototype ;
72+ } ;
6973
70- // Helper function to transform Bruno special types back to readable format
71- // Returns { data, metadata } where metadata contains type information
72- const transformBrunoTypes = ( obj , returnMetadata = false ) => {
73- if ( typeof obj !== 'object' || obj === null ) {
74- return returnMetadata ? { data : obj , metadata : { } } : obj ;
75- }
74+ // Helper function to transform Bruno special types back to readable format
75+ // Extracted outside component to avoid recreation on every render
76+ const transformBrunoTypes = ( obj , seen = new WeakSet ( ) ) => {
77+ if ( typeof obj !== 'object' || obj === null ) {
78+ return obj ;
79+ }
7680
77- // Handle Bruno special types
78- if ( obj . __brunoType ) {
79- switch ( obj . __brunoType ) {
80- case 'Set' :
81- // Transform Set to display values at top level with numeric indices
82- // Convert array of values to object with numeric keys (0, 1, 2, ...)
83- const setEntries = { } ;
84- if ( Array . isArray ( obj . __brunoValue ) ) {
85- obj . __brunoValue . forEach ( ( value , index ) => {
86- setEntries [ index ] = transformBrunoTypes ( value , false ) ;
87- } ) ;
88- }
89- return returnMetadata ? { data : setEntries , metadata : { type : 'Set' } } : setEntries ;
90- case 'Map' :
91- // Transform Map to display entries at top level with => notation
92- // Convert array of [key, value] pairs to object with "key => value" format
81+ // Guard against circular references
82+ if ( seen . has ( obj ) ) {
83+ return '[Circular]' ;
84+ }
85+ seen . add ( obj ) ;
86+
87+ // Handle Bruno special types
88+ if ( obj . __brunoType ) {
89+ switch ( obj . __brunoType ) {
90+ case 'Set' :
91+ // Transform Set to display values at top level with numeric indices
92+ if ( Array . isArray ( obj . __brunoValue ) ) {
93+ return Object . fromEntries (
94+ obj . __brunoValue . map ( ( value , index ) => [ index , transformBrunoTypes ( value , seen ) ] )
95+ ) ;
96+ }
97+ return { } ;
98+ case 'Map' :
99+ // Transform Map to display entries at top level with => notation
100+ if ( Array . isArray ( obj . __brunoValue ) ) {
93101 const mapEntries = { } ;
94- if ( Array . isArray ( obj . __brunoValue ) ) {
95- obj . __brunoValue . forEach ( ( [ key , value ] ) => {
96- // Use => notation to clearly indicate Map entries
97- const displayKey = ` ${ String ( key ) } =>` ;
98- mapEntries [ displayKey ] = transformBrunoTypes ( value , false ) ;
99- } ) ;
102+ for ( const entry of obj . __brunoValue ) {
103+ // Defensive check: ensure entry is a valid [key, value] pair
104+ if ( Array . isArray ( entry ) && entry . length >= 2 ) {
105+ const [ key , value ] = entry ;
106+ mapEntries [ ` ${ String ( key ) } =>` ] = transformBrunoTypes ( value , seen ) ;
107+ }
100108 }
101- return returnMetadata ? { data : mapEntries , metadata : { type : 'Map' } } : mapEntries ;
102- case 'Function' :
103- const funcData = `[Function: ${ obj . __brunoValue . split ( '\n' ) [ 0 ] . substring ( 0 , 50 ) } ...]` ;
104- return returnMetadata ? { data : funcData , metadata : { } } : funcData ;
105- case 'undefined' :
106- return returnMetadata ? { data : 'undefined' , metadata : { } } : 'undefined' ;
107- default :
108- return returnMetadata ? { data : obj , metadata : { } } : obj ;
109- }
109+ return mapEntries ;
110+ }
111+ return { } ;
112+ case 'Function' :
113+ return `[Function: ${ obj . __brunoValue ?. split ?. ( '\n' ) ?. [ 0 ] ?. substring ( 0 , 50 ) ?? 'anonymous' } ...]` ;
114+ case 'undefined' :
115+ return 'undefined' ;
116+ default :
117+ return obj ;
110118 }
119+ }
111120
112- // Recursively transform nested objects
113- if ( Array . isArray ( obj ) ) {
114- const transformed = obj . map ( ( item ) => transformBrunoTypes ( item , false ) ) ;
115- return returnMetadata ? { data : transformed , metadata : { } } : transformed ;
116- }
121+ // Handle arrays - recurse into elements
122+ if ( Array . isArray ( obj ) ) {
123+ return obj . map ( ( item ) => transformBrunoTypes ( item , seen ) ) ;
124+ }
117125
118- const transformed = { } ;
119- for ( const [ key , value ] of Object . entries ( obj ) ) {
120- transformed [ key ] = transformBrunoTypes ( value , false ) ;
121- }
122- return returnMetadata ? { data : transformed , metadata : { } } : transformed ;
123- } ;
126+ // Preserve non-plain objects (Date, Error, RegExp, class instances, etc.)
127+ if ( ! isPlainObject ( obj ) ) {
128+ return obj ;
129+ }
130+
131+ // Only deep-clone plain objects
132+ const transformed = { } ;
133+ for ( const [ key , value ] of Object . entries ( obj ) ) {
134+ transformed [ key ] = transformBrunoTypes ( value , seen ) ;
135+ }
136+ return transformed ;
137+ } ;
138+
139+ // Helper to get metadata about Bruno types for display purposes
140+ const getBrunoTypeMetadata = ( obj ) => {
141+ if ( typeof obj !== 'object' || obj === null ) {
142+ return { } ;
143+ }
144+ if ( obj . __brunoType === 'Set' || obj . __brunoType === 'Map' ) {
145+ return { type : obj . __brunoType } ;
146+ }
147+ return { } ;
148+ } ;
149+
150+ const LogMessage = ( { message, args } ) => {
151+ const { displayedTheme } = useTheme ( ) ;
124152
125153 const formatMessage = ( msg , originalArgs ) => {
126154 if ( originalArgs && originalArgs . length > 0 ) {
127155 return originalArgs . map ( ( arg , index ) => {
128156 if ( typeof arg === 'object' && arg !== null ) {
129- const { data : transformedArg , metadata } = transformBrunoTypes ( arg , true ) ;
157+ const metadata = getBrunoTypeMetadata ( arg ) ;
158+ const transformedArg = transformBrunoTypes ( arg ) ;
130159
131160 // Determine the name to display based on the type
132161 let displayName = false ;
133162 let shouldCollapse = 1 ; // Default: collapse at depth 1 for regular objects
134163
135- if ( metadata . type === 'Map' ) {
136- displayName = 'Map' ;
137- shouldCollapse = true ; // Fully collapse Maps by default
138- } else if ( metadata . type === 'Set' ) {
139- displayName = 'Set' ;
140- shouldCollapse = true ; // Fully collapse Sets by default
164+ if ( metadata . type === 'Map' || metadata . type === 'Set' ) {
165+ displayName = metadata . type ;
166+ shouldCollapse = true ; // Fully collapse Maps/Sets by default
141167 }
142168
143169 return (
0 commit comments