Skip to main content

Receive Messages

Handle incoming messages from other players in a Standard Room using the onMessageReceived callback. This callback is triggered whenever another player sends a message to you or to the entire room.

Callback Signature​

onMessageReceived: (messageData, senderParticipantID, isReliable) => {
// Handle incoming message
}

Parameters​

ParameterTypeDescription
messageDataArrayBufferThe message content as binary data
senderParticipantIDstringID of the player who sent the message
isReliablebooleanWhether the message was sent reliably

Examples​

Basic Message Handling​

Handle incoming messages and route them based on type:

// In your room creation or join parameters
onMessageReceived: (messageData, senderParticipantID, isReliable) => {
try {
// Convert ArrayBuffer to string
const decoder = new TextDecoder();
const jsonString = decoder.decode(messageData);
const message = JSON.parse(jsonString);

console.log(`Message from ${senderParticipantID}:`, message.type);

// Route message based on type
switch (message.type) {
case 'gameState':
handleGameStateUpdate(message, senderParticipantID);
break;
case 'chat':
handleChatMessage(message, senderParticipantID);
break;
case 'gameAction':
handleGameAction(message, senderParticipantID);
break;
case 'position':
handlePositionUpdate(message, senderParticipantID);
break;
default:
console.warn('Unknown message type:', message.type);
}
} catch (error) {
console.error('Failed to parse message:', error);
}
}

// Message handlers
function handleGameStateUpdate(message, senderId) {
console.log('Game state updated by', senderId);
updateLocalGameState(message.data);
syncGameUI(message.data);
}

function handleChatMessage(message, senderId) {
console.log(`Chat from ${senderId}: ${message.message}`);
displayChatMessage(message.message, message.sender, senderId);
}

function handleGameAction(message, senderId) {
console.log(`Game action from ${senderId}: ${message.action}`);
processGameAction(message.action, message.data, senderId);
}

function handlePositionUpdate(message, senderId) {
console.log(`Position update from ${senderId}`);
updatePlayerPosition(senderId, message.x, message.y);
}

Handle Private Messages​

Process private messages sent specifically to you:

onMessageReceived: (messageData, senderParticipantID, isReliable) => {
const decoder = new TextDecoder();
const jsonString = decoder.decode(messageData);
const message = JSON.parse(jsonString);

// Handle private messages
if (message.type === 'privateMessage') {
console.log(`Private message from ${senderParticipantID}: ${message.message}`);

// Show private message notification
showPrivateMessageNotification(message.sender, message.message);

// Add to private chat
addPrivateMessageToChat(message.sender, message.message, senderParticipantID);

// Play notification sound if from different player
if (senderParticipantID !== currentChatPartner) {
playNotificationSound('privateMessage');
}
}
// Handle team messages
else if (message.type === 'teamMessage') {
console.log(`Team message from ${senderParticipantID}: ${message.message}`);

// Display in team chat
addTeamMessage(message.message, message.sender, senderParticipantID);

// Highlight team messages
highlightTeamChat();
}
// Handle other message types
else {
handleRegularMessage(message, senderParticipantID);
}
}

Handle Real-Time Updates​

Process frequent real-time updates efficiently:

// Track last update times to prevent processing old messages
const lastUpdateTime = {};

onMessageReceived: (messageData, senderParticipantID, isReliable) => {
const decoder = new TextDecoder();
const jsonString = decoder.decode(messageData);
const message = JSON.parse(jsonString);

// Handle position updates (high frequency)
if (message.type === 'position') {
// Skip old messages
if (lastUpdateTime[senderParticipantID] &&
lastUpdateTime[senderParticipantID] > message.timestamp) {
return;
}

lastUpdateTime[senderParticipantID] = message.timestamp;

// Update player position smoothly
updatePlayerPositionSmoothly(senderParticipantID, {
x: message.x,
y: message.y,
vx: message.vx,
vy: message.vy
});
}

// Handle animation updates
else if (message.type === 'animation') {
playPlayerAnimation(senderParticipantID, message.animation, message.duration);
}

// Handle effect updates
else if (message.type === 'effect') {
showVisualEffect(senderParticipantID, message.effect, message.position);
}

// Handle other game updates
else {
handleGameMessage(message, senderParticipantID);
}
}

// Smooth position interpolation
function updatePlayerPositionSmoothly(playerId, positionData) {
const player = getPlayerObject(playerId);
if (!player) return;

// Interpolate to new position
player.targetX = positionData.x;
player.targetY = positionData.y;
player.velocityX = positionData.vx;
player.velocityY = positionData.vy;

// Start interpolation if not already running
if (!player.interpolating) {
interpolatePlayerPosition(player);
}
}

Handle Server Responses​

Process responses from server messages:

