From 3f5a7cb2bc1430188337d06cec12543321273423 Mon Sep 17 00:00:00 2001 From: Miles Tjandrawidjaja Date: Fri, 11 Oct 2024 17:28:49 -0400 Subject: [PATCH 01/10] refactor(web-api): move transformRequest logic to Axios request interceptor --- packages/web-api/src/WebClient.ts | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/web-api/src/WebClient.ts b/packages/web-api/src/WebClient.ts index 7728f59c7..041d4021b 100644 --- a/packages/web-api/src/WebClient.ts +++ b/packages/web-api/src/WebClient.ts @@ -1,12 +1,16 @@ import type { Agent } from 'node:http'; import { basename } from 'node:path'; import { stringify as qsStringify } from 'node:querystring'; -import type { Readable } from 'node:stream'; import type { SecureContextOptions } from 'node:tls'; import { TextDecoder } from 'node:util'; import zlib from 'node:zlib'; -import axios, { type AxiosHeaderValue, type AxiosInstance, type AxiosResponse } from 'axios'; +import axios, { + type InternalAxiosRequestConfig, + type AxiosHeaderValue, + type AxiosInstance, + type AxiosResponse, +} from 'axios'; import FormData from 'form-data'; import isElectron from 'is-electron'; import isStream from 'is-stream'; @@ -245,7 +249,6 @@ export class WebClient extends Methods { headers: isElectron() ? headers : { 'User-Agent': getUserAgent(), ...headers }, httpAgent: agent, httpsAgent: agent, - transformRequest: [this.serializeApiCallOptions.bind(this)], validateStatus: () => true, // all HTTP status codes should result in a resolved promise (as opposed to only 2xx) maxRedirects: 0, // disabling axios' automatic proxy support: @@ -256,6 +259,7 @@ export class WebClient extends Methods { }); // serializeApiCallOptions will always determine the appropriate content-type this.axios.defaults.headers.post['Content-Type'] = undefined; + this.axios.interceptors.request.use(this.serializeApiCallOptions.bind(this), null, { synchronous: true }); this.logger.debug('initialized'); } @@ -667,13 +671,11 @@ export class WebClient extends Methods { * a string, used when posting with a content-type of url-encoded. Or, it can be a readable stream, used * when the options contain a binary (a stream or a buffer) and the upload should be done with content-type * multipart/form-data. - * @param options - arguments for the Web API method - * @param headers - a mutable object representing the HTTP headers for the outgoing request + * @param config - The Axios request configuration object */ - private serializeApiCallOptions( - options: Record, - headers?: Record, - ): string | Readable { + private serializeApiCallOptions(config: InternalAxiosRequestConfig): InternalAxiosRequestConfig { + const { data: options, headers } = config; + // The following operation both flattens complex objects into a JSON-encoded strings and searches the values for // binary content let containsBinaryData = false; @@ -730,14 +732,16 @@ export class WebClient extends Methods { headers[header] = value; } } - return form; + config.data = form; + config.headers = headers; + return config; } // Otherwise, a simple key-value object is returned if (headers) headers['Content-Type'] = 'application/x-www-form-urlencoded'; // biome-ignore lint/suspicious/noExplicitAny: form values can be anything const initialValue: { [key: string]: any } = {}; - return qsStringify( + config.data = qsStringify( flattened.reduce((accumulator, [key, value]) => { if (key !== undefined && value !== undefined) { accumulator[key] = value; @@ -745,6 +749,8 @@ export class WebClient extends Methods { return accumulator; }, initialValue), ); + config.headers = headers; + return config; } /** From 1d49ad6809ad0d08599d1c8baea564f6d4828f34 Mon Sep 17 00:00:00 2001 From: Miles Tjandrawidjaja Date: Fri, 11 Oct 2024 17:36:15 -0400 Subject: [PATCH 02/10] feat(web-api): WebClient can be configured with a request interceptor --- packages/web-api/src/WebClient.spec.ts | 51 +++++++++++++++++++++++++- packages/web-api/src/WebClient.ts | 12 ++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/packages/web-api/src/WebClient.spec.ts b/packages/web-api/src/WebClient.spec.ts index 03a4165c1..0d8ff05dd 100644 --- a/packages/web-api/src/WebClient.spec.ts +++ b/packages/web-api/src/WebClient.spec.ts @@ -2,7 +2,13 @@ import fs from 'node:fs'; import { assert, expect } from 'chai'; import nock from 'nock'; import sinon from 'sinon'; -import { type WebAPICallResult, WebClient, WebClientEvent, buildThreadTsWarningMessage } from './WebClient'; +import { + type RequestConfig, + type WebAPICallResult, + WebClient, + WebClientEvent, + buildThreadTsWarningMessage, +} from './WebClient'; import { ErrorCode, type WebAPIRequestError } from './errors'; import { buildGeneralFilesUploadWarning, @@ -964,6 +970,49 @@ describe('WebClient', () => { }); }); + describe('requestInterceptor', () => { + it('can intercept out going requests', async () => { + let expectedBody: Record; + + const client = new WebClient(token, { + requestInterceptor: (config: RequestConfig) => { + expectedBody = Object.freeze({ + method: config.method, + base_url: config.baseURL, + path: config.url, + body: config.data ?? {}, + query: config.params ?? {}, + headers: structuredClone(config.headers), + test: 'static-body-value', + }); + config.data = expectedBody; + + config.headers.test = 'static-header-value'; + config.headers['Content-Type'] = 'application/json'; + + return config; + }, + }); + + nock('https://slack.com/api', { + reqheaders: { + test: 'static-header-value', + 'Content-Type': 'application/json', + }, + }) + .post(/method/, (requestBody) => { + expect(requestBody).to.deep.equal(expectedBody); + return true; + }) + .reply(200, (_uri, requestBody) => { + expect(requestBody).to.deep.equal(expectedBody); + return { ok: true, response_metadata: requestBody }; + }); + + await client.apiCall('method'); + }); + }); + it('should throw an error if the response has no retry info', async () => { // @ts-expect-error header values cannot be undefined const scope = nock('https://slack.com').post(/api/).reply(429, {}, { 'retry-after': undefined }); diff --git a/packages/web-api/src/WebClient.ts b/packages/web-api/src/WebClient.ts index 041d4021b..d9b89839f 100644 --- a/packages/web-api/src/WebClient.ts +++ b/packages/web-api/src/WebClient.ts @@ -94,6 +94,7 @@ export interface WebClientOptions { * @default true */ attachOriginalToWebAPIRequestError?: boolean; + requestInterceptor?: RequestInterceptor; } export type TLSOptions = Pick; @@ -134,6 +135,10 @@ export type PageAccumulator = R extends ( ? A : never; +export type RequestConfig = InternalAxiosRequestConfig; + +export type RequestInterceptor = (config: RequestConfig) => RequestConfig | Promise; + /** * A client for Slack's Web API * @@ -216,6 +221,7 @@ export class WebClient extends Methods { headers = {}, teamId = undefined, attachOriginalToWebAPIRequestError = true, + requestInterceptor = undefined, }: WebClientOptions = {}, ) { super(); @@ -259,6 +265,12 @@ export class WebClient extends Methods { }); // serializeApiCallOptions will always determine the appropriate content-type this.axios.defaults.headers.post['Content-Type'] = undefined; + + // request interceptors have reversed execution order + // see: https://github.com/axios/axios/blob/v1.x/test/specs/interceptors.spec.js#L88 + if (requestInterceptor) { + this.axios.interceptors.request.use(requestInterceptor, null, { synchronous: true }); + } this.axios.interceptors.request.use(this.serializeApiCallOptions.bind(this), null, { synchronous: true }); this.logger.debug('initialized'); From 5e27c82e244c01d2981a1add39dc53b501f02478 Mon Sep 17 00:00:00 2001 From: Miles Tjandrawidjaja Date: Fri, 11 Oct 2024 18:01:33 -0400 Subject: [PATCH 03/10] feat(web-api): WebClient can be configured with an adapter --- packages/web-api/src/WebClient.spec.ts | 28 ++++++++++++++++++++++++++ packages/web-api/src/WebClient.ts | 5 +++++ 2 files changed, 33 insertions(+) diff --git a/packages/web-api/src/WebClient.spec.ts b/packages/web-api/src/WebClient.spec.ts index 0d8ff05dd..ca5d6e5d8 100644 --- a/packages/web-api/src/WebClient.spec.ts +++ b/packages/web-api/src/WebClient.spec.ts @@ -1,4 +1,5 @@ import fs from 'node:fs'; +import axios from 'axios'; import { assert, expect } from 'chai'; import nock from 'nock'; import sinon from 'sinon'; @@ -1013,6 +1014,33 @@ describe('WebClient', () => { }); }); + describe('adapter', () => { + it('allows for custom handling of requests', async () => { + nock('https://slack.com/api', { + reqheaders: { + 'User-Agent': 'custom-axios-client', + }, + }) + .post(/method/) + .reply(200, (_uri, requestBody) => { + return { ok: true, response_metadata: requestBody }; + }); + + const customAxiosClient = axios.create(); + const requestSpy = sinon.spy(customAxiosClient, 'request'); + + const client = new WebClient(token, { + adapter: (config: RequestConfig) => { + config.headers['User-Agent'] = 'custom-axios-client'; + return customAxiosClient.request(config); + }, + }); + + await client.apiCall('method'); + expect(requestSpy.calledOnce).to.be.true; + }); + }); + it('should throw an error if the response has no retry info', async () => { // @ts-expect-error header values cannot be undefined const scope = nock('https://slack.com').post(/api/).reply(429, {}, { 'retry-after': undefined }); diff --git a/packages/web-api/src/WebClient.ts b/packages/web-api/src/WebClient.ts index d9b89839f..ca7a6dd01 100644 --- a/packages/web-api/src/WebClient.ts +++ b/packages/web-api/src/WebClient.ts @@ -95,6 +95,7 @@ export interface WebClientOptions { */ attachOriginalToWebAPIRequestError?: boolean; requestInterceptor?: RequestInterceptor; + adapter?: AdapterConfig; } export type TLSOptions = Pick; @@ -139,6 +140,8 @@ export type RequestConfig = InternalAxiosRequestConfig; export type RequestInterceptor = (config: RequestConfig) => RequestConfig | Promise; +export type AdapterConfig = (config: InternalAxiosRequestConfig) => Promise; + /** * A client for Slack's Web API * @@ -222,6 +225,7 @@ export class WebClient extends Methods { teamId = undefined, attachOriginalToWebAPIRequestError = true, requestInterceptor = undefined, + adapter = undefined, }: WebClientOptions = {}, ) { super(); @@ -250,6 +254,7 @@ export class WebClient extends Methods { if (this.token && !headers.Authorization) headers.Authorization = `Bearer ${this.token}`; this.axios = axios.create({ + adapter: adapter ? (config: InternalAxiosRequestConfig) => adapter({ ...config, adapter: undefined }) : undefined, timeout, baseURL: slackApiUrl, headers: isElectron() ? headers : { 'User-Agent': getUserAgent(), ...headers }, From dc3bbd2f72896d1776a9d49a1078ca096a94f19c Mon Sep 17 00:00:00 2001 From: Miles Tjandrawidjaja Date: Wed, 16 Oct 2024 09:51:01 -0400 Subject: [PATCH 04/10] chore(web-api): add JSDocs for new WebClientOptions --- packages/web-api/src/WebClient.ts | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/web-api/src/WebClient.ts b/packages/web-api/src/WebClient.ts index ca7a6dd01..42b89bbd4 100644 --- a/packages/web-api/src/WebClient.ts +++ b/packages/web-api/src/WebClient.ts @@ -10,6 +10,7 @@ import axios, { type AxiosHeaderValue, type AxiosInstance, type AxiosResponse, + type AxiosAdapter, } from 'axios'; import FormData from 'form-data'; import isElectron from 'is-electron'; @@ -94,7 +95,18 @@ export interface WebClientOptions { * @default true */ attachOriginalToWebAPIRequestError?: boolean; + /** + * Custom function to modify outgoing requests. + * @type {Function | undefined} + * @default undefined + */ requestInterceptor?: RequestInterceptor; + /** + * Custom functions for modifing and handling outgoing requests. + * Useful if you would like to manage outgoing request with a custom http client. + * @type {Function | undefined} + * @default undefined + */ adapter?: AdapterConfig; } @@ -136,11 +148,23 @@ export type PageAccumulator = R extends ( ? A : never; +/** + * An alias to {@link https://github.com/axios/axios/blob/v1.x/index.d.ts#L367 Axios' `InternalAxiosRequestConfig`} object, + * which is the main parameter type provided to Axios interceptors and adapters. + */ export type RequestConfig = InternalAxiosRequestConfig; +/** + * An alias to {@link https://github.com/axios/axios/blob/v1.x/index.d.ts#L489 Axios' `AxiosInterceptorManager` onFufilled} method, + * which controls the custom request interceptor logic + */ export type RequestInterceptor = (config: RequestConfig) => RequestConfig | Promise; -export type AdapterConfig = (config: InternalAxiosRequestConfig) => Promise; +/** + * An alias to {@link https://github.com/axios/axios/blob/v1.x/index.d.ts#L112 Axios' `AxiosAdapter`} interface, + * which is the contract required to specify an adapter + */ +export type AdapterConfig = AxiosAdapter; /** * A client for Slack's Web API @@ -208,6 +232,9 @@ export class WebClient extends Methods { /** * @param token - An API token to authenticate/authorize with Slack (usually start with `xoxp`, `xoxb`) + * @param {Object} [webClientOptions] - Configuration options. + * @param {Function} [webClientOptions.requestInterceptor] - An interceptor to mutate outgoing requests. See {@link https://axios-http.com/docs/interceptors Axios interceptors} + * @param {Function} [webClientOptions.adapter] - An adapter to allow custom handling of requests. Useful if you would like to use a pre-configured http client. See {@link https://github.com/axios/axios/blob/v1.x/README.md?plain=1#L586 Axios adapter} */ public constructor( token?: string, From 9cfa19648362324a74f5a15326ec071b4d414703 Mon Sep 17 00:00:00 2001 From: Miles Tjandrawidjaja Date: Wed, 16 Oct 2024 09:53:36 -0400 Subject: [PATCH 05/10] refactor(web-api): rename serializeApiCallOptions -> serializeApiCallData and remove data alias --- packages/web-api/src/WebClient.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/web-api/src/WebClient.ts b/packages/web-api/src/WebClient.ts index 42b89bbd4..2e8754a52 100644 --- a/packages/web-api/src/WebClient.ts +++ b/packages/web-api/src/WebClient.ts @@ -295,7 +295,7 @@ export class WebClient extends Methods { // protocols), users of this package should use the `agent` option to configure a proxy. proxy: false, }); - // serializeApiCallOptions will always determine the appropriate content-type + // serializeApiCallData will always determine the appropriate content-type this.axios.defaults.headers.post['Content-Type'] = undefined; // request interceptors have reversed execution order @@ -303,7 +303,7 @@ export class WebClient extends Methods { if (requestInterceptor) { this.axios.interceptors.request.use(requestInterceptor, null, { synchronous: true }); } - this.axios.interceptors.request.use(this.serializeApiCallOptions.bind(this), null, { synchronous: true }); + this.axios.interceptors.request.use(this.serializeApiCallData.bind(this), null, { synchronous: true }); this.logger.debug('initialized'); } @@ -717,14 +717,14 @@ export class WebClient extends Methods { * multipart/form-data. * @param config - The Axios request configuration object */ - private serializeApiCallOptions(config: InternalAxiosRequestConfig): InternalAxiosRequestConfig { - const { data: options, headers } = config; + private serializeApiCallData(config: InternalAxiosRequestConfig): InternalAxiosRequestConfig { + const { data, headers } = config; // The following operation both flattens complex objects into a JSON-encoded strings and searches the values for // binary content let containsBinaryData = false; // biome-ignore lint/suspicious/noExplicitAny: call options can be anything - const flattened = Object.entries(options).map<[string, any] | []>(([key, value]) => { + const flattened = Object.entries(data).map<[string, any] | []>(([key, value]) => { if (value === undefined || value === null) { return []; } From b0ab5cfa61343cd4681c7c37465518550ffd01dc Mon Sep 17 00:00:00 2001 From: Miles Tjandrawidjaja Date: Wed, 16 Oct 2024 10:06:55 -0400 Subject: [PATCH 06/10] fix(web-api): ensure support for async requestInterceptor as defined by interface --- packages/web-api/src/WebClient.spec.ts | 62 ++++++++++++++++++++------ packages/web-api/src/WebClient.ts | 2 +- 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/packages/web-api/src/WebClient.spec.ts b/packages/web-api/src/WebClient.spec.ts index ca5d6e5d8..b394b3704 100644 --- a/packages/web-api/src/WebClient.spec.ts +++ b/packages/web-api/src/WebClient.spec.ts @@ -972,7 +972,24 @@ describe('WebClient', () => { }); describe('requestInterceptor', () => { - it('can intercept out going requests', async () => { + function configureMockServer(expectedBody: () => Record) { + nock('https://slack.com/api', { + reqheaders: { + test: 'static-header-value', + 'Content-Type': 'application/json', + }, + }) + .post(/method/, (requestBody) => { + expect(requestBody).to.deep.equal(expectedBody()); + return true; + }) + .reply(200, (_uri, requestBody) => { + expect(requestBody).to.deep.equal(expectedBody()); + return { ok: true, response_metadata: requestBody }; + }); + } + + it('can intercept out going requests, synchronously modifying the request body and headers', async () => { let expectedBody: Record; const client = new WebClient(token, { @@ -995,20 +1012,37 @@ describe('WebClient', () => { }, }); - nock('https://slack.com/api', { - reqheaders: { - test: 'static-header-value', - 'Content-Type': 'application/json', + configureMockServer(() => expectedBody); + + await client.apiCall('method'); + }); + + it('can intercept out going requests, asynchronously modifying the request body and headers', async () => { + let expectedBody: Record; + + const client = new WebClient(token, { + requestInterceptor: async (config: RequestConfig) => { + expectedBody = Object.freeze({ + method: config.method, + base_url: config.baseURL, + path: config.url, + body: config.data ?? {}, + query: config.params ?? {}, + headers: structuredClone(config.headers), + test: 'static-body-value', + }); + + await new Promise((resolve) => setTimeout(resolve, 100)); + config.data = expectedBody; + + config.headers.test = 'static-header-value'; + config.headers['Content-Type'] = 'application/json'; + + return config; }, - }) - .post(/method/, (requestBody) => { - expect(requestBody).to.deep.equal(expectedBody); - return true; - }) - .reply(200, (_uri, requestBody) => { - expect(requestBody).to.deep.equal(expectedBody); - return { ok: true, response_metadata: requestBody }; - }); + }); + + configureMockServer(() => expectedBody); await client.apiCall('method'); }); diff --git a/packages/web-api/src/WebClient.ts b/packages/web-api/src/WebClient.ts index 2e8754a52..426fac9a2 100644 --- a/packages/web-api/src/WebClient.ts +++ b/packages/web-api/src/WebClient.ts @@ -303,7 +303,7 @@ export class WebClient extends Methods { if (requestInterceptor) { this.axios.interceptors.request.use(requestInterceptor, null, { synchronous: true }); } - this.axios.interceptors.request.use(this.serializeApiCallData.bind(this), null, { synchronous: true }); + this.axios.interceptors.request.use(this.serializeApiCallData.bind(this), null); this.logger.debug('initialized'); } From e587a2ab7658f431ff0407123ff890d8c9e6b6f1 Mon Sep 17 00:00:00 2001 From: Miles Tjandrawidjaja Date: Wed, 16 Oct 2024 10:20:56 -0400 Subject: [PATCH 07/10] test(web-api): update custom adapter test to better reflect real use case --- packages/web-api/src/WebClient.spec.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/web-api/src/WebClient.spec.ts b/packages/web-api/src/WebClient.spec.ts index b394b3704..cfa84b729 100644 --- a/packages/web-api/src/WebClient.spec.ts +++ b/packages/web-api/src/WebClient.spec.ts @@ -1,5 +1,5 @@ import fs from 'node:fs'; -import axios from 'axios'; +import axios, { type InternalAxiosRequestConfig } from 'axios'; import { assert, expect } from 'chai'; import nock from 'nock'; import sinon from 'sinon'; @@ -1049,7 +1049,7 @@ describe('WebClient', () => { }); describe('adapter', () => { - it('allows for custom handling of requests', async () => { + it('allows for custom handling of requests with preconfigured http client', async () => { nock('https://slack.com/api', { reqheaders: { 'User-Agent': 'custom-axios-client', @@ -1060,8 +1060,16 @@ describe('WebClient', () => { return { ok: true, response_metadata: requestBody }; }); + const customLoggingInterceptor = (config: InternalAxiosRequestConfig) => { + // client with custom logging behaviour + return config; + }; + const customLoggingSpy = sinon.spy(customLoggingInterceptor); + const customAxiosClient = axios.create(); - const requestSpy = sinon.spy(customAxiosClient, 'request'); + customAxiosClient.interceptors.request.use(customLoggingSpy); + + const customClientRequestSpy = sinon.spy(customAxiosClient, 'request'); const client = new WebClient(token, { adapter: (config: RequestConfig) => { @@ -1071,7 +1079,9 @@ describe('WebClient', () => { }); await client.apiCall('method'); - expect(requestSpy.calledOnce).to.be.true; + + expect(customLoggingSpy.calledOnce).to.be.true; + expect(customClientRequestSpy.calledOnce).to.be.true; }); }); From c95a8780d5adac7afb3467cfd21cd28543661af4 Mon Sep 17 00:00:00 2001 From: Miles Tjandrawidjaja Date: Wed, 16 Oct 2024 10:49:52 -0400 Subject: [PATCH 08/10] docs(web-api): add documentation to requestInterceptor and adapter WebClient options --- docs/content/packages/web-api.md | 59 ++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/docs/content/packages/web-api.md b/docs/content/packages/web-api.md index 97961e1af..ae569f053 100644 --- a/docs/content/packages/web-api.md +++ b/docs/content/packages/web-api.md @@ -662,6 +662,65 @@ const web = new WebClient(token, { agent: proxy }); --- +### Modify outgoing requests with a request interceptor + +The client allows you to customize a request +[`interceptor`](https://axios-http.com/docs/interceptors) to modify outgoing requests. +Using this option allows you to modify outgoing requests to conform to the requirements of a proxy, which is a common requirement in many corporate settings. + +For example you may want to convert wrap all the original request information within a POST request. + +```javascript +const { WebClient } = require('@slack/web-api'); + +const token = process.env.SLACK_TOKEN; + +const webClient = new WebClient(token, { + requestInterceptor: (config: RequestConfig) => { + config.headers['Content-Type'] = 'application/json'; + + config.data = { + method: config.method, + base_url: config.baseURL, + path: config.url, + body: config.data ?? {}, + query: config.params ?? {}, + headers: structuredClone(config.headers), + test: 'static-body-value', + }; + + return config; + } +}); +``` + +--- + +### Using a pre-configured http client to handle outgoing requests + +The client allows you to specify an +[`adapter`](https://github.com/axios/axios/blob/v1.x/README.md?plain=1#L586) to handle outgoing requests. +Using this option allows you to used a pre-configured http client, which is a common requirement in many corporate settings. + +For example you may want to use a http which is already configured with logging capabilities, desired timeouts, etc. + +```javascript +const { WebClient } = require('@slack/web-api'); +const { CustomHttpClient } = require('@company/http-client') + +const token = process.env.SLACK_TOKEN; + +const customClient = CustomHttpClient(); + +const webClient = new WebClient(token, { + adapter: (config: RequestConfig) => { + return customClient.request(config); + } +}); +``` + +--- + ### Rate limits When your app calls API methods too frequently, Slack will politely ask (by returning an error) the app to slow down, From 06fe3d19782bc0728dabadb0a0eba9b647e7d939 Mon Sep 17 00:00:00 2001 From: Miles Tjandrawidjaja Date: Wed, 16 Oct 2024 15:26:07 -0400 Subject: [PATCH 09/10] chore(web-api): address typos --- docs/content/packages/web-api.md | 8 ++++---- packages/web-api/src/WebClient.ts | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/content/packages/web-api.md b/docs/content/packages/web-api.md index ae569f053..4983b8f9d 100644 --- a/docs/content/packages/web-api.md +++ b/docs/content/packages/web-api.md @@ -668,7 +668,7 @@ The client allows you to customize a request [`interceptor`](https://axios-http.com/docs/interceptors) to modify outgoing requests. Using this option allows you to modify outgoing requests to conform to the requirements of a proxy, which is a common requirement in many corporate settings. -For example you may want to convert wrap all the original request information within a POST request. +For example you may want to wrap the original request information within a POST request: ```javascript const { WebClient } = require('@slack/web-api'); @@ -676,7 +676,7 @@ const { WebClient } = require('@slack/web-api'); const token = process.env.SLACK_TOKEN; const webClient = new WebClient(token, { - requestInterceptor: (config: RequestConfig) => { + requestInterceptor: (config) => { config.headers['Content-Type'] = 'application/json'; config.data = { @@ -700,9 +700,9 @@ const webClient = new WebClient(token, { The client allows you to specify an [`adapter`](https://github.com/axios/axios/blob/v1.x/README.md?plain=1#L586) to handle outgoing requests. -Using this option allows you to used a pre-configured http client, which is a common requirement in many corporate settings. +Using this option allows you to use a pre-configured http client, which is a common requirement in many corporate settings. -For example you may want to use a http which is already configured with logging capabilities, desired timeouts, etc. +For example you may want to use an HTTP client which is already configured with logging capabilities, desired timeouts, etc. ```javascript const { WebClient } = require('@slack/web-api'); diff --git a/packages/web-api/src/WebClient.ts b/packages/web-api/src/WebClient.ts index 426fac9a2..25a2e3f69 100644 --- a/packages/web-api/src/WebClient.ts +++ b/packages/web-api/src/WebClient.ts @@ -96,7 +96,7 @@ export interface WebClientOptions { */ attachOriginalToWebAPIRequestError?: boolean; /** - * Custom function to modify outgoing requests. + * Custom function to modify outgoing requests. See {@link https://axios-http.com/docs/interceptors Axios interceptor documentation} for more details. * @type {Function | undefined} * @default undefined */ @@ -104,6 +104,7 @@ export interface WebClientOptions { /** * Custom functions for modifing and handling outgoing requests. * Useful if you would like to manage outgoing request with a custom http client. + * See {@link https://github.com/axios/axios/blob/v1.x/README.md?plain=1#L586 Axios adapter documentation} for more information. * @type {Function | undefined} * @default undefined */ @@ -723,7 +724,7 @@ export class WebClient extends Methods { // The following operation both flattens complex objects into a JSON-encoded strings and searches the values for // binary content let containsBinaryData = false; - // biome-ignore lint/suspicious/noExplicitAny: call options can be anything + // biome-ignore lint/suspicious/noExplicitAny: HTTP request data can be anything const flattened = Object.entries(data).map<[string, any] | []>(([key, value]) => { if (value === undefined || value === null) { return []; From 5dabe0feab477eba0b847dd33a36455380400b51 Mon Sep 17 00:00:00 2001 From: Miles Tjandrawidjaja Date: Wed, 16 Oct 2024 15:34:23 -0400 Subject: [PATCH 10/10] test(web-api): remove unneeded timeout --- packages/web-api/src/WebClient.spec.ts | 1 - packages/web-api/src/WebClient.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/web-api/src/WebClient.spec.ts b/packages/web-api/src/WebClient.spec.ts index cfa84b729..a65c4e8d9 100644 --- a/packages/web-api/src/WebClient.spec.ts +++ b/packages/web-api/src/WebClient.spec.ts @@ -1032,7 +1032,6 @@ describe('WebClient', () => { test: 'static-body-value', }); - await new Promise((resolve) => setTimeout(resolve, 100)); config.data = expectedBody; config.headers.test = 'static-header-value'; diff --git a/packages/web-api/src/WebClient.ts b/packages/web-api/src/WebClient.ts index 25a2e3f69..8389a6361 100644 --- a/packages/web-api/src/WebClient.ts +++ b/packages/web-api/src/WebClient.ts @@ -302,7 +302,7 @@ export class WebClient extends Methods { // request interceptors have reversed execution order // see: https://github.com/axios/axios/blob/v1.x/test/specs/interceptors.spec.js#L88 if (requestInterceptor) { - this.axios.interceptors.request.use(requestInterceptor, null, { synchronous: true }); + this.axios.interceptors.request.use(requestInterceptor, null); } this.axios.interceptors.request.use(this.serializeApiCallData.bind(this), null);