GeddyJS (Node.js+Socket.io framework) realtime option - javascript

I have a Geddy app that has some realtime models (I remember using -rt to generate some models), and I'd like to revisit the realtime-ness of my Geddy app.
I don't need my models to be updated automatically (I'm not sharing models with the client; I am using Geddy only as a REST backend)
But I would like to explicitly emit events through socket.io and use its room functionality in my controllers, and I'll handle those events in the client side appropriately.
So, my questions are: 1. how do I clean up my existing code in that I don't want realtime models in my app 2. what would I need to do in order to explicitly events from my controllers?
I tried doing the following in after_start.js as shown here: https://github.com/geddy/geddy/wiki/Realtime-and-MVC in "Realtime for existing projects" section, but none of the messages get logged...
console.log('Here 1');
geddy.io.sockets.on('connection', function(socket) {
console.log('Here 2');
socket.emit('hello', {message: "world"});
socket.on('message', function(message) {
console.log('Message!');
});
});
Any help is much appreciated. Thanks!

Just found out that the after_start.js was a workaround for the lack of events from the application to know when the server had actually started (so you would know when you could attach Socket.io). Now the geddy object in the worker process emits a 'started' event you can use:
http://geddyjs.org/reference#global
So set a listener in your init.js for that event, and set your RT code up in there.

Related

Broadcasting Logout

Someone posted something very similar to the following code on a thread about resources that I can no longer locate:
$scope.logout = function(){
var outRes = $resource("/admin/logout");
outRes.save({},
function () {
$scope.userObject.lastActivity_type = "Log out";
$rootScope.isLoggedIn = false;
$location.path("/login-register");
},
function errorHandling(err) {
console.log("Not cool, error");
});
}
It was super helpful for me on my assignment and seemed fairly recent so I was hoping to ask: could someone explain how to rewrite this with a broadcast method to let other users/every part of architecture know a user has logged out? The assignment's over and everything, I'm just curious
Assuming this is for Angular 1, broadcasting an event makes it visible to all child scopes of the scope in which it is broadcast. If you want it to be visible to all scopes, you should broadcast it in the root scope, as in this example:
$rootScope.broadcast('my-event');
Documentation is here if you want some more information.
EDIT: I note that you also asked about broadcasting to other users. That's a very different kettle of fish!
Angular is purely a front-end framework so it can't broadcast a message to another client. If you wanted something like a chat or notification system whereby users received notification of changes made by other users in real time, you'd probably want to use websockets via a library like Socket.io. You'd send some kind of notification to the server, either via websockets or an AJAX request, and then on the server that would trigger broadcasting the message to those connected clients that should receive it. You can then use the method described above to trigger a corresponding event in the root scope of your Angular code. I won't go into the details for this as there's loads of tutorials on doing this with Socket.io.

Changing a Meteor collection subscription for all clients

I am developing a webapp in which I'd need one client, associated with the admin, to trigger an event (e.g., a new value selected in a dropdown list) which in turns will tell all the other connected clients to change the subscription, possibly using a parameter, i.e., the new selected value.
Something along the lines of
Template.bid.events
"change .roles": (e, tpl) ->
e.preventDefault()
role = tpl.$("select[name='role']").val()
Meteor.subscribe role
Of course this works for the current client only.
One way I thought would be keeping a separate collection that points a the current collection to be used, so the clients can programmatically act on that. It feels cumbersome, thou.
Is there a Meteor-way to achieve this?
Thanks
In meteor, whenever you have a problem that sounds like: "I need to synchronize data across clients", you should use a collection. I realize it seems like overkill just to send one piece of data, but I assure you it's currently the path of least resistance.
There are ways you can expose pseudo-collections which don't actually write to mongo, but for your use case that really sounds like overkill - new Mongo.Collection is the way to go.
You can use streams to setup a simple line of communication between connected clients and the server. It doesn't store data in MongoDB. Just let all connected clients listen to a stream and switch subscriptions when a new message comes in with the subscription name. Make sure only your client associated to your admin can push messages to the stream.
Available package: https://atmospherejs.com/lepozepo/streams
Examples: http://arunoda.github.io/meteor-streams/

Keeping a client-side sync of Sails.js collection, using sockets

