Examples

Switchboard allows developers to develop a wide variety of clients. This section will point you towards existing example clients, as well as walk you through how to write your own client in Ruby.

Switchboard’s first role was sending new email push notifications to the Spatch iOS app. Due to mobile process backgrounding restrictions and battery abuse, it’s difficult to monitor for new emails client-side. Instead, the Spatch app securely posts users’ OAuth tokens to a server running Switchboard, which begins to monitor the accounts for incoming emails. When a new email arrives, Switchboard notifies a subscribed worker, which then sends down a push notification to the app.

In this case, Switchboard handled creating the IMAP connections, keeping tabs on new emails coming down, and giving the worker the data that it needed to send down a nicely formatted push notification.

Python Worker/Client and Examples

There is a Switchboard worker/client Python implementation. The repository’s README explains how to get connected to Switchboard, and run the examples.

The examples include workers for:

  • Sending Apple push notifications to an iOS app as new emails arrive.
  • Sending a text message via Twilio as new emails arrive.

Javascript Worker/Client

Since the Switchboard protocol uses JSON over WebSockets, it’s only fitting for there to be a JavaScript client. Switchboard comes packaged with one.

To try the client out, start the Switchboard application and point your browser at http://192.168.50.2:8080/jsclient - it should display a page with some getting started commands. Open your browser’s javascript console and try them out. The sourcefile contains comments mapping out common components of a Switchboard client, and is a great reference for understanding the interfaces.

Ruby Sample App Tutorial - Email Dropbox Uploader

Follow this example tutorial to build your first Switchboard client in Ruby. This simple example, to be run in the command line, syncs attachments from a user’s emails to his or her Dropbox.

Setup

First off, make sure switchboard is running on your local server (see Switchboard Installation for details)

After the Switchboard server is up, you’re ready to start building your Ruby client so you can communicate with it.

To get started, create the following Gemfile:

source 'https://rubygems.org'
ruby '2.1.1' # we haven't tested other versions yet. Let us know if you have any issues

gem 'faye-websocket'
gem 'Dropbox-sdk'

faye-websocket is a pretty handy gem extracted from the faye project, which provides classes for easily building WebSocket servers and clients in Ruby. Dropbox’s ‘ruby SDK’ is well developed and documented, which will make it simple to upload files to your Dropbox.

The initial client

The beginnings of the client come straight from the faye-websocket documentation, it’s pretty easy to set up:

awesome-Dropbox-uploader.rb

require 'faye/websocket'
require 'eventmachine'
require 'json'

EM.run {
  ws = Faye::WebSocket::Client.new('ws://192.168.50.2:8080/clients') # this tutorial uses the default switchboard url -> read below for an explanation.

  ws.on :open do |event|
    p [:open]
  end

  ws.on :message do |event|
    p [:message, JSON.parse(event.data)]
  end

  ws.on :close do |event|
    p [:close, event.code, event.reason]
    ws = nil
  end
}

The Faye::Websocket::Client takes an url to listen to, so you can use the default local Switchboard url. The client responds to the usual websocket methods, which currently output notifications/data to the console upon being called. When the client receives a message from the server, it’ll parse it for later use.

If you execute ruby your-file-name.rb (we’ll call it awesome-Dropbox-uploader.rb for the rest of the tutorial) you’ll see:

[:open]

…and nothing else. This is because without adding an account to Switchboard it doesn’t know what to send your way. So close your client for the moment (Ctrl + c, upon which you’ll see a closing message) and move on to the next section: adding an account.

Adding/listening to an account

The Switchboard client protocol is currently a subset of JMAP, with data encoded in UTF8 JSON. Clients can issue commands in the form [["methodName", {options}]], using the send method. This example uses Switchboard’s plain auth:

ws.on :open do |event|
  p[:open]

  ws.send '[
              ["connect",
                {"host": "imap.gmail.com",
                  "port": 993,
                  "auth": {"type": "plain",
                            "username": "YOUR EMAIL",
                            "password": "YOUR PASSWORD"
                          }
                }
              ]
            ]'
end

This time, if you run our client (ruby awesome-Dropbox-uploader.rb) it’ll automatically connect, and you should see the message

[:message, [["connected", {}]]]

Although you’re now connected there’s one last thing you’ve got to do before you’ll start getting emails through. We need to tell Switchboard which mailbox to listen to. That’s easily done with the watchMailboxes command, which takes as argument a list of mailboxes.

ws.send '[["watchMailboxes", {"list": ["INBOX"]}]]'

The server will respond with [:message, [["watchedMailboxes", {}]]]

If you restart your client now, when you receive an email Switchboard will automatically send through the email’s messageId and its mailboxId:

["newMessage", {"mailboxId"=>"INBOX!671882951", "messageId"=>"INBOX!671882951?2743"}]

By now you probably have noticed that messages sent from the server take the following form:

[["messageName", {params}]]

You can use this to make what’s output to your console when you receive messages a little more specific:

ws.on :message do |event|
  m = JSON.parse(event.data).flatten!

  case m[0] #using the method name to decide what to do
    when "connected"
      puts 'connection made'
    when "idling"
      puts 'idling away'
    when "newMessage"
      puts 'new message'
      puts "#{m[1]}" #the message content
    else
      puts "#{m}"
  end
end

Hardcoding an email and password is pretty simplistic, so let’s change that to enable you to pass them in through the command line:

puts "let's add an account to switchboard"
puts 'what is your email?'
email = gets.strip
puts 'what is your password?'
password = gets.strip

