20

Is there a way to detect when a client disconnects from a meteor server, either by refreshing or navigating away from the page, so that the server can attempt some cleanup?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
greggreg
  • 11,945
  • 6
  • 37
  • 52

5 Answers5

19

One technique is to implement a "keepalive" method that each client regularly calls. This assumes you've got a user_id held in each client's Session.

// server code: heartbeat method
Meteor.methods({
  keepalive: function (user_id) {
    if (!Connections.findOne(user_id))
      Connections.insert({user_id: user_id});

    Connections.update(user_id, {$set: {last_seen: (new Date()).getTime()}});
  }
});

// server code: clean up dead clients after 60 seconds
Meteor.setInterval(function () {
  var now = (new Date()).getTime();
  Connections.find({last_seen: {$lt: (now - 60 * 1000)}}).forEach(function (user) {
    // do something here for each idle user
  });
});

// client code: ping heartbeat every 5 seconds
Meteor.setInterval(function () {
  Meteor.call('keepalive', Session.get('user_id'));
}, 5000);
ty.
  • 10,924
  • 9
  • 52
  • 71
debergalis
  • 11,870
  • 2
  • 49
  • 43
  • After much searching i think this is the best solution for now. Thanks!! – greggreg Apr 24 '12 at 05:38
  • 6
    This is almost pseudocode, as in it's not functional as is: the first setInterval has no interval specified. Also the Connections.update command doesn't specify to update {'user_id':user_id}. There may be other errors. It's a fine start though. – Luke Stanley Sep 08 '12 at 16:53
  • 3
    @debergalis Is this still the recommended way to see if clients are dead? – user2602152 Jan 19 '14 at 12:46
  • 2
    @debergalis I'd like to know if you'd still recommend this method – Ayrton Senna Jun 25 '15 at 00:43
13

I think better way is to catch socket close event in publish function.

Meteor.publish("your_collection", function() {
    this.session.socket.on("close", function() { /*do your thing*/});
}

UPDATE:

Newer version of meteor uses _session like this:

this._session.socket.on("close", function() { /*do your thing*/});
Nearpoint
  • 7,202
  • 13
  • 46
  • 74
poordeveloper
  • 2,272
  • 1
  • 23
  • 36
  • That's great. But then I apparently run into this problem: http://stackoverflow.com/questions/10192938/meteor-code-must-always-run-within-a-fiber-when-calling-collection-insert-on-s – huyz Sep 28 '12 at 08:53
  • Thanks. This is the answer to my question: [Meteor observe running forever](http://stackoverflow.com/q/12902392/599991) – zVictor Oct 16 '12 at 17:29
  • 3
    I use meteor 0.6.1 and for this line `this.session.socket.on("close", function() { /*do your thing*/});` my server returns _TypeError: Cannot read property 'socket' of undefined_ But when I correct it to `this._session.socket.on("close", function() { /*do your thing*/});` it works great, thanks – fantom Apr 13 '13 at 18:04
  • In 0.6.3.1 it's not 100% reliable, sadly. – Hubert OG Jun 05 '13 at 07:51
  • 5
    How come the Meteor docs doesn't say anything about this or sockets? I couldn't find any mention of _session variable or sockets? – Nearpoint Oct 05 '13 at 22:20
2

I've implemented a Meteor smart package that tracks all connected sessions from different sessions and detects both session logout and disconnect events, without an expensive keepalive.

https://github.com/mizzao/meteor-user-status

To detect disconnect/logout events, you can just do the following:

UserStatus.on "connectionLogout", (info) ->
  console.log(info.userId + " with session " + info.connectionId + " logged out")

You can also use it reactively. Check it out!

EDIT: v0.3.0 of user-status now tracks users being idle as well!

Andrew Mao
  • 35,740
  • 23
  • 143
  • 224
1

if you're using Auth you have access to the user's ID in Method and Publish functions, you could implement your tracking there.. e.g. you could set a "last seen" when the user switches room:

Meteor.publish("messages", function(roomId) {
    // assuming ActiveConnections is where you're tracking user connection activity
    ActiveConnections.update({ userId: this.userId() }, {
        $set:{ lastSeen: new Date().getTime() }
    });
    return Messages.find({ roomId: roomId});
});
Lloyd
  • 8,204
  • 2
  • 38
  • 53
-1

I'm using Iron Router and call my cleanup code on the unload event of my main controller. Sure this will not catch the event of a tab closing, but still feels good enough for many use cases

ApplicationController = RouteController.extend({
    layoutTemplate: 'root',
    data: {},
    fastRender: true,
    onBeforeAction: function () {
        this.next();
    },
    unload: function () {
        if(Meteor.userId())
            Meteor.call('CleanUpTheUsersTrash');
    },
    action: function () {
        console.log('this should be overridden by our actual controllers!');
    }
});
Loupax
  • 4,728
  • 6
  • 41
  • 68