I very much like Meteor's pub/sub. I wonder if there is a way to get a similar workflow, using sails.js or just a socket library in general.
In particular, what I would like to be able to do is something along the lines of:
// Server-side:
App.publish('myCollection', -> collection.find({}))
// Client-side:
let myCollection = App.subscribe('myCollection')
let bob = myCollection.find({name: 'Bob'})
myCollection.insert({name: 'Amelie'}, callback)
All interaction with the server should happen in the background.
I very much like Meteor's pub/sub. I wonder if there is a way to get a similar workflow, using sails.js or just a socket library in general
Basically yes, at least about realtime sync between backend and frontend. Let's review what meteor's have and answer point by point.
Pub/sub
The Pub / Sub concept, as stated by Sabbir, is also supported by sails.js. Though the basics are slightly different :
In meteor, the client can subscribes to everything he wants, and the server control what it receives by only publishing to who he wants;
whereas in sails.js, the server both does subscribe some clients sockets and publish to all binded sockets
Note that, by default:
meteor contains the autopublish package that just notify every client without any kind of filtering. To acheive some filtering, you have to meteor remove autopublish then you can handle what will your client receive by adding a mongo request to it, like explained here.
sails by default, on its automatic "select" blueprints actions, auto-subscribes the calling socket to the events on the objects returned by the "select".
As a server-side conclusion:
Subscribe: just call findor findOne blueprint default action, through a socket (attaching some where filters or not) and your socket will automatically be subscribed to every events concerning returned objects => you don't have to code anything on the server, in most cases, for the Subscribe logic.
Publish: every blueprint default actions (create, update, destroy, add, remove) auto-publish to subscribed sockets => you don't have to code anything on the server, in most cases, for the Publish logic.
(Though, if you find yourself implementing some manual controller actions, sails API helps you publishing and subscribing easily)
Client handling
Therefore, with both meteor and sails, clients only receive what they're supposed to receive. Time for front-end now.
Philosophy
meteor in one hand, with it's isomorphic dimension, does provide a front-end connector by nature, exposing it's data-bound collections.
sails on the other hand, is front-end agnostic, and can be attacked by any http REST connector (JS or not), such as $http, $resource, or more advanced ones like Restangular.
Though, being aware of the complexity using raw sockets on their API (when it comes to session, CORS, CSRF and stuff), they developped a javascript socket.io wrapper called sails.io.js designed to be REST-like-over-socket, and just works like a charm.
Basically, The main difference is that meteor is one step higher-level than sails, because it provides the logic of syncing collections and objects.
All interaction with the server should happen in the background.
sails.io.js, the official front-end component, is just not that high-level. When it comes to Angular.js.
Though, you can find some community connectors that aim to, kinda, provide the same feature as mongo data-bound collections and objects. There is sails-resource, spinnaker or angular resource sails. I tried both of them, and I should say that I was disapointed. The abstraction level is so high that it just becomes annoying, IMHO. For example, with not-very-RESTful-friendly custom actions, like a login, it becomes very hard to adapt it for your needs.
==> I would advice to use a low-level connector, such as angularSails or (my prefered) https://github.com/janpantel/angular-sails, or even raw sails.io.js if you're not using Angular.
Edit: just foun a backbone version, by the sails' creator
It just works great, and believe me, the "keep my collection in sync with that socket" code is so ridiculous, that finding a module for this is just not worth it.
Some code please, stop talking
In particular, what I would like to be able to do is something along the lines of:
Server
Meteor
# Server-side:
App.publish('myCollection', -> collection.find({}))
Sails
//Nothing to do, just sails generate api myCollection
Client
Meteor
# Client-side:
myCollection = App.subscribe('myCollection')
Sails, with sails.io.js
(Here using lodash for convenience)
var myCollection;
sails.io.get('/myCollection').then(
function(res) {
myCollection = res.data;
},
function(err) {
//Handle error
}
);
sails.io.on('myCollection').function(msg) {
switch(msg.verb) {
case 'created':
myCollection.push(msg.data);
break;
case 'updated':
_.extend(_.find(myCollection, 'id', msg.id), msg.data);
break;
case 'destroyed':
_.remove(myCollection, 'id', msg.id);
break;
};
});
(I leave the find where and create to your imagination with [the doc])
All interaction with the server should happen in the background.
Well, Sails, only for angular, with sails ressources
I'm not pretty used to that process, so I leave you reading here or here, but once again I'd choose manual .on()method.
Since I asked this question, I've learned a few things and some new projects have popped up. I decided against sails.io, because when developing with React.js, most of the community's weight is behind webpack, but sails.io uses gulp. I realize these can be used together and there is even an npm package for this, but I wasn't too keen on making my stack bigger than it had to be, so I went with a simple express.js server that I could tailor to my needs.
In order to sync my data, I'm using rethinkdb which allows me to asynchronously watch the database for changes and then publish the changes to the clients through websockets.
I've set up a simple script where I keep an instance of a baobab tree on both the client and the server.
When the tree gets modified on the server, it sends transaction data to the appropriate clients through the websocket
The client merges the transaction with the tree.
This method does not make use of local storage and keeps the data in memory in the node.js process. The data in the transaction is also quite redundant.
The future plan has always been to set something up using redis and local storage ...
... until yesterday when I found deepstream.io!
This is a tool that does exactly what I want and need! Nothing more, nothing less.
Another project worth mention is meatier: "like meteor, but meatier". It is composed of many other well supported open source projects, so you could even pick and choose.

