* Low-level function to make a single API request. handles queuing, retries, and http-level errors
(url: string, body: any, headers: any = {})
| 566 | * Low-level function to make a single API request. handles queuing, retries, and http-level errors |
| 567 | */ |
| 568 | private async makeRequest(url: string, body: any, headers: any = {}): Promise<AxiosResponse> { |
| 569 | // TODO: better input types - remove any |
| 570 | const task = () => this.requestQueue.add(async () => { |
| 571 | this.logger.debug('will perform http request'); |
| 572 | try { |
| 573 | const response = await this.axios.post(url, body, Object.assign( |
| 574 | { |
| 575 | headers, |
| 576 | }, |
| 577 | this.tlsConfig, |
| 578 | )); |
| 579 | this.logger.debug('http response received'); |
| 580 | |
| 581 | if (response.status === 429) { |
| 582 | const retrySec = parseRetryHeaders(response); |
| 583 | if (retrySec !== undefined) { |
| 584 | this.emit(WebClientEvent.RATE_LIMITED, retrySec); |
| 585 | if (this.rejectRateLimitedCalls) { |
| 586 | throw new AbortError(rateLimitedErrorWithDelay(retrySec)); |
| 587 | } |
| 588 | this.logger.info(`API Call failed due to rate limiting. Will retry in ${retrySec} seconds.`); |
| 589 | // pause the request queue and then delay the rejection by the amount of time in the retry header |
| 590 | this.requestQueue.pause(); |
| 591 | // NOTE: if there was a way to introspect the current RetryOperation and know what the next timeout |
| 592 | // would be, then we could subtract that time from the following delay, knowing that it the next |
| 593 | // attempt still wouldn't occur until after the rate-limit header has specified. an even better |
| 594 | // solution would be to subtract the time from only the timeout of this next attempt of the |
| 595 | // RetryOperation. this would result in the staying paused for the entire duration specified in the |
| 596 | // header, yet this operation not having to pay the timeout cost in addition to that. |
| 597 | await delay(retrySec * 1000); |
| 598 | // resume the request queue and throw a non-abort error to signal a retry |
| 599 | this.requestQueue.start(); |
| 600 | throw Error('A rate limit was exceeded.'); |
| 601 | } else { |
| 602 | // TODO: turn this into some CodedError |
| 603 | throw new AbortError(new Error('Retry header did not contain a valid timeout.')); |
| 604 | } |
| 605 | } |
| 606 | |
| 607 | // Slack's Web API doesn't use meaningful status codes besides 429 and 200 |
| 608 | if (response.status !== 200) { |
| 609 | throw httpErrorFromResponse(response); |
| 610 | } |
| 611 | |
| 612 | return response; |
| 613 | } catch (error) { |
| 614 | this.logger.warn('http request failed', error.message); |
| 615 | if (error.request) { |
| 616 | throw requestErrorWithOriginal(error); |
| 617 | } |
| 618 | throw error; |
| 619 | } |
| 620 | }); |
| 621 | |
| 622 | return pRetry(task, this.retryConfig); |
| 623 | } |
| 624 | |
| 625 | /** |