Event Reporting
Event Reporting enables the Game Platform to send real-time tournament events to external systems via AWS SQS queues. This provides reliable, asynchronous communication for webhook delivery, analytics, and business logic integration.SQS Event Architecture
Event Flow
- Game Platform generates tournament events during gameplay
- SQS Queue receives events for reliable, ordered processing
Tournament Platform Event Notifications
New Feature: Tournament Platform now sends automatic event notifications to Game Platform for tournament lifecycle events.
Configuration
Add the Game Platform URL to your Tournament Platform environment:Copy
# .env
GAME_PLATFORM_URL=https://gameplatform.example.com
Supported Events
Tournament Start Event
Sent when a tournament begins (manual start or scheduled start):Copy
POST {GAME_PLATFORM_URL}/internal/tournament/events
Content-Type: application/json
{
"event": "tournament.started",
"tournament_id": "12345",
"tournament_name": "Daily Championship",
"game_slug": "crash-classic",
"started_at": "2024-01-15T20:00:00Z",
"player_count": 87,
"tournament_config": {
"tournament_type": "points_based",
"max_players": 100,
"entry_settings": {
"rebuy_enabled": true,
"max_entries_per_player": 3
}
}
}
Tournament Cancellation Event
Sent when a tournament is cancelled (scheduled or in-progress):Copy
POST {GAME_PLATFORM_URL}/internal/tournament/events
Content-Type: application/json
{
"event": "tournament.cancelled",
"tournament_id": "12345",
"tournament_name": "Daily Championship",
"game_slug": "crash-classic",
"cancelled_at": "2024-01-15T19:45:00Z",
"reason": "Insufficient player registration",
"status_before_cancellation": "scheduled"
}
Event Reliability
- Non-blocking: Event notifications don’t block tournament operations
- Error Handling: Failed notifications are logged but don’t affect tournaments
- Retry Logic: Automatic retries with exponential backoff
- Comprehensive Logging: All event notifications are logged for debugging
- Event Processor consumes events and triggers appropriate actions
- External Systems receive notifications via webhooks or API calls
SQS Event Types
Tournament Lifecycle Events
Tournament Started
Copy
{
"eventType": "room.tournament.started",
"timestamp": "2024-01-15T20:02:15Z",
"tournamentId": "12345",
"roomId": "room_abc123",
"operatorId": "op_1234567890abcdef",
"data": {
"actualStartTime": "2024-01-15T20:02:15Z",
"playerCount": 87,
"gameSlug": "crash-classic",
"estimatedDuration": 4500
},
"metadata": {
"gameVersion": "1.2.3",
"serverRegion": "us-east-1"
}
}
Tournament Completed
Copy
{
"eventType": "room.tournament.completed",
"timestamp": "2024-01-15T21:15:30Z",
"tournamentId": "12345",
"roomId": "room_abc123",
"operatorId": "op_1234567890abcdef",
"data": {
"completedAt": "2024-01-15T21:15:30Z",
"duration": 4395,
"finalPlayerCount": 87,
"totalRounds": 5,
"prizePoolTotal": 870.00,
"topPlayers": [
{
"playerId": "tp_player_456",
"position": 1,
"points": 2150,
"prizeAmount": 435.00
},
{
"playerId": "tp_player_789",
"position": 2,
"points": 1980,
"prizeAmount": 261.00
}
]
}
}
Player Events
Player Connected
Copy
{
"eventType": "room.player.connected",
"timestamp": "2024-01-15T19:45:20Z",
"tournamentId": "12345",
"playerId": "tp_player_456",
"roomId": "room_abc123",
"operatorId": "op_1234567890abcdef",
"data": {
"displayName": "PlayerName",
"connectionTime": "2024-01-15T19:45:20Z",
"ipAddress": "192.168.1.100",
"userAgent": "Mozilla/5.0...",
"entryCount": 2,
"totalPaid": 20.00
}
}
Player Score Updated
Copy
{
"eventType": "player.score.updated",
"timestamp": "2024-01-15T20:15:45Z",
"tournamentId": "12345",
"playerId": "tp_player_456",
"roomId": "room_abc123",
"operatorId": "op_1234567890abcdef",
"data": {
"previousScore": 1100,
"newScore": 1250,
"scoreDelta": 150,
"currentPosition": 15,
"previousPosition": 18,
"round": 3,
"gameAction": "successful_cashout"
}
}
Player Eliminated
Copy
{
"eventType": "player.eliminated",
"timestamp": "2024-01-15T20:45:30Z",
"tournamentId": "12345",
"playerId": "tp_player_789",
"roomId": "room_abc123",
"operatorId": "op_1234567890abcdef",
"data": {
"eliminatedAt": "2024-01-15T20:45:30Z",
"finalPosition": 23,
"finalScore": 850,
"eliminationRound": 3,
"eliminationReason": "insufficient_score",
"survivalTime": 2610
}
}
Game Events
Round Started
Copy
{
"eventType": "game.round.started",
"timestamp": "2024-01-15T20:20:00Z",
"tournamentId": "12345",
"roomId": "room_abc123",
"operatorId": "op_1234567890abcdef",
"data": {
"roundNumber": 3,
"roundStartTime": "2024-01-15T20:20:00Z",
"activePlayers": 67,
"eliminatedPlayers": 20,
"roundDuration": 300,
"gameConfig": {
"eliminationRate": 0.3,
"minCrashMultiplier": 1.2
}
}
}
Event Publishing Implementation
SQS Event Publisher
Copy
const AWS = require('aws-sdk');
class SQSEventPublisher {
constructor(queueUrl, region = 'us-east-1') {
this.sqs = new AWS.SQS({ region });
this.queueUrl = queueUrl;
this.batchSize = 10; // SQS batch limit
this.messageQueue = [];
}
async publishEvent(eventType, tournamentId, data, metadata = {}) {
const event = {
eventType: eventType,
timestamp: new Date().toISOString(),
tournamentId: tournamentId,
eventId: this.generateEventId(),
data: data,
metadata: {
source: 'game-platform',
version: '1.0',
...metadata
}
};
try {
await this.sendToSQS(event);
console.log(`Published event: ${eventType} for tournament ${tournamentId}`);
} catch (error) {
console.error('Failed to publish event:', error);
throw error;
}
}
async sendToSQS(event) {
const params = {
QueueUrl: this.queueUrl,
MessageBody: JSON.stringify(event),
MessageAttributes: {
'EventType': {
DataType: 'String',
StringValue: event.eventType
},
'TournamentId': {
DataType: 'String',
StringValue: event.tournamentId
},
'OperatorId': {
DataType: 'String',
StringValue: event.operatorId || 'unknown'
}
},
MessageGroupId: event.tournamentId, // For FIFO queues
MessageDeduplicationId: event.eventId
};
return await this.sqs.sendMessage(params).promise();
}
generateEventId() {
return `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// Batch publishing for high-volume events
queueEvent(event) {
this.messageQueue.push(event);
if (this.messageQueue.length >= this.batchSize) {
this.flushBatch();
}
}
async flushBatch() {
if (this.messageQueue.length === 0) return;
const batch = this.messageQueue.splice(0, this.batchSize);
const params = {
QueueUrl: this.queueUrl,
Entries: batch.map((event, index) => ({
Id: index.toString(),
MessageBody: JSON.stringify(event),
MessageGroupId: event.tournamentId,
MessageDeduplicationId: event.eventId,
MessageAttributes: {
'EventType': {
DataType: 'String',
StringValue: event.eventType
}
}
}))
};
try {
await this.sqs.sendMessageBatch(params).promise();
console.log(`Batch published ${batch.length} events`);
} catch (error) {
console.error('Batch publish failed:', error);
// Re-queue failed events
this.messageQueue.unshift(...batch);
}
}
}
Tournament Event Manager
Copy
class TournamentEventManager {
constructor(publisher) {
this.publisher = publisher;
this.eventBuffer = new Map();
this.flushInterval = setInterval(() => {
this.flushBufferedEvents();
}, 1000); // Flush every second
}
// Tournament lifecycle events
async tournamentStarted(tournamentId, roomId, startData) {
await this.publisher.publishEvent(
'room.tournament.started',
tournamentId,
{
roomId: roomId,
actualStartTime: new Date().toISOString(),
playerCount: startData.playerCount,
gameSlug: startData.gameSlug,
estimatedDuration: startData.estimatedDuration
}
);
}
async tournamentCompleted(tournamentId, roomId, results) {
await this.publisher.publishEvent(
'room.tournament.completed',
tournamentId,
{
roomId: roomId,
completedAt: new Date().toISOString(),
duration: results.duration,
finalPlayerCount: results.playerCount,
totalRounds: results.rounds,
prizePoolTotal: results.prizePool,
topPlayers: results.winners.slice(0, 3) // Top 3 players
}
);
}
// Player events
async playerConnected(tournamentId, playerId, connectionData) {
await this.publisher.publishEvent(
'room.player.connected',
tournamentId,
{
playerId: playerId,
displayName: connectionData.displayName,
connectionTime: new Date().toISOString(),
ipAddress: connectionData.ipAddress,
entryCount: connectionData.entryCount,
totalPaid: connectionData.totalPaid
}
);
}
// Buffered score updates to reduce event volume
bufferScoreUpdate(tournamentId, playerId, scoreData) {
const key = `${tournamentId}_${playerId}`;
this.eventBuffer.set(key, {
eventType: 'player.score.updated',
tournamentId: tournamentId,
playerId: playerId,
data: scoreData,
lastUpdate: new Date()
});
}
async flushBufferedEvents() {
const events = Array.from(this.eventBuffer.values());
this.eventBuffer.clear();
for (const event of events) {
try {
await this.publisher.publishEvent(
event.eventType,
event.tournamentId,
{
playerId: event.playerId,
timestamp: event.lastUpdate.toISOString(),
...event.data
}
);
} catch (error) {
console.error('Failed to flush buffered event:', error);
}
}
}
async playerEliminated(tournamentId, playerId, eliminationData) {
await this.publisher.publishEvent(
'player.eliminated',
tournamentId,
{
playerId: playerId,
eliminatedAt: new Date().toISOString(),
finalPosition: eliminationData.position,
finalScore: eliminationData.score,
eliminationRound: eliminationData.round,
eliminationReason: eliminationData.reason,
survivalTime: eliminationData.survivalTime
}
);
}
}
Game Integration Example
Copy
class CrashGameEventIntegration {
constructor(eventManager) {
this.eventManager = eventManager;
}
// Game-specific event handlers
async onGameRoundStart(tournamentId, roundData) {
await this.eventManager.publisher.publishEvent(
'game.round.started',
tournamentId,
{
roundNumber: roundData.roundNumber,
roundStartTime: new Date().toISOString(),
activePlayers: roundData.activePlayers,
eliminatedPlayers: roundData.eliminatedPlayers,
roundDuration: roundData.duration,
gameConfig: roundData.config
}
);
}
async onCrashEvent(tournamentId, crashData) {
await this.eventManager.publisher.publishEvent(
'game.crash.occurred',
tournamentId,
{
crashMultiplier: crashData.multiplier,
crashTime: crashData.crashTime,
playersActive: crashData.playersActive,
playersCashedOut: crashData.playersCashedOut,
totalWinnings: crashData.totalWinnings
}
);
}
onPlayerAction(tournamentId, playerId, action, actionData) {
// Buffer high-frequency events
this.eventManager.bufferScoreUpdate(tournamentId, playerId, {
action: action,
actionData: actionData,
timestamp: new Date().toISOString()
});
}
}
Error Handling & Reliability
Dead Letter Queue Handling
Copy
class ReliableEventPublisher extends SQSEventPublisher {
constructor(queueUrl, dlqUrl, region = 'us-east-1') {
super(queueUrl, region);
this.dlqUrl = dlqUrl;
this.retryAttempts = 3;
this.retryDelay = 1000; // 1 second
}
async publishEventWithRetry(eventType, tournamentId, data, metadata = {}) {
let lastError;
for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
try {
await this.publishEvent(eventType, tournamentId, data, metadata);
return; // Success
} catch (error) {
lastError = error;
console.warn(`Event publish attempt ${attempt} failed:`, error.message);
if (attempt < this.retryAttempts) {
await this.delay(this.retryDelay * Math.pow(2, attempt - 1)); // Exponential backoff
}
}
}
// All retries failed - send to DLQ
console.error(`Event publish failed after ${this.retryAttempts} attempts:`, lastError);
await this.sendToDeadLetterQueue({
originalEvent: { eventType, tournamentId, data, metadata },
error: lastError.message,
attempts: this.retryAttempts,
timestamp: new Date().toISOString()
});
}
async sendToDeadLetterQueue(failedEvent) {
try {
await this.sqs.sendMessage({
QueueUrl: this.dlqUrl,
MessageBody: JSON.stringify(failedEvent)
}).promise();
console.log('Event sent to dead letter queue');
} catch (error) {
console.error('Failed to send to dead letter queue:', error);
}
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
Event Monitoring
Copy
class EventMetrics {
constructor() {
this.metrics = {
published: 0,
failed: 0,
retries: 0,
averageLatency: 0,
eventTypes: new Map()
};
this.startTime = Date.now();
// Report metrics every 60 seconds
setInterval(() => {
this.reportMetrics();
}, 60000);
}
recordPublished(eventType, latencyMs) {
this.metrics.published++;
// Track by event type
const typeCount = this.metrics.eventTypes.get(eventType) || 0;
this.metrics.eventTypes.set(eventType, typeCount + 1);
// Update average latency
this.updateAverageLatency(latencyMs);
}
recordFailed(eventType, error) {
this.metrics.failed++;
console.error(`Event publication failed - ${eventType}:`, error);
}
recordRetry(eventType) {
this.metrics.retries++;
}
updateAverageLatency(latencyMs) {
const count = this.metrics.published;
this.metrics.averageLatency =
(this.metrics.averageLatency * (count - 1) + latencyMs) / count;
}
reportMetrics() {
const uptime = Date.now() - this.startTime;
const publishRate = (this.metrics.published / (uptime / 1000)).toFixed(2);
console.log('Event Metrics Report:');
console.log(` Published: ${this.metrics.published}`);
console.log(` Failed: ${this.metrics.failed}`);
console.log(` Retries: ${this.metrics.retries}`);
console.log(` Publish Rate: ${publishRate} events/second`);
console.log(` Average Latency: ${this.metrics.averageLatency.toFixed(2)}ms`);
// Reset counters
Object.assign(this.metrics, {
published: 0,
failed: 0,
retries: 0,
eventTypes: new Map()
});
}
}
Testing & Debugging
Event Testing Framework
Copy
class EventTestFramework {
constructor() {
this.publishedEvents = [];
this.mockPublisher = {
publishEvent: this.mockPublishEvent.bind(this)
};
}
async mockPublishEvent(eventType, tournamentId, data, metadata) {
const event = {
eventType,
tournamentId,
data,
metadata,
timestamp: new Date().toISOString()
};
this.publishedEvents.push(event);
console.log('Mock published event:', event);
return event;
}
getPublishedEvents(eventType = null) {
if (eventType) {
return this.publishedEvents.filter(e => e.eventType === eventType);
}
return [...this.publishedEvents];
}
clearEvents() {
this.publishedEvents = [];
}
assertEventPublished(eventType, tournamentId) {
const found = this.publishedEvents.find(e =>
e.eventType === eventType && e.tournamentId === tournamentId
);
if (!found) {
throw new Error(`Expected event ${eventType} for tournament ${tournamentId} was not published`);
}
return found;
}
}
// Usage in tests
const testFramework = new EventTestFramework();
const eventManager = new TournamentEventManager(testFramework.mockPublisher);
// Test tournament started event
await eventManager.tournamentStarted('12345', 'room_abc123', {
playerCount: 50,
gameSlug: 'crash-classic',
estimatedDuration: 3600
});
// Assert event was published
const event = testFramework.assertEventPublished('room.tournament.started', '12345');
console.log('Event published successfully:', event);

