"Is typing..." notification


#1

Hey everybody,
I’m developing a chat application with Meteor and try now to implement a “Is typing…” notification. My problem is, that I don’t know the way how I should implement it.

At the moment, I’ve this two ideas:

1: Saving the state with timestamp to Collection

Chats:

{
  _id:abcdef....
  users: [
{  
  id:abc
  isTyping: 1441627753114 //Server Timestamp
}
]
}

2: Using a Socket.IO connection and sending events with timeout - but that means, that I’ve to use a second connection for each user (Meteor Instance and Socket.IO Instance) - also it is possible, that the client doesn’t support web sockets.

That are my 2 ideas how to implement it. Are there any other “better” ways to implement that function?


#2

You could use viewmodel for that, namely the value binding.


#3

Likely you have a collection already with friends. So there is a user record for the friend. In that collection you can add a field.

friends 
[
  {name: "John Doe", isTypingTo: "yourUserId"},
  {name: "Jane Doe other friend", isTypingTo: ""},
]

Now when a user starts typing you just update his record and it will show up. Will save you from an additional collection.


#4

Don’t use collections for this. Its useless data that shouldn’t touch your backend.

Things like https://atmospherejs.com/yuukan/streamy or similar allow you to access the underlying websocket connection that all meteor clients have.

Then just send messages around.


#5

Agreed with @ahref, it’s unnecessary to write this to db and would become a performance issue fast when scaling. Another alternative would be to write it all yourself using streams, so feel free to use what fits you more: http://arunoda.github.io/meteor-streams/


#6

Thanks for that link looks interesting!


#7

Agree on some kind of streams implementation for performance. Makes your stack more complex though so would start with a simple update of the already available collection and later “upgrade” to a stream solution unless you are already expecting performance issues.


#8

Actually everything required is already in your stack. Your clients maintain a websocket connection to the server. Use it for realtime messages, don’t use collections.

Implementing a collection level handler for this feature is just bad practice and will still lead to additional code not to mention unnecessary template re-runs.

Why not finish the rest of the app before you worry about syncronised UX experiences.


#9

@ahref Thank you for the plugin recommendation. I’ve now implemented the “streamy” plugin, works fine, only problem now is when a user has multiple instances running (f.e. smartphone and web browser), then only the latest will get a notification (I save the socket id as a single value to my user profile). Think a solution would be if I save all socket ids of a user to an array in my user collection. But at this time, I don’t need it :smile:.


#10

You need use session id (or socket id) as a key instead of user id


#11

Hi XTA,

Can you share the solution you implemented? I’m also having the same feature in my app.


#12

@nutanlade Yeah, I’ve used the “streamy plugin”.

This are my events and functions in “chat.js”:

Event:

'keydown #myMessage': function () {

Streamy.sessions(users[0].details().profile.socket_session).emit('typing', {});

if (typing == false && $("#myMessage").is(":focus")) {
    Streamy.sessions(users[0].details().profile.socket_session).emit('typing', {});
    typing = true;
}
else {
    clearTimeout(timeout);
    timeout = setTimeout(timeoutFunction, 2000);

}
}

I write the socket id of every user into his document (on every app visit/login).

To have a timeout for the typing message, I’ve created this function:

function timeoutFunction() {
typing = false;
Streamy.sessions(users[0].details().profile.socket_session).emit('typingStop', {});

}

And here are the 2 streamy events, that I’ve created:

Streamy.on('typingStop', function (d, s) {
$("#typing").css("display","none");
});

Streamy.on('typing', function (d, s) {
$("#typing").css("display","block");
});

#13

Does streams really work for this? Last I knew streams didn’t scale to multiple instances properly. Also what happens when you have several people in a chat conversation and you need to show the typing message to each of them? I don’t think a couple small updates to a field on a collection is going to be a huge performance bottleneck.

If you take a look at my socialize:messaging package it uses a participants collection which has a record for each participant in the conversation and a field called typing. Using the provided typing subscription you can subscribe when typing begins and it will update typing field of the participant record the current user, then you can stop the subscription when typing stops and it will update the record again.

Template.conversation.events({
    'keydown textarea': function(event, template) {
        if(!template.typingSubscription){
            template.typingSubscription = template.subscribe("typing", this._id);
        }

        if(template.typingTimeout){
            Meteor.clearTimeout(template.typingTimeout);
        }

        template.typingTimeout = Meteor.setTimeout(function() {
            template.typingSubscription.stop();
            template.typingSubscription = null;
            template.typingTimeout = null;
        }, 3000);
    },
    'submit form': function(event, template) {
        if(template.typingSubscription){
            template.typingSubscription.stop();
            template.typingSubscription = null;
        }

        if(template.typingTimeout){
            Meteor.clearTimeout(template.typingTimeout);
            template.typingTimeout = null;
        }
    }
});

This allows you to display the typing status to multiple users that are participating in the conversation even if they are all connected to many different instances of your application.


#14

Storing such details in a collection is a big mistake. You don’t want potentially thousands of database ops because a thousand users are typing.

While I Reccomeneded streamy(Looks to be more active atm) it looks like RocketChat is using a Meteor Stream

Of particular note in the Meteor Streams Documentation is:

With Meteor Streams, you can communicate between

client to clients
server to clients
client to server
server to servers

Ideal for stuff like this. RocketChat looks to use streams extensively :smile:


#15

Could you explain how exactly you did this:

I write the socket id of every user into his document (on every app visit/login).


#16

@atez

Meteor.startup(function() { 

 Tracker.autorun(function (tracker) {

        if (Meteor.userId() && Streamy.id()) {


            Meteor.users.update({_id: Meteor.userId()}, {$set: {'profile.socket_session': Streamy.id()}});
            tracker.stop();
        }

    })
});

#17

Hi, this is a similar question to what I tried to check once. For us it was and is best to use collections but not storing to database, just publish the data to a local mini mongo collection but only change the one and only record. See MeteorPads in the comment. Maybe this is a good decision for you too.

In addition have a look at this stack exchange question and my (self) answer at the end: http://stackoverflow.com/questions/31180799/meteor-get-all-subscriber-session-handles-for-a-publisher-method

This allows you to post to any subscriber if you need.

Cheers
Tom