This package includes a RTM client that makes it simple to use the Slack RTM API. Here are some of the goodies you get right out of the box:
typing
, message
, and presence_sub
events.An RTM client allows your app to listen for incoming events as well as send outgoing events. It’s a quick solution for many apps – especially for those that may only be about to create outgoing connections due to firewall restrictions, or only ever connect to one workspace.
But the RTM client does not have access to the full power of the Slack platform, including message attachments, buttons, menus, and dialogs. It also does not scale to many instances because it is stateful, and does not scale well for apps that are connected to many workspaces because of Shared Channels. The most robust app building solution is through a combination of the Web API and the Events API.
Here are some of the common recipies for using the RtmClient
class.
Your app will interact with the RTM API through the RtmClient
object, which is a top level
export from this package. At a minimum, you need to instantiate it with a token, usually a
bot token.
const { RtmClient, CLIENT_EVENTS } = require('@slack/client');
// An access token (from your Slack app or custom integration - usually xoxb)
const token = process.env.SLACK_TOKEN;
// Cache of data
const appData = {};
// Initialize the RTM client with the recommended settings. Using the defaults for these
// settings is deprecated.
const rtm = new RtmClient(token, {
dataStore: false,
useRtmConnect: true,
});
// The client will emit an RTM.AUTHENTICATED event on when the connection data is avaiable
// (before the connection is open)
rtm.on(CLIENT_EVENTS.RTM.AUTHENTICATED, (connectData) => {
// Cache the data necessary for this app in memory
appData.selfId = connectData.self.id;
console.log(`Logged in as ${appData.selfId} of team ${connectData.team.id}`);
});
// The client will emit an RTM.RTM_CONNECTION_OPEN the connection is ready for
// sending and recieving messages
rtm.on(CLIENT_EVENTS.RTM.RTM_CONNECTION_OPEN, () => {
console.log(`Ready`);
});
// Start the connecting process
rtm.start();
Of course, just starting a bot running doesn’t accomplish much! It would be nice to be able to
send messages, right? The RTM client has a method called sendMessage(text, channelId)
that will do
just that.
The example below shows an example where the RtmClient
and WebClient
are used
together. The web API method channels.list
is used to get a current list of all channels and
can be iterated over to find one where the bot is a member. Once we have a channel ID, its as easy
as calling rtm.sendMessage()
.
const { RtmClient, CLIENT_EVENTS, WebClient } = require('@slack/client');
const token = process.env.SLACK_TOKEN;
const rtm = new RtmClient(token, {
dataStore: false,
useRtmConnect: true,
});
// Need a web client to find a channel where the app can post a message
const web = new WebClient(token);
// Load the current channels list asynchrously
let channelListPromise = web.channels.list();
rtm.on(CLIENT_EVENTS.RTM.RTM_CONNECTION_OPEN, () => {
console.log(`Ready`);
// Wait for the channels list response
channelsListPromise.then((res) => {
// Take any channel for which the bot is a member
const channel = res.channels.find(c => c.is_member);
if (channel) {
// We now have a channel ID to post a message in!
// use the `sendMessage()` method to send a simple string to a channel using the channel ID
rtm.sendMessage('Hello, world!', channel.id)
// Returns a promise that resolves when the message is sent
.then(() => console.log(`Message sent to channel ${channel.name}`))
.catch(console.error);
} else {
console.log('This bot does not belong to any channels, invite it to at least one and try again');
}
});
});
// Start the connecting process
rtm.start();
The client will emit message events whenever a new message
arrives in a channel in which your bot has access. The client can handle messages using the
on(eventType, event)
method.
const { RtmClient, CLIENT_EVENTS, RTM_EVENTS, WebClient } = require('@slack/client');
// SNIP: the initialization code shown above is skipped for brevity
rtm.on(RTM_EVENTS.MESSAGE, (message) => {
// For structure of `message`, see https://api.slack.com/events/message
// Skip messages that are from a bot or my own user ID
if ( (message.subtype && message.subtype === 'bot_message') ||
(!message.subtype && message.user === appData.selfId) ) {
return;
}
// Log the message
console.log('New message: ', message);
});
There’s a shortcut syntax for listening to specific message subtypes, just format the event type
as message::${message_subtype}
. See the example below.
const { RtmClient, CLIENT_EVENTS, RTM_EVENTS, RTM_MESSAGE_SUBTYPES, WebClient } = require('@slack/client');
// SNIP: the initialization code shown above is skipped for brevity
rtm.on(`${RTM_EVENTS.MESSAGE}::${RTM_MESSAGE_SUBTYPES.CHANNEL_NAME}`, (message) => {
// For structure of `message`, see https://api.slack.com/events/message/channel_name
console.log(`A channel was renamed from ${message.old_name} to ${message.name}`);
});
If you rename a channel in which the bot is a member, you should see the handler get triggered.
Anything that happens in a Slack workspace, and that is visible to the bot (i.e. happens in a
channel to which the bot belongs) is communicated as an event as well. For a complete list of other
events, see https://api.slack.com/rtm#events. The event type values are available in a dictionary
in a top level export called RTM_EVENTS
. The naming follows a convention of
RTM_EVENTS.EVENT_NAME_UPPERCASED
. For example, we can listen for emoji reactions being added to
messages by electing to be notified on RTM_EVENTS.REACTION_ADDED
:
const { RtmClient, CLIENT_EVENTS, RTM_EVENTS, RTM_MESSAGE_SUBTYPES, WebClient } = require('@slack/client');
// SNIP: the initialization code shown above is skipped for brevity
rtm.on(RTM_EVENTS.REACTION_ADDED, (event) => {
// For structure of `event`, see https://api.slack.com/events/reaction_added
console.log(`User ${event.user} reacted with ${event.reaction}`);
});
The SlackDataStore
(and its implementaion SlackMemoryDataStore
) has been deprecated since
v3.15.0. Initializing the RtmClient
with the option dataStore: false
disables the data store,
and that is recommended.
The guide has been removed, but the reference documentation in the source is still available.
The maintainers highly recommend preparing your app to migrate if your app is using the data store.
The useRtmConnect
option on RtmClient
initialization can give you control of how much workspace
state you recieve when you connect and recieve the CLIENT_EVENTS.RTM.AUTHENTICATED
event.
If you set the value to false
(not recommended for large teams), you’ll recieve a cache the
relevent client state for your app. See the response in the
rtm.start
reference documentation for a description of
the workspace cache.
This should be treated as a cache because this information can go stale quickly if you aren’t listening for every event to update the state. The maintainers recommend instead storing only the state your app needs to operate in a variable, and then updating that variable in each event that might mutate that state. A simpler alternative is to fetch the state you need from the Web API whenever you need it.
The RtmClient
will retry sending any message that fails for a recoverable error. The policy is
configurable, but the default is retrying forever with an exponential backoff, capped at thirty
minutes but with some randomization. You can use the retryConfig
option to customize that policy.
The value is an options
object as described in the following library:
https://github.com/tim-kos/node-retry.
const { RtmClient } = require('@slack/client');
const token = process.env.SLACK_TOKEN;
const rtm = new RtmClient(token, {
retryConfig: {
// This would turn the retrying feature off
retries: 0,
},
});
The RtmClient
also logs interesting events as it operates. By default, the log level is set to
INFO
and it should not log anything as long as nothing goes wrong. You can adjust the log level
using the logLevel
option and use any of the
npm log levels.
You can also capture the logs without writing them to stdout by setting the logger
option. It
should be set to a function that takes fn(level: string, message: string)
.
const fs = require('fs');
const { RtmClient } = require('@slack/client');
// open a file to write logs
// TODO: make sure to call `logStream.end()` when the app is shutting down
const logStream = fs.createWriteStream('/tmp/app.log');
const token = process.env.SLACK_TOKEN;
logStream.on('open', () => {
const rtm = new RtmClient(token, {
// increased logging, great for debugging
logLevel: 'debug',
logger: (level, message) => {
// write to disk
logStream.write(`[${level}]: ${message}`);
}
});
});
If you need to send all your HTTP requests through a proxy, the RtmClient
class allows for you
to do this with the transport
option.
const { RtmClient } = require('@slack/client');
const { proxiedRequestTransport } = require('@slack/client/lib/clients/transports/request');
const { factory: wsTransportFactory } = require('@slack/client/lib/clients/transports/ws');
const wsTransport = factory(console.log.bind(console));
const rtm = new RtmClient(token, {
transport: proxiedRequestTransport('your proxy url'),
socketFn: function(socketURL) {
return wsTransport(socketURL, { proxyURL: 'your proxy url' });
}
});