#Slack |> App |> Smee |> My Local App |> #Slack Reply : How to interact when you’re behind Firewall/Proxy

#Slack |> App |> Smee |> My Local App |> #Slack Reply : How to interact when you’re behind Firewall/Proxy

2019-05-24 0 By Nordes

Introduction

All I wanted to do is simply have a way to connect to my local server on the network and then when receiving a specific request from #Slack. Once received act on it, do a some background work or send reports. This server is obviously not exposed on the internet and reside behind a firewall/proxy. For that reason, I had to be a bit more creative and think out of the box.

In the past I resolved this by using a WebSocket to an Heroku WebSite. It works but it was not perfect. So, I started to look around for a better way to stream a service where I a simple Webhook would be forwarded. In that manner, I could watch a service where it send me back the data. I recently came across Smee.io which use the Server Side Events (SSE) but as a streaming service on the HTTP port. Following is a story of how to make all that working together.

Before going further, I would have loved a solution like
ngrok, but it’s not possible behind a proxy/firewall such as where I work.

Step 1. Grab a new Smee URL

To get the URI, it’s deadly simple. Simply go on Smee.io and then click on create the big button in order to create a new Webhook. Keep the Webhook URL for the next step.

Step 2. Configure the #Slack App/Bot

In that App, we will setup a bot, a hook and a bot for commands.

Configure the App

Open your #Slack page and add an App and if you don’t know how, simply go on https://api.slack.com/ and click on Start Building button and fill the form. It should look like this:

Once you have created your app, you will need to enable “Bot” and give him a name, and then go into the Create a new command page and then add a new command like the following:

Install your app

For that, you go for example in OAuth & Permissions and click install app To Workspace. (Note, you might also have to add a permission File.Upload.User)

You can see the beginning of the token. The 4 first characters indicate what kind of token it is. (Documentation in slack documentation exists for more details)

Step 3. Keep it simple with a sample app in NodeJS or Dotnet Core

Due to some missing implementation in the original repository/server, I’ve added what’s missing in my fork within GitHub. I then use my Heroku account to generate automatically my Docker container with the service. In the future, the issues should be fixed (PR currently waiting to be approved).

The main issue with the Smee.IO website is that it does not yet forward the content if it’s using x-www-form-urlencoded. If you use my branch, for now you will be ok. You can even use it for building your docker service on heroku.

Sample receiving data using NodeJS (index.js + smee-client & express packages)

const SmeeClient = require('smee-client')

/// Smee client (websocket)
const smee = new SmeeClient({
  source: 'https://smee.io/5sFasdasdukH5VQidvpGv', // In case Smee is not updated, please use my repository + an heroku deployment. It is that easy.
  target: 'http://localhost:3000/events',
  logger: console
})
const events = smee.start()
/// WebServer
const express = require('express');
const app = express();
const router = express.Router();

