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.
Related
I am using Firebase Cloud Functions with Express and Firebase Hosting to serve my multi-page site. I have successfully implemented server-side cookies as explained here and as implemented below:
function verifyLogin(request, response, next) {
const sessionCookie = request.cookies.__session || '';
firebase.auth().verifySessionCookie(sessionCookie, true /** checkRevoked */ )
.then((decodedClaims) => {
//serve content for user
return next();
}).catch(error => {
// Session cookie is unavailable or invalid. Force user to log in.
console.log(error);
response.redirect('/login');
return;
});
}
app.get('/', verifyLogin, (request, response) => {
var page_title = "Home";
response.render('index', {
page_title,
});
});
I am using the Firebase Web SDK (JavaScript) to access the Firebase Cloud Firestore. In order to do this, I need to get the idToken on the client side for the currently-logged-in user, like so:
firebase.auth().onAuthStateChanged(firebaseUser => {
if (firebaseUser) {
firebase.auth().currentUser.getIdTokenResult()
.then((idTokenResult) => {
// Access the Firebase Cloud Firestore here
});
}
});
This seems redundant to me, since I already know which user is signed in via my server cookie, but how else can I get the idToken without another call to Firebase Auth?
Is there a way to extrapolate it from my session cookie somehow and pass it to my client from my cloud function as a variable?
Or is there another way to look at this philosophically that I am overlooking?
In the same way you can listen to user sign-in state changes using onAuthStateChanged(), you can also listen to user ID token changes using onIdTokenChanged(). You will only need to use a new token when the observer you attach shows that a new one appears (for example, when it's refreshed every hour by the SDK).
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
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
}
})
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.
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.