Filter notifications within policy - javascript

Whenever a model is created (or deleted/modified) every connected socket is notified through Sails autowatch setting. That's fine to some extent, but I'd like to filter these notifications at some point.
My application has its own "Notifications" which should be sent to their respective receiver. So their anatomy is somewhat like: id, message, receiver, sender.
Authentication is a local passport implementation.
Listening for notification events result in getting notified every time a notification is created.
// client: app.js
io.socket.on('notification', function(evt) { console.log(evt); });
What I try to achieve now is to filter these notifications to match the user id. I've written a policy which gets applied to the /notification events.
// Policy: forUser
module.exports = function(req, res, next) {
// ... whatever ... //
return next();
}
Within the policies
'notification': {
'create': ['passport', 'forUser']
}
My problem now is: how to implement this policy? I thought of just checking for notification.receiver == req.user.id, but how to get the notification model within the policy (if this is the right way at all)?
Thanks.
Edit: Tried implementing the room solution, but I don't get any notifications on the client.
I've altered my subscribe function within my NotificationController:
subscribe: function(req, res) {
sails.log.info('Your user id: ' + req.user.id);
sails.sockets.join(req.socket, 'user_notifications_' + req.user.id);
res.json({
room: 'user_notifications_' + req.user.id
});
},
And added a afterCreate method to my model:
afterCreate: function(model, next) {
sails.sockets.broadcast('user_notifications_' + model.receiver, { test: 'hello' });
next();
}
Code on client is now:
io.socket.get("/notification/subscribe", function(data, jwr) {
io.socket.on(data.room, function(obj) {
console.log(obj);
});
});
The subscription method is called and returns the right room name. But I don't get any messages when calling /notification/create?message=test&receiver=1. The afterCreate method is called, all user ids are right (since there's only one user), but nothing happens.
Edit2:
It seems like joining the rooms fails.
sails.sockets.join(req.socket, 'testroom');
// For testing
sails.log.debug(sails.sockets.socketRooms(req.socket));
The room gets created, but the socket is not subscribed to it.
Edit3:
Found the solution. I'll post the GitHub link as soon as the interface is done.

Are you using sails.sockets.blast() to send your notifications?
To send custom events, you could use sails.sockets.emit()
// Controller action
actionSendingNotification: function(req, res) {
// Retrieve the user socket ID and the data to send
// ...
sails.sockets.emit(userSocketId, 'notification', data);
}
You have to be able to know if the user has an active websocket connection and retrieve his socket ID. He could have several tabs opened in his browser and several websocket connections ...
A probably better solution would be to use sails.sockets.join() and sails.sockets.broadcast(). You would then create the association between the connected user and the observed event within socket.io.
// When the user creates a websocket connection, subscribe him to the model room
// This action MUST be called by a websocket request
// Here I assume your event is related to a model
subscribeToModelRoom: function(req, res) {
var roomName = 'myModelRoom' + req.param('model_id');
sails.sockets.join(req.socket, roomName);
res.json({
message: 'Subscribed to a room called '+roomName+'!'
});
}
Then every time you send a message to the room, the subscribed user will receive it.
// When the model has to send a notification, use broadcast() to send it to the room associated to the model.
sails.sockets.broadcast('myModelRoom' + modelInstance.id, 'notification', data);
Edit
Reading your question again, I will add some explanations.
It seems that you try to send your notification to a user, based on his user.id. You cannot assume that this user will be connected via websocket when you send this notification. You don't send events to a user, but to a opened websocket connection (that may be authenticated).
If the user must not miss the notification, you have to store it in a database. You will then show it to the user when he will be connected.
If you want the user to be informed in real time while he is connected, you could subscribe him to a room "user_notifications_" + user.id when you initialize his authenticated websocket connection. Then emit an event to this room when you create a new notification.
You could add the logic to manage seen / not seen notifications and delete the obsolete records.
This way, you can send information to your users in real time and you will not lose the information if nobody is here to receive it.

