Skip to content

Commit

Permalink
socket-mode: prep for major release, start now completes only once …
Browse files Browse the repository at this point in the history
…`Connected` state is emitted (#1732)
  • Loading branch information
filmaj authored Jan 25, 2024
1 parent e23dfd3 commit a6f2b28
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 70 deletions.
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",
"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

0 comments on commit a6f2b28

Please sign in to comment.