           (when (notify-rules/notify? uid rules new-message)
             (if (online? uid)
               (chsk-send! uid [:chat/notify-message new-message])
-              (-> (email/create-message [(db/get-thread (new-message :thread-id))])
+              (-> (email/create-message
+                    [(db/get-thread (new-message :thread-id))])
                   (assoc :subject "Notification from Braid")
                   (->> (email/send-message (db/user-email uid)))))))))))
 
+;; Handlers
+
+(defmethod event-msg-handler :chsk/ws-ping
+  [ev-msg]
+  ; Do nothing, just avoid unhandled event message
+  )
+
+(defmethod event-msg-handler :chsk/uidport-open
+  [{:as ev-msg :keys [event id ring-req]}]
+  (when-let [user-id (get-in ring-req [:session :user-id])]
+    (broadcast-user-change user-id [:user/connected user-id])))
+
+(defmethod event-msg-handler :chsk/uidport-close
+  [{:as ev-msg :keys [event id ring-req]}]
+  (when-let [user-id (get-in ring-req [:session :user-id])]
+    (broadcast-user-change user-id [:user/disconnected user-id])))
 
 (defmethod event-msg-handler :chat/new-message
   [{:as ev-msg :keys [event id ?data ring-req ?reply-fn send-fn]}]

(let [{:keys [ch-recv send-fn ajax-post-fn ajax-get-or-ws-handshake-fn
              connected-uids]}
      (sente/make-channel-socket! sente-web-server-adapter
                                  {:user-id-fn (fn [ob]
                                                 (get-in ob [:session :user-id]))})]
  (def ring-ajax-post                ajax-post-fn)
  (def ring-ajax-get-or-ws-handshake ajax-get-or-ws-handshake-fn)
  (def ch-chsk                       ch-recv)

    (if (db/with-conn (db/user-in-group? user-id (?data :group-id)))
      (let [data (assoc ?data :inviter-id user-id)
            invitation (db/with-conn (db/create-invitation! data))]
        (if-let [invited-user (db/with-conn
                                (db/user-with-email (invitation :invitee-email)))]
          (chsk-send! (invited-user :id) [:chat/invitation-received invitation])
          (invites/send-invite invitation)))
      ; TODO: indicate permissions error to user?
      (timbre/warnf
        "User %s attempted to invite %s to a group %s they don't have access to"
        user-id (?data :invitee-email) (?data :group-id)))))

(defmethod event-msg-handler :chat/invitation-accept
  [{:as ev-msg :keys [event id ?data ring-req ?reply-fn send-fn]}]
