1+ import { createHash } from 'node:crypto' ;
12import type { TransportType } from './constants.js' ;
23
34/**
@@ -77,6 +78,9 @@ export interface ClientMetrics {
7778 activeConnections : number ;
7879 totalConnections : number ;
7980 toolCallCount : number ;
81+ newIpCount : number ;
82+ anonCount : number ;
83+ uniqueAuthCount : number ;
8084}
8185
8286/**
@@ -228,6 +232,9 @@ export interface TransportMetricsResponse {
228232 activeConnections : number ;
229233 totalConnections : number ;
230234 toolCallCount : number ;
235+ newIpCount : number ;
236+ anonCount : number ;
237+ uniqueAuthCount : number ;
231238 } > ;
232239
233240 sessions : SessionData [ ] ;
@@ -409,6 +416,13 @@ class RollingWindowCounter {
409416 }
410417}
411418
419+ /**
420+ * Hash auth tokens before counting them to avoid storing raw secrets.
421+ */
422+ function hashToken ( token : string ) : string {
423+ return createHash ( 'sha256' ) . update ( token ) . digest ( 'hex' ) ;
424+ }
425+
412426/**
413427 * Centralized metrics counter for transport operations
414428 */
@@ -418,13 +432,17 @@ export class MetricsCounter {
418432 private rollingHour : RollingWindowCounter ;
419433 private rolling3Hours : RollingWindowCounter ;
420434 private uniqueIps : Set < string > ;
435+ private clientIps : Map < string , Set < string > > ; // Map of clientKey -> Set of IPs
436+ private clientAuthHashes : Map < string , Set < string > > ; // Map of clientKey -> Set of auth token hashes
421437
422438 constructor ( ) {
423439 this . metrics = createEmptyMetrics ( ) ;
424440 this . rollingMinute = new RollingWindowCounter ( 1 ) ;
425441 this . rollingHour = new RollingWindowCounter ( 60 ) ;
426442 this . rolling3Hours = new RollingWindowCounter ( 180 ) ;
427443 this . uniqueIps = new Set ( ) ;
444+ this . clientIps = new Map ( ) ;
445+ this . clientAuthHashes = new Map ( ) ;
428446 }
429447
430448 /**
@@ -509,6 +527,70 @@ export class MetricsCounter {
509527 }
510528 }
511529
530+ /**
531+ * Track an IP address for a specific client
532+ */
533+ trackClientIpAddress ( ipAddress : string | undefined , clientInfo ?: { name : string ; version : string } ) : void {
534+ // Always track globally
535+ this . trackIpAddress ( ipAddress ) ;
536+
537+ // Track per-client if client info is available
538+ if ( ipAddress && clientInfo ) {
539+ const clientKey = getClientKey ( clientInfo . name , clientInfo . version ) ;
540+ const clientMetrics = this . metrics . clients . get ( clientKey ) ;
541+
542+ if ( clientMetrics ) {
543+ // Get or create the IP set for this client
544+ let clientIpSet = this . clientIps . get ( clientKey ) ;
545+ if ( ! clientIpSet ) {
546+ clientIpSet = new Set ( ) ;
547+ this . clientIps . set ( clientKey , clientIpSet ) ;
548+ }
549+
550+ // Check if this is a new IP for this client
551+ const isNewIp = ! clientIpSet . has ( ipAddress ) ;
552+ if ( isNewIp ) {
553+ clientIpSet . add ( ipAddress ) ;
554+ clientMetrics . newIpCount ++ ;
555+ }
556+ }
557+ }
558+ }
559+
560+ /**
561+ * Track auth status for a specific client
562+ */
563+ trackClientAuth ( authToken : string | undefined , clientInfo ?: { name : string ; version : string } ) : void {
564+ if ( ! clientInfo ) return ;
565+
566+ const clientKey = getClientKey ( clientInfo . name , clientInfo . version ) ;
567+ const clientMetrics = this . metrics . clients . get ( clientKey ) ;
568+
569+ if ( clientMetrics ) {
570+ if ( ! authToken ) {
571+ // Anonymous request
572+ clientMetrics . anonCount ++ ;
573+ } else {
574+ // Authenticated request - hash the token for privacy
575+ const tokenHash = hashToken ( authToken ) ;
576+
577+ // Get or create the auth hash set for this client
578+ let clientAuthSet = this . clientAuthHashes . get ( clientKey ) ;
579+ if ( ! clientAuthSet ) {
580+ clientAuthSet = new Set ( ) ;
581+ this . clientAuthHashes . set ( clientKey , clientAuthSet ) ;
582+ }
583+
584+ // Check if this is a new auth token for this client
585+ const isNewAuth = ! clientAuthSet . has ( tokenHash ) ;
586+ if ( isNewAuth ) {
587+ clientAuthSet . add ( tokenHash ) ;
588+ clientMetrics . uniqueAuthCount ++ ;
589+ }
590+ }
591+ }
592+ }
593+
512594 /**
513595 * Update active connection count
514596 */
@@ -544,6 +626,9 @@ export class MetricsCounter {
544626 activeConnections : 1 ,
545627 totalConnections : 1 ,
546628 toolCallCount : 0 ,
629+ newIpCount : 0 ,
630+ anonCount : 0 ,
631+ uniqueAuthCount : 0 ,
547632 } ;
548633 this . metrics . clients . set ( clientKey , clientMetrics ) ;
549634 } else {
0 commit comments