@@ -54,6 +54,8 @@ export class EventBuffer extends BaseBuffer {
54
54
)
55
55
: 1000 ;
56
56
57
+ private activeVisitorsExpiration = 60 * 5 ; // 5 minutes
58
+
57
59
private sessionEvents = [ 'screen_view' , 'session_end' ] ;
58
60
59
61
// LIST - Stores events without sessions
@@ -246,8 +248,11 @@ return "OK"
246
248
}
247
249
248
250
if ( event . profile_id ) {
249
- multi . sadd ( `live:visitors:${ event . project_id } ` , event . profile_id ) ;
250
- multi . expire ( `live:visitors:${ event . project_id } ` , 60 * 5 ) ; // 5 minutes
251
+ this . incrementActiveVisitorCount (
252
+ multi ,
253
+ event . project_id ,
254
+ event . profile_id ,
255
+ ) ;
251
256
}
252
257
253
258
if ( ! _multi ) {
@@ -689,4 +694,44 @@ return "OK"
689
694
) ;
690
695
return count ;
691
696
}
697
+
698
+ private async incrementActiveVisitorCount (
699
+ multi : ReturnType < Redis [ 'multi' ] > ,
700
+ projectId : string ,
701
+ profileId : string ,
702
+ ) {
703
+ // Add/update visitor with current timestamp as score
704
+ const now = Date . now ( ) ;
705
+ const zsetKey = `live:visitors:${ projectId } ` ;
706
+ return (
707
+ multi
708
+ // To keep the count
709
+ . zadd ( zsetKey , now , profileId )
710
+ // To trigger the expiration listener
711
+ . set (
712
+ `live:visitor:${ projectId } :${ profileId } ` ,
713
+ '1' ,
714
+ 'EX' ,
715
+ this . activeVisitorsExpiration ,
716
+ )
717
+ ) ;
718
+ }
719
+
720
+ public async getActiveVisitorCount ( projectId : string ) : Promise < number > {
721
+ const redis = getRedisCache ( ) ;
722
+ const zsetKey = `live:visitors:${ projectId } ` ;
723
+ const cutoff = Date . now ( ) - this . activeVisitorsExpiration * 1000 ;
724
+
725
+ const multi = redis . multi ( ) ;
726
+ multi
727
+ . zremrangebyscore ( zsetKey , '-inf' , cutoff )
728
+ . zcount ( zsetKey , cutoff , '+inf' ) ;
729
+
730
+ const [ , count ] = ( await multi . exec ( ) ) as [
731
+ [ Error | null , any ] ,
732
+ [ Error | null , number ] ,
733
+ ] ;
734
+
735
+ return count [ 1 ] || 0 ;
736
+ }
692
737
}
0 commit comments