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
| Parameter | Type | Description |
|---|---|---|
messageData | ArrayBuffer | The message content as binary data |
senderParticipantID | string | ID of the player who sent the message |
isReliable | boolean | Whether the message was sent reliably |
Examples
Basic Message Handling
Handle incoming messages and route them based on type:
- JavaScript
- TypeScript
// 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);
}
import type { RealTimeMessage } from '@veniso/moitribe-js';
// Message type interfaces
interface GameStateMessage {
type: 'gameState';
data: any;
timestamp: number;
}
interface ChatMessage {
type: 'chat';
message: string;
sender: string;
timestamp: number;
}
interface GameActionMessage {
type: 'gameAction';
action: string;
data: any;
timestamp: number;
}
interface PositionMessage {
type: 'position';
x: number;
y: number;
timestamp: number;
}
type GameMessage = GameStateMessage | ChatMessage | GameActionMessage | PositionMessage;
// In your room creation or join parameters
onMessageReceived: (messageData: ArrayBuffer, senderParticipantID: string, isReliable: boolean) => {
try {
// Convert ArrayBuffer to string
const decoder = new TextDecoder();
const jsonString = decoder.decode(messageData);
const message: GameMessage = JSON.parse(jsonString);
console.log(`Message from ${senderParticipantID}:`, message.type);
// Route message based on type
switch (message.type) {
case 'gameState':
handleGameStateUpdate(message as GameStateMessage, senderParticipantID);
break;
case 'chat':
handleChatMessage(message as ChatMessage, senderParticipantID);
break;
case 'gameAction':
handleGameAction(message as GameActionMessage, senderParticipantID);
break;
case 'position':
handlePositionUpdate(message as PositionMessage, senderParticipantID);
break;
default:
console.warn('Unknown message type:', (message as any).type);
}
} catch (error) {
console.error('Failed to parse message:', error);
}
}
// Message handlers
function handleGameStateUpdate(message: GameStateMessage, senderId: string): void {
console.log('Game state updated by', senderId);
updateLocalGameState(message.data);
syncGameUI(message.data);
}
function handleChatMessage(message: ChatMessage, senderId: string): void {
console.log(`Chat from ${senderId}: ${message.message}`);
displayChatMessage(message.message, message.sender, senderId);
}
function handleGameAction(message: GameActionMessage, senderId: string): void {
console.log(`Game action from ${senderId}: ${message.action}`);
processGameAction(message.action, message.data, senderId);
}
function handlePositionUpdate(message: PositionMessage, senderId: string): void {
console.log(`Position update from ${senderId}`);
updatePlayerPosition(senderId, message.x, message.y);
}
Handle Private Messages
Process private messages sent specifically to you:
- JavaScript
- TypeScript
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);
}
}
interface PrivateMessage {
type: 'privateMessage';
message: string;
sender: string;
timestamp: number;
}
interface TeamMessage {
type: 'teamMessage';
message: string;
sender: string;
timestamp: number;
}
onMessageReceived: (messageData: ArrayBuffer, senderParticipantID: string, isReliable: boolean) => {
const decoder = new TextDecoder();
const jsonString = decoder.decode(messageData);
const message = JSON.parse(jsonString);
// Handle private messages
if (message.type === 'privateMessage') {
const privateMsg = message as PrivateMessage;
console.log(`Private message from ${senderParticipantID}: ${privateMsg.message}`);
// Show private message notification
showPrivateMessageNotification(privateMsg.sender, privateMsg.message);
// Add to private chat
addPrivateMessageToChat(privateMsg.sender, privateMsg.message, senderParticipantID);
// Play notification sound if from different player
if (senderParticipantID !== currentChatPartner) {
playNotificationSound('privateMessage');
}
}
// Handle team messages
else if (message.type === 'teamMessage') {
const teamMsg = message as TeamMessage;
console.log(`Team message from ${senderParticipantID}: ${teamMsg.message}`);
// Display in team chat
addTeamMessage(teamMsg.message, teamMsg.sender, senderParticipantID);
// Highlight team messages
highlightTeamChat();
}
// Handle other message types
else {
handleRegularMessage(message, senderParticipantID);
}
}
Handle Real-Time Updates
Process frequent real-time updates efficiently:
- JavaScript
- TypeScript
// 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);
}
}
interface PositionMessage {
type: 'position';
x: number;
y: number;
vx: number;
vy: number;
timestamp: number;
}
interface AnimationMessage {
type: 'animation';
animation: string;
duration: number;
}
interface EffectMessage {
type: 'effect';
effect: string;
position: { x: number; y: number };
}
// Track last update times to prevent processing old messages
const lastUpdateTime: Record<string, number> = {};
onMessageReceived: (messageData: ArrayBuffer, senderParticipantID: string, isReliable: boolean) => {
const decoder = new TextDecoder();
const jsonString = decoder.decode(messageData);
const message = JSON.parse(jsonString);
// Handle position updates (high frequency)
if (message.type === 'position') {
const posMsg = message as PositionMessage;
// Skip old messages
if (lastUpdateTime[senderParticipantID] &&
lastUpdateTime[senderParticipantID] > posMsg.timestamp) {
return;
}
lastUpdateTime[senderParticipantID] = posMsg.timestamp;
// Update player position smoothly
updatePlayerPositionSmoothly(senderParticipantID, {
x: posMsg.x,
y: posMsg.y,
vx: posMsg.vx,
vy: posMsg.vy
});
}
// Handle animation updates
else if (message.type === 'animation') {
const animMsg = message as AnimationMessage;
playPlayerAnimation(senderParticipantID, animMsg.animation, animMsg.duration);
}
// Handle effect updates
else if (message.type === 'effect') {
const effectMsg = message as EffectMessage;
showVisualEffect(senderParticipantID, effectMsg.effect, effectMsg.position);
}
// Handle other game updates
else {
handleGameMessage(message, senderParticipantID);
}
}
// Smooth position interpolation
function updatePlayerPositionSmoothly(playerId: string, positionData: {
x: number;
y: number;
vx: number;
vy: number;
}): void {
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:
- JavaScript
- TypeScript
// 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
});
}
interface ServerResponse {
type: 'serverResponse';
requestId: string;
success: boolean;
data?: any;
error?: string;
}
interface ServerNotification {
type: 'serverNotification';
notification: string;
data: any;
}
interface PendingRequest {
resolve: (data: any) => void;
reject: (error: Error) => void;
}
// Track pending requests
const pendingRequests = new Map<string, PendingRequest>();
onMessageReceived: (messageData: ArrayBuffer, senderParticipantID: string, isReliable: boolean) => {
const decoder = new TextDecoder();
const jsonString = decoder.decode(messageData);
const message = JSON.parse(jsonString);
// Handle server responses
if (message.type === 'serverResponse') {
const response = message as ServerResponse;
console.log('Server response received:', response.requestId);
// Find the pending request
const pendingRequest = pendingRequests.get(response.requestId);
if (pendingRequest) {
// Resolve the pending request
if (response.success) {
pendingRequest.resolve(response.data);
} else {
pendingRequest.reject(new Error(response.error));
}
// Remove from pending requests
pendingRequests.delete(response.requestId);
}
}
// Handle server notifications
else if (message.type === 'serverNotification') {
const notification = message as ServerNotification;
console.log('Server notification:', notification.notification);
switch (notification.notification) {
case 'tournamentStarting':
showTournamentAlert(notification.data);
break;
case 'maintenanceWarning':
showMaintenanceWarning(notification.data);
break;
case 'globalEvent':
showGlobalEvent(notification.data);
break;
}
}
// Handle regular player messages
else {
handlePlayerMessage(message, senderParticipantID);
}
}
// Helper function to create pending requests
function sendServerRequest(requestData: any): Promise<any> {
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
isReliableflag 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