Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/socket-mode-app-rate-limited.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@slack/socket-mode": patch
---

Fix a crash on `events_api` messages without an inner event ([#2167](https://github.com/slackapi/node-slack-sdk/issues/2167)). Slack sends `app_rate_limited` notifications this way, and the client threw `Cannot read properties of undefined (reading 'type')`. These messages are now emitted under their payload type, e.g. `app_rate_limited`.
38 changes: 38 additions & 0 deletions packages/socket-mode/src/SocketModeClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,20 @@ describe('SocketModeClient', () => {
retry_attempt: 2,
retry_reason: 'timeout',
});
const appRateLimited = JSON.stringify({
envelope_id: envelopeId,
payload: {
token: 'verification-token',
api_app_id: 'A111',
minute_rate_limited: 1792487520,
type: 'app_rate_limited',
team_id: 'T111',
},
type: 'events_api',
accepts_response_payload: false,
retry_attempt: 1,
retry_reason: 'timeout',
});

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-' });
Expand Down Expand Up @@ -316,6 +330,30 @@ describe('SocketModeClient', () => {
client.emit('ws_message', message, false /* isBinary */);
sinon.assert.callCount(spy, 1);
});
it('should send payloads without an inner event to the payload type and slack_event listeners', async () => {
const client = new SocketModeClient({ appToken: 'xapp-' });
const rateLimitedSpy = sinon.spy();
const slackEventSpy = sinon.spy();
client.on('app_rate_limited', rateLimitedSpy);
client.on('slack_event', slackEventSpy);
client.emit('ws_message', appRateLimited, false /* isBinary */);
await sleep(30);
sinon.assert.calledOnce(rateLimitedSpy);
sinon.assert.calledOnce(slackEventSpy);
});
it('should pass all the properties to listeners of payloads without an inner event', async () => {
const client = new SocketModeClient({ appToken: 'xapp-' });
const spy = sinon.spy();
client.on('app_rate_limited', spy);
client.emit('ws_message', appRateLimited, false /* isBinary */);
await sleep(30);
const args = spy.firstCall.args[0];
assert.strictEqual(args.envelope_id, envelopeId);
assert.strictEqual(args.body.minute_rate_limited, 1792487520);
assert.strictEqual(args.retry_num, 1);
assert.strictEqual(args.retry_reason, 'timeout');
assert.strictEqual('event' in args, false);
});
});

describe('interactivity messages', () => {
Expand Down
16 changes: 15 additions & 1 deletion packages/socket-mode/src/SocketModeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ export class SocketModeClient extends EventEmitter {
};

// For events_api messages, expose the type of the event
if (event.type === 'events_api') {
if (event.type === 'events_api' && event.payload?.event?.type) {
this.emit(event.payload.event.type, {
ack,
envelope_id: event.envelope_id,
Expand All @@ -372,6 +372,20 @@ export class SocketModeClient extends EventEmitter {
retry_reason: event.retry_reason,
accepts_response_payload: event.accepts_response_payload,
});
} else if (event.type === 'events_api') {
// Some events_api payloads carry no inner event, e.g. app_rate_limited
if (event.payload?.type) {
this.emit(event.payload.type, {
ack,
envelope_id: event.envelope_id,
body: event.payload,
retry_num: event.retry_attempt,
retry_reason: event.retry_reason,
accepts_response_payload: event.accepts_response_payload,
});
} else {
this.logger.debug(`events_api message has no event or payload type: ${event.envelope_id}`);
}
} else {
// Emit just ack and body for all other types of messages
this.emit(event.type, {
Expand Down