(
request: { method: string; url: string; signal?: AbortSignal },
callback: () => Promise<Response>,
{
attempt,
previousError,
}: {
attempt: number;
previousError?: unknown;
} = {
attempt: 0,
},
)
| 274 | // u=5 for background (e.g., upload, download) |
| 275 | // u=7 for optional (e.g., metrics, telemetry) |
| 276 | private async fetch( |
| 277 | request: { method: string; url: string; signal?: AbortSignal }, |
| 278 | callback: () => Promise<Response>, |
| 279 | { |
| 280 | attempt, |
| 281 | previousError, |
| 282 | }: { |
| 283 | attempt: number; |
| 284 | previousError?: unknown; |
| 285 | } = { |
| 286 | attempt: 0, |
| 287 | }, |
| 288 | ): Promise<Response> { |
| 289 | if (request.signal?.aborted) { |
| 290 | throw new AbortError(c('Error').t`Request aborted`); |
| 291 | } |
| 292 | |
| 293 | if (attempt > 0) { |
| 294 | this.logger.debug(`${request.method} ${request.url}: retry ${attempt}`); |
| 295 | } else { |
| 296 | this.logger.debug(`${request.method} ${request.url}`); |
| 297 | } |
| 298 | |
| 299 | if (this.hasReachedServerErrorLimit) { |
| 300 | this.logger.warn('Server errors limit reached'); |
| 301 | throw new ServerError(c('Error').t`Too many server errors, please try again later`); |
| 302 | } |
| 303 | if (this.hasReachedTooManyRequestsErrorLimit) { |
| 304 | this.logger.warn('Too many requests limit reached'); |
| 305 | throw new RateLimitedError(c('Error').t`Too many server requests, please try again later`); |
| 306 | } |
| 307 | |
| 308 | const start = Date.now(); |
| 309 | |
| 310 | let response; |
| 311 | try { |
| 312 | response = await callback(); |
| 313 | } catch (error: unknown) { |
| 314 | if (error instanceof Error) { |
| 315 | if (error.name === 'AbortError') { |
| 316 | this.logger.debug(`${request.method} ${request.url}: Aborted`); |
| 317 | throw new AbortError(c('Error').t`Request aborted`); |
| 318 | } |
| 319 | |
| 320 | if (error.name === 'OfflineError') { |
| 321 | this.offlineErrorHappened(); |
| 322 | this.logger.info(`${request.method} ${request.url}: Offline error, retrying`); |
| 323 | await waitSeconds(OFFLINE_RETRY_DELAY_SECONDS); |
| 324 | return this.fetch(request, callback, { attempt: attempt + 1, previousError: error }); |
| 325 | } |
| 326 | |
| 327 | if (error.name === 'TimeoutError' && attempt + 1 < MAX_TIMEOUT_ERROR_RETRY_ATTEMPTS) { |
| 328 | this.logger.warn(`${request.method} ${request.url}: Timeout error, retrying`); |
| 329 | await waitSeconds(SERVER_ERROR_RETRY_DELAY_SECONDS); |
| 330 | return this.fetch(request, callback, { attempt: attempt + 1, previousError: error }); |
| 331 | } |
| 332 | |
| 333 | if (isNetworkError(error) && attempt + 1 < MAX_NETWORK_ERROR_RETRY_ATTEMPTS) { |
no test coverage detected