// Track pending requests
const pendingRequests = new Map();

onMessageReceived: (messageData, senderParticipantID, isReliable) => {
const decoder = new TextDecoder();
const jsonString = decoder.decode(messageData);
const message = JSON.parse(jsonString);

// Handle server responses
if (message.type === 'serverResponse') {
console.log('Server response received:', message.requestId);

// Find the pending request
const pendingRequest = pendingRequests.get(message.requestId);
if (pendingRequest) {
// Resolve the pending request
if (message.success) {
pendingRequest.resolve(message.data);
} else {
pendingRequest.reject(new Error(message.error));
}

// Remove from pending requests
pendingRequests.delete(message.requestId);
}
}

// Handle server notifications
else if (message.type === 'serverNotification') {
console.log('Server notification:', message.notification);

switch (message.notification) {
case 'tournamentStarting':
showTournamentAlert(message.data);
break;
case 'maintenanceWarning':
showMaintenanceWarning(message.data);
break;
case 'globalEvent':
showGlobalEvent(message.data);
break;
}
}

// Handle regular player messages
else {
handlePlayerMessage(message, senderParticipantID);
}
}

// Helper function to create pending requests
function sendServerRequest(requestData) {
return new Promise((resolve, reject) => {
const requestId = generateRequestId();

// Store the pending request
pendingRequests.set(requestId, { resolve, reject });

// Send the request
const message = {
...requestData,
requestId: requestId
};

sendToServer(message);

// Set timeout
setTimeout(() => {
if (pendingRequests.has(requestId)) {
pendingRequests.delete(requestId);
reject(new Error('Request timeout'));
}
}, 10000); // 10 second timeout
});
}

Message Data Processing​

ArrayBuffer to String Conversion​

const decoder = new TextDecoder();
const jsonString = decoder.decode(messageData);
const message = JSON.parse(jsonString);

Binary Data Handling​

For binary game data:

// Handle binary position data
if (messageData.byteLength === 16) { // 4 floats (x, y, vx, vy)
const floatArray = new Float32Array(messageData);
const x = floatArray[0];
const y = floatArray[1];
const vx = floatArray[2];
const vy = floatArray[3];

updatePlayerPosition(senderParticipantID, x, y, vx, vy);
}

Message Filtering and Validation​

Sender Validation​

onMessageReceived: (messageData, senderParticipantID, isReliable) => {
// Validate sender is in the room
if (!isPlayerInRoom(senderParticipantID)) {
console.warn('Message from unknown player:', senderParticipantID);
return;
}

// Process message
processMessage(messageData, senderParticipantID);
}

Message Rate Limiting​

const messageCounts = new Map();
const RATE_LIMIT = 10; // Max 10 messages per second
const RATE_WINDOW = 1000; // 1 second window

onMessageReceived: (messageData, senderParticipantID, isReliable) => {
const now = Date.now();
const count = messageCounts.get(senderParticipantID) || { count: 0, resetTime: now + RATE_WINDOW };

// Reset counter if window expired
if (now > count.resetTime) {
count.count = 0;
count.resetTime = now + RATE_WINDOW;
}

// Check rate limit
if (count.count >= RATE_LIMIT) {
console.warn('Rate limit exceeded for:', senderParticipantID);
return;
}

count.count++;
messageCounts.set(senderParticipantID, count);

// Process message
processMessage(messageData, senderParticipantID);
}

Error Handling​

Message Parsing Errors​

onMessageReceived: (messageData, senderParticipantID, isReliable) => {
try {
const decoder = new TextDecoder();
const jsonString = decoder.decode(messageData);
const message = JSON.parse(jsonString);

// Validate message structure
if (!message.type) {
console.warn('Invalid message structure from:', senderParticipantID);
return;
}

// Process valid message
handleMessage(message, senderParticipantID);

} catch (error) {
console.error('Failed to parse message from', senderParticipantID, ':', error);

// Optionally report problematic player
if (error instanceof SyntaxError) {
reportInvalidMessage(senderParticipantID, messageData);
}
}
}

Best Practices​

Performance Optimization​

  • Use object pooling for frequent message types
  • Implement message batching for high-frequency updates
  • Cache decoder instances for better performance
  • Use binary formats for performance-critical data

Security Considerations​

  • Always validate message structure and content
  • Implement rate limiting to prevent spam
  • Sanitize incoming data before processing
  • Report suspicious message patterns

Reliability Handling​

  • Check isReliable flag for message importance
  • Implement message ordering for critical updates
  • Handle duplicate messages gracefully
  • Use timestamps to detect and ignore old messages

User Experience​

  • Provide visual feedback for different message types
  • Show sender information clearly
  • Handle message loss gracefully
  • Implement message history for chat

Next Steps​