Using Ably Realtime for a web based reservation system.

I'm getting a few errors in the js console consistently, though everything is working properly.

Basically, there is a date selector and when a visitor selects a date, I set a global variable day to the date (something like 2018-05-25) and call VisitorMessages.start(), which subscribes them to messages and makes them present on the channel visitor:2018-05-25, and unsubscribes them from all other channels.

I also have a visitor:all channel that everyone gets messages on and where presence is not needed.

Here's what I'm doing (please excuse the CoffeeScript):

VisitorMessages =
  realtime: null
  connected_first_time: false
  start: ->
    unless @realtime
      @realtime = new Ably.Realtime
        authUrl: '/auth'
        recover: (lastConnectionDetails, cb) ->
          cb(true)
          return

    @unsubscribe()

    dayChannel = @realtime.channels.get("visitor:#{day}")
    allChannel = @realtime.channels.get("visitor:all")

    allChannel.subscribe (m) ->
      switch m.name
        when " . . . "
          # . . .
    dayChannel.subscribe (m) ->
      switch m.name
        when " . . . "
          # . . .

    dayChannel.presence.subscribe 'enter', (member) -> VisitorMessages.setNumOnline(dayChannel)
    dayChannel.presence.subscribe 'leave', (member) -> VisitorMessages.setNumOnline(dayChannel)
    dayChannel.presence.enter()
    VisitorMessages.setNumOnline(dayChannel)

    @realtime.connection.on 'connected', ->
      VisitorMessages.refreshData() # not showing this function here
      VisitorMessages.connected_first_time = true # the refreshData() function returns if this is false
      dayChannel = VisitorMessages.realtime.channels.get("visitor:#{day}")
      dayChannel.attach()
      dayChannel.presence.enter()
      VisitorMessages.setNumOnline(dayChannel)
      allChannel = VisitorMessages.realtime.channels.get("visitor:all")
      allChannel.attach()

  setNumOnline: (channel) ->
    channel.presence.get (err, members) ->
      # I use ractive.js to manipulate the DOM
      ractive.set('number_online', members.length)

  unsubscribe: ->
    for channelName of VisitorMessages.realtime.channels.all
      unless channelName == 'visitor:all'
        channel = VisitorMessages.realtime.channels.get(channelName)
        channel.presence.leave()
        channel.presence.unsubscribe()
        channel.unsubscribe()
        channel.detach()
        VisitorMessages.realtime.channels.release(channelName)

In the js console, the visitor gets a bunch of these:

Ably: ConnectionManager.onChannelMessage() received message with different connectionSerial, but same message id as a previous; discarding

Sometimes this:

Ably: RealtimePresence._ensureMyMembersPresent(): Presence auto-re-enter failed: [c: Unable to enter presence channel (incompatible state); code=90001]

