I would like to use websockets to track the time when a user was last seen, along with their current online status (if they are currently authenticated and making use of the application).
I have the a policy that applies to certain parts of the application, so the user will sign in then be directed to an overview where the policy applies.
If the user is not authenticated or if they are but the session information conflicts with that of the database then the overview will respond with notFound.
If these simple checks pass, then the user exists so two event handlers should be created:
- If there is no activity until the session expires, notify the client to return to the auth page
- When the client is disconnecting, update the time when they were last seen and alter the online state
Here is the policy code:
const schedule = require('node-schedule')
var sheduledJob
module.exports = async function isSignedIn(req, res, next) {
// If the user has not signed in, pretend the page does not exist
if (!req.session.app || !req.session.app.userId) return res.notFound()
// Store the users id
let userId = req.session.app.userId
try {
// Update the user
var users = await User.update({
id: userId
})
.set({
last_accessed: new Date().getTime(),
online: true
})
.meta({
fetch:true
})
} catch (err) {
// Handle errors
return res.serverError(err.message)
}
// If the user does not exist
if (!users || users.length !== 1) {
console.log('policies/isSignedIn: no matching user '+userId)
// Sign the user out
delete(req.session.app)
// Pretend the page does not exist
return res.notFound()
}
// When the user exists
else {
// When there is a new client connection
var io = sails.io
io.on('connection', socket => {
console.log("==> Socket Connection Established")
console.log(socket.id, socket.request.headers.cookie.replace('sails.sid=',''))
// Cancel an existing job if one exists
if (sheduledJob) sheduledJob.cancel()
// Shedule a job to notify the client when the session has expired
var d = req.session.cookie._expires
sheduledJob = schedule.scheduleJob(d, () => {
console.log('The cookie has expired')
// The client should return to the auth page (will fire disconnecting when doing so)
req.socket.emit('logout', true)
})
// When the client is disconnecting
socket.on('disconnecting', async reason => {
console.log('Client disconnecting')
// As of Sails v1.0.0-46 reason is undefined when the application is lowering
if (reason) {
try {
// Update the user
var users = await User.update({
id: userId
})
.set({
last_accessed: new Date().getTime(),
online: false
})
} catch (err) {
// Handle errors
return res.serverError(err.message)
}
}
})
})
// Proceed as the user exists and we can handle the disconnecting event
return next()
}
};
The problem that I have is that this will work when the page loads initially but if I reload the overview page then I end up with duplicate event handlers:
Loading the page once (bearing in mind that the sessions age is 10 seconds for testing):
==> Socket Connection Established
<socketId> <sessionId>
The cookie has expired
Client disconnecting
But if I reload the page:
==> Socket Connection Established
<socketId> <sessionId>
Client disconnecting
==> Socket Connection Established
<socketId> <sessionId>
==> Socket Connection Established
<socketId> <sessionId>
The cookie has expired
Client disconnecting
Client disconnecting
So I thought okay fine if that is the case then maybe I can just create a named function for the event listener and then remove the event listener initially:
const schedule = require('node-schedule')
var sheduledJob
function connectionHandler(req, res, socket) {
console.log("==> Socket Connection Established")
console.log(socket.id, socket.request.headers.cookie.replace('sails.sid=',''))
...same as before...
}
module.exports = async function isSignedIn(req, res, next) {
...
// When the user exists
else {
// When there is a new client connection
var io = sails.io
var nsp = io.of('/')
nsp.removeListener('connection', connectionHandler)
io.on('connection', socket => connectionHandler(req, res, socket))
// Proceed as the user exists and we can handle the disconnecting event
return next()
}
};
But that results in the same duplicate handlers, so I removed all of the req/res code from the handler:
function connectionHandler(socket) {
console.log("==> Socket Connection Established")
console.log(socket.id, socket.request.headers.cookie.replace('sails.sid=',''))
socket.on('disconnecting', async reason => {
console.log('Client disconnecting')
})
}
And amended when the event is created:
io.on('connection', connectionHandler)
The result works as I had intended, without any duplicate event handlers being created when reloading the page:
==> Socket Connection Established
<socketId> <sessionId>
Client disconnecting
==> Socket Connection Established
<socketId> <sessionId>
Could someone please explain to me where I am going wrong here, I really do not understand why:
io.on('connection', socket => connectionHandler(req, res, socket))
Results in duplicated event handlers whereas:
io.on('connection', connectionHandler)
Does not?
If anyone could offer any suggestion as to where I am going wrong here or how I could better reach the desired result then that would be greatly appreciated, many thanks in advance!
Here are some of the references that I used to get to this point:
- https://gist.github.com/mikermcneil/6598661
- https://github.com/balderdashy/sails-docs/blob/1.0/concepts/Sessions/sessions.md#when-does-the-sailssid-change
- how to disconnect socket on session expire
- https://stackoverflow.com/a/5422730/2110294
- https://github.com/expressjs/session/issues/204#issuecomment-141473499
- https://stackoverflow.com/a/33308406/2110294