Skip to content

Commit 6a3d21c

Browse files
author
Fil Maj
authored
Prep for major release of rtm-api (#1764)
1 parent f4f62da commit 6a3d21c

File tree

5 files changed

+131
-32
lines changed

5 files changed

+131
-32
lines changed

packages/rtm-api/.mocharc.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"require": ["ts-node/register", "source-map-support/register"],
3+
"timeout": 3000
4+
}

packages/rtm-api/README.md

+1-4
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ const rtm = new RTMClient(token, {
430430

431431
## Requirements
432432

433-
This package supports Node v14 and higher. It's highly recommended to use [the latest LTS version of
433+
This package supports Node v18 and higher. It's highly recommended to use [the latest LTS version of
434434
node](https://github.com/nodejs/Release#release-schedule), and the documentation is written using syntax and features
435435
from that version.
436436

@@ -440,6 +440,3 @@ If you get stuck, we're here to help. The following are the best ways to get ass
440440

441441
* [Issue Tracker](http://github.com/slackapi/node-slack-sdk/issues) for questions, feature requests, bug reports and
442442
general discussion related to these packages. Try searching before you create a new issue.
443-
* [Email us](mailto:[email protected]) in Slack developer support: `[email protected]`
444-
* [Bot Developers Hangout](https://community.botkit.ai/): a Slack community for developers
445-
building all types of bots. You can find the maintainers and users of these packages in **#sdk-node-slack-sdk**.

packages/rtm-api/package.json

+31-23
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
"dist/**/*"
2323
],
2424
"engines": {
25-
"node": ">= 12.13.0",
26-
"npm": ">= 6.12.0"
25+
"node": ">=18",
26+
"npm": ">=8.6.0"
2727
},
2828
"repository": "slackapi/node-slack-sdk",
2929
"homepage": "https://slack.dev/node-slack-sdk/rtm-api",
@@ -37,34 +37,42 @@
3737
"prepare": "npm run build",
3838
"build": "npm run build:clean && tsc",
3939
"build:clean": "shx rm -rf ./dist",
40-
"lint": "eslint --ext .ts src",
41-
"test": "npm run lint && npm run build && echo \"Tests are not implemented.\" && exit 0",
40+
"lint": "eslint --fix --ext .ts src",
41+
"test": "npm run lint && npm run build && npm run test:integration",
42+
"test:integration": "mocha --config .mocharc.json test/integration.spec.js",
4243
"ref-docs:model": "api-extractor run"
4344
},
4445
"dependencies": {
45-
"@slack/logger": ">=1.0.0 <3.0.0",
46-
"@slack/web-api": "^6.11.2",
47-
"@types/node": ">=12.0.0",
48-
"@types/p-queue": "^2.3.2",
49-
"@types/ws": "^7.4.7",
50-
"eventemitter3": "^3.1.0",
46+
"@slack/logger": "^4",
47+
"@slack/web-api": "^7",
48+
"@types/node": ">=18",
49+
"eventemitter3": "^5",
5150
"finity": "^0.5.4",
52-
"p-cancelable": "^1.1.0",
53-
"p-queue": "^2.4.2",
54-
"ws": "^7.5.3"
51+
"p-cancelable": "^2",
52+
"p-queue": "^6",
53+
"ws": "^8"
5554
},
5655
"devDependencies": {
57-
"@microsoft/api-extractor": "^7.38.0",
58-
"@typescript-eslint/eslint-plugin": "^6.4.1",
59-
"@typescript-eslint/parser": "^6.4.0",
60-
"eslint": "^8.47.0",
61-
"eslint-config-airbnb-base": "^15.0.0",
62-
"eslint-config-airbnb-typescript": "^17.1.0",
63-
"eslint-plugin-import": "^2.28.1",
56+
"@microsoft/api-extractor": "^7",
57+
"@typescript-eslint/eslint-plugin": "^6",
58+
"@typescript-eslint/parser": "^6",
59+
"@types/chai": "^4",
60+
"@types/mocha": "^10",
61+
"@types/sinon": "^17",
62+
"@types/ws": "^8",
63+
"chai": "^4",
64+
"eslint": "^8",
65+
"eslint-config-airbnb-base": "^15",
66+
"eslint-config-airbnb-typescript": "^17",
67+
"eslint-plugin-import": "^2",
6468
"eslint-plugin-import-newlines": "^1.3.4",
65-
"eslint-plugin-jsdoc": "^46.5.0",
66-
"eslint-plugin-node": "^11.1.0",
69+
"eslint-plugin-jsdoc": "^48",
70+
"eslint-plugin-node": "^11",
71+
"mocha": "^10",
6772
"shx": "^0.3.2",
68-
"typescript": "^4.1.0"
73+
"sinon": "^17",
74+
"source-map-support": "^0.5.21",
75+
"ts-node": "^10",
76+
"typescript": "5.3.3"
6977
}
7078
}

packages/rtm-api/src/RTMClient.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ export class RTMClient extends EventEmitter {
122122
/* eslint-disable @typescript-eslint/indent, newline-per-chained-call */
123123
.initialState('disconnected')
124124
.on('start').transitionTo('connecting')
125+
.on('explicit disconnect').transitionTo('disconnected')
125126
.onEnter(() => {
126127
// each client should start out with the outgoing event queue paused
127128
this.logger.debug('pausing outgoing event queue');
@@ -137,7 +138,7 @@ export class RTMClient extends EventEmitter {
137138
// determine which Web API method to use for the connection
138139
const connectMethod = this.useRtmConnect ? 'rtm.connect' : 'rtm.start';
139140

140-
return this.webClient.apiCall(connectMethod, this.startOpts !== undefined ? this.startOpts : {})
141+
return this.webClient.apiCall(connectMethod, this.startOpts !== undefined ? { ...this.startOpts } : {})
141142
.then((result: WebAPICallResult) => {
142143
const startData = result as RTMStartResult;
143144

@@ -601,7 +602,7 @@ export class RTMClient extends EventEmitter {
601602
this.logger.error(`A websocket error occurred: ${event.message}`);
602603
this.emit('error', websocketErrorWithOriginal(event.error));
603604
});
604-
this.websocket.addEventListener('message', this.onWebsocketMessage.bind(this));
605+
this.websocket.on('message', this.onWebsocketMessage.bind(this));
605606
}
606607

607608
/**
@@ -621,13 +622,13 @@ export class RTMClient extends EventEmitter {
621622
* `onmessage` handler for the client's websocket. This will parse the payload and dispatch the relevant events for
622623
* each incoming message.
623624
*/
624-
private onWebsocketMessage({ data }: { data: string }): void {
625-
this.logger.debug(`received message on websocket: ${data}`);
625+
private onWebsocketMessage(data: WebSocket.RawData): void {
626+
this.logger.debug(`received message on websocket: ${data.toString()}`);
626627

627628
// parse message into slack event
628629
let event;
629630
try {
630-
event = JSON.parse(data);
631+
event = JSON.parse(data.toString());
631632
// eslint-disable-next-line @typescript-eslint/no-explicit-any
632633
} catch (parseError: any) {
633634
// prevent application from crashing on a bad message, but log an error to bring attention
+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
const { assert } = require('chai');
2+
const { RTMClient } = require('../src/RTMClient');
3+
const { LogLevel } = require('../src/logger');
4+
const { WebSocketServer} = require('ws');
5+
const { createServer } = require('http');
6+
const sinon = require('sinon');
7+
8+
const HTTP_PORT = 12345;
9+
const WSS_PORT = 23456;
10+
// Basic HTTP server to 'fake' behaviour of `apps.connections.open` endpoint
11+
let server = null;
12+
13+
// Basic WS server to fake slack WS endpoint
14+
let wss = null;
15+
let exposed_ws_connection = null;
16+
17+
// Socket mode client pointing to the above two posers
18+
let client = null;
19+
20+
describe('Integration tests with a WebSocket server', () => {
21+
beforeEach(() => {
22+
server = createServer((req, res) => {
23+
res.writeHead(200, { 'content-type': 'application/json' });
24+
res.end(JSON.stringify({
25+
ok: true,
26+
url: `ws://localhost:${WSS_PORT}/`,
27+
self: { id: 'elclassico' },
28+
team: { id: 'T12345' },
29+
}));
30+
});
31+
server.listen(HTTP_PORT);
32+
wss = new WebSocketServer({ port: WSS_PORT });
33+
wss.on('connection', (ws) => {
34+
ws.on('error', (err) => {
35+
assert.fail(err);
36+
});
37+
// Send `Event.ServerHello`
38+
ws.send(JSON.stringify({type: 'hello'}));
39+
exposed_ws_connection = ws;
40+
});
41+
});
42+
afterEach(() => {
43+
server.close();
44+
server = null;
45+
wss.close();
46+
wss = null;
47+
exposed_ws_connection = null;
48+
client = null;
49+
});
50+
describe('establishing connection, receiving valid messages', () => {
51+
beforeEach(() => {
52+
client = new RTMClient('token', {
53+
slackApiUrl: `http://localhost:${HTTP_PORT}/`,
54+
logLevel: LogLevel.DEBUG,
55+
});
56+
});
57+
it('connects to a server via `start()` and gracefully disconnects via `disconnect()`', async () => {
58+
await client.start();
59+
await sleep(50); // TODO: this is due to `start()` resolving once the authenticated event is raised
60+
// however, the handshake on the WS side still needs to complete at this point, so calling disconnect()
61+
// will raise an error.
62+
await client.disconnect();
63+
});
64+
it('can listen on slack event types and receive payload properties', async () => {
65+
client.on('connected', () => {
66+
exposed_ws_connection.send(JSON.stringify({
67+
type: 'team_member_joined',
68+
envelope_id: 12345,
69+
}));
70+
});
71+
await client.start();
72+
await new Promise((res, _rej) => {
73+
client.on('team_member_joined', (evt) => {
74+
assert.equal(evt.envelope_id, 12345);
75+
res();
76+
});
77+
});
78+
await client.disconnect();
79+
});
80+
it('should not raise an exception if calling disconnect() when already disconnected', async () => {
81+
// https://github.com/slackapi/node-slack-sdk/issues/842
82+
await client.disconnect();
83+
});
84+
});
85+
});
86+
87+
function sleep(ms) {
88+
return new Promise(resolve => setTimeout(resolve, ms));
89+
}

0 commit comments

Comments
 (0)