* Module dependencies.
*/
-var app = require('../app');
-var http = require('http');
+const app = require('../app');
+const http = require('http');
/**
* Get port from environment and store in Express.
*/
-let port = normalizePort(process.env.PORT || '3000');
+const port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
-let server = http.createServer(app);
+const server = http.createServer(app);
/*
* CRON tasks
*/
-var cron = require('node-cron');
-var UserModel = require("../models/user");
+const cron = require('node-cron');
+const UserModel = require("../models/user");
cron.schedule('0 0 0 * * *', function() {
// Remove unlogged users every 24h
UserModel.cleanUsersDb();
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
-let io = require('socket.io').listen(server); //sockets too
-//https://stackoverflow.com/a/24610678/4640434
+const io = require('socket.io').listen(server);
/*
* Sockets handling
function normalizePort(val)
{
- let port = parseInt(val, 10);
+ const port = parseInt(val, 10);
if (isNaN(port)) // named pipe
return val;
function onListening()
{
- let addr = server.address();
- let bind = typeof addr === 'string'
+ const addr = server.address();
+ const bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
console.log('Listening on ' + bind);
* number: student number
* inputs: array of indexed arrays of integers (or html text if not quiz)
* startTime, endTime
+ * discoTime, totalDisco: last disconnect timestamp (if relevant) + total
+ * discoCount: total disconnections
* password: random string identifying student for exam session TEMPORARY
*/
startTime: Date.now(),
endTime: undefined,
password: password,
+ totalDisco: 0,
+ discoCount: 0,
inputs: [ ], //TODO: this is stage 1, stack indexed answers.
// then build JSON tree for easier access / correct
}}},
);
},
+ // NOTE: no callbacks for 2 next functions, failures are not so important
+ // (because monitored: teachers can see what's going on)
+
+ addDisco: function(aid, number, deltaTime)
+ {
+ db.assessments.update(
+ {
+ _id: aid,
+ "papers.number": number,
+ },
+ { $inc: {
+ "papers.$.discoCount": 1,
+ "papers.$.totalDisco": deltaTime,
+ } },
+ { $set: { "papers.$.discoTime": null } }
+ );
+ },
+
+ setDiscoTime: function(aid, number)
+ {
+ db.assessments.update(
+ {
+ _id: aid,
+ "papers.number": number,
+ },
+ { $set: { "papers.$.discoTime": Date.now() } }
+ );
+ },
+
+ getDiscoTime: function(aid, number, cb)
+ {
+ db.assessments.findOne(
+ { _id: aid },
+ (err,a) => {
+ if (!!err)
+ return cb(err, null);
+ const idx = a.papers.findIndex( item => { return item.number == number; });
+ cb(null, a.papers[idx].discoTime);
+ }
+ );
+ },
hasInput: function(aid, number, password, idx, cb)
{
});
},
+ // NOTE: no callbacks for 2 next functions, failures are not so important
+ // (because monitored: teachers can see what's going on)
+
+ newConnection: function(aid, number)
+ {
+ //increment discoCount, reset discoTime to NULL, update totalDisco
+ AssessmentEntity.getDiscoTime(aid, number, (err,discoTime) => {
+ if (!!discoTime)
+ AssessmentEntity.addDisco(aid, number, discoTime - Date.now());
+ });
+ },
+
endSession: function(aid, number, password, cb)
{
AssessmentEntity.endAssessment(aid, number, password, (err,ret) => {
// Got password: students answers locked to this page until potential teacher
// action (power failure, computer down, ...)
}
- socket = io.connect("/" + assessment.name, {
+ socket = io.connect("/", {
query: "aid=" + assessment._id + "&number=" + this.student.number + "&password=" + this.student.password
});
socket.on(message.allAnswers, this.setAnswers);
{
this.stage = 4;
this.answers.showSolution = true;
+ this.answers.displayAll = true;
return;
}
$.ajax("/end/assessment", {
assessment.conclusion = ret.conclusion;
this.stage = 3;
delete this.student["password"]; //unable to send new answers now
- socket.disconnect();
- socket = null;
},
});
},
// stage 3 --> 4 (on socket message "feedback")
setAnswers: function(m) {
- for (let i=0; i<m.answers.length; i++)
- assessment.questions[i].answer = m.answers[i];
+ const answers = JSON.parse(m.answers);
+ for (let i=0; i<answers.length; i++)
+ assessment.questions[i].answer = answers[i];
this.answers.showSolution = true;
+ this.answers.displayAll = true;
this.stage = 4;
+ socket.disconnect();
+ socket = null;
},
},
});
});
}
// TODO: notations not coherent (input / answer... when, which ?)
- this.assessment.papers[paperIdx].inputs.push(m.answer); //input+index
+ this.assessment.papers[paperIdx].inputs.push(JSON.parse(m.answer)); //input+index
});
},
});
// In the end, send answers to students
socket.emit(
message.allAnswers,
- { answers: this.assessment.questions.map( q => { return q.answer; }) }
+ { answers: JSON.stringify(this.assessment.questions.map( q => { return q.answer; })) }
);
},
},
// Socket message list, easier life in case of a message renaming
let message = {
- // send answer (student --> server --> monitor)
+ // Send answer (student --> server --> monitor)
newAnswer: "new answer",
- // receive all answers to an exam (server --> student)
+ // Receive all answers to an exam (monitor --> server --> student)
allAnswers: "all answers",
+ // Next 2 to monitor students disconnections
+ studentConnect: "student connect",
+ studentDisconnect: "student disconnect",
};
try { module.exports = message; } catch (err) {} //for server
"inputs": Validator.Input,
"startTime": "positiveInteger",
"endTime": "positiveInteger",
+ "discoTime": "positiveInteger",
+ "discoCount": "positiveInteger",
+ "totalDisco": "positiveInteger",
"password": "password",
};
const message = require("./public/javascripts/utils/socketMessages");
const params = require("./config/parameters");
+const AssessmentEntity = require("./entities/assessment");
const AssessmentModel = require("./models/assessment");
const ObjectId = require("bson-objectid");
module.exports = function(io)
{
- io.of("/").on("connection", socket => {
+ io.of("/").on("connection", socket => { //student or monitor connexion
const aid = socket.handshake.query.aid;
- socket.join(aid);
- // Student or monitor connexion
const isTeacher = !!socket.handshake.query.secret && socket.handshake.query.secret == params.secret;
if (isTeacher)
{
- socket.on(message.newAnswer, m => { //got answer from student
- socket.emit(message.newAnswer, m);
- });
+ socket.join(aid + "_teacher");
socket.on(message.allAnswers, m => { //send feedback to student (answers)
- socket.broadcast.to(aid).emit(message.allAnswers, m);
+ socket.broadcast.to(aid + "_student").emit(message.allAnswers, m);
});
}
else //student
AssessmentModel.checkPassword(ObjectId(aid), number, password, (err,ret) => {
if (!!err || !ret)
return; //wrong password, or some unexpected error...
- socket.on("disconnect", () => {
- //TODO: notify monitor (grey low opacity background)
- //Also send to server: discoTime in assessment.papers ...
+ AssessmentModel.newConnection(ObjectId(aid), number);
+ socket.broadcast.to(aid + "_teacher").emit(message.studentConnect, {number: number});
+ socket.join(aid + "_student");
+ socket.on(message.newAnswer, m => { //got answer from student client
+ socket.broadcast.to(aid + "_teacher").emit(message.newAnswer, m);
+ });
+ socket.on("disconnect", () => { //notify monitor + server
+ AssessmentEntity.setDiscoTime(ObjectId(aid), number);
+ socket.broadcast.to(aid + "_teacher").emit(message.studentDisconnect, {number: number});
});
});
}