Skip to main content

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

ParameterTypeDescription
messageDataArrayBufferRaw message data received
senderParticipantIDstringID of the participant who sent the message
isReliablebooleanWhether 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

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.