mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-26 03:04:36 +00:00
store client ips in bigquery table
This commit is contained in:
@@ -15,11 +15,14 @@ export async function archive(gameRecord: GameRecord) {
|
||||
end: new Date(gameRecord.endTimestampMS),
|
||||
duration_seconds: gameRecord.durationSeconds,
|
||||
number_turns: gameRecord.num_turns,
|
||||
usernames: gameRecord.usernames,
|
||||
game_mode: gameRecord.gameConfig.gameType,
|
||||
winner: null,
|
||||
difficulty: gameRecord.gameConfig.difficulty,
|
||||
map: gameRecord.gameConfig.gameMap,
|
||||
players: gameRecord.players.map(p => ({
|
||||
username: p.username,
|
||||
ip: p.ip,
|
||||
})),
|
||||
};
|
||||
|
||||
await bigquery
|
||||
@@ -29,6 +32,8 @@ export async function archive(gameRecord: GameRecord) {
|
||||
|
||||
console.log(`wrote game metadata to BigQuery: ${gameRecord.id}`);
|
||||
if (gameRecord.turns.length > 0) {
|
||||
// Players will see this so make sure to clear PII.
|
||||
gameRecord.players.forEach(p => p.ip = "REDACTED")
|
||||
console.log(`writing game ${gameRecord.id} to gcs`)
|
||||
const bucket = storage.bucket("openfront-games");
|
||||
const file = bucket.file(gameRecord.id);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
import { Client } from "./Client";
|
||||
import { GamePhase, GameServer } from "./GameServer";
|
||||
import { Difficulty, GameMap, GameType } from "../core/game/Game";
|
||||
import { generateGameID } from "../core/Util";
|
||||
import { generateID } from "../core/Util";
|
||||
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ export class GameManager {
|
||||
}
|
||||
|
||||
createPrivateGame(): string {
|
||||
const id = generateGameID()
|
||||
const id = generateID()
|
||||
this.games.push(new GameServer(
|
||||
id,
|
||||
Date.now(),
|
||||
@@ -79,7 +79,7 @@ export class GameManager {
|
||||
if (now > this.lastNewLobby + this.config.gameCreationRate()) {
|
||||
this.lastNewLobby = now
|
||||
lobbies.push(new GameServer(
|
||||
generateGameID(),
|
||||
generateID(),
|
||||
now,
|
||||
true,
|
||||
this.config,
|
||||
|
||||
+26
-17
@@ -1,10 +1,10 @@
|
||||
import { ClientMessage, ClientMessageSchema, GameConfig, GameRecordSchema, Intent, ServerPingMessageSchema, ServerStartGameMessage, ServerStartGameMessageSchema, ServerTurnMessageSchema, Turn } from "../core/Schemas";
|
||||
import { ClientID, ClientMessage, ClientMessageSchema, GameConfig, GameRecordSchema, Intent, PlayerRecord, ServerPingMessageSchema, ServerStartGameMessage, ServerStartGameMessageSchema, ServerTurnMessageSchema, Turn } from "../core/Schemas";
|
||||
import { Config } from "../core/configuration/Config";
|
||||
import { Client } from "./Client";
|
||||
import WebSocket from 'ws';
|
||||
import { slog } from "./StructuredLog";
|
||||
import { Storage } from '@google-cloud/storage';
|
||||
import { CreateGameRecord, CreateGameRecord as ProcessRecord } from "../core/Util";
|
||||
import { CreateGameRecord } from "../core/Util";
|
||||
import { archive } from "./Archive";
|
||||
import { arc } from "d3";
|
||||
|
||||
@@ -22,7 +22,9 @@ export class GameServer {
|
||||
|
||||
private turns: Turn[] = []
|
||||
private intents: Intent[] = []
|
||||
private clients: Client[] = []
|
||||
private activeClients: Client[] = []
|
||||
// Used for record record keeping
|
||||
private allClients: Map<ClientID, Client> = new Map()
|
||||
private _hasStarted = false
|
||||
private _startTime: number = null
|
||||
|
||||
@@ -35,7 +37,7 @@ export class GameServer {
|
||||
private config: Config,
|
||||
private gameConfig: GameConfig,
|
||||
|
||||
) {}
|
||||
) { }
|
||||
|
||||
public updateGameConfig(gameConfig: GameConfig): void {
|
||||
if (gameConfig.gameMap != null) {
|
||||
@@ -55,13 +57,16 @@ export class GameServer {
|
||||
isRejoin: lastTurn > 0
|
||||
})
|
||||
// Remove stale client if this is a reconnect
|
||||
const existing = this.clients.find(c => c.id == client.id)
|
||||
const existing = this.activeClients.find(c => c.id == client.id)
|
||||
if (existing != null) {
|
||||
existing.ws.removeAllListeners('message')
|
||||
}
|
||||
this.clients = this.clients.filter(c => c.id != client.id)
|
||||
this.clients.push(client)
|
||||
this.activeClients = this.activeClients.filter(c => c.id != client.id)
|
||||
this.activeClients.push(client)
|
||||
client.lastPing = Date.now()
|
||||
|
||||
this.allClients.set(client.id, client)
|
||||
|
||||
client.ws.on('message', (message: string) => {
|
||||
const clientMsg: ClientMessage = ClientMessageSchema.parse(JSON.parse(message))
|
||||
if (clientMsg.type == "intent") {
|
||||
@@ -77,7 +82,7 @@ export class GameServer {
|
||||
})
|
||||
client.ws.on('close', () => {
|
||||
console.log(`client ${client.id} disconnected`)
|
||||
this.clients = this.clients.filter(c => c.id != client.id)
|
||||
this.activeClients = this.activeClients.filter(c => c.id != client.id)
|
||||
})
|
||||
|
||||
// In case a client joined the game late and missed the start message.
|
||||
@@ -87,7 +92,7 @@ export class GameServer {
|
||||
}
|
||||
|
||||
public numClients(): number {
|
||||
return this.clients.length
|
||||
return this.activeClients.length
|
||||
}
|
||||
|
||||
public startTime(): number {
|
||||
@@ -104,7 +109,7 @@ export class GameServer {
|
||||
this._startTime = Date.now()
|
||||
|
||||
this.endTurnIntervalID = setInterval(() => this.endTurn(), this.config.turnIntervalMs());
|
||||
this.clients.forEach(c => {
|
||||
this.activeClients.forEach(c => {
|
||||
console.log(`game ${this.id} sending start message to ${c.id}`)
|
||||
this.sendStartGameMsg(c.ws, 0)
|
||||
})
|
||||
@@ -139,7 +144,7 @@ export class GameServer {
|
||||
turn: pastTurn
|
||||
}
|
||||
))
|
||||
this.clients.forEach(c => {
|
||||
this.activeClients.forEach(c => {
|
||||
c.ws.send(msg)
|
||||
})
|
||||
}
|
||||
@@ -147,7 +152,7 @@ export class GameServer {
|
||||
async endGame() {
|
||||
// Close all WebSocket connections
|
||||
clearInterval(this.endTurnIntervalID);
|
||||
this.clients.forEach(client => {
|
||||
this.activeClients.forEach(client => {
|
||||
client.ws.removeAllListeners('message'); // TODO: remove this?
|
||||
if (client.ws.readyState === WebSocket.OPEN) {
|
||||
client.ws.close(1000, "game has ended");
|
||||
@@ -156,7 +161,11 @@ export class GameServer {
|
||||
console.log(`ending game ${this.id} with ${this.turns.length} turns`)
|
||||
try {
|
||||
if (this.turns.length > 100) {
|
||||
const record = CreateGameRecord(this.id, this.gameConfig, this.turns, this._startTime, Date.now())
|
||||
const playerRecords: PlayerRecord[] = Array.from(this.allClients.values()).map(client => ({
|
||||
ip: client.ip,
|
||||
clientID: client.id,
|
||||
}));
|
||||
const record = CreateGameRecord(this.id, this.gameConfig, playerRecords, this.turns, this._startTime, Date.now())
|
||||
archive(record)
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -167,7 +176,7 @@ export class GameServer {
|
||||
phase(): GamePhase {
|
||||
const now = Date.now()
|
||||
const alive = []
|
||||
for (const client of this.clients) {
|
||||
for (const client of this.activeClients) {
|
||||
if (now - client.lastPing > 60_000) {
|
||||
console.log(`no pings from ${client.id}, terminating connection`)
|
||||
if (client.ws.readyState === WebSocket.OPEN) {
|
||||
@@ -177,14 +186,14 @@ export class GameServer {
|
||||
alive.push(client)
|
||||
}
|
||||
}
|
||||
this.clients = alive
|
||||
this.activeClients = alive
|
||||
if (now > this.createdAt + this.config.lobbyLifetime() + this.maxGameDuration) {
|
||||
console.warn(`game past max duration ${this.id}`)
|
||||
return GamePhase.Finished
|
||||
}
|
||||
if (!this.isPublic) {
|
||||
if (this._hasStarted) {
|
||||
if (this.clients.length == 0) {
|
||||
if (this.activeClients.length == 0) {
|
||||
console.log(`private game: ${this.id} complete`)
|
||||
return GamePhase.Finished
|
||||
} else {
|
||||
@@ -199,7 +208,7 @@ export class GameServer {
|
||||
return GamePhase.Lobby
|
||||
}
|
||||
|
||||
if (this.clients.length == 0 && now > this.createdAt + this.config.lobbyLifetime() + 30 * 60) { // wait at least 30s before ending game
|
||||
if (this.activeClients.length == 0 && now > this.createdAt + this.config.lobbyLifetime() + 30 * 60) { // wait at least 30s before ending game
|
||||
return GamePhase.Finished
|
||||
}
|
||||
|
||||
|
||||
+11
-7
@@ -43,17 +43,18 @@ app.post('/private_lobby', (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
app.post('/archive_singleplayer_game', (req, res) => {
|
||||
try {
|
||||
const gameRecord = req.body
|
||||
const gameRecord: GameRecord = req.body
|
||||
const clientIP = req.ip || req.socket.remoteAddress || 'unknown'; // Added this line
|
||||
|
||||
if (!gameRecord) {
|
||||
console.log('game record not found in request')
|
||||
res.status(404).json({ error: 'Game record not found' });
|
||||
return;
|
||||
}
|
||||
gameRecord.players.forEach(p => p.ip = clientIP)
|
||||
GameRecordSchema.parse(gameRecord);
|
||||
console.log(`archiving singleplayer game ${gameRecord.id}`)
|
||||
archive(gameRecord)
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -91,17 +92,20 @@ app.get('/private_lobby/:id', (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
wss.on('connection', (ws) => {
|
||||
|
||||
wss.on('connection', (ws, req) => {
|
||||
ws.on('message', (message: string) => {
|
||||
const clientMsg: ClientMessage = ClientMessageSchema.parse(JSON.parse(message))
|
||||
slog('websocket_msg', 'server received websocket message', clientMsg, LogSeverity.DEBUG)
|
||||
if (clientMsg.type == "join") {
|
||||
gm.addClient(new Client(clientMsg.clientID, clientMsg.clientIP, ws), clientMsg.gameID, clientMsg.lastTurn)
|
||||
const forwarded = req.headers['x-forwarded-for']
|
||||
const ip = Array.isArray(forwarded)
|
||||
? forwarded[0] // Get the first IP if it's an array
|
||||
: forwarded || req.socket.remoteAddress;
|
||||
|
||||
gm.addClient(new Client(clientMsg.clientID, ip, ws), clientMsg.gameID, clientMsg.lastTurn)
|
||||
}
|
||||
// TODO: send error message
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
function runGame() {
|
||||
|
||||
Reference in New Issue
Block a user