Slack is fully awesome. At wehkamp we use it for our internal communication and as a tool for our DevOps. The Slack API allows us to build even more advanced integrations. In this blog I’ll explore how to use the API to create stuff like a powerful progress indicator, just by updating a Slack message:

A powerful progress indicator by updating a Slack message.
How cool is this effect?
Imagine what you could build with this.

Promise the update

We use Axios as a promise-based HTTP client to connect to Slack. Each call needs an authentication-token and a channel ID (even for private messages). There are two end-points we are going to be using:

  • chat.postMessage – this will send a new message to the channel and give us a message identifier ts. We will store and reuse it to update the message.
  • chat.update – this will update the message that corresponds to the ts.

Let’s create a function that uses both end-points to send a message:

const axios = require("axios");

function sendMessage(token, channel, ts, msg) {

  token = encodeURIComponent(token);
  channel = encodeURIComponent(channel);
  msg = encodeURIComponent(msg);

  const action = ts ? "update" : "postMessage";
  const url = `https://slack.com/api/chat.${action}?token=${token}` +
              `&channel=${channel}&text=${msg}&as_user=true&ts=${ts}`;

  return axios.post(url).then(response => response.data.ts);
}

The function returns the message identifier in the form of a promise. The only thing we need to do is save the message identifier and reuse it when calling the function.

Asynchronous problems

The first problem I ran into had to do with messages not arriving in the same order that I expected. Sometimes I ended up with an earlier message as end-result. And sometimes I wanted to send an update for a message that had not even been created yet. If we want to work asynchronously we need to synchronize the way we send messages.

Sequence diagram showing the message flow.

Remember: only send 1 message at the time!

We need to build a class that will handle the process of sending messages. It will store the message identifier so we can update the message. The class should also make sure that only a single message is being send at the same time.

If a message is being send and a new message comes in, the class should store that new message and send it after the first message has been sent.

But what if a 3rd message comes in? We are only interested in sending the latest message, so we should only store the last message. A pattern like this prevents useless updates / excessive use of the Slack API. Be aware: rate limiting applies to the Slack API, so you might want to consider only updating the message once a second.

Class me!

So let’s look at the implementation:

const UpdatableMessage = class {

  constructor(token, channel) {
    this.token = token;
    this.channel = channel;
    this.ts = null;
    this.message = null;
    this.nextMessage = null;
    this.sending = false;
  }

  send(msg) {

    // don't send empty or the same message
    if (!msg || msg === this.message)
      return;

    // if a message is being send, set is as the next message
    if (this.sending) {
      this.nextMessage = msg;
      return;
    }

    this.sending = true;
    this.message = msg;

    sendMessage(this.token, this.channel, this.ts, msg)
      .catch(ex => console.log("Something went wrong: " + ex))
      .then(ts => {
        this.ts = ts || this.ts;
        this.sending = false;
        const msg = this.nextMessage;
        this.nextMessage = null;
        this.send(msg);
      });
  }
};

By implementing a simple Boolean that checks if a message is being send, we solve most – if not all – of our asynchronous problems.

This class is used in the bot-zero project to show an example of a progress indicator. Go check it out and let us know what you think.

Lucky Luke, a gunslinger known as the "man who shoots faster than his shadow".
Async problems feel like…

Want to know more about the use and the power of truthy / falsy values in JavaScript, check out this blog. More info about Promises can be found here. More on classes and class expressions here.