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