Skip to main content

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​

ParameterTypeRequiredDescription
onLeftRoomfunctionYesCalled 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​

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.