Skip to content

presence channel + multiple tabs #96

robj opened this Issue Feb 23, 2013 · 6 comments

2 participants

robj commented Feb 23, 2013

If a user enters a presence channel multiple times (eg. multiple tabs), closing just one of those tabs fires a 'member_removed'.

This is not the behaviour when using Pusher, ie.

The pusher:member_removed is triggered when a user leaves a channel. It's quite possible that a user can have multiple connections to the same channel (for example by having multiple browser tabs open) and in this case the events will only be triggered when the last one is closed.


we're aware of how pusher treats multiple connections to the same presence channel from the same user. slanger handles this the same way. i checked using the example apps and pusher:member_removed is only fired when the last connection is closed.

how are you producing this behaviour. if there is something amiss, of course i am happy to fix it.

robj commented Feb 24, 2013

I tracked down the cause of the issue as follows:

Slanger requires the user_info hash to stay consistent, whereas does not.

ie. I have this in my PusherController auth action

    :user_info => { # => optional - for example
      :id =>, #repeat as easier to pass hash to backbone/roster template
      :name =>,
      :joined_at =>,
      :profile_image_small => current_user.profile_image_small,

'joined_at' is used to render a roster client side sorted in order that the user joined the channel ( just to make sure I also tested adding :random => SecureRandom.hex(4) to the hash, and it had the same effect )

If the user hash differs then multiple member_added/member_removed events are triggered in a presence channel when opening/closing multiple tabs/windows.


i see. how pusher determines if a user is new or the same as the another is unspecified in pusher's docs. this was an assumption i made when writing the code, not seeing the use case for the same user having multiple connections with non-identical user info objects. the code you're looking for is probably

i'll get to this in time, but you're more than welcome to contribute a fix yourself. if you want to do that, please discuss how you intend to implement your fix with @markburns and me beforehand, in order to ensure it can be merged with minimum friction.

robj commented Feb 25, 2013

I imagine there is a more concise way, but I have used the following code:

def user_subscribed?(subscriptions,channel_data)

  subscriptions.each do |sub|

     if sub[1]['user_id'] == channel_data['user_id']
       return true 





   #unless subscriptions.has_value? message['channel_data']
   unless user_subscribed?(subscriptions,message['channel_data'])


    #unless subscriptions.has_value? subscriber
    unless user_subscribed?(subscriptions,subscriber)

Subscriptions is a method so it doesn't need to be passed in as an argument. Try this:

def user_subscribed?(channel_data)
  subscriptions.any? do |_,user_info|
    user_info['user_id'] == channel_data['user_id']
robj commented Feb 25, 2013

great, thats a better way for sure, tested and working, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.