Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

socket-mode: prep for major release #1732

Merged
merged 5 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions packages/socket-mode/.nycrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"include": [
"src/**/*.ts"
],
"exclude": [
"**/*.spec.js"
],
"reporter": ["lcov"],
"extension": [
".ts"
],
"all": false,
"cache": true
}
18 changes: 11 additions & 7 deletions packages/socket-mode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ $ npm install @slack/socket-mode
## Usage

These examples show the most common features of `Socket Mode`. You'll find even more extensive [documentation on the
package's website](https://slack.dev/node-slack-sdk/socket-mode) and our [api site](https://api.slack.com/socket-mode).
package's website](https://slack.dev/node-slack-sdk/socket-mode) and our [api site][socket-mode].

<!-- END: Remove before copying into the docs directory -->

---

### Initialize the client

This package is designed to support [**Socket Mode**](https://api.slack.com/socket-mode), which allows your app to receive events from Slack over a WebSocket connection.
This package is designed to support [**Socket Mode**][socket-mode], which allows your app to receive events from Slack over a WebSocket connection.

The package exports a `SocketModeClient` class. Your app will create an instance of the class for each workspace it communicates with. Creating an instance requires an **app-level token** from Slack. Apps connect to the **Socket Mode** API using an **app-level token**, which starts with `xapp`.
The package exports a `SocketModeClient` class. Your app will create an instance of the class for each workspace it communicates with. Creating an instance requires an [**app-level token**][app-token] from Slack. Apps connect to the **Socket Mode** API using an [**app-level token**][app-token], which starts with `xapp`.

Note: **Socket Mode** requires the `connections:write` scope. Navigate to your [app configuration](https://api.slack.com/apps) and go to the **OAuth and Permissions** section to add the scope.

Expand Down Expand Up @@ -83,7 +83,7 @@ socketModeClient.on('message', (event) => {

### Send a message

To respond to events and send messages back into Slack, we recommend using the `@slack/web-api` package with a `bot token`.
To respond to events and send messages back into Slack, we recommend using the `@slack/web-api` package with a [bot token](https://api.slack.com/authentication/token-types#bot).

```javascript
const { SocketModeClient } = require('@slack/socket-mode');
Expand Down Expand Up @@ -140,7 +140,6 @@ In the table below, the client's states are listed, which are also the names of
| `connecting` | | The client is in the process of connecting to the platform. |
| `authenticated` | `(connectData)` - the response from `apps.connections.open` | The client has authenticated with the platform. This is a sub-state of `connecting`. |
| `connected` | | The client is connected to the platform and incoming events will start being emitted. |
| `ready` | | The client is ready to send outgoing messages. This is a sub-state of `connected` |
| `disconnecting` | | The client is no longer connected to the platform and cleaning up its resources. It will soon transition to `disconnected`. |
| `reconnecting` | | The client is no longer connected to the platform and cleaning up its resources. It will soon transition to `connecting`. |
| `disconnected` | `(error)` | The client is not connected to the platform. This is a steady state - no attempt to connect is occurring. The `error` argument will be `undefined` when the client initiated the disconnect (normal). |
Expand Down Expand Up @@ -182,10 +181,11 @@ All the log levels, in order of most to least information are: `DEBUG`, `INFO`,
<strong><i>Sending log output somewhere besides the console</i></strong>
</summary>

You can also choose to have logs sent to a custom logger using the `logger` option. A custom logger needs to implement specific methods (known as the `Logger` interface):
You can also choose to have logs sent to a custom logger using the `logger` option. A custom logger needs to implement specific methods (known as the `Logger` interface, see the [`@slack/logger` package](https://www.npmjs.com/package/@slack/logger) for details). A minimal interface should implement the following methods:

| Method | Parameters | Return type |
|--------------|-------------------|-------------|
| `getLevel()` | n/a | `LogLevel` |
| `setLevel()` | `level: LogLevel` | `void` |
| `setName()` | `name: string` | `void` |
| `debug()` | `...msgs: any[]` | `void` |
Expand All @@ -207,6 +207,7 @@ const socketModeClient = new SocketModeClient({
info(...msgs): { logWritable.write('info: ' + JSON.stringify(msgs)); },
warn(...msgs): { logWritable.write('warn: ' + JSON.stringify(msgs)); },
error(...msgs): { logWritable.write('error: ' + JSON.stringify(msgs)); },
getLevel(): { return 'info'; },
setLevel(): { },
setName(): { },
},
Expand All @@ -222,11 +223,14 @@ const socketModeClient = new SocketModeClient({

## 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.

## Getting Help

If you get stuck, we're here to help. The following are the best ways to get assistance working through your issue:

* [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.

[socket-mode]: https://api.slack.com/apis/connections/socket
[app-token]: https://api.slack.com/authentication/token-types#app
56 changes: 28 additions & 28 deletions packages/socket-mode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,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/socket-mode",
Expand All @@ -40,40 +40,40 @@
"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 && nyc mocha --config .mocharc.json src/*.spec.js",
"mocha": "mocha --config .mocharc.json src/*.spec.js",
"test:integration": "mocha --config .mocharc.json test/integration.spec.js",
"test": "npm run lint && npm run build && nyc --reporter=text-summary npm run mocha && npm run test:integration",
"watch": "npx nodemon --watch 'src' --ext 'ts' --exec npm run build"
},
"dependencies": {
"@slack/logger": "^3.0.0",
"@slack/web-api": "^6.11.2",
"@types/node": ">=12.0.0",
"@types/p-queue": "^2.3.2",
"@types/ws": "^7.4.7",
"eventemitter3": "^3.1.0",
"@slack/logger": "^4",
"@slack/web-api": "^7.0.1",
"@types/node": ">=18",
"@types/ws": "^8",
"eventemitter3": "^5",
"finity": "^0.5.4",
"p-cancelable": "^1.1.0",
"p-queue": "^2.4.2",
filmaj marked this conversation as resolved.
Show resolved Hide resolved
"ws": "^7.5.3"
"ws": "^8"
},
"devDependencies": {
"@types/chai": "^4.3.5",
"@types/mocha": "^10.0.1",
"@typescript-eslint/eslint-plugin": "^6.4.1",
"@typescript-eslint/parser": "^6.4.0",
"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",
"@types/chai": "^4",
"@types/mocha": "^10",
"@types/sinon": "^17",
"@typescript-eslint/eslint-plugin": "^6",
"@typescript-eslint/parser": "^6",
"chai": "^4",
"eslint": "^8",
"eslint-config-airbnb-base": "^15",
"eslint-config-airbnb-typescript": "^17",
"eslint-plugin-import": "^2",
"eslint-plugin-import-newlines": "^1.3.4",
"eslint-plugin-jsdoc": "^46.5.0",
"eslint-plugin-node": "^11.1.0",
"mocha": "^10.2.0",
"nyc": "^15.1.0",
"eslint-plugin-jsdoc": "^48",
"eslint-plugin-node": "^11",
"mocha": "^10",
"nyc": "^15",
"shx": "^0.3.2",
"sinon": "^15.2.0",
"sinon": "^17",
"source-map-support": "^0.5.21",
"ts-node": "^10.8.1",
"typescript": "^4.1.0"
"ts-node": "^10",
"typescript": "5.3.3"
}
}
38 changes: 19 additions & 19 deletions packages/socket-mode/src/SocketModeClient.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe('SocketModeClient', () => {
});

describe('slash_commands messages', () => {
const message = {
const message = Buffer.from(JSON.stringify({
"envelope_id": "1d3c79ab-0ffb-41f3-a080-d19e85f53649",
"payload": {
"token": "verification-token",
Expand All @@ -37,9 +37,9 @@ describe('SocketModeClient', () => {
},
"type": "slash_commands",
"accepts_response_payload": true
};
}));

it('should be sent to two listeners', async () => {
it('should be sent to both slash_commands and slack_event listeners', async () => {
const client = new SocketModeClient({ appToken: 'xapp-' });
let commandListenerCalled = false;
client.on("slash_commands", async (args) => {
Expand All @@ -52,7 +52,7 @@ describe('SocketModeClient', () => {
&& args.retry_num === undefined
&& args.retry_reason === undefined;
});
await client.onWebSocketMessage({ data: JSON.stringify(message) });
await client.onWebSocketMessage(message);
await sleep(30);
assert.isTrue(commandListenerCalled);
assert.isTrue(slackEventListenerCalled);
Expand All @@ -64,7 +64,7 @@ describe('SocketModeClient', () => {
client.on("slash_commands", async ({ envelope_id }) => {
passedEnvelopeId = envelope_id;
});
await client.onWebSocketMessage({ data: JSON.stringify(message) });
await client.onWebSocketMessage(message);
await sleep(30);
assert.equal(passedEnvelopeId, '1d3c79ab-0ffb-41f3-a080-d19e85f53649');
});
Expand All @@ -74,14 +74,14 @@ describe('SocketModeClient', () => {
client.on("slack_event", async ({ envelope_id }) => {
passedEnvelopeId = envelope_id;
});
await client.onWebSocketMessage({ data: JSON.stringify(message) });
await client.onWebSocketMessage(message);
await sleep(30);
assert.equal(passedEnvelopeId, '1d3c79ab-0ffb-41f3-a080-d19e85f53649');
});
});

describe('events_api messages', () => {
const message = {
const message = Buffer.from(JSON.stringify({
"envelope_id": "cda4159a-72a5-4744-aba3-4d66eb52682b",
"payload": {
"token": "verification-token",
Expand Down Expand Up @@ -133,12 +133,12 @@ describe('SocketModeClient', () => {
"accepts_response_payload": false,
"retry_attempt": 2,
"retry_reason": "timeout"
};
}));

it('should be sent to two listeners', async () => {
it('should be sent to the specific and generic event listeners, and should not trip an unrelated event listener', async () => {
const client = new SocketModeClient({ appToken: 'xapp-' });
let otherListenerCalled = false;
client.on("app_home_opend", async () => {
client.on("app_home_opened", async () => {
otherListenerCalled = true;
});
let eventsApiListenerCalled = false;
Expand All @@ -154,7 +154,7 @@ describe('SocketModeClient', () => {
&& args.retry_num === 2
&& args.retry_reason === 'timeout';
});
await client.onWebSocketMessage({ data: JSON.stringify(message) });
await client.onWebSocketMessage(message);
await sleep(30);
assert.isFalse(otherListenerCalled);
assert.isTrue(eventsApiListenerCalled);
Expand All @@ -167,7 +167,7 @@ describe('SocketModeClient', () => {
client.on("app_mention", async ({ envelope_id }) => {
passedEnvelopeId = envelope_id;
});
await client.onWebSocketMessage({ data: JSON.stringify(message) });
await client.onWebSocketMessage(message);
await sleep(30);
assert.equal(passedEnvelopeId, 'cda4159a-72a5-4744-aba3-4d66eb52682b');
});
Expand All @@ -177,14 +177,14 @@ describe('SocketModeClient', () => {
client.on("slack_event", async ({ envelope_id }) => {
passedEnvelopeId = envelope_id;
});
await client.onWebSocketMessage({ data: JSON.stringify(message) });
await client.onWebSocketMessage(message);
await sleep(30);
assert.equal(passedEnvelopeId, 'cda4159a-72a5-4744-aba3-4d66eb52682b');
});
});

describe('interactivity messages', () => {
const message = {
const message = Buffer.from(JSON.stringify({
"envelope_id": "57d6a792-4d35-4d0b-b6aa-3361493e1caf",
"payload": {
"type": "shortcut",
Expand All @@ -206,9 +206,9 @@ describe('SocketModeClient', () => {
},
"type": "interactive",
"accepts_response_payload": false
};
}));

it('should be sent to two listeners', async () => {
it('should be sent to the specific and generic event type listeners, and should not trip an unrelated event listener', async () => {
const client = new SocketModeClient({ appToken: 'xapp-' });
let otherListenerCalled = false;
client.on("slash_commands", async () => {
Expand All @@ -222,7 +222,7 @@ describe('SocketModeClient', () => {
client.on("slack_event", async (args) => {
slackEventListenerCalled = args.ack !== undefined && args.body !== undefined;
});
await client.onWebSocketMessage({ data: JSON.stringify(message) });
await client.onWebSocketMessage(message);
await sleep(30);
assert.isFalse(otherListenerCalled);
assert.isTrue(interactiveListenerCalled);
Expand All @@ -235,7 +235,7 @@ describe('SocketModeClient', () => {
client.on("interactive", async ({ envelope_id }) => {
passedEnvelopeId = envelope_id;
});
await client.onWebSocketMessage({ data: JSON.stringify(message) });
await client.onWebSocketMessage(message);
await sleep(30);
assert.equal(passedEnvelopeId, '57d6a792-4d35-4d0b-b6aa-3361493e1caf');
});
Expand All @@ -245,7 +245,7 @@ describe('SocketModeClient', () => {
client.on("slack_event", async ({ envelope_id }) => {
passedEnvelopeId = envelope_id;
});
await client.onWebSocketMessage({ data: JSON.stringify(message) });
await client.onWebSocketMessage(message);
await sleep(30);
assert.equal(passedEnvelopeId, '57d6a792-4d35-4d0b-b6aa-3361493e1caf');
});
Expand Down
Loading