@@ -15,6 +15,7 @@ export interface StatusClientOptions {
1515 baseURL : string ;
1616 fetchImpl ?: typeof fetch ;
1717 headers ?: Record < string , string > ;
18+ timeoutMs ?: number ;
1819}
1920
2021export class StatusApiError extends Error {
@@ -71,45 +72,53 @@ export class KeeperStatusClient {
7172 readonly baseURL : string ;
7273 readonly fetchImpl : typeof fetch ;
7374 readonly headers : Record < string , string > ;
75+ readonly timeoutMs : number ;
7476
7577 constructor ( opts : StatusClientOptions ) {
7678 this . baseURL = opts . baseURL ;
7779 this . fetchImpl = opts . fetchImpl ?? globalThis . fetch ;
7880 this . headers = opts . headers ?? { } ;
81+ this . timeoutMs = opts . timeoutMs ?? 10_000 ;
7982 if ( ! this . fetchImpl ) {
8083 throw new Error (
8184 "No global fetch found. Pass `fetchImpl` in StatusClientOptions." ,
8285 ) ;
8386 }
8487 }
8588
86- async getStatus ( ) : Promise < KeeperStatusResponse > {
87- return this . getJSON < KeeperStatusResponse > ( "/status" ) ;
89+ async getStatus ( signal ?: AbortSignal ) : Promise < KeeperStatusResponse > {
90+ return this . getJSON < KeeperStatusResponse > ( "/status" , signal ) ;
8891 }
8992
90- async getRound ( roundId : number | bigint | string ) : Promise < KeeperRoundStatusView > {
91- return this . getJSON < KeeperRoundStatusView > ( `/status/rounds/${ roundId } ` ) ;
93+ async getRound ( roundId : number | bigint | string , signal ?: AbortSignal ) : Promise < KeeperRoundStatusView > {
94+ return this . getJSON < KeeperRoundStatusView > ( `/status/rounds/${ roundId } ` , signal ) ;
9295 }
9396
94- async getHealth ( ) : Promise < KeeperHealthResponse > {
95- return this . getJSON < KeeperHealthResponse > ( "/status/health" ) ;
97+ async getHealth ( signal ?: AbortSignal ) : Promise < KeeperHealthResponse > {
98+ return this . getJSON < KeeperHealthResponse > ( "/status/health" , signal ) ;
9699 }
97100
98- async healthz ( ) : Promise < { ok : boolean ; [ k : string ] : unknown } > {
99- return this . getJSON < { ok : boolean ; [ k : string ] : unknown } > ( "/healthz" ) ;
101+ async healthz ( signal ?: AbortSignal ) : Promise < { ok : boolean ; [ k : string ] : unknown } > {
102+ return this . getJSON < { ok : boolean ; [ k : string ] : unknown } > ( "/healthz" , signal ) ;
100103 }
101104
102- async getJSON < T > ( path : string ) : Promise < T > {
105+ async getJSON < T > ( path : string , callerSignal ?: AbortSignal ) : Promise < T > {
103106 const url = fullURL ( this . baseURL , path ) ;
104- const res = await this . fetchImpl ( url , {
105- method : "GET" ,
106- headers : { Accept : "application/json" , ...this . headers } ,
107- } ) ;
108- if ( ! res . ok ) {
109- const body = await parseErrorBody ( res ) ;
110- throw new StatusApiError ( res . status , body ) ;
107+ const controller = new AbortController ( ) ;
108+ const timer = setTimeout ( ( ) => controller . abort ( new Error ( "keeper status request timed out" ) ) , this . timeoutMs ) ;
109+ const forwardAbort = ( ) => controller . abort ( callerSignal ?. reason ) ;
110+ callerSignal ?. addEventListener ( "abort" , forwardAbort , { once : true } ) ;
111+ try {
112+ const res = await this . fetchImpl ( url , { method : "GET" , headers : { Accept : "application/json" , ...this . headers } , signal : controller . signal } ) ;
113+ if ( ! res . ok ) {
114+ const body = await parseErrorBody ( res ) ;
115+ throw new StatusApiError ( res . status , body ) ;
116+ }
117+ return parseSuccessBody < T > ( res ) ;
118+ } finally {
119+ clearTimeout ( timer ) ;
120+ callerSignal ?. removeEventListener ( "abort" , forwardAbort ) ;
111121 }
112- return parseSuccessBody < T > ( res ) ;
113122 }
114123}
115124
0 commit comments