From 6e8a31ea92d3afe9b419e9bf727a790868484eec Mon Sep 17 00:00:00 2001 From: Fil Maj Date: Thu, 5 Oct 2023 08:56:45 -0400 Subject: [PATCH] @slack/web-api: prep for next major release. bump min node to v18 (#1667) --- packages/web-api/README.md | 19 +--- packages/web-api/package.json | 65 ++++++----- packages/web-api/src/WebClient.spec.js | 15 --- packages/web-api/src/WebClient.ts | 7 +- packages/web-api/src/errors.ts | 8 +- packages/web-api/src/methods.ts | 148 ++++++++++++++++--------- packages/web-api/src/retry-policies.ts | 4 +- 7 files changed, 145 insertions(+), 121 deletions(-) diff --git a/packages/web-api/README.md b/packages/web-api/README.md index cf31dc993..5c18d10a9 100644 --- a/packages/web-api/README.md +++ b/packages/web-api/README.md @@ -6,27 +6,16 @@ The `@slack/web-api` package contains a simple, convenient, and configurable HTT ## Requirements -This package supports Node v14 and higher. It's highly recommended to use [the latest LTS version of +This package supports Node v18 and higher. It's highly recommended to use [the latest LTS version of node](https://github.com/nodejs/Release#release-schedule), and the documentation is written using syntax and features from that version. -This package also has experimental support for Deno v1.15.2 and higher, though not all features are supported at this -time. - ## Installation -### Node.js - ```shell $ npm install @slack/web-api ``` -### Deno - -```typescript -import { WebClient } from 'https://deno.land/x/slack_web_api/mod.js'; -``` - ## Usage @@ -394,6 +383,6 @@ If you get stuck, we're here to help. The following are the best ways to get ass * [Issue Tracker](http://github.com/slackapi/node-slack-sdk/issues) for questions, feature requests, bug reports and general discussion related to these packages. Try searching before you create a new issue. - * [Email us](mailto:developers@slack.com) in Slack developer support: `developers@slack.com` - * [Bot Developers Hangout](https://community.botkit.ai/): a Slack community for developers - building all types of bots. You can find the maintainers and users of these packages in **#sdk-node-slack-sdk**. + * [Email us](mailto:feedback@slack.com): `feedback@slack.com` + * [Community Slack](https://community.slack.com/): a Slack community for developers + building all kinds of Slack apps. You can find the maintainers and users of these packages in **#lang-javascript**. diff --git a/packages/web-api/package.json b/packages/web-api/package.json index 4699b2409..fc92bf02c 100644 --- a/packages/web-api/package.json +++ b/packages/web-api/package.json @@ -21,8 +21,8 @@ "dist/**/*" ], "engines": { - "node": ">= 12.13.0", - "npm": ">= 6.12.0" + "node": ">= 18", + "npm": ">= 8.6.0" }, "repository": "slackapi/node-slack-sdk", "homepage": "https://slack.dev/node-slack-sdk/web-api", @@ -37,51 +37,48 @@ "build": "npm run build:clean && tsc", "build:clean": "shx rm -rf ./dist ./coverage ./.nyc_output", "lint": "eslint --ext .ts src", - "test": "npm run lint && npm run build && npm run test:mocha && npm run test:types", - "test:mocha": "nyc mocha --config .mocharc.json src/*.spec.js", + "mocha": "mocha --config .mocharc.json src/*.spec.js", + "test": "npm run lint && npm run test:unit && npm run test:types", + "test:unit": "npm run build && nyc --reporter=text-summary npm run mocha", "test:types": "tsd", - "coverage": "codecov -F webapi --root=$PWD", "ref-docs:model": "api-extractor run", - "watch": "npx nodemon --watch 'src' --ext 'ts' --exec npm run build", - "build:deno": "esbuild --bundle --define:process.cwd=String --define:process.version='\"v1.15.2\"' --define:process.title='\"deno\"' --define:Buffer=dummy_buffer --inject:./deno-shims/buffer-shim.js --inject:./deno-shims/xhr-shim.js --target=esnext --format=esm --outfile=./mod.js src/index.ts" + "watch": "npx nodemon --watch 'src' --ext 'ts' --exec npm run build" }, "dependencies": { - "@slack/logger": "^3.0.0", - "@slack/types": "^2.8.0", - "@types/is-stream": "^1.1.0", - "@types/node": ">=12.0.0", - "axios": "^0.27.2", - "eventemitter3": "^3.1.0", - "form-data": "^2.5.0", + "@slack/logger": "^4.0.0", + "@slack/types": "^2.9.0", + "@types/node": ">=18.0.0", + "axios": "^1.5.1", + "eventemitter3": "^5.0.1", + "form-data": "^4.0.0", "is-electron": "2.2.2", - "is-stream": "^1.1.0", - "p-queue": "^6.6.1", - "p-retry": "^4.0.0" + "is-stream": "^2.0.0", + "p-queue": "^6.6.2", + "p-retry": "^4.6.1", + "retry": "^0.13.1" }, "devDependencies": { "@aoberoi/capture-console": "^1.1.0", - "@microsoft/api-extractor": "^7.3.4", - "@types/chai": "^4.1.7", - "@types/mocha": "^5.2.6", - "@typescript-eslint/eslint-plugin": "^4.4.1", - "@typescript-eslint/parser": "^4.4.0", + "@microsoft/api-extractor": "^7.38.0", + "@types/chai": "^4.3.5", + "@types/mocha": "^10.0.1", + "@typescript-eslint/eslint-plugin": "^6.4.1", + "@typescript-eslint/parser": "^6.4.0", "busboy": "^1.6.0", - "chai": "^4.2.0", - "codecov": "^3.2.0", - "esbuild": "^0.13.15", - "eslint": "^7.32.0", - "eslint-config-airbnb-base": "^14.2.1", - "eslint-config-airbnb-typescript": "^12.3.1", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jsdoc": "^30.6.1", + "chai": "^4.3.8", + "eslint": "^8.47.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.1.0", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsdoc": "^46.5.0", "eslint-plugin-node": "^11.1.0", - "mocha": "^9.1.0", - "nock": "^13.2.6", + "mocha": "^10.2.0", + "nock": "^13.3.3", "nyc": "^15.1.0", "shelljs": "^0.8.3", "shx": "^0.3.2", - "sinon": "^7.2.7", - "source-map-support": "^0.5.10", + "sinon": "^15.2.0", + "source-map-support": "^0.5.21", "ts-node": "^10.8.1", "tsd": "0.29.0", "typescript": "^4.1" diff --git a/packages/web-api/src/WebClient.spec.js b/packages/web-api/src/WebClient.spec.js index 5ee8412a9..fc9a73b25 100644 --- a/packages/web-api/src/WebClient.spec.js +++ b/packages/web-api/src/WebClient.spec.js @@ -34,20 +34,6 @@ describe('WebClient', function () { assert.instanceOf(client, WebClient); assert.notExists(client.axios.defaults.headers.Authorization); }); - it('should not modify global defaults in axios', function () { - // https://github.com/slackapi/node-slack-sdk/issues/1037 - const client = new WebClient(); - - const globalDefault = axios.defaults.headers.post['Content-Type']; - // The axios.default's defaults should not be modified. - // Specifically, defaults.headers.post should be kept as-is - assert.exists(globalDefault); - - const instanceDefault = client.axios.defaults.headers.post['Content-Type']; - // WebClient intentionally removes the default Content-Type - // from the underlying AxiosInstance used for performing web API calls - assert.notExists(instanceDefault) - }); }); describe('Methods superclass', function () { @@ -149,7 +135,6 @@ describe('WebClient', function () { assert.equal(error.code, ErrorCode.RequestError); assert.equal(error.original.config.timeout, timeoutOverride); assert.equal(error.original.isAxiosError, true); - assert.equal(error.original.request.aborted, true); done(); } catch (err) { done(err); diff --git a/packages/web-api/src/WebClient.ts b/packages/web-api/src/WebClient.ts index 7709348a0..462146e20 100644 --- a/packages/web-api/src/WebClient.ts +++ b/packages/web-api/src/WebClient.ts @@ -200,6 +200,7 @@ export class WebClient extends Methods { this.axios = axios.create({ timeout, baseURL: slackApiUrl, + // eslint-disable-next-line @typescript-eslint/naming-convention headers: isElectron() ? headers : { 'User-Agent': getUserAgent(), ...headers }, httpAgent: agent, httpsAgent: agent, @@ -220,7 +221,6 @@ export class WebClient extends Methods { /** * Generic method for calling a Web API method - * * @param method - the Web API method to call {@link https://api.slack.com/methods} * @param options - options */ @@ -299,7 +299,6 @@ export class WebClient extends Methods { * The for-await-of syntax is part of ES2018. It is available natively in Node starting with v10.0.0. You may be able * to use it in earlier JavaScript runtimes by transpiling your source with a tool like Babel. However, the * transpiled code will likely sacrifice performance. - * * @param method - the cursor-paginated Web API method to call {@link https://api.slack.com/docs/pagination} * @param options - options * @param shouldStop - a predicate that is called with each page, and should return true when pagination can end. @@ -592,7 +591,6 @@ 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 */ @@ -760,7 +758,8 @@ export default WebClient; * @param pageSize - the maximum number of additional items to fetch in the next request. */ function paginationOptionsForNextPage( - previousResult: WebAPICallResult | undefined, pageSize: number, + previousResult: WebAPICallResult | undefined, + pageSize: number, ): CursorPaginationEnabled | undefined { if ( previousResult !== undefined && diff --git a/packages/web-api/src/errors.ts b/packages/web-api/src/errors.ts index 03e09734f..9e8e7b720 100644 --- a/packages/web-api/src/errors.ts +++ b/packages/web-api/src/errors.ts @@ -94,7 +94,13 @@ export function httpErrorFromResponse(response: AxiosResponse): WebAPIHTTPError ) as Partial; error.statusCode = response.status; error.statusMessage = response.statusText; - error.headers = response.headers; + const nonNullHeaders: Record = {}; + Object.keys(response.headers).forEach((k) => { + if (k && response.headers[k]) { + nonNullHeaders[k] = response.headers[k]; + } + }); + error.headers = nonNullHeaders; error.body = response.data; return (error as WebAPIHTTPError); } diff --git a/packages/web-api/src/methods.ts b/packages/web-api/src/methods.ts index eb6eb3128..e52c8db74 100644 --- a/packages/web-api/src/methods.ts +++ b/packages/web-api/src/methods.ts @@ -317,73 +317,88 @@ export abstract class Methods extends EventEmitter { bulkMove: bindApiCall(this, 'admin.conversations.bulkMove'), convertToPrivate: bindApiCall( - this, 'admin.conversations.convertToPrivate', + this, + 'admin.conversations.convertToPrivate', ), convertToPublic: bindApiCall( - this, 'admin.conversations.convertToPublic', + this, + 'admin.conversations.convertToPublic', ), create: bindApiCall(this, 'admin.conversations.create'), delete: bindApiCall(this, 'admin.conversations.delete'), disconnectShared: bindApiCall( - this, 'admin.conversations.disconnectShared', + this, + 'admin.conversations.disconnectShared', ), ekm: { listOriginalConnectedChannelInfo: bindApiCall( - this, 'admin.conversations.ekm.listOriginalConnectedChannelInfo', + this, + 'admin.conversations.ekm.listOriginalConnectedChannelInfo', ), }, getConversationPrefs: bindApiCall( - this, 'admin.conversations.getConversationPrefs', + this, + 'admin.conversations.getConversationPrefs', ), getTeams: bindApiCall( - this, 'admin.conversations.getTeams', + this, + 'admin.conversations.getTeams', ), invite: bindApiCall(this, 'admin.conversations.invite'), rename: bindApiCall(this, 'admin.conversations.rename'), restrictAccess: { addGroup: bindApiCall( - this, 'admin.conversations.restrictAccess.addGroup', + this, + 'admin.conversations.restrictAccess.addGroup', ), listGroups: bindApiCall( - this, 'admin.conversations.restrictAccess.listGroups', + this, + 'admin.conversations.restrictAccess.listGroups', ), removeGroup: bindApiCall( - this, 'admin.conversations.restrictAccess.removeGroup', + this, + 'admin.conversations.restrictAccess.removeGroup', ), }, getCustomRetention: bindApiCall( - this, 'admin.conversations.getCustomRetention', + this, + 'admin.conversations.getCustomRetention', ), setCustomRetention: bindApiCall( - this, 'admin.conversations.setCustomRetention', + this, + 'admin.conversations.setCustomRetention', ), removeCustomRetention: bindApiCall( - this, 'admin.conversations.removeCustomRetention', + this, + 'admin.conversations.removeCustomRetention', ), lookup: bindApiCall(this, 'admin.conversations.lookup'), search: bindApiCall(this, 'admin.conversations.search'), setConversationPrefs: bindApiCall( - this, 'admin.conversations.setConversationPrefs', + this, + 'admin.conversations.setConversationPrefs', ), setTeams: bindApiCall( - this, 'admin.conversations.setTeams', + this, + 'admin.conversations.setTeams', ), unarchive: bindApiCall( - this, 'admin.conversations.unarchive', + this, + 'admin.conversations.unarchive', ), }, emoji: { @@ -402,16 +417,19 @@ export abstract class Methods extends EventEmitter { }, inviteRequests: { approve: bindApiCall( - this, 'admin.inviteRequests.approve', + this, + 'admin.inviteRequests.approve', ), approved: { list: bindApiCall( - this, 'admin.inviteRequests.approved.list', + this, + 'admin.inviteRequests.approved.list', ), }, denied: { list: bindApiCall( - this, 'admin.inviteRequests.denied.list', + this, + 'admin.inviteRequests.denied.list', ), }, deny: bindApiCall(this, 'admin.inviteRequests.deny'), @@ -430,22 +448,27 @@ export abstract class Methods extends EventEmitter { info: bindApiCall(this, 'admin.teams.settings.info'), setDefaultChannels: bindApiCall( - this, 'admin.teams.settings.setDefaultChannels', + this, + 'admin.teams.settings.setDefaultChannels', ), setDescription: bindApiCall( - this, 'admin.teams.settings.setDescription', + this, + 'admin.teams.settings.setDescription', ), setDiscoverability: bindApiCall( - this, 'admin.teams.settings.setDiscoverability', + this, + 'admin.teams.settings.setDiscoverability', ), setIcon: bindApiCall( - this, 'admin.teams.settings.setIcon', + this, + 'admin.teams.settings.setIcon', ), setName: bindApiCall( - this, 'admin.teams.settings.setName', + this, + 'admin.teams.settings.setName', ), }, }, @@ -456,16 +479,20 @@ export abstract class Methods extends EventEmitter { }, usergroups: { addChannels: bindApiCall( - this, 'admin.usergroups.addChannels', + this, + 'admin.usergroups.addChannels', ), addTeams: bindApiCall( - this, 'admin.usergroups.addTeams', + this, + 'admin.usergroups.addTeams', ), listChannels: bindApiCall( - this, 'admin.usergroups.listChannels', + this, + 'admin.usergroups.listChannels', ), removeChannels: bindApiCall( - this, 'admin.usergroups.removeChannels', + this, + 'admin.usergroups.removeChannels', ), }, users: { @@ -478,33 +505,41 @@ export abstract class Methods extends EventEmitter { reset: bindApiCall(this, 'admin.users.session.reset'), resetBulk: bindApiCall(this, 'admin.users.session.resetBulk'), invalidate: bindApiCall( - this, 'admin.users.session.invalidate', + this, + 'admin.users.session.invalidate', ), getSettings: bindApiCall( - this, 'admin.users.session.getSettings', + this, + 'admin.users.session.getSettings', ), setSettings: bindApiCall( - this, 'admin.users.session.setSettings', + this, + 'admin.users.session.setSettings', ), clearSettings: bindApiCall( - this, 'admin.users.session.clearSettings', + this, + 'admin.users.session.clearSettings', ), }, unsupportedVersions: { export: bindApiCall( - this, 'admin.users.unsupportedVersions.export', + this, + 'admin.users.unsupportedVersions.export', ), }, setAdmin: bindApiCall(this, 'admin.users.setAdmin'), setExpiration: bindApiCall( - this, 'admin.users.setExpiration', + this, + 'admin.users.setExpiration', ), setOwner: bindApiCall( - this, 'admin.users.setOwner', + this, + 'admin.users.setOwner', ), setRegular: bindApiCall( - this, 'admin.users.setRegular', + this, + 'admin.users.setRegular', ), }, workflows: { @@ -531,7 +566,8 @@ export abstract class Methods extends EventEmitter { event: { authorizations: { list: bindApiCall( - this, 'apps.event.authorizations.list', + this, + 'apps.event.authorizations.list', ), }, }, @@ -577,12 +613,14 @@ export abstract class Methods extends EventEmitter { postEphemeral: bindApiCall(this, 'chat.postEphemeral'), postMessage: bindApiCall(this, 'chat.postMessage'), scheduleMessage: bindApiCall( - this, 'chat.scheduleMessage', + this, + 'chat.scheduleMessage', ), scheduledMessages: { list: bindApiCall( - this, 'chat.scheduledMessages.list', + this, + 'chat.scheduledMessages.list', ), }, unfurl: bindApiCall(this, 'chat.unfurl'), @@ -591,24 +629,28 @@ export abstract class Methods extends EventEmitter { public readonly conversations = { acceptSharedInvite: bindApiCall( - this, 'conversations.acceptSharedInvite', + this, + 'conversations.acceptSharedInvite', ), approveSharedInvite: bindApiCall( - this, 'conversations.approveSharedInvite', + this, + 'conversations.approveSharedInvite', ), archive: bindApiCall(this, 'conversations.archive'), close: bindApiCall(this, 'conversations.close'), create: bindApiCall(this, 'conversations.create'), declineSharedInvite: bindApiCall( - this, 'conversations.declineSharedInvite', + this, + 'conversations.declineSharedInvite', ), history: bindApiCall(this, 'conversations.history'), info: bindApiCall(this, 'conversations.info'), invite: bindApiCall(this, 'conversations.invite'), inviteShared: bindApiCall( - this, 'conversations.inviteShared', + this, + 'conversations.inviteShared', ), join: bindApiCall(this, 'conversations.join'), kick: bindApiCall(this, 'conversations.kick'), @@ -616,7 +658,8 @@ export abstract class Methods extends EventEmitter { list: bindApiCall(this, 'conversations.list'), listConnectInvites: bindApiCall( - this, 'conversations.listConnectInvites', + this, + 'conversations.listConnectInvites', ), mark: bindApiCall(this, 'conversations.mark'), members: bindApiCall(this, 'conversations.members'), @@ -626,10 +669,12 @@ export abstract class Methods extends EventEmitter { setPurpose: bindApiCall(this, 'conversations.setPurpose'), setTopic: bindApiCall( - this, 'conversations.setTopic', + this, + 'conversations.setTopic', ), unarchive: bindApiCall( - this, 'conversations.unarchive', + this, + 'conversations.unarchive', ), }; @@ -770,10 +815,12 @@ export abstract class Methods extends EventEmitter { update: bindApiCall(this, 'usergroups.update'), users: { list: bindApiCall( - this, 'usergroups.users.list', + this, + 'usergroups.users.list', ), update: bindApiCall( - this, 'usergroups.users.update', + this, + 'usergroups.users.update', ), }, }; @@ -803,7 +850,8 @@ export abstract class Methods extends EventEmitter { public readonly workflows = { stepCompleted: bindApiCall( - this, 'workflows.stepCompleted', + this, + 'workflows.stepCompleted', ), stepFailed: bindApiCall(this, 'workflows.stepFailed'), updateStep: bindApiCall(this, 'workflows.updateStep'), @@ -1869,7 +1917,7 @@ export interface FilesSharedPublicURLArguments extends WebAPICallOptions, TokenO } /** * Legacy files.upload API files upload arguments - * */ + */ export interface FilesUploadArguments extends FileUpload, WebAPICallOptions, TokenOverridable {} interface FileUpload { channels?: string; // comma-separated list of channels @@ -1917,7 +1965,7 @@ export interface FilesGetUploadURLExternalArguments extends WebAPICallOptions, T /** * Finishes an upload started with files.getUploadURLExternal. Method: * {@link https://api.slack.com/methods/files.completeUploadExternal files.completeUploadExternal} - * */ + */ export interface FilesCompleteUploadExternalArguments extends WebAPICallOptions, TokenOverridable { files: FileUploadComplete[]; channel_id?: string, // if omitted, file will be private diff --git a/packages/web-api/src/retry-policies.ts b/packages/web-api/src/retry-policies.ts index e49636e83..9e511e5d7 100644 --- a/packages/web-api/src/retry-policies.ts +++ b/packages/web-api/src/retry-policies.ts @@ -1,10 +1,10 @@ -import { Options } from 'p-retry'; +import { OperationOptions } from 'retry'; /** * Options to create retry policies. Extends from https://github.com/tim-kos/node-retry. */ // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface RetryOptions extends Options { +export interface RetryOptions extends OperationOptions { } /**