Related

SSE/Redis - how to recover messages sent when SSE goes offline

On a website I have a very simple Live chat setup that uses SSE/Redis and pub/sub structure.
The basic setup (without going into details) is:
Client-side using EventSource
Opens SSE connection and subscribes to live events sent by SSE daemon. Sends messages to an API endpoint
connect(hash, eventListener) {
const url = `${url}?client=$hash=${hash}`;
sseSource = new EventSource(url);
sseSource.onopen = function(e) {
reconnectFrequencySeconds = 1;
}
sseSource.onerror = err => {
this.closeSSEStream();
this.reconnectSSEStream(hash, eventListener);
};
sseSource.addEventListener('messages', event => {
const messages = JSON.parse(event.data);
eventListener(messages);
});
},
API endpoint
That stores message in the database and pushes it to a Redis channel.
Redis DB
That keeps and serves the messages.
Server-side SSE daemon
Subscribes client to a channel in a Redis DB and forwards messages to the subscribers using SSE stream.
const subscriber = redis.createClient();
subscriber.select(config.redisDatabase);
subscriber.on('message', function (channel, message) {
log(connectionId, 'Redis: new msg on channel: ' + channel, message);
let event = {
event: 'messages',
data: message
};
currentClient.connection.write(event);
});
The whole thing works pretty well, however, it is one tweak away from perfection.
During deploy we restart our workers (including SSE daemon) and while it goes offline users do not receive LIVE updates. It reconnects just fine but messages that have been sent during down time are lost (as daemon starts listening to messages on reconnect only).
My only idea for a workaround involves an overengineered solution where "lost" messages are collected with a separate API endpoint on reconnect and displayed to the user.
Is there an out-of-the-box way to receive messages that have been stored to Redis BEFORE subscribing to a channel? E.g. "pop" unprocessed messages or something like that?
when you have reconnected send request to check if you are new msg with time of last msg
and if you are newer msg send it in result msg to avoid new request

Socket.IO - how can I tell the server which specific client disconnected?

I followed a different S/O answer to figure out how to communicate to my server that a client disconnected by using
var socket = io.connect(<your_url>, {
'sync disconnect on unload': true });
The problem is, since this is part of the original socket configuration, I can't tell from the server which of my clients actually disconnected. On my client-side, I display a list of usernames for all connected clients, so I need to know which username to remove from that list for the remaining clients.
server-side code that gets triggered when a client closes out is:
socket.on('disconnect', reason => {
console.log('user disconnected', reason);
});
but the "reason" variable turns out to just be a string that says: "transport close" with no information about the actual client that disconnected.
One approach I thought of was, whenever a client disconnects, the server can request a response from all connected clients and use that to send out an updated list every time, but this seems excessive. I'd much prefer to know which client disconnected when they disconnect, and simply broadcast the id of the newly disconnected client, so the other clients are able to update their respective user lists locally. When a new client joins, after all, I broadcast that client's username, so all clients can update locally - I'd like to use the same pattern when a client disconnects.
In short, does anyone know a way to, within the "sync disconnect on unload" configuration of socket.io, also send the client's ID on unload?
Turns out I needed to keep an array in the server of active participants, adding to it every time they connect.
let users = []
socket.on('join', data => {
data.id === socket.id
users.push(data)
})
socket.on('disconnect', reason => {
let user = users.find(u => u.id === socket.id)
// we emit an event from the server, not from a particular socket
io.emit('player disconnected', {
user
}
})

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);
})

Send message to specific client with socket.io and node.js