app.use(express.json()) // Instead of body-parser
app.use(express.urlencoded()) // Instead of body-parser
app.use(function(req, res, next) { // Allow CORS if you want.
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

console.log(app)

// from top level path e.g. localhost:3000, this response will be sent
app.get('/', (request, response) => response.send('Hello World'));
app.use('/events', router);

router.post('/', (request, response, next) => {
  console.log(request.body) // Raw data
  response.end();
});

app.listen(3000, () => console.log('Listening on port 3000'));

Sample receiving data using Dotnet Core

NuGet Package: Smee.IO.Client (That library simply do the SSE streaming for smee.io event types for you. So, if you want to use it for different SSE messages types, you will have to adapt the code).

The following code is also available in my Github repo (Demo).

using System;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace Smee.IO.Client.ConsoleDemo
{
    class Program
    {
        private static CancellationTokenSource source;

        static async Task Main(string[] args)
        {
            source = new CancellationTokenSource();
            var token = source.Token;

            // This could also be done automatically, but since we expect some developer to test, let's
            // keep it this way.
            Console.WriteLine("Hi there, please get a valid url on https://smee.io/new (or your own server. In case of this demo, I will use my own instance)");
            Console.Write("Please enter a valid URI because no validation will be made: ");
            var smeeUri = new Uri(Console.ReadLine());

            Console.ForegroundColor = ConsoleColor.DarkRed;
            Console.WriteLine(" > Hit CTRL-C in order to stop everything.");
            Console.WriteLine();
            Console.ResetColor();

            var smeeCli = new SmeeClient(smeeUri);
            smeeCli.OnConnect += (sender, a) => Console.WriteLine($"Connected to Smee.io ({smeeUri}){Environment.NewLine}");
            smeeCli.OnDisconnect += (sender, a) => Console.WriteLine($"Disconnected from Smee.io ({smeeUri}){Environment.NewLine}");
            smeeCli.OnMessage += (sender, smeeEvent) =>
            {
                Console.Write("Message received: ");
                Console.ForegroundColor = ConsoleColor.DarkYellow;
                Console.Write(JsonConvert.SerializeObject(smeeEvent)); // This is a typed object.
                Console.ResetColor();
                Console.WriteLine();
                Console.WriteLine();
            };
            smeeCli.OnPing += (sender, a) => Console.WriteLine($"Ping from Smee{Environment.NewLine}");
            smeeCli.OnError += (sender, e) => Console.WriteLine($"Error was raised (Disconnect/Anything else: {e.Message}{Environment.NewLine}");

            Console.CancelKeyPress += (sender, eventArgs) =>
            {
                source.Cancel();
                eventArgs.Cancel = true;
            };

            await smeeCli.StartAsync(token);
            Console.WriteLine("Finish executing. Thank you!");
        }
    }
}

Example of what you can see in Smee.io console

Slack Webhook call (Command)

The command in the previous capture is using a different command, don’t worry. Anything will do.

If the payload is empty, it’s because the content type is application/x-www-form-urlencoded (Current limitation on Smee.io). If you use my branch, as said earlier, you will have the proper data.

We can receive, now let’s send

For this part, you have a few option. I will be using the Dotnet implementation from now on. However, know that you can either use a library or use raw HTTP command using the Slack API. Basically, you will do either HTTP post to a specific URL or to the response_url. The response_url is not something that stays forever. As of now, it stays 30 minutes alive, after that, normally, it won’t be usable. It can be useful to make a small dialog with options that needs to be selected. Here we will now focus to send a simple message back to say “Message received!”.

In Dotnet, but also in NodeJS, some people already have written some libraries (e.g.: SlackAPI). It’s up to you to use them. The following code will be using a raw lazy HTTP post. For that, let’s replace smeeCli.OnMessage += ... by the following (not fully completed, but you get the idea):

smeeCli.OnMessage += (sender, smeeEvent) =>
{
    IncomingMessage msgBody = null;
    if (e.Data.Body != null)
    {
        msgBody = (e.Data.Body as JObject)?.ToObject<IncomingMessage>();
    }

    // If it's for command listener, you could use a strategy pattern.
    // Some code is not in previous block, but you can easily find out what to put in order to make
    // it work.
    _slackHttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _slackConfiguration.BotOAuthToken);
    var msg = new
    {
        token = _slackConfiguration.BotOAuthToken,
        channel = msgBody.UserId,
        // response_type = "ephemeral", // See doc.
        text = "Message received!"
    };
    var content = new StringContent(JsonConvert.SerializeObject(msg), Encoding.UTF8, "application/json");
    var result = await _slackHttpClient.PostAsync("chat.postMessage", content).ConfigureAwait(false);
    // https://api.slack.com/methods/im.open/test (open)
    // send message to specific channel...
    // https://api.slack.com/methods/im.close/test (open)
    // Conversation could be created, but for now let's consider no conversation.
    // e.g.: https://api.slack.com/dialogs#select_elements <== create a select box...
};

What’s next?

  • Basically, if you wish, you could also link yourself with Luis.AI, TensorFlow or anything in order to make a Chat Bot more alive and more intelligent.
  • Create some command to interact with your servers
  • Link your slack account with some of your service in order to be notified when some jobs are completed for example.
  • Link/Sync/Send stuff between your #Slack and your MS Team
  • …the sky is the limit…

The limits are actually your limits and how secure you want your stuff to be (as usual).

Conclusion

I hope you’ve enjoyed that article ;). I don’t have much time recently, so it’s hard to write more often.