Web игрa Spy на Node.JS, Angular и Websockets (Часть 8)

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

Остаётся проверить если это работает. Скрестим пальцы.

Продолжение следует ...

Leave a Reply

Your email address will not be published. Required fields are marked *