@@ -20,6 +20,12 @@ import type {
2020const CALCOM_API_URL = process . env . CALCOM_API_URL ?? "https://api.cal.com" ;
2121const API_VERSION = "2024-08-13" ;
2222
23+ const FETCH_TIMEOUT_MS = 10_000 ;
24+ const MAX_RETRIES = 2 ;
25+ const RETRY_BASE_MS = 500 ;
26+ const RETRY_MULTIPLIER = 3 ;
27+ const RETRYABLE_STATUS_CODES = new Set ( [ 500 , 502 , 503 , 504 ] ) ;
28+
2329export class CalcomApiError extends Error {
2430 constructor (
2531 message : string ,
@@ -31,22 +37,62 @@ export class CalcomApiError extends Error {
3137 }
3238}
3339
40+ async function fetchWithRetry (
41+ url : string ,
42+ init : RequestInit = { } ,
43+ maxRetries : number = MAX_RETRIES
44+ ) : Promise < Response > {
45+ let lastError : Error | undefined ;
46+
47+ for ( let attempt = 0 ; attempt <= maxRetries ; attempt ++ ) {
48+ try {
49+ const res = await fetch ( url , {
50+ ...init ,
51+ signal : AbortSignal . timeout ( FETCH_TIMEOUT_MS ) ,
52+ } ) ;
53+
54+ if ( RETRYABLE_STATUS_CODES . has ( res . status ) && attempt < maxRetries ) {
55+ await sleep ( RETRY_BASE_MS * RETRY_MULTIPLIER ** attempt ) ;
56+ continue ;
57+ }
58+
59+ return res ;
60+ } catch ( err ) {
61+ lastError = err instanceof Error ? err : new Error ( String ( err ) ) ;
62+ if ( attempt < maxRetries ) {
63+ await sleep ( RETRY_BASE_MS * RETRY_MULTIPLIER ** attempt ) ;
64+ }
65+ }
66+ }
67+
68+ throw new CalcomApiError (
69+ `Cal.com API request failed after ${ maxRetries + 1 } attempts: ${ lastError ?. message ?? "unknown error" } ` ,
70+ undefined ,
71+ "FETCH_RETRY_EXHAUSTED"
72+ ) ;
73+ }
74+
75+ function sleep ( ms : number ) : Promise < void > {
76+ return new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
77+ }
78+
3479async function calcomFetch < T > (
3580 path : string ,
3681 accessToken : string ,
3782 options : RequestInit = { } ,
38- apiVersion : string = API_VERSION
83+ apiVersion : string = API_VERSION ,
84+ retries : number = MAX_RETRIES
3985) : Promise < T > {
4086 const url = `${ CALCOM_API_URL } ${ path } ` ;
41- const res = await fetch ( url , {
87+ const res = await fetchWithRetry ( url , {
4288 ...options ,
4389 headers : {
4490 "cal-api-version" : apiVersion ,
4591 Authorization : `Bearer ${ accessToken } ` ,
4692 "Content-Type" : "application/json" ,
4793 ...options . headers ,
4894 } ,
49- } ) ;
95+ } , retries ) ;
5096
5197 if ( ! res . ok ) {
5298 let errorMessage = `Cal.com API error: ${ res . status } ${ res . statusText } ` ;
@@ -147,7 +193,7 @@ export async function getAvailableSlotsPublic(
147193 ...( params . bookingUidToReschedule ? { bookingUidToReschedule : params . bookingUidToReschedule } : { } ) ,
148194 } ) ;
149195 const url = `${ CALCOM_API_URL } /v2/slots?${ query } ` ;
150- const res = await fetch ( url , {
196+ const res = await fetchWithRetry ( url , {
151197 headers : {
152198 "cal-api-version" : "2024-09-04" ,
153199 "Content-Type" : "application/json" ,
@@ -232,21 +278,21 @@ export async function createBooking(
232278 return calcomFetch < CalcomBooking > ( "/v2/bookings" , accessToken , {
233279 method : "POST" ,
234280 body : JSON . stringify ( input ) ,
235- } ) ;
281+ } , API_VERSION , 0 ) ;
236282}
237283
238284export async function createBookingPublic (
239285 input : CreatePublicBookingInput
240286) : Promise < CalcomBooking > {
241287 const url = `${ CALCOM_API_URL } /v2/bookings` ;
242- const res = await fetch ( url , {
288+ const res = await fetchWithRetry ( url , {
243289 method : "POST" ,
244290 headers : {
245291 "cal-api-version" : "2024-08-13" ,
246292 "Content-Type" : "application/json" ,
247293 } ,
248294 body : JSON . stringify ( input ) ,
249- } ) ;
295+ } , 0 ) ;
250296 if ( ! res . ok ) {
251297 const body = await res . text ( ) ;
252298 let message = `Booking failed (${ res . status } )` ;
@@ -471,14 +517,14 @@ export async function addBookingAttendee(
471517 await calcomFetch < unknown > ( `/v2/bookings/${ bookingUid } /attendees` , accessToken , {
472518 method : "POST" ,
473519 body : JSON . stringify ( input ) ,
474- } ) ;
520+ } , API_VERSION , 0 ) ;
475521}
476522
477523// ─── Public event types (no auth) ─────────────────────────────────────────────
478524
479525export async function getEventTypesByUsername ( username : string ) : Promise < CalcomEventType [ ] > {
480526 const url = `${ CALCOM_API_URL } /v2/event-types?username=${ encodeURIComponent ( username ) } ` ;
481- const res = await fetch ( url , {
527+ const res = await fetchWithRetry ( url , {
482528 headers : {
483529 "cal-api-version" : "2024-06-14" ,
484530 "Content-Type" : "application/json" ,
0 commit comments