ws.once("obs_logon", async (msg) => { try { const authenticationData: IAuthenticationData = JSON.parse(msg.toString()); if (WebsocketIncoming.authedClients.find((client) => client.ws.id === ws.id) != undefined) return; // Check if the packet is valid if (authenticationData.type !== DataTypes.AUTH) { ws.emit( "obs_logon_ack", JSON.stringify({ type: DataTypes.AUTH, value: false, reason: `Invalid packet.` }), ); ws.disconnect(); Log.info(`Received BAD auth request, invalid packet.`); return; } // Check if the client version is compatible with the server version if (!isCompatibleVersion(authenticationData.clientVersion)) { ws.emit( "obs_logon_ack", JSON.stringify({ type: DataTypes.AUTH, value: false, reason: `Client version ${authenticationData.clientVersion} is not compatible with server version ${module.exports.version}.`, }), ); ws.disconnect(); Log.info( `Received BAD auth request from ${authenticationData.obsName}, using Group Code ${authenticationData.groupCode} and key ${authenticationData.key}, incompatible client version ${authenticationData.clientVersion}.`, ); return; } // Check if the key is valid const validity = await this.isValidKey(authenticationData.key); if (validity.valid === false) { ws.emit( "obs_logon_ack", JSON.stringify({ type: DataTypes.AUTH, value: false, reason: validity.reason }), ); ws.disconnect(); Log.info( `Received BAD auth request from ${authenticationData.obsName}, using Group Code ${authenticationData.groupCode} and key ${authenticationData.key}`, ); return; } else { //key is valid if (validity.organizationId) { authenticationData.organizationId = validity.organizationId; } authenticationData.isSupporter = validity.isSupporter; } // Check if the match can be created successfully const groupSecret = await this.matchController.createMatch(authenticationData); if (!groupSecret || groupSecret === "") { ws.emit( "obs_logon_ack", JSON.stringify({ type: DataTypes.AUTH, value: false, reason: `Game with Group Code ${authenticationData.groupCode} exists and is still live.`, }), ); ws.disconnect(); Log.info( `Received BAD auth request from ${authenticationData.obsName}, using Group Code ${authenticationData.groupCode} and key ${authenticationData.key}`, ); return; } // All checks passed, send logon acknolwedgement ws.emit( "obs_logon_ack", JSON.stringify({ type: DataTypes.AUTH, value: true, reason: groupSecret }), ); user.name = authenticationData.obsName; user.groupCode = authenticationData.groupCode; WebsocketIncoming.authedClients.push(user); Log.info( `Received VALID auth request from ${authenticationData.obsName}, using Group Code ${authenticationData.groupCode} and with teams ${authenticationData.leftTeam.name} and ${authenticationData.rightTeam.name}`, ); this.onAuthSuccess(user); } catch (e) { Log.error(`Error parsing incoming auth request: ${e}`); Log.error(e); }});
ws.once("aux_logon", async (msg) => { try { const authenticationData: IAuxAuthenticationData = JSON.parse(msg.toString()); if (WebsocketIncoming.authedClients.find((client) => client.ws.id === ws.id) != undefined) return; // Check if the packet is valid if (authenticationData.type !== DataTypes.AUX_AUTH) { ws.emit( "aux_logon_ack", JSON.stringify({ type: DataTypes.AUTH, value: false, reason: `Invalid packet.` }), ); ws.disconnect(); Log.info(`Received BAD aux auth request, invalid packet.`); return; } // Check if the client version is compatible with the server version if (!isCompatibleVersion(authenticationData.clientVersion)) { ws.emit( "aux_logon_ack", JSON.stringify({ type: DataTypes.AUTH, value: false, reason: `Client version ${authenticationData.clientVersion} is not compatible with server version ${module.exports.version}.`, }), ); ws.disconnect(); Log.info( `Received BAD aux auth request from ${authenticationData.playerId} for match ${authenticationData.matchId}, incompatible client version ${authenticationData.clientVersion}.`, ); return; } const groupCode = this.matchController.findMatch(authenticationData.matchId); // Check if the match exists if (groupCode == null) { ws.emit( "aux_logon_ack", JSON.stringify({ type: DataTypes.AUTH, value: false, reason: `Game with Match ID ${authenticationData.matchId} not found.`, }), ); ws.disconnect(); Log.info( `Received BAD aux auth request from ${authenticationData.playerId} for match ${authenticationData.matchId}, match not found.`, ); return; } // All checks passed, send logon acknolwedgement ws.emit("aux_logon_ack", JSON.stringify({ type: DataTypes.AUX_AUTH, value: true })); user.name = authenticationData.name; user.groupCode = groupCode; user.isAuxiliary = true; user.playerId = authenticationData.playerId; WebsocketIncoming.authedClients.push(user); Log.info( `Received VALID aux auth request from ${authenticationData.playerId} for Group Code ${groupCode}`, ); this.onAuthSuccess(user); } catch (e) { Log.error(`Error parsing incoming auth request: ${e}`); Log.error(e); }});
Both authentication flows check client version compatibility using isCompatibleVersion().This ensures observer/auxiliary clients are running compatible software versions with the server.
Version compatibility logic is defined in src/util/CompatibleClients.ts (not shown in source files).
Clients should listen for disconnection and handle errors gracefully:
socket.on("obs_logon_ack", (msg) => { const response = JSON.parse(msg); if (!response.value) { console.error("Authentication failed:", response.reason); // Socket will be disconnected by server }});socket.on("disconnect", () => { console.log("Disconnected from server");});