@@ -747,15 +747,27 @@ function wrapOnCallHandler<Req = any, Res = any>(
747
747
const abortController = new AbortController ( ) ;
748
748
let heartbeatInterval : NodeJS . Timeout | null = null ;
749
749
750
- const clearHeartbeatInterval = ( ) => {
750
+ const clearScheduledHeartbeat = ( ) => {
751
751
if ( heartbeatInterval ) {
752
- clearInterval ( heartbeatInterval ) ;
752
+ clearTimeout ( heartbeatInterval ) ;
753
753
heartbeatInterval = null ;
754
754
}
755
755
} ;
756
756
757
+ const scheduleHeartbeat = ( heartbeatSeconds : number ) => {
758
+ clearScheduledHeartbeat ( ) ; // Clear any existing timeout
759
+ if ( ! abortController . signal . aborted ) {
760
+ heartbeatInterval = setTimeout ( ( ) => {
761
+ if ( ! abortController . signal . aborted ) {
762
+ res . write ( ": ping\n" ) ;
763
+ scheduleHeartbeat ( heartbeatSeconds ) ;
764
+ }
765
+ } , heartbeatSeconds * 1000 ) ;
766
+ }
767
+ } ;
768
+
757
769
req . on ( "close" , ( ) => {
758
- clearHeartbeatInterval ( ) ;
770
+ clearScheduledHeartbeat ( ) ;
759
771
abortController . abort ( ) ;
760
772
} ) ;
761
773
@@ -828,6 +840,12 @@ function wrapOnCallHandler<Req = any, Res = any>(
828
840
...context ,
829
841
data,
830
842
} ;
843
+
844
+ const heartbeatSeconds =
845
+ options . heartbeatSeconds === undefined
846
+ ? DEFAULT_HEARTBEAT_SECONDS
847
+ : options . heartbeatSeconds ;
848
+
831
849
const responseProxy : CallableProxyResponse = {
832
850
write ( chunk ) : boolean {
833
851
// if client doesn't accept sse-protocol, response.write() is no-op.
@@ -839,23 +857,29 @@ function wrapOnCallHandler<Req = any, Res = any>(
839
857
return false ;
840
858
}
841
859
const formattedData = encodeSSE ( { message : chunk } ) ;
842
- return res . write ( formattedData ) ;
860
+ const wrote = res . write ( formattedData ) ;
861
+ //
862
+ // Reset heartbeat timer after successful write
863
+ if ( wrote && heartbeatInterval !== null && heartbeatSeconds > 0 ) {
864
+ scheduleHeartbeat ( heartbeatSeconds ) ;
865
+ }
866
+ return wrote ;
843
867
} ,
844
868
acceptsStreaming,
845
869
signal : abortController . signal ,
846
870
} ;
847
871
if ( acceptsStreaming ) {
848
872
// SSE always responds with 200
849
873
res . status ( 200 ) ;
850
- const heartbeatSeconds = options . heartbeatSeconds ?? DEFAULT_HEARTBEAT_SECONDS ;
874
+
851
875
if ( heartbeatSeconds !== null && heartbeatSeconds > 0 ) {
852
- heartbeatInterval = setInterval ( ( ) => res . write ( ": ping\n" ) , heartbeatSeconds * 1000 ) ;
876
+ scheduleHeartbeat ( heartbeatSeconds ) ;
853
877
}
854
878
}
855
879
// For some reason the type system isn't picking up that the handler
856
880
// is a one argument function.
857
881
result = await ( handler as any ) ( arg , responseProxy ) ;
858
- clearHeartbeatInterval ( ) ;
882
+ clearScheduledHeartbeat ( ) ;
859
883
}
860
884
861
885
if ( ! abortController . signal . aborted ) {
@@ -894,7 +918,7 @@ function wrapOnCallHandler<Req = any, Res = any>(
894
918
res . end ( ) ;
895
919
}
896
920
} finally {
897
- clearHeartbeatInterval ( ) ;
921
+ clearScheduledHeartbeat ( ) ;
898
922
}
899
923
} ;
900
924
}
0 commit comments