I'm working with socket.io and node.js and until now it seems pretty good, but I don't know how to send a message from the server to an specific client, something like this:
client.send(message, receiverSessionId)
But neither the .send() nor the .broadcast() methods seem to supply my need.
What I have found as a possible solution, is that the .broadcast() method accepts as a second parameter an array of SessionIds to which not send the message, so I could pass an array with all the SessionIds connected at that moment to the server, except the one I wish send the message, but I feel there must be a better solution.
Any ideas?
Ivo Wetzel's answer doesn't seem to be valid in Socket.io 0.9 anymore.
In short you must now save the socket.id and use io.sockets.socket(savedSocketId).emit(...) to send messages to it.
This is how I got this working in clustered Node.js server:
First you need to set Redis store as the store so that messages can go cross processes:
var express = require("express");
var redis = require("redis");
var sio = require("socket.io");
var client = redis.createClient()
var app = express.createServer();
var io = sio.listen(app);
io.set("store", new sio.RedisStore);
// In this example we have one master client socket
// that receives messages from others.
io.sockets.on('connection', function(socket) {
// Promote this socket as master
socket.on("I'm the master", function() {
// Save the socket id to Redis so that all processes can access it.
client.set("mastersocket", socket.id, function(err) {
if (err) throw err;
console.log("Master socket is now" + socket.id);
});
});
socket.on("message to master", function(msg) {
// Fetch the socket id from Redis
client.get("mastersocket", function(err, socketId) {
if (err) throw err;
io.sockets.socket(socketId).emit(msg);
});
});
});
I omitted the clustering code here, because it makes this more cluttered, but it's trivial to add. Just add everything to the worker code. More docs here http://nodejs.org/api/cluster.html
each socket joins a room with a socket id for a name, so you can just
io.to('socket#id').emit('hey')
docs: http://socket.io/docs/rooms-and-namespaces/#default-room
The simplest, most elegant way
verified working with socket.io v3.1.1
It's as easy as:
client.emit("your message");
And that's it. Ok, but how does it work?
Minimal working example
Here's an example of a simple client-server interaction where each client regularly receives a message containing a sequence number. There is a unique sequence for each client and that's where the "I need to send a message to a particular client" comes into play.
Server
server.js
const
{Server} = require("socket.io"),
server = new Server(8000);
let
sequenceNumberByClient = new Map();
// event fired every time a new client connects:
server.on("connection", (socket) => {
console.info(`Client connected [id=${socket.id}]`);
// initialize this client's sequence number
sequenceNumberByClient.set(socket, 1);
// when socket disconnects, remove it from the list:
socket.on("disconnect", () => {
sequenceNumberByClient.delete(socket);
console.info(`Client gone [id=${socket.id}]`);
});
});
// sends each client its current sequence number
setInterval(() => {
for (const [client, sequenceNumber] of sequenceNumberByClient.entries()) {
client.emit("seq-num", sequenceNumber);
sequenceNumberByClient.set(client, sequenceNumber + 1);
}
}, 1000);
The server starts listening on port 8000 for incoming connections. As soon as a new connection is established, that client is added to a map that keeps track of its sequence number. The server also listens for the disconnect event to remove the client from the map when it leaves.
Each and every second, a timer is fired. When it does, the server walks through the map and sends a message to every client with their current sequence number, incrementing it right after. That's all that is to it. Easy peasy.
Client
The client part is even simpler. It just connects to the server and listens for the seq-num message, printing it to the console every time it arrives.
client.js
const
io = require("socket.io-client"),
ioClient = io.connect("http://localhost:8000");
ioClient.on("seq-num", (msg) => console.info(msg));
Running the example
Install the required libraries:
npm install socket.io#3.1.1 socket.io-client#3.1.1
Run the server:
node server
Open other terminal windows and spawn as many clients as you want by running:
node client
I have also prepared a gist with the full code here.
Well you have to grab the client for that (surprise), you can either go the simple way:
var io = io.listen(server);
io.clients[sessionID].send()
Which may break, I doubt it, but it's always a possibility that io.clients might get changed, so use the above with caution
Or you keep track of the clients yourself, therefore you add them to your own clients object in the connection listener and remove them in the disconnect listener.
I would use the latter one, since depending on your application you might want to have more state on the clients anyway, so something like clients[id] = {conn: clientConnect, data: {...}} might do the job.
You can use
//send message only to sender-client
socket.emit('message', 'check this');
//or you can send to all listeners including the sender
io.emit('message', 'check this');
//send to all listeners except the sender
socket.broadcast.emit('message', 'this is a message');
//or you can send it to a room
socket.broadcast.to('chatroom').emit('message', 'this is the message to all');
In 1.0 you should use:
io.sockets.connected[socketid].emit();
Whatever version we are using if we just console.log() the "io" object that we use in our server side nodejs code, [e.g. io.on('connection', function(socket) {...});], we can see that "io" is just an json object and there are many child objects where the socket id and socket objects are stored.
I am using socket.io version 1.3.5, btw.
If we look in the io object, it contains,
sockets:
{ name: '/',
server: [Circular],
sockets: [ [Object], [Object] ],
connected:
{ B5AC9w0sYmOGWe4fAAAA: [Object],
'hWzf97fmU-TIwwzWAAAB': [Object] },
here we can see the socketids "B5AC9w0sYmOGWe4fAAAA" etc. So, we can do,
io.sockets.connected[socketid].emit();
Again, on further inspection we can see segments like,
eio:
{ clients:
{ B5AC9w0sYmOGWe4fAAAA: [Object],
'hWzf97fmU-TIwwzWAAAB': [Object] },
So, we can retrieve a socket from here by doing
io.eio.clients[socketid].emit();
Also, under engine we have,
engine:
{ clients:
{ B5AC9w0sYmOGWe4fAAAA: [Object],
'hWzf97fmU-TIwwzWAAAB': [Object] },
So, we can also write,
io.engine.clients[socketid].emit();
So, I guess we can achieve our goal in any of the 3 ways I listed above,
io.sockets.connected[socketid].emit();
OR
io.eio.clients[socketid].emit();
OR
io.engine.clients[socketid].emit();
You can do this
On server.
global.io=require("socket.io")(server);
io.on("connection",function(client){
console.log("client is ",client.id);
//This is handle by current connected client
client.emit('messages',{hello:'world'})
//This is handle by every client
io.sockets.emit("data",{data:"This is handle by every client"})
app1.saveSession(client.id)
client.on("disconnect",function(){
app1.deleteSession(client.id)
console.log("client disconnected",client.id);
})
})
//And this is handle by particular client
var socketId=req.query.id
if(io.sockets.connected[socketId]!=null) {
io.sockets.connected[socketId].emit('particular User', {data: "Event response by particular user "});
}
And on client, it is very easy to handle.
var socket=io.connect("http://localhost:8080/")
socket.on("messages",function(data){
console.log("message is ",data);
//alert(data)
})
socket.on("data",function(data){
console.log("data is ",data);
//alert(data)
})
socket.on("particular User",function(data){
console.log("data from server ",data);
//alert(data)
})
As of version 1.4.5, be sure you provide a properly prefixed socketId in io.to().
I was taking the socketId the Client logged to debug and it was without prefix so I ended up searching forever till I found out! So you might have to do it like this if the Id you have is not prefixed:
io.to('/#' + socketId).emit('myevent', {foo: 'bar'});
io.sockets.sockets[socket.id].emit(...) worked for me in v0.9
Also you can keep clients refferences. But this makes your memmory busy.
Create an empty object and set your clients into it.
const myClientList = {};
server.on("connection", (socket) => {
console.info(`Client connected [id=${socket.id}]`);
myClientList[socket.id] = socket;
});
socket.on("disconnect", (socket) => {
delete myClientList[socket.id];
});
then call your specific client by id from the object
myClientList[specificId].emit("blabla","somedata");
Socket.IO allows you to “namespace” your sockets, which essentially means assigning different endpoints or paths.
This might help:
http://socket.io/docs/rooms-and-namespaces/

Categories