Receive Messages
Handle incoming messages from other players and the game server in endless rooms. The onMessageReceived callback is the primary way to process real-time communications.
Callback
onMessageReceived: (messageData, senderParticipantID, isReliable) => {
// Handle incoming message
}
Parameters
| Parameter | Type | Description |
|---|---|---|
messageData | ArrayBuffer | Raw message data received |
senderParticipantID | string | ID of the participant who sent the message |
isReliable | boolean | Whether the message was sent reliably |
Examples
JavaScript Example
// Set up message receiver when joining room
MoitribeSDK('my-game', 'joinendlessroominvcode', {
invitationID: 'ROOM123',
onMessageReceived: (messageData, senderParticipantID, isReliable) => {
try {
// Convert ArrayBuffer to string for text messages
const text = new TextDecoder().decode(messageData);
const message = JSON.parse(text);
console.log(`Message from ${senderParticipantID}:`, message);
// Handle different message types
switch (message.type) {
case 'chat':
handleChatMessage(senderParticipantID, message.text);
break;
case 'game_update':
handleGameUpdate(senderParticipantID, message.data);
break;
case 'player_action':
handlePlayerAction(senderParticipantID, message.action);
break;
default:
console.log('Unknown message type:', message.type);
}
} catch (error) {
console.error('Failed to parse message:', error);
// Handle binary messages
handleBinaryMessage(messageData, senderParticipantID);
}
},
// ... other callbacks
});
TypeScript Example
import MoitribeSDK from '@veniso/moitribe-js';
// Define message types
interface GameMessage {
type: 'chat' | 'game_update' | 'player_action' | 'position_update';
data?: any;
text?: string;
action?: string;
timestamp: number;
}
interface PositionData {
x: number;
y: number;
z: number;
velocity?: { x: number; y: number; z: number };
}
// Set up typed message handler
function setupMessageHandler(): void {
MoitribeSDK('my-game', 'joinendlessroominvcode', {
invitationID: 'ROOM123',
onMessageReceived: (messageData: ArrayBuffer, senderParticipantID: string, isReliable: boolean) => {
try {
// Try to parse as JSON first
const text = new TextDecoder().decode(messageData);
const message: GameMessage = JSON.parse(text);
console.log(`Received ${message.type} from ${senderParticipantID}`);
// Type-safe message handling
switch (message.type) {
case 'chat':
if (message.text) {
handleChatMessage(senderParticipantID, message.text);
}
break;
case 'game_update':
if (message.data) {
handleGameUpdate(senderParticipantID, message.data);
}
break;
case 'player_action':
if (message.action) {
handlePlayerAction(senderParticipantID, message.action);
}
break;
case 'position_update':
if (message.data) {
handlePositionUpdate(senderParticipantID, message.data as PositionData);
}
break;
}
} catch (error) {
// Handle binary messages that can't be parsed as JSON
handleBinaryMessage(messageData, senderParticipantID, isReliable);
}
}
});
}
// Typed message handlers
function handleChatMessage(senderId: string, text: string): void {
const senderName = getPlayerName(senderId);
displayChatMessage(senderName, text);
}
function handleGameUpdate(senderId: string, data: any): void {
updateGameState(data);
syncWithServer(data);
}
function handlePositionUpdate(senderId: string, position: PositionData): void {
updatePlayerPosition(senderId, position);
}
function handlePlayerAction(senderId: string, action: string): void {
executePlayerAction(senderId, action);
}
Message Types
Text Messages
function handleTextMessage(messageData, senderId) {
const text = new TextDecoder().decode(messageData);
console.log(`Text message from ${senderId}: ${text}`);
// Display in chat UI
addChatMessage(senderId, text);
}
JSON Messages
function handleJsonMessage(messageData, senderId) {
const text = new TextDecoder().decode(messageData);
const message = JSON.parse(text);
// Handle structured game data
if (message.type === 'player_state') {
updatePlayerState(senderId, message.state);
} else if (message.type === 'game_event') {
processGameEvent(message.event);
}
}
Binary Messages
function handleBinaryMessage(messageData, senderId) {
const view = new DataView(messageData);
const messageType = view.getUint8(0);
switch (messageType) {
case 1: // Position update
const x = view.getFloat32(1, true);
const y = view.getFloat32(5, true);
const z = view.getFloat32(9, true);
updatePlayerPosition(senderId, { x, y, z });
break;
case 2: // Health update
const health = view.getFloat32(1, true);
updatePlayerHealth(senderId, health);
break;
case 3: // Score update
const score = view.getUint32(1, true);
updatePlayerScore(senderId, score);
break;
}
}
Message Processing Patterns
Message Queue
const messageQueue = [];
let isProcessing = false;
function queueMessage(messageData, senderId, isReliable) {
messageQueue.push({
data: messageData,
sender: senderId,
reliable: isReliable,
timestamp: Date.now()
});
if (!isProcessing) {
processMessageQueue();
}
}
function processMessageQueue() {
if (messageQueue.length === 0) {
isProcessing = false;
return;
}
isProcessing = true;
const message = messageQueue.shift();
try {
processMessage(message.data, message.sender, message.reliable);
} catch (error) {
console.error('Error processing message:', error);
}
// Process next message in next frame
requestAnimationFrame(processMessageQueue);
}
// Use in onMessageReceived
onMessageReceived: (messageData, senderId, isReliable) => {
queueMessage(messageData, senderId, isReliable);
}
Message Filtering
function setupFilteredMessageHandler() {
const blockedPlayers = new Set();
return function(messageData, senderId, isReliable) {
// Ignore blocked players
if (blockedPlayers.has(senderId)) {
return;
}
// Filter by message type
const messageType = getMessageType(messageData);
if (isMessageTypeBlocked(messageType)) {
return;
}
// Process valid messages
processMessage(messageData, senderId, isReliable);
};
}
// Usage
const messageHandler = setupFilteredMessageHandler();
onMessageReceived: messageHandler
Message Validation
function validateMessage(messageData, senderId) {
// Check message size
if (messageData.byteLength > MAX_MESSAGE_SIZE) {
console.warn(`Oversized message from ${senderId}: ${messageData.byteLength} bytes`);
return false;
}
// Check message rate
const now = Date.now();
const lastMessage = lastMessageTimes.get(senderId) || 0;
if (now - lastMessage < MIN_MESSAGE_INTERVAL) {
console.warn(`Rate limited message from ${senderId}`);
return false;
}
lastMessageTimes.set(senderId, now);
return true;
}
onMessageReceived: (messageData, senderId, isReliable) => {
if (!validateMessage(messageData, senderId)) {
return;
}
processMessage(messageData, senderId, isReliable);
}
Specific Use Cases
Chat System
function handleChatSystem() {
const chatHistory = [];
return function(messageData, senderId, isReliable) {
try {
const text = new TextDecoder().decode(messageData);
const chatMessage = JSON.parse(text);
if (chatMessage.type === 'chat') {
const chatEntry = {
sender: getPlayerName(senderId),
message: chatMessage.text,
timestamp: Date.now(),
senderId: senderId
};
chatHistory.push(chatEntry);
displayChatMessage(chatEntry);
// Limit history size
if (chatHistory.length > 100) {
chatHistory.shift();
}
}
} catch (error) {
console.error('Invalid chat message:', error);
}
};
}
Game State Synchronization
function handleGameStateSync() {
const gameState = {};
return function(messageData, senderId, isReliable) {
try {
const text = new TextDecoder().decode(messageData);
const update = JSON.parse(text);
if (update.type === 'state_sync') {
// Update local game state
Object.assign(gameState, update.data);
// Update UI
updateGameUI(gameState);
// Broadcast to other players if needed
if (shouldBroadcast(update)) {
broadcastStateUpdate(update.data);
}
}
} catch (error) {
console.error('Invalid state sync message:', error);
}
};
}
Position Updates
function handlePositionUpdates() {
const playerPositions = new Map();
return function(messageData, senderId, isReliable) {
// Handle binary position updates for performance
if (messageData.byteLength === 12) {
const view = new DataView(messageData);
const position = {
x: view.getFloat32(0, true),
y: view.getFloat32(4, true),
z: view.getFloat32(8, true)
};
playerPositions.set(senderId, position);
updatePlayerVisual(senderId, position);
} else {
// Handle JSON position updates
try {
const text = new TextDecoder().decode(messageData);
const update = JSON.parse(text);
if (update.type === 'position') {
playerPositions.set(senderId, update.position);
updatePlayerVisual(senderId, update.position);
}
} catch (error) {
console.error('Invalid position update:', error);
}
}
};
}
Error Handling
function safeMessageHandler(messageData, senderId, isReliable) {
try {
// Validate sender
if (!isValidParticipant(senderId)) {
console.warn(`Message from invalid participant: ${senderId}`);
return;
}
// Validate message size
if (messageData.byteLength > MAX_MESSAGE_SIZE) {
console.warn(`Oversized message: ${messageData.byteLength} bytes`);
return;
}
// Process message
processMessage(messageData, senderId, isReliable);
} catch (error) {
console.error('Error in message handler:', error);
// Report error for debugging
if (isDevelopment()) {
reportMessageError(error, messageData, senderId);
}
}
}
Performance Optimization
Message Batching
const batchedMessages = [];
let batchTimeout = null;
function batchMessage(messageData, senderId, isReliable) {
batchedMessages.push({
data: messageData,
sender: senderId,
reliable: isReliable,
timestamp: Date.now()
});
// Clear existing timeout
if (batchTimeout) {
clearTimeout(batchTimeout);
}
// Process batch after short delay
batchTimeout = setTimeout(() => {
processBatchedMessages();
}, 16); // ~60fps
}
function processBatchedMessages() {
if (batchedMessages.length === 0) return;
// Group by sender for efficient processing
const messagesBySender = {};
batchedMessages.forEach(msg => {
if (!messagesBySender[msg.sender]) {
messagesBySender[msg.sender] = [];
}
messagesBySender[msg.sender].push(msg);
});
// Process each sender's messages
Object.entries(messagesBySender).forEach(([senderId, messages]) => {
processMessagesFromSender(senderId, messages);
});
// Clear batch
batchedMessages.length = 0;
batchTimeout = null;
}
Best Practices
Message Deduplication
const processedMessages = new Set();
function deduplicateMessage(messageData, senderId) {
// Create simple hash of message content
const hash = createMessageHash(messageData, senderId);
if (processedMessages.has(hash)) {
return false; // Duplicate message
}
processedMessages.add(hash);
// Clean old hashes periodically
if (processedMessages.size > 1000) {
const oldHashes = Array.from(processedMessages).slice(0, 500);
oldHashes.forEach(h => processedMessages.delete(h));
}
return true; // New message
}
Message Prioritization
function prioritizeMessage(messageData, senderId, isReliable) {
// Reliable messages get higher priority
if (isReliable) {
return 'high';
}
// Check message type
const messageType = getMessageType(messageData);
switch (messageType) {
case 'chat':
return 'medium';
case 'position':
return 'low';
case 'game_action':
return 'high';
default:
return 'medium';
}
}
Next Steps
- Send Messages - Send messages to other players
- Room Callbacks - Handle all room events
- Message Encoding - Learn data serialization
- Message Reliability - Understand delivery guarantees
Performance Tip
Use binary messages for high-frequency updates like positions and animations. Use JSON messages for complex structured data that doesn't update as frequently.