diff --git a/lib/utils/fetch-utils.js b/lib/utils/fetch-utils.js new file mode 100644 --- /dev/null +++ b/lib/utils/fetch-utils.js @@ -0,0 +1,56 @@ +// @flow + +type RequestWithTimeoutOptions = { + ...$Exact, + +timeout?: number, // ms +}; + +async function fetchWithTimeout( + url: string, + options?: RequestWithTimeoutOptions, +): Promise { + const { timeout, signal: externalSignal, ...requestOptions } = options || {}; + + const abortController = new AbortController(); + + // Handle situation when callee has abort signal already set + let externalAbort = false; + const externalAbortEvent = () => { + externalAbort = true; + abortController.abort(); + }; + if (externalSignal) { + if (externalSignal.aborted) { + externalAbortEvent(); + } else { + externalSignal.addEventListener('abort', externalAbortEvent); + } + } + + let timeoutHandle; + if (timeout) { + timeoutHandle = setTimeout(() => { + abortController.abort(); + }, timeout); + } + + try { + return await fetch(url, { + ...requestOptions, + signal: abortController.signal, + }); + } catch (err) { + if (abortController.signal.aborted && !externalAbort) { + throw new Error('Request timed out'); + } else { + throw err; + } + } finally { + clearTimeout(timeoutHandle); + if (externalSignal) { + externalSignal.removeEventListener('abort', externalAbortEvent); + } + } +} + +export { fetchWithTimeout }; diff --git a/lib/utils/reports-service.js b/lib/utils/reports-service.js --- a/lib/utils/reports-service.js +++ b/lib/utils/reports-service.js @@ -1,5 +1,6 @@ // @flow +import { fetchWithTimeout } from './fetch-utils.js'; import { reportsServiceURL, sendReportEndpoint, @@ -9,6 +10,8 @@ ReportsServiceSendReportsResponse, } from '../types/report-types.js'; +const REQUEST_TIMEOUT = 60 * 1000; // 60s + async function sendReports( reports: ReportsServiceSendReportsRequest, ): Promise { @@ -23,12 +26,13 @@ return report; }); const url = `${reportsServiceURL}${sendReportEndpoint.path}`; - const response = await fetch(url, { + const response = await fetchWithTimeout(url, { method: sendReportEndpoint.method, body: JSON.stringify(requestBody), headers: { 'Content-Type': 'application/json', }, + timeout: REQUEST_TIMEOUT, }); if (!response.ok) {