158 lines
4.7 KiB
JavaScript
158 lines
4.7 KiB
JavaScript
import WebSocket from 'ws';
|
|
|
|
import { LOG_TYPES } from './constants';
|
|
|
|
export default class Player {
|
|
constructor({
|
|
accessToken,
|
|
userId,
|
|
username,
|
|
}) {
|
|
this.accessToken = accessToken;
|
|
this.userId = userId;
|
|
this.username = username;
|
|
this.logger = null;
|
|
this.broadcast = null;
|
|
this.socket = null;
|
|
this.controller = null;
|
|
this.lastAnswerId = null;
|
|
this.playerPingTimeout = null;
|
|
this.playerPingInterval = null;
|
|
this.hasExtraLife = false;
|
|
this.canSendAnswer = false;
|
|
this.usedExtraLifeThisQuestion = false;
|
|
}
|
|
|
|
async connect(controller, broadcast) {
|
|
const { broadcastId, socketUrl } = broadcast;
|
|
|
|
this.broadcast = broadcast;
|
|
this.controller = controller;
|
|
|
|
const options = {
|
|
headers: {
|
|
Authorization: `Bearer ${this.accessToken}`,
|
|
},
|
|
};
|
|
this.socket = new WebSocket(socketUrl, options);
|
|
this.socket.on('error', err => this.logger.log(`An error occurred: ${err}`, LOG_TYPES.ERROR));
|
|
this.socket.on('pong', this.pong.bind(this));
|
|
this.socket.on('open', this.handleOpen.bind(this));
|
|
this.socket.on('close', this.handleClose.bind(this));
|
|
this.socket.on('message', this.handleMessage.bind(this));
|
|
}
|
|
|
|
ping() {
|
|
if (!this.socket) return;
|
|
try {
|
|
this.socket.ping();
|
|
this.playerPingTimeout = setTimeout(() => {
|
|
this.socket.terminate();
|
|
this.logger.log(`Player ${this.username} connection closed. Attempting to reconnect...`, LOG_TYPES.ERROR);
|
|
this.connect(this.broadcast);
|
|
}, 2000);
|
|
} catch (error) {
|
|
this.logger.log(`Player ${this.username} connection closed. Attempting to reconnect...`, LOG_TYPES.ERROR);
|
|
}
|
|
}
|
|
|
|
pong() {
|
|
clearTimeout(this.playerPingTimeout);
|
|
}
|
|
|
|
async handleOpen() {
|
|
this.playerPingInterval = setInterval(this.ping.bind(this), 10000);
|
|
this.logger.log(`Player ${this.username} has connected to websocket!`, LOG_TYPES.SUCCESS);
|
|
|
|
const { broadcastId } = this.broadcast;
|
|
this.socket.send(JSON.stringify({ type: 'subscribe', broadcastId }), () => {
|
|
this.logger.log(`Player ${this.username} has subscribed to the game!`, LOG_TYPES.INFO);
|
|
});
|
|
}
|
|
|
|
handleClose() {
|
|
this.socket = null;
|
|
clearInterval(this.playerPingInterval);
|
|
clearTimeout(this.playerPingTimeout);
|
|
}
|
|
|
|
removePlayer() {
|
|
if (this.socket) {
|
|
this.socket.terminate();
|
|
this.socket.close();
|
|
}
|
|
this.handleClose();
|
|
}
|
|
|
|
async handleMessage(data) {
|
|
const parsed = JSON.parse(data);
|
|
|
|
switch (parsed.type) {
|
|
case 'gameStatus':
|
|
const { extraLivesRemaining = 0 } = parsed;
|
|
this.hasExtraLife = extraLivesRemaining > 0;
|
|
this.logger.logSilent(`Player ${this.username}'s game status: ${data}`);
|
|
break;
|
|
case 'question':
|
|
this.canSendAnswer = true;
|
|
break;
|
|
case 'questionClosed':
|
|
this.canSendAnswer = false;
|
|
break;
|
|
case 'questionSummary':
|
|
const { question, youGotItRight, savedByExtraLife } = parsed;
|
|
if (!youGotItRight) {
|
|
this.logger.logSilent(`Player ${this.username} got ${question} wrong.`);
|
|
}
|
|
if (!savedByExtraLife && this.usedExtraLifeThisQuestion) {
|
|
this.logger.logSilent(`Player ${this.username} used extra life but was not saved on question: ${question}.`);
|
|
this.usedExtraLifeThisQuestion = false;
|
|
}
|
|
break;
|
|
case 'questionFinished':
|
|
break;
|
|
}
|
|
}
|
|
|
|
sendAnswer(questionId, answer, answerNumber) {
|
|
const { answerId } = answer;
|
|
const answerPackage = {
|
|
type: 'answer',
|
|
broadcastId: this.broadcast.broadcastId,
|
|
questionId,
|
|
answerId,
|
|
};
|
|
this.lastAnswerId = answerId;
|
|
|
|
try {
|
|
if (!this.canSendAnswer) {
|
|
this.logger.logSilent(`Player ${this.username} tried to send answer for ${questionId}: ${answer.text} but time expired!`);
|
|
} else {
|
|
this.socket.send(JSON.stringify(answerPackage));
|
|
this.logger.log(`Player ${this.username} sent answer ${answerNumber}) ${answer.text}`);
|
|
}
|
|
} catch (error) {
|
|
this.logger.log(`WebSocket error: ${this.username} can\'t send answer.`, LOG_TYPES.ERROR);
|
|
this.logger.logSilent(error);
|
|
this.lastAnswerId = null;
|
|
}
|
|
}
|
|
|
|
sendExtraLife(questionId) {
|
|
const extraLifePackage = {
|
|
type: 'useExtraLife',
|
|
broadcastId: this.broadcast.broadcastId,
|
|
questionId,
|
|
};
|
|
|
|
try {
|
|
this.socket.send(JSON.stringify(extraLifePackage));
|
|
this.hasExtraLife = false;
|
|
this.usedExtraLifeThisQuestion = true;
|
|
this.logger.log(`Player ${this.username} has been saved by an extra life!`);
|
|
} catch (error) {
|
|
this.logger.log(`WebSocket error: ${this.username} can\'t send extra life.`, LOG_TYPES.ERROR);
|
|
}
|
|
}
|
|
}
|