Real-time Communication
Real-time Communication enables seamless interaction between the Game Platform and Casino UI through PostMessage events, WebSocket connections, and synchronized data updates during tournament gameplay.Communication Architecture
Communication Patterns
- Game Platform � Casino UI: PostMessage for cross-frame communication
- Game Platform � Tournament System: WebSocket for real-time updates
- Game Platform � Event System: SQS events for external notifications
- Event System � Casino System: Webhooks for business logic integration
PostMessage Communication
PostMessage Event Types
Tournament Events
Copy
// Tournament connected - player successfully joined tournament
{
type: 'tournament.connected',
tournamentId: '12345',
playerId: 'tp_player_456',
status: 'active',
playerInfo: {
displayName: 'PlayerName',
position: 15,
entryCount: 2
}
}
// Tournament started - gameplay begins
{
type: 'tournament.started',
tournamentId: '12345',
startTime: '2024-01-15T20:02:15Z',
playerCount: 87,
currentRound: 1,
totalRounds: 5
}
// Tournament completed - final results
{
type: 'tournament.completed',
tournamentId: '12345',
completedAt: '2024-01-15T21:15:00Z',
finalResults: {
position: 3,
points: 1450,
prizeAmount: 46.00
},
winners: [
{ position: 1, displayName: 'Winner1', prizeAmount: 115.00 }
]
}
Player Events
Copy
// Player eliminated
{
type: 'player.eliminated',
tournamentId: '12345',
playerId: 'tp_player_456',
eliminatedAt: '2024-01-15T20:45:00Z',
finalPosition: 23,
finalPoints: 850,
eliminationRound: 3
}
// Rebuy requested
{
type: 'rebuy.requested',
tournamentId: '12345',
playerId: 'tp_player_456',
rebuyAmount: 10.00,
currentEntries: 2,
maxEntries: 3
}
// Player score updated
{
type: 'player.score.updated',
tournamentId: '12345',
playerId: 'tp_player_456',
score: 1250,
position: 15,
round: 3
}
PostMessage Implementation
Game Platform Side (Sender)
Copy
class GamePlatformMessaging {
constructor(parentWindow) {
this.parentWindow = parentWindow;
this.targetOrigin = 'https://your-casino-domain.com';
}
sendTournamentEvent(eventType, data) {
const message = {
source: 'game-platform',
timestamp: new Date().toISOString(),
type: eventType,
...data
};
try {
this.parentWindow.postMessage(message, this.targetOrigin);
console.log('Sent event:', eventType, data);
} catch (error) {
console.error('PostMessage failed:', error);
}
}
// Tournament lifecycle events
onTournamentConnected(tournamentId, playerId, playerInfo) {
this.sendTournamentEvent('tournament.connected', {
tournamentId,
playerId,
status: 'active',
playerInfo
});
}
onTournamentStarted(tournamentId, startTime, playerCount, rounds) {
this.sendTournamentEvent('tournament.started', {
tournamentId,
startTime,
playerCount,
currentRound: 1,
totalRounds: rounds
});
}
onPlayerEliminated(tournamentId, playerId, eliminationData) {
this.sendTournamentEvent('player.eliminated', {
tournamentId,
playerId,
eliminatedAt: new Date().toISOString(),
...eliminationData
});
}
onRebuyRequested(tournamentId, playerId, rebuyData) {
this.sendTournamentEvent('rebuy.requested', {
tournamentId,
playerId,
...rebuyData
});
}
}
Casino UI Side (Receiver)
Copy
class CasinoUIMessaging {
constructor() {
this.gameFrame = null;
this.eventHandlers = new Map();
this.setupMessageListener();
}
setupMessageListener() {
window.addEventListener('message', (event) => {
// Verify origin for security
if (event.origin !== 'https://your-game-platform.com') {
console.warn('Ignored message from unknown origin:', event.origin);
return;
}
// Verify message structure
if (!event.data.source || event.data.source !== 'game-platform') {
return;
}
this.handleGameMessage(event.data);
});
}
handleGameMessage(message) {
const { type, ...data } = message;
console.log('Received game event:', type, data);
// Route to appropriate handler
switch (type) {
case 'tournament.connected':
this.handleTournamentConnected(data);
break;
case 'tournament.started':
this.handleTournamentStarted(data);
break;
case 'tournament.completed':
this.handleTournamentCompleted(data);
break;
case 'player.eliminated':
this.handlePlayerEliminated(data);
break;
case 'rebuy.requested':
this.handleRebuyRequested(data);
break;
case 'player.score.updated':
this.handleScoreUpdated(data);
break;
default:
console.log('Unhandled event type:', type);
}
}
// Event handlers
handleTournamentConnected(data) {
// Update UI to show tournament connected state
this.updateTournamentStatus('Connected');
this.displayPlayerInfo(data.playerInfo);
}
handleTournamentStarted(data) {
// Show tournament started notification
this.showNotification('Tournament started!', 'success');
this.updateTournamentTimer(data.startTime);
this.displayPlayerCount(data.playerCount);
}
handleRebuyRequested(data) {
// Show rebuy confirmation dialog
this.showRebuyDialog(data.tournamentId, data.playerId, data.rebuyAmount);
}
async handleRebuyConfirmation(tournamentId, playerId, rebuyAmount) {
try {
// Process rebuy via Casino API
const response = await fetch(`/api/tournaments/${tournamentId}/players/${playerId}/rebuy`, {
method: 'POST',
headers: {
'Authorization': 'Bearer casino-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({ rebuyAmount })
});
const result = await response.json();
if (result.rebuyProcessed) {
// Send confirmation back to game
this.sendToGame({
type: 'rebuy.success',
tournamentId,
playerId,
newEntryCount: result.newEntryCount,
remainingRebuys: result.remainingRebuys
});
this.showNotification('Rebuy successful!', 'success');
this.updatePlayerBalance(result.newBalance);
} else {
this.sendToGame({
type: 'rebuy.failed',
tournamentId,
playerId,
error: result.error
});
this.showNotification('Rebuy failed: ' + result.error, 'error');
}
} catch (error) {
console.error('Rebuy processing failed:', error);
this.showNotification('Rebuy failed due to technical error', 'error');
}
}
sendToGame(message) {
if (this.gameFrame && this.gameFrame.contentWindow) {
this.gameFrame.contentWindow.postMessage({
source: 'casino-ui',
timestamp: new Date().toISOString(),
...message
}, 'https://your-game-platform.com');
}
}
}
WebSocket Communication
Tournament System WebSocket
Copy
class TournamentWebSocket {
constructor(tournamentId, internalApiKey) {
this.tournamentId = tournamentId;
this.apiKey = internalApiKey;
this.ws = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.eventHandlers = new Map();
}
async connect() {
const wsUrl = `wss://tournament-system.internal/ws/tournaments/${this.tournamentId}`;
try {
this.ws = new WebSocket(wsUrl, {
headers: {
'Authorization': `Bearer ${this.apiKey}`
}
});
this.ws.onopen = () => {
console.log(`WebSocket connected for tournament ${this.tournamentId}`);
this.reconnectAttempts = 0;
// Send initial subscription
this.subscribe(['tournament.status', 'player.updates', 'game.events']);
};
this.ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
this.handleMessage(message);
} catch (error) {
console.error('Failed to parse WebSocket message:', error);
}
};
this.ws.onclose = (event) => {
console.log('WebSocket closed:', event.code, event.reason);
this.attemptReconnect();
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
} catch (error) {
console.error('WebSocket connection failed:', error);
throw error;
}
}
subscribe(eventTypes) {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
type: 'subscribe',
events: eventTypes
}));
}
}
handleMessage(message) {
const { type, data } = message;
switch (type) {
case 'tournament.status.changed':
this.handleTournamentStatusChange(data);
break;
case 'player.registered':
this.handlePlayerRegistered(data);
break;
case 'tournament.started':
this.handleTournamentStarted(data);
break;
case 'player.eliminated':
this.handlePlayerEliminated(data);
break;
default:
console.log('Unhandled WebSocket message:', type, data);
}
}
attemptReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
setTimeout(() => {
this.connect();
}, delay);
} else {
console.error('Max WebSocket reconnection attempts exceeded');
}
}
}
Game Room WebSocket
Copy
class GameRoomWebSocket {
constructor(roomId, playerId) {
this.roomId = roomId;
this.playerId = playerId;
this.ws = null;
this.heartbeatInterval = null;
}
async connect(jwtToken) {
const wsUrl = `wss://ts.playservices.tech/ws/room/${this.roomId}?token=${jwtToken}`;
this.ws = new WebSocket(wsUrl);
this.ws.onopen = () => {
console.log(`Connected to game room ${this.roomId}`);
this.startHeartbeat();
};
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data);
this.handleGameMessage(message);
};
this.ws.onclose = () => {
console.log('Game room WebSocket closed');
this.stopHeartbeat();
};
}
handleGameMessage(message) {
const { type, data } = message;
switch (type) {
case 'room.state':
this.updateRoomState(data);
break;
case 'game.round.started':
this.handleRoundStart(data);
break;
case 'game.round.ended':
this.handleRoundEnd(data);
break;
case 'player.action.required':
this.handleActionRequired(data);
break;
case 'leaderboard.updated':
this.updateLeaderboard(data);
break;
}
}
sendGameAction(action, data = {}) {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
type: 'player.action',
action: action,
playerId: this.playerId,
timestamp: new Date().toISOString(),
data: data
}));
}
}
startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000); // Send heartbeat every 30 seconds
}
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
}
}
Data Synchronization
Real-time Leaderboard Updates
Copy
class LeaderboardSync {
constructor(messaging, tournamentId) {
this.messaging = messaging;
this.tournamentId = tournamentId;
this.leaderboard = [];
this.updateThrottle = null;
}
updatePlayerScore(playerId, score, position) {
// Update local leaderboard
const playerIndex = this.leaderboard.findIndex(p => p.playerId === playerId);
if (playerIndex >= 0) {
this.leaderboard[playerIndex] = { playerId, score, position };
} else {
this.leaderboard.push({ playerId, score, position });
}
// Sort by score descending
this.leaderboard.sort((a, b) => b.score - a.score);
// Throttle updates to avoid spam
if (!this.updateThrottle) {
this.updateThrottle = setTimeout(() => {
this.syncLeaderboard();
this.updateThrottle = null;
}, 1000); // Update every second at most
}
}
syncLeaderboard() {
// Send to Casino UI
this.messaging.sendTournamentEvent('leaderboard.updated', {
tournamentId: this.tournamentId,
leaderboard: this.leaderboard.slice(0, 10), // Top 10 only
lastUpdated: new Date().toISOString()
});
}
}
State Persistence
Copy
class GameStateManager {
constructor(roomId) {
this.roomId = roomId;
this.state = {};
this.saveInterval = null;
}
updateState(key, value) {
this.state[key] = {
value: value,
timestamp: new Date().toISOString()
};
// Schedule state persistence
this.scheduleStateSave();
}
scheduleStateSave() {
if (!this.saveInterval) {
this.saveInterval = setTimeout(() => {
this.persistState();
this.saveInterval = null;
}, 5000); // Save every 5 seconds
}
}
async persistState() {
try {
await fetch(`/internal/rooms/${this.roomId}/state`, {
method: 'POST',
headers: {
'Authorization': 'Bearer internal-api-key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
state: this.state,
timestamp: new Date().toISOString()
})
});
console.log('Game state persisted successfully');
} catch (error) {
console.error('Failed to persist game state:', error);
}
}
}
Error Handling & Resilience
Connection Recovery
Copy
class CommunicationResilience {
constructor() {
this.connectionStates = new Map();
this.messageQueue = [];
this.maxQueueSize = 100;
}
trackConnection(name, connection) {
this.connectionStates.set(name, {
connection: connection,
status: 'connected',
lastActivity: new Date(),
failureCount: 0
});
}
handleConnectionFailure(name, error) {
const state = this.connectionStates.get(name);
if (state) {
state.status = 'disconnected';
state.failureCount++;
state.lastError = error;
console.error(`Connection ${name} failed:`, error);
// Queue messages during downtime
this.startMessageQueuing(name);
// Attempt recovery
this.attemptRecovery(name);
}
}
queueMessage(connectionName, message) {
if (this.messageQueue.length >= this.maxQueueSize) {
// Remove oldest message if queue is full
this.messageQueue.shift();
}
this.messageQueue.push({
connection: connectionName,
message: message,
timestamp: new Date(),
retries: 0
});
}
async flushQueuedMessages(connectionName) {
const queuedMessages = this.messageQueue.filter(m => m.connection === connectionName);
for (const queued of queuedMessages) {
try {
await this.sendMessage(connectionName, queued.message);
// Remove from queue on success
const index = this.messageQueue.indexOf(queued);
if (index >= 0) {
this.messageQueue.splice(index, 1);
}
} catch (error) {
queued.retries++;
if (queued.retries >= 3) {
// Remove failed messages after 3 retries
const index = this.messageQueue.indexOf(queued);
if (index >= 0) {
this.messageQueue.splice(index, 1);
}
}
}
}
}
}