You’ll need to interpolate the values into your command to the server (this means having to escape a lot of double quotation marks):

"[[\"connect\", {\"host\": \"imap.gmail.com\",
                \"port\": 993,
                \"auth\": {\"type\": \"plain\",
                            \"username\": \"#{email}\",
                            \"password\": \"#{password}\"
                          }
              }
]]"

Now when you run your ‘awesome-Dropbox-uploader’ you’re prompted to fill in your Gmail username and password, then Switchboard automatically connects and listens to your inbox for you. Pretty sweet!

So now your client’s in pretty good shape. Apart from one pretty crucial thing - you’re not retrieving any attachments to upload. Whoops.

In order to retrieve an attachment you need to use the messageId you get upon receiving an email to make another call to Switchboard, retrieving the specific email information you need. You can do this with getMessages, which takes as arguments message ids and the email properties you’re looking for, from subject to textBody.

Retrieving the attachments directly is still a work in progress, but we can get around that by retrieving the raw email data in order to parse it in your client:

ws.on :message do |event|
  m = JSON.parse(event.data).flatten!

  case m[0]
    .
    .
    .
    when "newMessage"
      puts 'new message'
      message_id = m[1]["messageId"]
      ws.send "[
                 [\"getMessages\", 
                  {
                    \"ids\": [\"#{message_id}\"], 
                    \"properties\": [\"raw\"]
                  }
                ]
              ]"
    else
    .
    .
    .
  end
end

Now you have the raw email data. You can use the handy mail gem to parse it and retrieve your attachments. After adding and bundling the gem, you’re ready to add a new case to the message method:

ws.on :message do |event|
  m = JSON.parse(event.data).flatten!

  case m[0]
    .
    .
    .
    when "messages"
      # first the client needs to retrieve the raw data from the server response
      raw_msg = m[1]["list"][0]["raw"]
      # then parse it with the mail gem and retrieve the attachments
      msg = Mail.new(raw_msg)
      attachments = msg.attachments
      puts "#{attachments}"
    else
    .
    .
    .
  end
end

Attachments are returned in an array. If an email has no attachments, Switchboard will return an empty array. If you’re interested, an attachment looks something like this:

[#<Mail::Part:2159230880, Multipart: false, Headers: 	<Content-Type: image/jpeg; name="swag.jpg">, 	<Content-Transfer-Encoding: base64>, <Content-	Disposition: attachment; filename="swag.jpg">, 	<X-Attachment-Id: f_hw297rj60>>]

Dropbox authentication

Now the client is retrieving your email attachments, the next step is to set up the Dropbox uploader. First off is authentication.

Setting up Dropbox OAuth is incredibly easy, and well documented. It is a little awkward to use though, due to it being split into two parts (authorising the app and then pasting in a confirmation code to finish the process).

Here’s a quick implementation taken from the Dropbox docs (you’ll need to register your app on the app console here to get a key and secret first).

APP_KEY = 'SOME KEY'
APP_SECRET = 'SOME SECRET'

flow = DropboxOAuth2FlowNoRedirect.new(APP_KEY, 	APP_SECRET)

authorize_url = flow.start()

puts '1. Go to: ' + authorize_url
puts '2. Click "Allow" (you might have to log in 	first)'
puts '3. Copy the authorization code'
print 'Enter the authorization code here: '
code = gets.strip

access_token, user_id = flow.finish(code)

You can implement a simple Dropbox uploader using the access token stored when you authenticated with Dropbox, passing in the attachments from your emails:

class DropboxUploader
  class << self
    def upload_file(attachments, token)
      attachments.each do | attachment |
        filename = attachment.filename
        begin
          file = attachment.body.decoded
          client = DropboxClient.new(token)
          client.put_file("/#{filename}", file)
        rescue => e
          puts "Unable to save data for #{filename} because #{e.message}"
        end
      end
    end
  end
end

Add the uploader to your messages method and you’re good to go:

ws.on :message do |event|
  m = JSON.parse(event.data).flatten!

  case m[0]
  .
  .
  .
    when "messages"
      .
      .
      .

      DropboxUploader::upload_file(attachments, access_token)
  .
  .
  .
  end
end

And there you have it. A quick command line application that syncs your email attachments with your Dropbox, and shows off the awesome power of Switchboard.

Tidying up

You can start extracting out the code into classes to neaten things up a bit, for example:

class SwitchboardClient
  class << self
    def open(ws, email, password)
      ws.send authorisation(email, password)

      ws.send '[["idle", {"list": ["INBOX"]}]]'
    end

    def authorisation(email, password)
      "[[\"connect\", {\"host\": \"imap.gmail.com\",
                 \"port\": 993,
                 \"auth\": {\"type\": \"plain\",
                         \"username\": \"#{email}\",
                         \"password\": \"#{password}\"
                                }
                    }
      ]]"
    end

    .
    .
    .

  end
end

And then you can call:

EM.run do
  ws = Faye::WebSocket::Client.new('ws://192.168.50.2:8080/clients')

  ws.on :open do |event|
    SwitchboardClient::open(ws, email, password)
  end

  ws.on :message do |event|
    SwitchboardClient::message(ws, event)
  end

  ws.on :close do |event|
    SwitchboardClient::close(event)
  end
end

Follow this example tutorial to build your first switchboard client in Ruby. This simple example, to be run in the command line, syncs attachments from a users’ emails to his or her Dropbox.