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.