99 * - POST body capture with automatic redaction
1010 * - Token request evidence collection
1111 * - PKCE verification support
12+ *
13+ * P1-3 ENHANCEMENT:
14+ * - Batch logging to reduce console spam
1215 */
1316
1417import { RequestBodyCapturer } from './modules/auth/request-body-capturer.js' ;
18+ import { BatchLogger } from './modules/utils/batch-logger.js' ;
1519
1620class EvidenceCollector {
1721 constructor ( ) {
@@ -42,10 +46,16 @@ class EvidenceCollector {
4246 this . lastSyncTime = null ;
4347 this . SYNC_INTERVAL_MS = 60000 ; // Auto-save every 60 seconds
4448 this . autoSaveTimer = null ;
49+
50+ // P1-3: Initialize batch logger to reduce console spam
51+ this . logger = new BatchLogger ( {
52+ interval : 10000 , // Flush logs every 10 seconds
53+ immediate : [ 'error' , 'warn' ] // Log errors/warnings immediately
54+ } ) ;
4555 }
4656
4757 async initialize ( ) {
48- if ( this . initialized ) return ;
58+ if ( this . initialized ) { return ; }
4959
5060 try {
5161 // P0 FIX: Initialize IndexedDB for persistent evidence storage
@@ -67,7 +77,7 @@ class EvidenceCollector {
6777 this . _activeFlows = new Map ( Object . entries ( evidence . activeFlows ) ) ;
6878 }
6979
70- console . log ( `[Evidence] Restored ${ this . _responseCache . size } responses, ${ this . _timeline . length } events from IndexedDB`) ;
80+ this . logger . info ( 'init' , ` Restored ${ this . _responseCache . size } responses, ${ this . _timeline . length } events from IndexedDB`) ;
7181 } else {
7282 // Fallback: Try chrome.storage.local (legacy)
7383 const data = await chrome . storage . local . get ( [ 'heraEvidence' , 'heraEvidenceSchemaVersion' ] ) ;
@@ -83,7 +93,7 @@ class EvidenceCollector {
8393 this . _proofOfConcepts = legacyEvidence . proofOfConcepts || [ ] ;
8494 this . _timeline = legacyEvidence . timeline || [ ] ;
8595
86- console . log ( `[Evidence] Migrated ${ this . _responseCache . size } responses from chrome.storage.local`) ;
96+ this . logger . info ( 'init' , ` Migrated ${ this . _responseCache . size } responses from chrome.storage.local`) ;
8797
8898 // Migrate to IndexedDB and clean up old storage
8999 await this . _saveToIndexedDB ( ) ;
@@ -122,7 +132,7 @@ class EvidenceCollector {
122132
123133 request . onsuccess = ( ) => {
124134 this . db = request . result ;
125- console . debug ( '[Evidence] IndexedDB initialized successfully' ) ;
135+ this . logger . debug ( 'init' , ' IndexedDB initialized successfully') ;
126136 resolve ( ) ;
127137 } ;
128138
@@ -145,7 +155,7 @@ class EvidenceCollector {
145155 * P0 FIX: Load evidence from IndexedDB
146156 */
147157 async _loadFromIndexedDB ( ) {
148- if ( ! this . db ) return null ;
158+ if ( ! this . db ) { return null ; }
149159
150160 try {
151161 return new Promise ( ( resolve , reject ) => {
@@ -241,7 +251,7 @@ class EvidenceCollector {
241251
242252 request . onsuccess = ( ) => {
243253 this . lastSyncTime = Date . now ( ) ;
244- console . debug ( '[Evidence] Saved to IndexedDB successfully' ) ;
254+ this . logger . debug ( 'save' , ' Saved to IndexedDB successfully') ;
245255 resolve ( ) ;
246256 } ;
247257 request . onerror = ( ) => {
@@ -283,7 +293,7 @@ class EvidenceCollector {
283293 await this . _saveToIndexedDB ( ) ;
284294 if ( this . db ) {
285295 const secondsAgo = Math . floor ( ( Date . now ( ) - this . lastSyncTime ) / 1000 ) ;
286- console . debug ( `[Evidence] Auto-saved (last sync: ${ secondsAgo } s ago)`) ;
296+ this . logger . debug ( 'auto-save' , ` Auto-saved (last sync: ${ secondsAgo } s ago)`) ;
287297 }
288298 } catch ( error ) {
289299 console . warn ( '[Evidence] Auto-save error:' , error . message ) ;
@@ -382,7 +392,7 @@ class EvidenceCollector {
382392 ? `✓ Saved ${ secondsSinceLastSync } s ago`
383393 : '⏳ Syncing...' ;
384394
385- console . log ( `[Evidence] ${ this . _responseCache . size } responses, ${ this . _timeline . length } events (${ evidenceMB } MB) - ${ syncStatus } `) ;
395+ this . logger . info ( 'status' , ` ${ this . _responseCache . size } responses, ${ this . _timeline . length } events (${ evidenceMB } MB) - ${ syncStatus } `) ;
386396
387397 } catch ( error ) {
388398 if ( error . message ?. includes ( 'QUOTA' ) ) {
@@ -477,7 +487,7 @@ class EvidenceCollector {
477487 _debouncedSync ( ) {
478488 // P0 FIX: Save to IndexedDB (persistent, no quota limits)
479489 // Debounced to avoid excessive writes on high-traffic sites
480- if ( this . _syncTimeout ) clearTimeout ( this . _syncTimeout ) ;
490+ if ( this . _syncTimeout ) { clearTimeout ( this . _syncTimeout ) ; }
481491 this . _syncTimeout = setTimeout ( async ( ) => {
482492 try {
483493 await this . _saveToIndexedDB ( ) ;
@@ -536,15 +546,15 @@ class EvidenceCollector {
536546 const originalSize = truncated . body . length ;
537547 truncated . body = truncated . body . substring ( 0 , this . MAX_BODY_SIZE ) +
538548 `\n\n[TRUNCATED - original size: ${ originalSize } bytes]` ;
539- console . debug ( `[Evidence] Truncated response body: ${ originalSize } → ${ this . MAX_BODY_SIZE } bytes`) ;
549+ this . logger . debug ( 'truncate' , `Response body truncated : ${ originalSize } → ${ this . MAX_BODY_SIZE } bytes`) ;
540550 }
541551
542552 // Step 2: Truncate request body if present
543553 if ( truncated . requestData ?. requestBody && truncated . requestData . requestBody . length > this . MAX_BODY_SIZE ) {
544554 const originalSize = truncated . requestData . requestBody . length ;
545555 truncated . requestData . requestBody = truncated . requestData . requestBody . substring ( 0 , this . MAX_BODY_SIZE ) +
546556 `\n\n[TRUNCATED - original size: ${ originalSize } bytes]` ;
547- console . debug ( `[Evidence] Truncated request body: ${ originalSize } → ${ this . MAX_BODY_SIZE } bytes`) ;
557+ this . logger . debug ( 'truncate' , `Request body truncated : ${ originalSize } → ${ this . MAX_BODY_SIZE } bytes`) ;
548558 }
549559
550560 // Step 3: Check size again after truncation
@@ -575,7 +585,7 @@ class EvidenceCollector {
575585 const originalSize = responseBody . length ;
576586 truncatedBody = responseBody . substring ( 0 , this . MAX_BODY_SIZE ) +
577587 `\n\n[TRUNCATED - original size: ${ originalSize } bytes]` ;
578- console . debug ( `[Evidence] Pre-truncated response body: ${ originalSize } → ${ this . MAX_BODY_SIZE } bytes`) ;
588+ this . logger . debug ( 'truncate' , ` Pre-truncated response body: ${ originalSize } → ${ this . MAX_BODY_SIZE } bytes`) ;
579589 }
580590
581591 // Truncate request body if present
@@ -587,7 +597,7 @@ class EvidenceCollector {
587597 requestBody : requestData . requestBody . substring ( 0 , this . MAX_BODY_SIZE ) +
588598 `\n\n[TRUNCATED - original size: ${ originalSize } bytes]`
589599 } ;
590- console . debug ( `[Evidence] Pre-truncated request body: ${ originalSize } → ${ this . MAX_BODY_SIZE } bytes`) ;
600+ this . logger . debug ( 'truncate' , ` Pre-truncated request body: ${ originalSize } → ${ this . MAX_BODY_SIZE } bytes`) ;
591601 }
592602
593603 let evidence = {
@@ -751,7 +761,7 @@ class EvidenceCollector {
751761 // CRITICAL FIX: Update the correct Map
752762 requestsMap . set ( requestId , existingEvidence ) ;
753763
754- console . debug ( `[Evidence] Found ${ findings . length } security findings in response body for ${ url } ` ) ;
764+ this . logger . debug ( 'findings' , ` Found ${ findings . length } security findings in response body` , { url, count : findings . length } ) ;
755765 }
756766 }
757767
@@ -820,7 +830,7 @@ class EvidenceCollector {
820830 * @returns {Object } HSTS analysis with preload check
821831 */
822832 checkHSTSHeader ( headers , url = null ) {
823- if ( ! headers ) return { present : false , reason : 'no_headers' } ;
833+ if ( ! headers ) { return { present : false , reason : 'no_headers' } ; }
824834
825835 // CRITICAL: HSTS is meaningless on HTTP connections
826836 let isHTTPS = true ;
@@ -904,7 +914,7 @@ class EvidenceCollector {
904914 * @returns {Object } Security headers analysis
905915 */
906916 analyzeSecurityHeaders ( headers ) {
907- if ( ! headers ) return { count : 0 , headers : [ ] , missing : [ ] } ;
917+ if ( ! headers ) { return { count : 0 , headers : [ ] , missing : [ ] } ; }
908918
909919 const securityHeaders = {
910920 'strict-transport-security' : null ,
@@ -948,7 +958,7 @@ class EvidenceCollector {
948958 * @returns {Object } Cookie security analysis
949959 */
950960 analyzeCookies ( headers ) {
951- if ( ! headers ) return { cookies : [ ] , vulnerabilities : [ ] } ;
961+ if ( ! headers ) { return { cookies : [ ] , vulnerabilities : [ ] } ; }
952962
953963 const setCookieHeaders = headers . filter ( h =>
954964 h . name . toLowerCase ( ) === 'set-cookie'
@@ -1047,13 +1057,13 @@ class EvidenceCollector {
10471057 * @returns {Object } Content type analysis
10481058 */
10491059 extractContentType ( headers ) {
1050- if ( ! headers ) return null ;
1060+ if ( ! headers ) { return null ; }
10511061
10521062 const contentTypeHeader = headers . find ( h =>
10531063 h . name . toLowerCase ( ) === 'content-type'
10541064 ) ;
10551065
1056- if ( ! contentTypeHeader ) return null ;
1066+ if ( ! contentTypeHeader ) { return null ; }
10571067
10581068 const value = contentTypeHeader . value ;
10591069 const [ mediaType , ...params ] = value . split ( ';' ) . map ( p => p . trim ( ) ) ;
@@ -1072,13 +1082,13 @@ class EvidenceCollector {
10721082 * @returns {Object } Cache control analysis
10731083 */
10741084 extractCacheControl ( headers ) {
1075- if ( ! headers ) return null ;
1085+ if ( ! headers ) { return null ; }
10761086
10771087 const cacheControlHeader = headers . find ( h =>
10781088 h . name . toLowerCase ( ) === 'cache-control'
10791089 ) ;
10801090
1081- if ( ! cacheControlHeader ) return null ;
1091+ if ( ! cacheControlHeader ) { return null ; }
10821092
10831093 const directives = cacheControlHeader . value . split ( ',' ) . map ( d => d . trim ( ) ) ;
10841094
@@ -1149,7 +1159,7 @@ class EvidenceCollector {
11491159 * @returns {Object } Flow correlation data
11501160 */
11511161 correlateWithFlow ( requestId , requestData ) {
1152- if ( ! requestData ) return null ;
1162+ if ( ! requestData ) { return null ; }
11531163
11541164 // Try to identify which flow this request belongs to
11551165 const url = new URL ( requestData . url ) ;
@@ -1189,7 +1199,7 @@ class EvidenceCollector {
11891199 */
11901200 calculateSecurityHeaderScore ( found , missing ) {
11911201 const totalHeaders = found . length + missing . length ;
1192- if ( totalHeaders === 0 ) return 0 ;
1202+ if ( totalHeaders === 0 ) { return 0 ; }
11931203
11941204 return Math . round ( ( found . length / totalHeaders ) * 100 ) ;
11951205 }
@@ -1247,10 +1257,10 @@ class EvidenceCollector {
12471257 // Return sanitized samples for evidence
12481258 const samples = [ ] ;
12491259 if ( body && typeof body === 'string' ) {
1250- if ( body . includes ( 'password' ) ) samples . push ( 'Contains password field' ) ;
1251- if ( body . includes ( 'api_key' ) || body . includes ( 'apikey' ) ) samples . push ( 'Contains API key' ) ;
1252- if ( body . includes ( 'secret' ) ) samples . push ( 'Contains secret' ) ;
1253- if ( body . includes ( 'token' ) ) samples . push ( 'Contains token' ) ;
1260+ if ( body . includes ( 'password' ) ) { samples . push ( 'Contains password field' ) ; }
1261+ if ( body . includes ( 'api_key' ) || body . includes ( 'apikey' ) ) { samples . push ( 'Contains API key' ) ; }
1262+ if ( body . includes ( 'secret' ) ) { samples . push ( 'Contains secret' ) ; }
1263+ if ( body . includes ( 'token' ) ) { samples . push ( 'Contains token' ) ; }
12541264 }
12551265 return samples ;
12561266 }
@@ -1265,10 +1275,10 @@ class EvidenceCollector {
12651275
12661276 detectAuthProtocol ( requestData ) {
12671277 const url = requestData . url . toLowerCase ( ) ;
1268- if ( url . includes ( 'oauth' ) || url . includes ( 'authorize' ) ) return 'OAuth2' ;
1269- if ( url . includes ( 'saml' ) ) return 'SAML' ;
1270- if ( url . includes ( 'openid' ) ) return 'OpenID' ;
1271- if ( url . includes ( 'auth' ) || url . includes ( 'login' ) ) return 'Custom' ;
1278+ if ( url . includes ( 'oauth' ) || url . includes ( 'authorize' ) ) { return 'OAuth2' ; }
1279+ if ( url . includes ( 'saml' ) ) { return 'SAML' ; }
1280+ if ( url . includes ( 'openid' ) ) { return 'OpenID' ; }
1281+ if ( url . includes ( 'auth' ) || url . includes ( 'login' ) ) { return 'Custom' ; }
12721282 return 'Unknown' ;
12731283 }
12741284
@@ -1310,7 +1320,7 @@ class EvidenceCollector {
13101320 h . name . toLowerCase ( ) === 'origin'
13111321 ) ?. value ;
13121322
1313- if ( ! origin ) return { isCrossOrigin : false } ;
1323+ if ( ! origin ) { return { isCrossOrigin : false } ; }
13141324
13151325 const requestUrl = new URL ( requestDetails . url ) ;
13161326 const originUrl = new URL ( origin ) ;
@@ -1353,10 +1363,10 @@ class EvidenceCollector {
13531363
13541364 identifyAuthStep ( requestDetails ) {
13551365 const url = requestDetails . url . toLowerCase ( ) ;
1356- if ( url . includes ( 'authorize' ) ) return 'authorization_request' ;
1357- if ( url . includes ( 'token' ) ) return 'token_request' ;
1358- if ( url . includes ( 'login' ) ) return 'login_form' ;
1359- if ( url . includes ( 'callback' ) ) return 'callback' ;
1366+ if ( url . includes ( 'authorize' ) ) { return 'authorization_request' ; }
1367+ if ( url . includes ( 'token' ) ) { return 'token_request' ; }
1368+ if ( url . includes ( 'login' ) ) { return 'login_form' ; }
1369+ if ( url . includes ( 'callback' ) ) { return 'callback' ; }
13601370 return 'unknown' ;
13611371 }
13621372
@@ -1401,7 +1411,7 @@ class EvidenceCollector {
14011411 */
14021412 calculateEvidenceQuality ( requestId ) {
14031413 const evidence = this . responseCache . get ( requestId ) ;
1404- if ( ! evidence ) return null ;
1414+ if ( ! evidence ) { return null ; }
14051415
14061416 const quality = {
14071417 completeness : 0 ,
0 commit comments