adding persistent id

This commit is contained in:
Evan
2024-12-16 20:55:33 -08:00
committed by evanpelle
parent d99b60b477
commit 722165c401
15 changed files with 65 additions and 23 deletions
+3 -1
View File
@@ -239,9 +239,11 @@
* add capture alert DONE 12/13/2024
* better emojis 🏳️🤦‍♂️🖕☮️🫡😡😈🤡 DONE 12/13/2024
* store ips in bigquery table DONE 12/14/2024
* better error logging in server
* better error logging in server DONE 12/16/2024
* store and archive player cookies
* make ips less precise
* send client logs back to server
* seperate server config from client config
* right click brings up player info menu
* give naval units health
* bug: player names not updating sometimes
+1 -1
View File
@@ -10,7 +10,7 @@ import { createMiniMap, loadTerrainMap, TerrainMapImpl } from "../core/game/Terr
import { and, bfs, dist, generateID, manhattanDist } from "../core/Util";
import { WinCheckExecution } from "../core/execution/WinCheckExecution";
import { SendAttackIntentEvent, SendSpawnIntentEvent, Transport } from "./Transport";
import { createCanvas } from "./graphics/Utils";
import { createCanvas } from "./Utils";
import { DisplayMessageEvent, MessageType } from "./graphics/layers/EventsDisplay";
import { v4 as uuidv4 } from 'uuid';
import { WorkerClient } from "../core/worker/WorkerClient";
View File
+3 -1
View File
@@ -3,6 +3,7 @@ import { EventBus, GameEvent } from "../core/EventBus"
import { AllianceRequest, AllPlayers, Cell, Player, PlayerID, PlayerType, Tile, UnitType } from "../core/game/Game"
import { ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, GameID, Intent, ServerMessage, ServerMessageSchema, ClientPingMessageSchema, GameConfig } from "../core/Schemas"
import { LocalServer } from "./LocalServer"
import { getPersistentIDFromCookie } from "./Utils"
export class SendAllianceRequestIntentEvent implements GameEvent {
@@ -194,7 +195,8 @@ export class Transport {
type: "join",
gameID: this.gameID,
clientID: this.clientID,
lastTurn: numTurns
lastTurn: numTurns,
persistentID: getPersistentIDFromCookie(),
})
)
)
@@ -34,3 +34,29 @@ export function createCanvas(): HTMLCanvasElement {
return canvas
}
// WARNING: DO NOT EXPOSE THIS ID
export function getPersistentIDFromCookie(): string {
const COOKIE_NAME = 'player_persistent_id';
// Try to get existing cookie
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
const [cookieName, cookieValue] = cookie.split('=').map(c => c.trim());
if (cookieName === COOKIE_NAME) {
return cookieValue;
}
}
// If no cookie exists, create new ID and set cookie
const newId = crypto.randomUUID(); // Using built-in UUID generator
document.cookie = [
`${COOKIE_NAME}=${newId}`,
`max-age=${5 * 365 * 24 * 60 * 60}`, // 5 years
'path=/',
'SameSite=Strict',
'Secure'
].join(';');
return newId;
}
+1 -1
View File
@@ -3,7 +3,7 @@ import { customElement, property, state } from 'lit/decorators.js';
import { Layer } from './Layer';
import { Game } from '../../../core/game/Game';
import { ClientID } from '../../../core/Schemas';
import { renderNumber, renderTroops } from '../Utils';
import { renderNumber, renderTroops } from '../../Utils';
import { EventBus } from '../../../core/EventBus';
import { UIState } from '../UIState';
import { SendSetTargetTroopRatioEvent } from '../../Transport';
+1 -1
View File
@@ -5,7 +5,7 @@ import { Theme } from "../../../core/configuration/Config"
import { Layer } from "./Layer"
import { placeName } from "../NameBoxCalculator"
import { TransformHandler } from "../TransformHandler"
import { renderTroops } from "../Utils"
import { renderTroops } from "../../Utils"
import traitorIcon from '../../../../resources/images/TraitorIcon.png';
import allianceIcon from '../../../../resources/images/AllianceIcon.png';
import crownIcon from '../../../../resources/images/CrownIcon.png';
@@ -12,7 +12,7 @@ import goldCoinIcon from '../../../../../resources/images/GoldCoinIcon.svg';
import portIcon from '../../../../../resources/images/PortIcon.svg';
import shieldIcon from '../../../../../resources/images/ShieldIconWhite.svg';
import cityIcon from '../../../../../resources/images/CityIconWhite.svg';
import { renderNumber } from '../../Utils';
import { renderNumber } from '../../../Utils';
import { ContextMenuEvent } from '../../../InputHandler';
interface BuildItemDisplay {
+3 -1
View File
@@ -224,6 +224,7 @@ export const ClientIntentMessageSchema = ClientBaseMessageSchema.extend({
export const ClientJoinMessageSchema = ClientBaseMessageSchema.extend({
type: z.literal('join'),
persistentID: z.string(),
lastTurn: z.number() // The last turn the client saw.
})
@@ -236,7 +237,8 @@ export const PlayerRecordSchema = z.object({
})
export const GameRecordSchema = z.object({
id: z.string(),
id: z.string(), // WARNING: PII
persistentID: z.string(), // WARNING: PII
gameConfig: GameConfigSchema,
players: z.array(PlayerRecordSchema),
startTimestampMS: z.number(),
+1 -1
View File
@@ -3,7 +3,7 @@ import { Cell, Execution, MutableGame, MutablePlayer, Player, PlayerID, PlayerTy
import { PseudoRandom } from "../PseudoRandom";
import { manhattanDist } from "../Util";
import { MessageType } from "../../client/graphics/layers/EventsDisplay";
import { renderNumber } from "../../client/graphics/Utils";
import { renderNumber } from "../../client/Utils";
export class AttackExecution implements Execution {
private breakAlliance = false
+1 -1
View File
@@ -1,5 +1,5 @@
import { MessageType } from "../../client/graphics/layers/EventsDisplay";
import { renderNumber } from "../../client/graphics/Utils";
import { renderNumber } from "../../client/Utils";
import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, Tile, Unit, UnitType } from "../game/Game";
import { PathFinder } from "../pathfinding/PathFinding";
import { PathFindResultType } from "../pathfinding/AStar";
+1 -1
View File
@@ -5,7 +5,7 @@ import { CellString, GameImpl } from "./GameImpl";
import { UnitImpl } from "./UnitImpl";
import { TileImpl } from "./TileImpl";
import { MessageType } from "../../client/graphics/layers/EventsDisplay";
import { renderTroops } from "../../client/graphics/Utils";
import { renderTroops } from "../../client/Utils";
interface Target {
tick: Tick
+2 -1
View File
@@ -7,7 +7,8 @@ export class Client {
public lastPing: number
constructor(
public readonly id: ClientID,
public readonly clientID: ClientID,
public readonly persistentID: string,
public readonly ip: string | null,
public readonly ws: WebSocket,
) { }
+11 -11
View File
@@ -50,23 +50,23 @@ export class GameServer {
}
public addClient(client: Client, lastTurn: number) {
console.log(`${this.id}: adding client ${client.id}`)
slog('client_joined_game', `client ${client.id} (re)joining game ${this.id}`, {
clientID: client.id,
console.log(`${this.id}: adding client ${client.clientID}`)
slog('client_joined_game', `client ${client.clientID} (re)joining game ${this.id}`, {
clientID: client.clientID,
clientIP: client.ip,
gameID: this.id,
isRejoin: lastTurn > 0
})
// Remove stale client if this is a reconnect
const existing = this.activeClients.find(c => c.id == client.id)
const existing = this.activeClients.find(c => c.clientID == client.clientID)
if (existing != null) {
existing.ws.removeAllListeners('message')
}
this.activeClients = this.activeClients.filter(c => c.id != client.id)
this.activeClients = this.activeClients.filter(c => c.clientID != client.clientID)
this.activeClients.push(client)
client.lastPing = Date.now()
this.allClients.set(client.id, client)
this.allClients.set(client.clientID, client)
client.ws.on('message', (message: string) => {
const clientMsg: ClientMessage = ClientMessageSchema.parse(JSON.parse(message))
@@ -83,8 +83,8 @@ export class GameServer {
}
})
client.ws.on('close', () => {
console.log(`${this.id}: client ${client.id} disconnected`)
this.activeClients = this.activeClients.filter(c => c.id != client.id)
console.log(`${this.id}: client ${client.clientID} disconnected`)
this.activeClients = this.activeClients.filter(c => c.clientID != client.clientID)
})
// In case a client joined the game late and missed the start message.
@@ -112,7 +112,7 @@ export class GameServer {
this.endTurnIntervalID = setInterval(() => this.endTurn(), this.config.turnIntervalMs());
this.activeClients.forEach(c => {
console.log(`${this.id}: sending start message to ${c.id}`)
console.log(`${this.id}: sending start message to ${c.clientID}`)
this.sendStartGameMsg(c.ws, 0)
})
}
@@ -165,7 +165,7 @@ export class GameServer {
if (this.allClients.size > 0) {
const playerRecords: PlayerRecord[] = Array.from(this.allClients.values()).map(client => ({
ip: client.ip,
clientID: client.id,
clientID: client.clientID,
}));
const record = CreateGameRecord(this.id, this.gameConfig, playerRecords, this.turns, this._startTime, Date.now())
archive(record)
@@ -202,7 +202,7 @@ export class GameServer {
const alive = []
for (const client of this.activeClients) {
if (now - client.lastPing > 60_000) {
console.log(`${this.id}: no pings from ${client.id}, terminating connection`)
console.log(`${this.id}: no pings from ${client.clientID}, terminating connection`)
if (client.ws.readyState === WebSocket.OPEN) {
client.ws.close(1000, "no heartbeats received, closing connection");
}
+10 -1
View File
@@ -110,7 +110,16 @@ wss.on('connection', (ws, req) => {
? 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)
gm.addClient(
new Client(
clientMsg.clientID,
clientMsg.persistentID,
ip,
ws
),
clientMsg.gameID,
clientMsg.lastTurn
)
}
// TODO: send error message
})