Leave Endless Room
Leave an endless room cleanly and properly clean up resources. This ensures other players are notified of your departure and prevents connection issues.
Method
MoitribeSDK('game-id', 'leaveendlessroom', params, callback)
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
onLeftRoom | function | Yes | Called when successfully left the room |
Response Format
{
success: boolean;
msg?: string;
}
Examples
JavaScript Example
// Leave room with proper cleanup
function leaveRoom() {
showLeavingIndicator();
MoitribeSDK('my-game', 'leaveendlessroom', {
onLeftRoom: (status, roomID) => {
if (status) {
console.log('Successfully left room:', roomID);
cleanupGameState();
returnToMainMenu();
hideLeavingIndicator();
} else {
console.error('Failed to leave room');
showLeaveError();
hideLeavingIndicator();
}
}
}, (result) => {
console.log('Leave room request sent:', result);
});
}
// Leave room when user clicks exit button
document.getElementById('leave-room-btn').addEventListener('click', () => {
if (confirm('Are you sure you want to leave the room?')) {
leaveRoom();
}
});
// Auto-leave room on page unload
window.addEventListener('beforeunload', (event) => {
if (isInRoom()) {
// Try to leave room gracefully
MoitribeSDK('my-game', 'leaveendlessroom', {
onLeftRoom: () => {
console.log('Left room on page unload');
}
});
}
});
TypeScript Example
import MoitribeSDK from '@veniso/moitribe-js';
class EndlessRoomManager {
private currentRoomId: string | null = null;
private isLeaving: boolean = false;
leaveRoom(): Promise<boolean> {
return new Promise((resolve) => {
if (this.isLeaving) {
resolve(false);
return;
}
if (!this.currentRoomId) {
console.warn('Not in a room');
resolve(true);
return;
}
this.isLeaving = true;
this.showLeavingIndicator();
MoitribeSDK('my-game', 'leaveendlessroom', {
onLeftRoom: (status: boolean, roomID: string) => {
this.isLeaving = false;
this.hideLeavingIndicator();
if (status) {
console.log(`Successfully left room: ${roomID}`);
this.cleanupGameState();
this.returnToMainMenu();
this.currentRoomId = null;
resolve(true);
} else {
console.error('Failed to leave room');
this.showLeaveError();
resolve(false);
}
}
}, (result: any) => {
console.log('Leave room request sent:', result);
});
});
}
// Force leave room (for error cases)
forceLeaveRoom(): void {
console.log('Force leaving room');
this.cleanupGameState();
this.returnToMainMenu();
this.currentRoomId = null;
this.isLeaving = false;
}
// Leave room with timeout
leaveRoomWithTimeout(timeoutMs: number = 5000): Promise<boolean> {
const leavePromise = this.leaveRoom();
const timeoutPromise = new Promise<boolean>((resolve) => {
setTimeout(() => {
console.log('Leave room timeout, forcing exit');
this.forceLeaveRoom();
resolve(false);
}, timeoutMs);
});
return Promise.race([leavePromise, timeoutPromise]);
}
private cleanupGameState(): void {
// Stop game loops
this.stopGameLoop();
// Clear player states
this.clearPlayerStates();
// Cancel pending operations
this.cancelPendingOperations();
// Clear message handlers
this.clearMessageHandlers();
}
private returnToMainMenu(): void {
// Navigate to main menu
window.location.hash = '#main-menu';
// Show main menu UI
this.showMainMenu();
// Hide game UI
this.hideGameUI();
}
private showLeavingIndicator(): void {
const indicator = document.getElementById('leaving-indicator');
if (indicator) {
indicator.style.display = 'block';
}
}
private hideLeavingIndicator(): void {
const indicator = document.getElementById('leaving-indicator');
if (indicator) {
indicator.style.display = 'none';
}
}
private showLeaveError(): void {
alert('Failed to leave room. Please try again.');
}
private showMainMenu(): void {
// Show main menu implementation
}
private hideGameUI(): void {
// Hide game UI implementation
}
private stopGameLoop(): void {
// Stop game loop implementation
}
private clearPlayerStates(): void {
// Clear player states implementation
}
private cancelPendingOperations(): void {
// Cancel pending operations implementation
}
private clearMessageHandlers(): void {
// Clear message handlers implementation
}
}
// Usage
const roomManager = new EndlessRoomManager();
// Leave room when user requests
async function handleLeaveRoom() {
const success = await roomManager.leaveRoomWithTimeout();
if (success) {
console.log('Left room successfully');
} else {
console.log('Left room with timeout or error');
}
}
Leave Room Scenarios
User-Initiated Leave
// Leave room when user clicks leave button
function setupLeaveButton() {
const leaveBtn = document.getElementById('leave-room-btn');
leaveBtn.addEventListener('click', async () => {
// Show confirmation dialog
const shouldLeave = await showConfirmDialog(
'Leave Room',
'Are you sure you want to leave the room?'
);
if (shouldLeave) {
await leaveRoom();
}
});
}
// Show confirmation dialog
function showConfirmDialog(title, message) {
return new Promise((resolve) => {
const dialog = document.createElement('div');
dialog.className = 'confirm-dialog';
dialog.innerHTML = `
<h3>${title}</h3>
<p>${message}</p>
<div class="dialog-buttons">
<button id="confirm-yes">Yes</button>
<button id="confirm-no">No</button>
</div>
`;
document.body.appendChild(dialog);
document.getElementById('confirm-yes').addEventListener('click', () => {
document.body.removeChild(dialog);
resolve(true);
});
document.getElementById('confirm-no').addEventListener('click', () => {
document.body.removeChild(dialog);
resolve(false);
});
});
}
Automatic Leave on Error
// Leave room on critical errors
function handleCriticalError(error) {
console.error('Critical error, leaving room:', error);
// Show error message
showErrorMessage('A critical error occurred. Returning to main menu.');
// Leave room immediately
leaveRoomImmediately();
}
function leaveRoomImmediately() {
MoitribeSDK('my-game', 'leaveendlessroom', {
onLeftRoom: (status, roomID) => {
console.log('Emergency leave completed:', status);
forceCleanup();
}
});
}
function forceCleanup() {
// Force cleanup regardless of leave success
cleanupGameState();
returnToMainMenu();
}
Leave on Connection Loss
// Handle connection loss
function setupConnectionMonitoring() {
let connectionLostTimeout = null;
const callbacks = {
onDisconnectedFromRoom: (room) => {
console.log('Disconnected from room');
// Start timeout for automatic leave
connectionLostTimeout = setTimeout(() => {
console.log('Connection lost for too long, leaving room');
leaveRoom();
}, 10000); // 10 seconds
showReconnectDialog();
},
onConnectedToRoom: (room) => {
console.log('Reconnected to room');
// Clear automatic leave timeout
if (connectionLostTimeout) {
clearTimeout(connectionLostTimeout);
connectionLostTimeout = null;
}
hideReconnectDialog();
},
reconnectSuccessful: () => {
console.log('Reconnection successful');
hideReconnectDialog();
}
};
return callbacks;
}
Cleanup Procedures
Game State Cleanup
function cleanupGameState() {
// Stop all game loops
if (gameLoop) {
cancelAnimationFrame(gameLoop);
gameLoop = null;
}
// Clear timers
timers.forEach(timerId => clearTimeout(timerId));
timers.clear();
// Clear intervals
intervals.forEach(intervalId => clearInterval(intervalId));
intervals.clear();
// Reset game variables
gameState = {
players: new Map(),
score: 0,
level: 1,
isPlaying: false
};
// Clear canvas
if (canvas) {
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
// Reset audio
if (audioContext) {
audioContext.suspend();
}
console.log('Game state cleaned up');
}
Network Cleanup
function cleanupNetworkState() {
// Clear message handlers
messageHandlers.clear();
// Cancel pending requests
pendingRequests.forEach(request => {
if (request.abort) {
request.abort();
}
});
pendingRequests.clear();
// Clear participant data
participants.clear();
// Reset connection state
connectionState = {
isConnected: false,
isReconnecting: false,
lastPing: 0
};
console.log('Network state cleaned up');
}
UI Cleanup
function cleanupUI() {
// Hide game UI elements
const gameElements = document.querySelectorAll('.game-ui');
gameElements.forEach(element => {
element.style.display = 'none';
});
// Show main menu
const mainMenu = document.getElementById('main-menu');
if (mainMenu) {
mainMenu.style.display = 'block';
}
// Clear notifications
const notifications = document.querySelectorAll('.notification');
notifications.forEach(notification => {
notification.remove();
});
// Reset input states
const inputs = document.querySelectorAll('input, button');
inputs.forEach(input => {
input.disabled = false;
input.classList.remove('loading');
});
console.log('UI cleaned up');
}
Error Handling
Leave Room Failures
function leaveRoomWithErrorHandling() {
let leaveAttempts = 0;
const maxAttempts = 3;
function attemptLeave() {
leaveAttempts++;
MoitribeSDK('my-game', 'leaveendlessroom', {
onLeftRoom: (status, roomID) => {
if (status) {
console.log('Left room successfully');
performCleanup();
} else if (leaveAttempts < maxAttempts) {
console.log(`Leave failed, retrying (${leaveAttempts}/${maxAttempts})`);
setTimeout(attemptLeave, 1000);
} else {
console.error('Failed to leave room after max attempts');
forceLeave();
}
}
});
}
attemptLeave();
}
function forceLeave() {
console.log('Forcing room exit');
// Perform cleanup regardless of API success
performCleanup();
// Show error to user
showErrorMessage('Unable to leave room gracefully. Returning to main menu.');
}
Timeout Handling
function leaveRoomWithTimeout(timeoutMs = 5000) {
return new Promise((resolve) => {
let completed = false;
// Set timeout
const timeout = setTimeout(() => {
if (!completed) {
completed = true;
console.log('Leave room timeout, forcing exit');
forceCleanup();
resolve(false);
}
}, timeoutMs);
// Attempt to leave
MoitribeSDK('my-game', 'leaveendlessroom', {
onLeftRoom: (status, roomID) => {
if (!completed) {
completed = true;
clearTimeout(timeout);
if (status) {
console.log('Left room successfully');
performCleanup();
resolve(true);
} else {
console.error('Leave room failed');
forceCleanup();
resolve(false);
}
}
}
});
});
}
Best Practices
Graceful Shutdown
async function gracefulShutdown() {
try {
// Save game progress
await saveGameProgress();
// Notify other players
await notifyLeavingPlayers();
// Leave room with timeout
const leftSuccessfully = await leaveRoomWithTimeout(3000);
if (!leftSuccessfully) {
console.warn('Room leave was not graceful');
}
} catch (error) {
console.error('Error during graceful shutdown:', error);
forceCleanup();
}
}
async function notifyLeavingPlayers() {
const message = {
type: 'player_leaving',
playerId: getCurrentPlayerId(),
message: 'Goodbye!',
timestamp: Date.now()
};
return new Promise((resolve) => {
MoitribeSDK('my-game', 'endlessmessagetoall', {
messageData: new TextEncoder().encode(JSON.stringify(message)).buffer,
isReliable: true
}, (result) => {
resolve(result.success);
});
});
}
State Preservation
function preserveRoomState() {
const state = {
roomId: getCurrentRoomId(),
playerPositions: getPlayerPositions(),
gameScore: getGameScore(),
timestamp: Date.now()
};
// Save to localStorage for potential reconnection
localStorage.setItem('lastRoomState', JSON.stringify(state));
}
function restoreRoomState() {
const savedState = localStorage.getItem('lastRoomState');
if (savedState) {
try {
const state = JSON.parse(savedState);
console.log('Restored room state:', state);
// Offer to rejoin if recent
const timeSinceLeave = Date.now() - state.timestamp;
if (timeSinceLeave < 5 * 60 * 1000) { // 5 minutes
showRejoinOption(state.roomId);
}
} catch (error) {
console.error('Failed to restore room state:', error);
}
}
}
Next Steps
- Create Room - Create new endless rooms
- Join Room - Join existing rooms
- Room Callbacks - Handle room events
- Drop-in/Drop-out - Understand dynamic player management
Pro Tip
Always implement proper cleanup when leaving rooms to prevent memory leaks and connection issues. Use timeouts to handle cases where the leave operation hangs.