How do I subscribe to a model instance in Sails.JS? - javascript

I am attempting to use the subscribe function described here. However, when editing /assets/js/app.js, I am getting this error:
Uncaught ReferenceError: Room is not defined
So, I am not entirely sure why, but it cannot find my model. Here is my code:
Room.subscribe(req, [{id: "5278861ab9a0d2cd0e000001"}], function (response) {
console.log('subscribed?');
console.log(response);
});
and here is is in the context of app.js
(function (io) {
// as soon as this file is loaded, connect automatically,
var socket = io.connect();
if (typeof console !== 'undefined') {
log('Connecting to Sails.js...');
}
socket.on('connect', function socketConnected() {
// Listen for Comet messages from Sails
socket.on('message', function messageReceived(message) {
///////////////////////////////////////////////////////////
// Replace the following with your own custom logic
// to run when a new message arrives from the Sails.js
// server.
///////////////////////////////////////////////////////////
log('New comet message received :: ', message);
//////////////////////////////////////////////////////
});
///////////////////////////////////////////////////////////
// Here's where you'll want to add any custom logic for
// when the browser establishes its socket connection to
// the Sails.js server.
///////////////////////////////////////////////////////////
log(
'Socket is now connected and globally accessible as `socket`.\n' +
'e.g. to send a GET request to Sails, try \n' +
'`socket.get("/", function (response) ' +
'{ console.log(response); })`'
);
///////////////////////////////////////////////////////////
// This is the part I added:
Room.subscribe(req, [{id: "5278861ab9a0d2cd0e000001"}], function (response) {
console.log('subscribed?');
console.log(response);
});
//
});
// Expose connected `socket` instance globally so that it's easy
// to experiment with from the browser console while prototyping.
window.socket = socket;
// Simple log function to keep the example simple
function log () {
if (typeof console !== 'undefined') {
console.log.apply(console, arguments);
}
}
})(
Am I going about this the right way? should I be storing this directly in app.js?

To subscribe to a model instance, I use the following Real-Time Model Event pattern, some of which resides on the client and some on the server. Keep in mind the client can’t just subscribe itself- you have to send a request to the server letting it know that you’d like to be subscribed-- this is the only way to do it securely. (e.g. you might want to publish notifications with sensitive information-- you want to make sure a connected socket has permission to see that information before subscribing them to it.)
I’m going to use an example of an app with a User model. Let’s say I want to notify folks when existing users login.
Client-Side (Part I)
On the client-side, for simplicity, I’m going to use the existing app.js file in the /assets/js folder (or /assets/linker/js folder if you used the --linker switch when you built the app.)
To send my socket request to the server within assets/js/app.js, I’m going to use the socket.get() method. This method mimics the functionality of an AJAX “get” request (i.e. $.get() ) but uses sockets instead of HTTP. (FYI: You also have access to socket.post(), socket.put(), and socket.delete()).
The code would look something like this:
// Client-side (assets/js/app.js)
// This will run the `welcome()` action in `UserController.js` on the server-side.
//...
socket.on('connect', function socketConnected() {
console.log("This is from the connect: ", this.socket.sessionid);
socket.get(‘/user/welcome’, function gotResponse () {
// we don’t really care about the response
});
//...
Server-Side (Part I)
Over in the welcome() action in UserController.js, now we can actually subscribe this client (socket) to notifications using the User.subcribe() method.
// api/UserController.js
//...
welcome: function (req, res) {
// Get all of the users
User.find().exec(function (err, users) {
// Subscribe the requesting socket (e.g. req.socket) to all users (e.g. users)
User.subscribe(req.socket, users);
});
}
//...
Back on the client-side (Part II)...
I want the socket to ‘listen’ for messages I’m going to send it from the server. To do this I’ll use:
// Client-side (assets/js/app.js)
// This will run the `welcome()` action in `UserController.js` on the backend.
//...
socket.on('connect', function socketConnected() {
console.log("This is from the connect: ", this.socket.sessionid);
socket.on('message', function notificationReceivedFromServer ( message ) {
// e.g. message ===
// {
// data: { name: ‘Roger Rabbit’},
// id: 13,
// verb: ‘update’
// }
});
socket.get(‘/user/welcome’, function gotResponse () {
// we don’t really care about the response
});
// ...
Back on the server-side (Part II)...
Finally, I’ll start sending out messages, server-side, by using: User.publishUpdate(id);
// api/SessionController.js
//...
// User session is created
create: function(req, res, next) {
User.findOneByEmail(req.param('email'), function foundUser(err, user) {
if (err) return next(err);
// Authenticate the user using the existing encrypted password...
// If authenticated log the user in...
// Inform subscribed sockets that this user logged in
User.publishUpdate(user.id, {
loggedIn: true,
id: user.id,
name: user.name,
action: ' has logged in.'
});
});
}
//...
You can also check out Building a Sails Application: Ep21 - Integrating socket.io and sails with custom controller actions using Real Time Model Events for more information.

Related

How to log in automatically on the server for Meteor integration testing with a custom login handler

I'm writing integration tests for my Meteor app using Mocha and Chai. These tests are for the server functionality only.
I could fake up a Meteor user with Sinon but I would ideally like to start by logging the user in for real. However I can't work out how to do this on the server.
I'm authenticating against a remote server;
Server code:
Accounts.registerLoginHandler((loginRequest) => {
if ((remoteAuthenticate(loginRequest.username, loginRequest.password)) === true) {
// remoteAuthenticate is a function that handles login against the remote server
...
// handle login success and failure
}
});
This is called on the client like so:
Accounts.callLoginMethod({
'methodArguments': [{
'username': username,
'password': password,
}],
'userCallback': function (err) {
if (err) {
Session.set('invalidCredentials', true);
}
},
});
Login from the client works fine, but I can't figure out how to call the login method directly on the server in my tests. None of the Meteor or Accounts methods seems to do this. Thanks for any suggestions!
Assuming the user has already been created you can use a DDP connection and a package named ongoworks:ddp-login to authenticate from remote. It is designed to work on both, server and client.
First add the package to your project:
meteor add ongoworks:ddp-login
Then create the connection in your tests and pass it to the login call:
const url = https://your.server.tld
const connection = DDP.connect(url);
// describe login
it('logs in to the remote if the user exists', function (done) {
DDP.loginWithPassword(conn, {username: 'admin'}, 'admin', function (error) {
if (error) {
done(error)
} else {
done()
}
})
})
The connection instance behaves similar to your Meteor global, which itself has always a default connection configured by default.
For example: If you need to call methods from the remote with the logged in user you can use the connection instance to call the methods:
// describe method x
it('returns some value', function (done) {
DDP.loginWithPassword(conn, {username: 'admin'}, 'admin', function (error) {
// handle error...
connection.call('method x', (err, res) => { /* handle err / res */ })
})
})
Resources:
https://atmospherejs.com/ongoworks/ddp-login
https://docs.meteor.com/api/connections.html#DDP-connect

Events of feathersjs channels do not come through to client

I setup a very basic Featherjs Channel following their guide. So on the server I have:
module.exports = app => {
// If no real-time functionality has been configured just return
if (typeof app.channel !== 'function') return
app.on('connection', connection => {
// On a new real-time connection, add it to the anonymous channel
app.channel('anonymous').join(connection)
})
app.on('login', (authResult, {connection}) => {
// connection can be undefined if there is no
// real-time connection, e.g. when logging in via REST
if (connection) {
// Obtain the logged in user from the connection
const {user} = connection
// When the connection is no longer anonymous (as you the user is logged in), remove it
app.channel('anonymous').leave(connection)
// Add it to the authenticated user channel
app.channel('authenticated').join(connection)
}
})
app.publish((data, hook) => {
return app.channel('authenticated')
})
app.service('points').publish('created', () => app.channel('authenticated'))
}
And in my client:
api.on('authenticated', response => {
console.log('Yes, here is the event from the channel: ', response)
})
This setup should give all events from all my featherjs services. However I only get an event on my client when I login. When I subsequently create objects through my feathers api service, nothing is shown/ no events come through. Why not?
The authenticated event is a purely client side event which will be triggered when the client authenticates successfully. It is not an event that is sent from the server.
Channels only apply to service events sent from the server. For your example this would mean using something like
app.service('points').on('created', point => {})
On the client. The client will only receive the created event once it has been authenticated.

Why does this very basic sails socket not work?

just wondering why the following basic web socket is not working?
io.socket.on('user', function(event){
console.log("RECIEVED EVENT:",event);
})
sails.io.js is included in my index and that code from above is located in an test.js file that lives under assets/js. I would expect that each time I make any request to the user api I would see a log. Oh and yes the user api does exist. I read the documentation and don't see where i am going wrong here.
Turns out you need to register for events via the io.socket.get
// The automatically-created socket is exposed as io.socket.
// Use .on() to subscribe to the 'user' event on the client.
// This event is sent by the Sails "create", "update",
// "delete", "add" and "remove" blueprints to any socket that
// is subscribed to one or more User model instances.
io.socket.on('user', function gotHelloMessage (data) {
console.log('User alert!', data);
});
// Using .get('/user') will retrieve a list of current User models,
// subscribe this socket to those models, AND subscribe this socket
// to notifications about new User models when they are created.
io.socket.get('/user', function gotResponse(body, response) {
console.log('Current users: ', body);
})

How to handle multiple, dynamically created listeners to a single ChildProcess in NodeJS?

I have a child process worker, that receives some data and sends back results to dynamically attached listener.
Simplified code:
//app.js
var worker = childProcess.fork('./app_modules/workers/worker1.js');
worker.setMaxListeners(0);
require('./app_modules/sockets-user/foobar.js')(io, worker);
//foobar.js
io.sockets.on('connection', function (socket) {
socket.on('trigger', function (data) {
worker.send(data);
worker.once('message', function(responseData) {
//here I get a response from worker
socket.emit('response', responseData);
});
});
});
It was working great until I discovered that If socket.on('trigger' is triggered at the very exact moment by different users every listener would receive the same message.
I could change worker.once to worker.on but its not a fix, because I would have to filter incoming data and then probably find a way to clear dynamically added listeners. What did I do wrong here?
Probably one of the easiest solutions would be to pass some user-specific data (e.g. remote IP address and port or some other unique identifier) to the worker than merely gets passed right back to the parent in the response. This way you can match up the response with the correct socket.
This means that you would only have one message listener (added outside of the socket.io connection handler). You would then look up the socket based on the information passed in the response, and send whatever data back to that client. For example:
//foobar.js
worker.on('message', function(responseData) {
// assuming worker returns `{id: ..., data: ...}`
var socket = io.sockets.sockets[responseData.id];
if (socket)
socket.emit('response', responseData.data);
});
io.sockets.on('connection', function (socket) {
socket.on('trigger', function (data) {
worker.send({ id: socket.id, data: data });
});
});

socket.io disconnect after emitting to specific client NODE javascript

Hello I am currently working on some kind of "Chat"-system.
Server: NodeJs with express - https
Communication: Socket.io#1.0
client:
var socket = io.connect();
socket.emit('HELLO', {user:'someuserid'});
socket.on('WELCOME', function(data){
socket.emit('REGISTER', {});
});
socket.on('SIGNED', function(data){
console.log(data.user);
socket.emit('GO', {});
});
socket.on('message', function(data){
console.log(data.somedata);
});
server:
io.sockets.on('connection', function(socket){
socket.on('HELLO', function(data){
socket.user = data.user;
socket.emit('WELCOME', socket.user);
});
socket.on('REGISTER', function(data){
console.log('room: '+socket.user);
socket.join(socket.user);
socket.emit('SIGNED', socket.user);
console.log('SIGNED IN: '+socket.user);
});
socket.on('GO', function(data){
//some logic happens where a list of users gets loaded
//which is identical to the socket.user and its room
//... loop
io.in(partners[i].user).emit('message', { somedata: 'asd' });
}
socket.on('disconnect' ..... blaaa blaa)
So, basicly what I tried to do here is create a workaround to sending a message to a specific user by sending a message to a specific room.
this:
io.in(partners[i].user).emit('message', { somedata: 'asd' });
and this:
partners[i].socket.emit('message', { somedata: 'asd' });
result in the same:
room: 540246a3e4b0a64a28e1ec59
SIGNED IN: 540246a3e4b0a64a28e1ec59
room: 540504ba0b526b274398480e
SIGNED IN: 540504ba0b526b274398480e
to: 540246a3e4b0a64a28e1ec59
disconnected:540246a3e4b0a64a28e1ec59
the user registers, gets connected and wants to emit a message to specific chatpartners in the array partners[i].
once the emit is fired the user, the message is supposed to be emitted to disconnects...
what am I doing wrong?
(the script is obviously not complete.. i pasted the most important parts)
Thanks for your help.
I think I have found the solution:
sometimes using google is more effort than debugging by yourself.
After scrolling through the debug log in firefox I found out that this problem actually had to do with the code inside my socket 'message' handler. A snippet that might help others to find errors with their sockets:
socket.on('error', function (err) {
if (err.description) throw err.description;
else throw err; // Or whatever you want to do
});
I think this is an issue in socket.io
although it was my wrong code - the socket.io errorhandler should have passed that through.
Have fun

Categories