And, when switching to a different date (which sets the day and calls VisitorMessages.start(), they get this:

Channels.onChannelMessage(): received event for non-existent channel: visitor:2018-05-26

I know that this is probably because I am explicitly releasing the channel when switching days, but when I wasn't doing that, VisitorMessages.realtime.channels.all would contain all of the channels that I had ever joined, and I was still receiving messages for unsubscribed channels.

So, there are a lot of different things going on here, but can someone see some big picture flaw in my approach or help me to understand why these errors are happening? Again, everything works fine, but something is not right!

Thanks!


Here's the above code compiled into javascript:

var VisitorMessages;

VisitorMessages = {
  realtime: null,
  connected_first_time: false,
  start: function() {
    var allChannel, dayChannel;
    if (!this.realtime) {
      this.realtime = new Ably.Realtime({
        authUrl: '/auth',
        recover: function(lastConnectionDetails, cb) {
          cb(true);
        }
      });
    }
    this.unsubscribe();
    dayChannel = this.realtime.channels.get("visitor:" + day);
    allChannel = this.realtime.channels.get("visitor:all");
    allChannel.subscribe(function(m) {
      switch (m.name) {
        case " . . . ":
          // . . .
      }
    });
    dayChannel.subscribe(function(m) {
      switch (m.name) {
        case " . . . ":
          // . . .
      }
    });
    dayChannel.presence.subscribe('enter', function(member) {
      VisitorMessages.setNumOnline(dayChannel);
    });
    dayChannel.presence.subscribe('leave', function(member) {
      VisitorMessages.setNumOnline(dayChannel);
    });
    dayChannel.presence.enter();
    VisitorMessages.setNumOnline(dayChannel);
    return this.realtime.connection.on('connected', function() {
      VisitorMessages.refreshData();
      VisitorMessages.connected_first_time = true;
      dayChannel = VisitorMessages.realtime.channels.get("visitor:" + day);
      dayChannel.attach();
      dayChannel.presence.enter();
      VisitorMessages.setNumOnline(dayChannel);
      allChannel = VisitorMessages.realtime.channels.get("visitor:all");
      allChannel.attach();
    });
  },
  setNumOnline: function(channel) {
    channel.presence.get(function(err, members) {
      ractive.set('number_online', members.length);
    });
  },
  unsubscribe: function() {
    var channel, channelName, results;
    for (channelName in VisitorMessages.realtime.channels.all) {
      if (channelName !== 'visitor:all') {
        channel = VisitorMessages.realtime.channels.get(channelName);
        channel.presence.leave();
        channel.presence.unsubscribe();
        channel.unsubscribe();
        channel.detach();
        VisitorMessages.realtime.channels.release(channelName);
      }
    }
  }
};
1

There are 1 best solutions below

4
On BEST ANSWER

I'm an engineer at Ably.

No particular big pictuer, just a bunch of different things going on here.

Ably: ConnectionManager.onChannelMessage() received message with different connectionSerial, but same message id as a previous; discarding

This means the client lib is has received multiple copies of a message and is automatically deduplicating them. This is known to happen occasionally just after the client lib does a live comet->websocket upgrade. If it happens consistently, there may be something else going on -- contact us and we'll try and debug it live.

Channels.onChannelMessage(): received event for non-existent channel: visitor:2018-05-26

As you correctly noted, that's because you're releasing the channel. channels.release() is a rarely used feature that's really only useful for when a client attaches and detaches from so many channels that channels.all collectively starts taking a nontrivial amount of memory, so release() deletes them so they can be garbage collected. It isn't something that 99% of people will ever need to do or know about -- I don't think it's even included in our api docs. It should certainly never be used on a channel that isn't yet detached, that'll result in undefined behaviour.

When you call it in your code, the channel is indeed not yet detached, even though you've called detach(). Per the api docs for channel#detach(), it's an async operation -- it requests a detach from Ably, and puts the channel into the detaching state in the meantime. If it is necessary to release a channel (and clientside it almost never is), it should be done in the callback to detach() (or a once('detached') listener).

when I wasn't doing that, VisitorMessages.realtime.channels.all would contain all of the channels that I had ever joined

Yup, but that's fine, being in that object doesn't mean they're attached. You can show only attached ones by filtering those entries for ones whose state is 'attached'.

and I was still receiving messages for unsubscribed channels.

Unsubscribing is a local operation (it just synchronously removes the listener you added); if you want to stop the lib from receiving messages from the server you need to detach from the channel -- see the channels/messages docs

(If you mean your message listener is still being called after you call unsubscribe(), or the lib is still receiving messages on a channel that's in the detached state, neither of those should be possible -- either your unsubscribe code's broken (I don't know coffeescript too well I'm afraid), or the listener being called isn't for the channel you thought it was, or there's a bug in ably-js. Setting log: {level: 4} in the lib constructor will enable debug logging which can help you see what the lib is doing; and let us know if you need help analysing a log).

Ably: RealtimePresence._ensureMyMembersPresent(): Presence auto-re-enter failed: [c: Unable to enter presence channel (incompatible state); code=90001]

Normally, this means a channel tried to automatically re-attach and re-enter after becoming suspended (e.g. because you were disconnected from the internet for > 2 minutes), but was unable to, e.g. because the client is now using a token that doesn't have permissions to access that channel.

But in your case I'd guess it's noise from channels that you manually release()d, and were then left in limbo as they could never be told by the server that they've been detached.

(I've actually just filed a feature suggestion to have a guard on channels.release() to stop you doing it for channels in active states -- https://github.com/ably/docs/issues/437 ).