How do I send a channel message using channel.trigger with websocket-rails gem

I'm building a simple real-time chat app to learn how to use websockets with RoR and I don't think I'm understanding how channels work because they're not doing what I expect. I can successfully send a message to my Rails app using the dispatcher.trigger() method, and use my websocket controller to broadcast a message to all clients that subscribe to the channel. That all works fine. What does NOT work is using a channel (via the channel.trigger() method) to send a message to other clients. The websocket-rails wiki says...
Channel events currently happen outside of the Event Router flow. They
are meant for broadcasting events to a group of connected clients
simultaneously. If you wish to handle events with actions on the
server, trigger the event on the main dispatcher and specify which
controller action should handle it using the Event Router.
If I understand this correctly, I should be able to user the channel.trigger() method to broadcast a message to clients connected to the channel, without the message being routed through my RoR app, but it should still reach the other connected clients. So here's my code...
var dispatcher = new WebSocketRails('localhost:3000/websocket');
var channel = dispatcher.subscribe('channel_name');
channel.bind('channel_message', function(data) {
alert(data.message);
});
$("#send_message_button").click(function() {
obj = {message: "test"};
channel.trigger('channel_message', obj);
});
With the code listed above, I would expect that when I click the button, it sends a channel message using channel.trigger() and the channel_message binding should be executed on all clients, displaying an alert that reads "test". That doesn't happen. I'm using Chrome tools to inspect the websocket traffic and it shows the message being sent...
["channel_message",{"id":113458,"channel":'channel_name',"data":{"message":"test"},"token":"96fd4f51-6321-4309-941f-38110635f86f"}]
...but no message is received. My questions are...
Am I misunderstanding how channel-based websockets work with the websocket-rails gem?
If not, what am I doing wrong?
Thanks in advance for all your wisdom!
I was able to reproduce a working copy based on an off-the-shelf solution from the wiki along with your very own code.
I've packaged the whole thing here. The files you might be interested are home_controller.rb, application.js and home/index.html.erb.
It seems your understanding of channel-based websockets is correct. About the code, make sure to load the websocket javascript files and to enclose your code inside a document.ready. I had the exact same problem you're having without the latter.
//= require websocket_rails/main
$(function() {
// your code here...
});
Let me know if it works. Best Luck!

Conditional publish events

Introduction
I'm building a private messaging system using sails, but this question can apply to pretty much anything. I'll be using the messaging system as an example to make the question more clear. As a bit of background info, I'm working with the latest sails 0.10 RC.
The problem
Sails allows you to use redis for sessions and pubsub, which allows you to scale over multiple servers. This is all very neat and works brilliantly, but it leaves me with the question of how to publish events to specific connected sockets (clients).
Sometimes you wish to only publish events to participants, as is the case with a private messaging system. Only the author and recipient should be notified of new messages in the thread. How would you accomplish this? I know you can subscribe a client to a specific model instance, notifying the client of changes in said model; I also know it's possible to subscribe a client to a model, notifying them of newly created (saved) model instances. It's the latter, the create verb that's causing me a bit of trouble. I don't want everyone that's using the messaging system to receive updates for new messages in threads they're not in. This would be a privacy issue.
TL;DR
How can I filter which clients receive the create verb event based on the value of a property (author and recipient) on the model in question? Is there any other way to make sure only these clients receive updates for the model?
You have a few options here, but all of them involve not really using the default publishCreate method, which will just blast out the created message to everyone who was subscribed to it via .watch().
The first option is to use associations to link your Message model to the users who should know about it, and then listen for the publishAdd message instead of publishCreate. For example, if there's an association between a Message instance and the User instances who represent the sender and recipient, then the default publishCreate logic will also trigger a publishAdd for the related users, indicating that a new Message has been added to their messages (or whatever you name it) collection.
The second option is to override the default publishCreate for Message, to have it send only to the correct users. For example, if only the recipient should be notified, then in api/models/Message.js you could do:
attributes: {...},
publishCreate: function (values, req, options) {
User.publish(values.recipient, {
verb: "created",
data: values,
id: values.id
}, req);
}
As a slight alternative, you can place your custom code in the model's afterPublishCreate method instead, which the default publishCreate will then call. This has the benefit of maintaining the default code that handles calling publishAdd for associated models; the trick would be just to make sure that no one was subscribed to the model classroom via .watch(), so that the default publishCreate doesn't send out created messages to users who shouldn't see them.

Categories

Resources