How we implemented in-browser alerts with Pusher, Rails and Pnotify

Today we're announcing the launch of a fun feature: In-browser notifications. Once enabled on the Notifications tab of the Project Settings page, you'll get a popup notification in the corner of the browser window when one of the events occurs that you've selected to be alerted about (like an error occurring, or a comment being added). As an added bonus, you can also choose to have a popup window appear on your desktop. The combination of Pusher and pNotify made this easy to implement -- here's how I did it.

First, start off by creating a Pusher account. Ok, sure, you could use something like Faye to build this, but hey, Pusher is awesome, so why not? :) With a Pusher account in hand, let's take a look at the client-side implementation.

After including the Pusher javascript and setting up a new Pusher instance with our Pusher key, I subscribe each user to his own presence channel, and then look for events on that channel:

presenceChannel = pusher.subscribe("presence-user-#{user_id}")
presenceChannel.bind "alert", (data) ->
  notice = new PNotify
    title: data.title
    text: data.text
    type: data.type || 'alert'
    icon: null
      desktop: true
      icon: ""
      tag: "#{data.title}:#{data.url}"

  if data.url
    notice.get().click ->
      if notice.state is 'open'

The desktop hash specified when creating the notice enables the desktop module support in pNotify, which uses the Notifications API to do the desktop notifications. If there's a URL associated with the alert, the notice is set up to load that URL when the popup is clicked, unless the click is a result of the popup being closed with the close button (in which case the notice.state is 'closing'). This click behavior works on both the in-browser popup and the desktop notification, opening the URL in a new tab. Now on to the server side...

We have a whole lot of notifications flying around here at Honeybadger, and we really don't want to be sending payloads to Pusher if you don't have a browser window open to our site. So when an event happens, we first check to see if you are on the site before sending the notification to Pusher. Our notification engine knows if you're using the site thanks to Pusher's webhooks. Whenever you join or leave your personal presence channel, Pusher's webhook hits our endpoint to let us know, and we keep a tally of who's around in a redis set:

Array(params[:events]).each do |e|
  next unless e[:name] == 'member_added' || e[:name] == 'member_removed'
  Redis.current.send(e[:name] == 'member_added' ? :sadd : :srem, "active_users", e[:user_id])

And finally, we use Pusher to send a message to you:

Pusher.trigger("presence-user-#{user_id}", "alert",
               text: event.message,
               url: event.url,
               title: event.title)

And with that, you can get a popup on your desktop as soon as an error happens in your Rails app, and that's pretty cool. :)