Autoreconnect
Как выяснилось на практике, на смартфонах, соединение установленное через вебсокеты не постоянное. Когда на телефоне нет активности, экран гаснет и соединение прерывается, для экономии батареи. Это в общем случае, с настройками по умолчанию. При новой активности соединение восстанавливается. Подробнее о том как это работает можно почитать тут:
https://socket.io/docs/v4/connection-state-recovery
Также некоторые подходы для восстановления данных (состояния) при реконнекте:
https://socket.io/docs/v4/tutorial/step-6
К счастью socket io умеет восстанавливать socketId, комнаты и данные при реконнекте.
Наши игроки все могут уйти в оффлайн из-за отсутствия активности, но это не будет означать что они вышли из игры. Однако в текущей реализации игра будет удалена. И это неправильно. Попробуем как-то решить проблему. Например добавим минимальное "время жизни" для игровой комнаты, пусть это будет один час. Добавим в server.js в объект gameRoom новое поле expiresAt.
const gameRoom = {
…
expiresAt: 1*60*60*1000 + Date.now(), //1h from now
removePlayer: function(socketId) {
this.players = this.players.filter(i=>i.socketId != socketId);
this.isOpen = true;
},
…
И новую функцию для сбора "мусора" garbage collection для пустых, неактивных комнат.
const removeExpiredGameRooms = () => {
gameRooms = new Map([...gameRooms].filter((k,i) => Date.now() < i.expiresAt || players.length));
}
const заменим на let, т.к. объект будет меняться
let gameRooms = new Map();
'Чистку' ненужных комнат будем осуществлять при старте новой игры
const createGameRoom = (socketId, numberOfPlayers) => {
//gc
removeExpiredGameRooms();
..
По документации подключаем восстановление состояния (state recovery) при реконнекте
const io = new Server(server, {
..
connectionStateRecovery: {
maxDisconnectionDuration: 1 * 60 * 1000
}
});
Добавим логгирование переподключений.
io.on('connection', (socket) => {
..
/**
* This case is for autoreconnection
*/
if (socket.recovered) {
// recovery was successful: socket.id, socket.rooms and socket.data were restored
console.log('Client reconnected:', socket.id);
}
..
К счастью нам не нужно вручную подключаться к комнатам при реконнекте. Но если бы вдруг пришлось, то было бы примерно так:
/*
if (socket.handshake.auth?.gameRoomId) {
const gameRoomId = socket.handshake.auth?.gameRoomId;
if (joinGameRoom(socket.id, gameRoomId)) {
const gameRoom = getOpenGameRoomById(gameRoomId);
if (!gameRoom) {
socket.emit('reconnect-game-error-response');
return;
}
socket.join(gameRoomId);
socket.emit('reconnect-game-success-response');
console.log('Client reconnected:', socket.id);
} else {
socket.emit('reconnect-game-error-response');
console.log('Client not reconnected:', socket.id);
}
}
*/
И на стороне клиента (не обязательно)
socket.service.ts
setAuth(auth: any): void {
this.socket.auth = auth;
}
join-game-page.ts
this.joinGameSubscription = this.socketService.on('join-game-success-response').subscribe((data: any) => {
..
//set auth token in order to have for autorejoin after reconnection on server side
this.socketService.setAuth({
gameRoomId: this.gameStateService.getData().id
});
..
Всё это хорошо в теории, однако на практике эти реконнекты работали как-то не стабильно, из-за чего падал сервер. Разбираться с этим как-то не особо хотелось, поэтому решил попробовать другой подход в решении проблемы. А именно, не позволять уходить телефону в спящий режим.
Wake Lock
В современных браузерах есть интерфейс WakeLock для отмены ухода в спящий режим.
https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API
Попробуем реализовать его. Для этого добавим новый скрипт wake-lock.js в наш фронт-энд проект. С таким содержимым:
const requestWakeLock = async () => {
if ("wakeLock" in navigator) {
// Create a reference for the Wake Lock.
let wakeLock = null;
// create an async function to request a wake lock
try {
wakeLock = await navigator.wakeLock.request("screen");
} catch (err) {
// The Wake Lock request has failed - usually system related, such as battery.
console.error(err);
}
} else {
//TODO workaround: infinite loop 1sec video
}
}
requestWakeLock();
Для подключения этого файла, его надо добавить в angular.js конфиг:
"projects": {
"frontend": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"browser": "src/main.ts",
…
"styles": [
"src/styles.css"
],
"scripts": [
"src/assets/js/wake-lock.js"
]
И в браузере это работает, но к сожалению, не в окружении node.js. Там объект navigator не определён. Для решения существует такой workaround как добавить на страницу, в невидимую область, небольшое односекундное видео, и зациклить его воспроизведение. Немного безобразно, но пишут, что работает. Даже есть такая библиотека NoSleep.js
https://github.com/richtr/NoSleep.js
npm install nosleep.js
Остаётся проверить если это работает. Скрестим пальцы.
Продолжение следует ...
