Skip to main content

Connection Callbacks

The Moitribe SDK provides connection state monitoring through callbacks, allowing you to handle network interruptions, reconnections, and connection failures gracefully.

Connection Callback Interface​

The SDK supports four connection-related callbacks:

interface ConnectionCallbacks {
onConnectionLost?: (errorCode: number, errorMsg: string) => void;
onConnected?: (isReconnect: boolean, URI: string) => void;
onSuccessConnect?: () => void;
onFailureConnect?: () => void;
}

Basic Setup​

Initialize with Connection Callbacks​

const connectionCallbacks = {
onConnectionLost: (errorCode: number, errorMsg: string) => {
console.error('Connection lost:', errorCode, errorMsg);
showConnectionError(errorMsg);
},

onConnected: (isReconnect: boolean, URI: string) => {
console.log('Connected to:', URI);
if (isReconnect) {
console.log('This is a reconnection');
refreshGameData();
}
hideConnectionError();
},

onSuccessConnect: () => {
console.log('Connection established successfully');
enableGameFeatures();
},

onFailureConnect: () => {
console.error('Failed to establish connection');
showConnectionFailedMessage();
}
};

// Initialize SDK with connection callbacks
MoitribeSDK('my-game', 'init', {
connectionCallbacks: connectionCallbacks
}, (result) => {
console.log('SDK initialization:', result.success);
});

Advanced Connection Management​

Connection State Manager​

enum ConnectionState {
DISCONNECTED = 'disconnected',
CONNECTING = 'connecting',
CONNECTED = 'connected',
RECONNECTING = 'reconnecting',
FAILED = 'failed'
}

class ConnectionManager {
private static state: ConnectionState = ConnectionState.DISCONNECTED;
private static reconnectAttempts: number = 0;
private static maxReconnectAttempts: number = 5;
private static reconnectDelay: number = 1000;
private static listeners: Array<(state: ConnectionState) => void> = [];

static getState(): ConnectionState {
return this.state;
}

static setState(newState: ConnectionState): void {
const oldState = this.state;
this.state = newState;
console.log(`Connection state: ${oldState} → ${newState}`);

this.notifyListeners(newState);

// Reset reconnect attempts on successful connection
if (newState === ConnectionState.CONNECTED) {
this.reconnectAttempts = 0;
}
}

static addListener(listener: (state: ConnectionState) => void): void {
this.listeners.push(listener);
}

static removeListener(listener: (state: ConnectionState) => void): void {
const index = this.listeners.indexOf(listener);
if (index > -1) {
this.listeners.splice(index, 1);
}
}

private static notifyListeners(state: ConnectionState): void {
this.listeners.forEach(listener => {
try {
listener(state);
} catch (error) {
console.error('Error in connection state listener:', error);
}
});
}

static shouldAttemptReconnect(): boolean {
return this.reconnectAttempts < this.maxReconnectAttempts;
}

static incrementReconnectAttempts(): void {
this.reconnectAttempts++;
}

static getReconnectDelay(): number {
// Exponential backoff
return this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
}
}

// Usage
ConnectionManager.addListener((state) => {
updateConnectionUI(state);
});

Enhanced Connection Callbacks​

class EnhancedConnectionCallbacks {
private static gameId: string = '';
private static reconnectTimer: NodeJS.Timeout | null = null;

static create(gameId: string): ConnectionCallbacks {
this.gameId = gameId;

return {
onConnectionLost: (errorCode: number, errorMsg: string) => {
ConnectionManager.setState(ConnectionState.DISCONNECTED);
this.handleConnectionLost(errorCode, errorMsg);
},

onConnected: (isReconnect: boolean, URI: string) => {
ConnectionManager.setState(ConnectionState.CONNECTED);
this.handleConnected(isReconnect, URI);
},

onSuccessConnect: () => {
ConnectionManager.setState(ConnectionState.CONNECTED);
this.handleConnectionSuccess();
},

onFailureConnect: () => {
ConnectionManager.setState(ConnectionState.FAILED);
this.handleConnectionFailure();
}
};
}

private static handleConnectionLost(errorCode: number, errorMsg: string): void {
console.error('Connection lost:', { errorCode, errorMsg });

// Clear any existing reconnect timer
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}

// Show user notification
this.showConnectionNotification('Connection lost. Attempting to reconnect...', 'warning');

// Attempt reconnection if appropriate
if (ConnectionManager.shouldAttemptReconnect()) {
ConnectionManager.setState(ConnectionState.RECONNECTING);
this.scheduleReconnect();
} else {
this.showConnectionNotification('Connection failed. Please refresh the page.', 'error');
}
}

private static handleConnected(isReconnect: boolean, URI: string): void {
console.log('Connected to server:', { isReconnect, URI });

if (isReconnect) {
this.showConnectionNotification('Reconnected successfully', 'success');
this.refreshGameData();
} else {
this.showConnectionNotification('Connected to game server', 'success');
}

// Clear reconnect timer
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
}

private static handleConnectionSuccess(): void {
console.log('Connection established successfully');
this.enableGameFeatures();
}

private static handleConnectionFailure(): void {
console.error('Failed to establish connection');
this.showConnectionNotification('Failed to connect to game server', 'error');
this.disableGameFeatures();
}

private static scheduleReconnect(): void {
if (!ConnectionManager.shouldAttemptReconnect()) {
return;
}

ConnectionManager.incrementReconnectAttempts();
const delay = ConnectionManager.getReconnectDelay();

console.log(`Scheduling reconnect attempt ${ConnectionManager['reconnectAttempts']} in ${delay}ms`);

this.reconnectTimer = setTimeout(() => {
this.attemptReconnect();
}, delay);
}

private static attemptReconnect(): void {
console.log(`Attempting reconnect (${ConnectionManager['reconnectAttempts']}/${ConnectionManager['maxReconnectAttempts']})`);

// Reinitialize the SDK
MoitribeSDK(this.gameId, 'init', {
connectionCallbacks: this.create(this.gameId)
}, (result) => {
if (!result.success) {
// Reconnect failed, schedule another attempt
this.scheduleReconnect();
}
});
}

private static refreshGameData(): void {
// Refresh critical game data after reconnection
console.log('Refreshing game data after reconnection');

// Reload player profile
MoitribeSDK(this.gameId, 'getprofile', {}, (result) => {
if (result.success) {
console.log('Profile refreshed after reconnection');
}
});

// Reload leaderboard data if needed
MoitribeSDK(this.gameId, 'loadleaderboardmetadata', {
leaderboardid: 'main'
}, (result) => {
if (result.success) {
console.log('Leaderboard refreshed after reconnection');
}
});
}

private static showConnectionNotification(message: string, type: 'success' | 'warning' | 'error'): void {
// Implementation depends on your UI framework
console.log(`[${type.toUpperCase()}] ${message}`);

// Example: Show toast notification
if (typeof window !== 'undefined' && (window as any).showToast) {
(window as any).showToast(message, type);
}
}

private static enableGameFeatures(): void {
// Enable game features that require connection
console.log('Enabling game features');
}

private static disableGameFeatures(): void {
// Disable game features that require connection
console.log('Disabling game features');
}
}

UI Integration​

React Component Example​

import React, { useState, useEffect } from 'react';
import { ConnectionState } from './ConnectionManager';

const ConnectionStatus: React.FC = () => {
const [connectionState, setConnectionState] = useState<ConnectionState>(ConnectionState.DISCONNECTED);
const [isVisible, setIsVisible] = useState(false);

useEffect(() => {
ConnectionManager.addListener(setConnectionState);

return () => {
ConnectionManager.removeListener(setConnectionState);
};
}, []);

useEffect(() => {
// Show connection status only when not connected
setIsVisible(connectionState !== ConnectionState.CONNECTED);

const timer = setTimeout(() => {
setIsVisible(false);
}, 5000);

return () => clearTimeout(timer);
}, [connectionState]);

if (!isVisible) return null;

const getStatusColor = () => {
switch (connectionState) {
case ConnectionState.CONNECTED: return 'green';
case ConnectionState.CONNECTING:
case ConnectionState.RECONNECTING: return 'orange';
case ConnectionState.FAILED:
case ConnectionState.DISCONNECTED: return 'red';
default: return 'gray';
}
};

const getStatusText = () => {
switch (connectionState) {
case ConnectionState.CONNECTED: return 'Connected';
case ConnectionState.CONNECTING: return 'Connecting...';
case ConnectionState.RECONNECTING: return 'Reconnecting...';
case ConnectionState.FAILED: return 'Connection Failed';
case ConnectionState.DISCONNECTED: return 'Disconnected';
default: return 'Unknown';
}
};

return (
<div style={{
position: 'fixed',
top: 20,
right: 20,
backgroundColor: getStatusColor(),
color: 'white',
padding: '10px 15px',
borderRadius: '5px',
fontSize: '14px',
fontWeight: 'bold',
zIndex: 1000
}}>
{getStatusText()}
</div>
);
};

export default ConnectionStatus;

Vanilla JavaScript Example​

class ConnectionUI {
private static element: HTMLElement | null = null;

static initialize(): void {
// Create connection status element
this.element = document.createElement('div');
this.element.id = 'connection-status';
this.element.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 10px 15px;
border-radius: 5px;
font-size: 14px;
font-weight: bold;
color: white;
z-index: 1000;
display: none;
transition: all 0.3s ease;
`;

document.body.appendChild(this.element);

// Listen to connection state changes
ConnectionManager.addListener((state) => {
this.updateUI(state);
});
}

private static updateUI(state: ConnectionState): void {
if (!this.element) return;

const colors = {
[ConnectionState.CONNECTED]: '#4CAF50',
[ConnectionState.CONNECTING]: '#FF9800',
[ConnectionState.RECONNECTING]: '#FF9800',
[ConnectionState.FAILED]: '#F44336',
[ConnectionState.DISCONNECTED]: '#F44336'
};

const messages = {
[ConnectionState.CONNECTED]: 'Connected',
[ConnectionState.CONNECTING]: 'Connecting...',
[ConnectionState.RECONNECTING]: 'Reconnecting...',
[ConnectionState.FAILED]: 'Connection Failed',
[ConnectionState.DISCONNECTED]: 'Disconnected'
};

this.element.style.backgroundColor = colors[state];
this.element.textContent = messages[state];
this.element.style.display = 'block';

// Auto-hide when connected
if (state === ConnectionState.CONNECTED) {
setTimeout(() => {
if (this.element) {
this.element.style.display = 'none';
}
}, 3000);
}
}

static showNotification(message: string, type: 'success' | 'warning' | 'error'): void {
if (!this.element) return;

const colors = {
success: '#4CAF50',
warning: '#FF9800',
error: '#F44336'
};

this.element.style.backgroundColor = colors[type];
this.element.textContent = message;
this.element.style.display = 'block';

setTimeout(() => {
if (this.element) {
this.element.style.display = 'none';
}
}, 5000);
}
}

// Initialize when DOM is ready
if (typeof document !== 'undefined') {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
ConnectionUI.initialize();
});
} else {
ConnectionUI.initialize();
}
}

Real-Time Multiplayer Connection Handling​

Room Connection Management​

class RoomConnectionManager {
private static currentRoomId: string | null = null;
private static isReconnecting: boolean = false;

static handleRoomConnection(roomId: string): void {
this.currentRoomId = roomId;
console.log('Connected to room:', roomId);
}

static handleRoomDisconnection(): void {
if (this.currentRoomId && !this.isReconnecting) {
console.log('Disconnected from room:', this.currentRoomId);
this.attemptRoomReconnection();
}
}

private static attemptRoomReconnection(): void {
if (!this.currentRoomId) return;

this.isReconnecting = true;
console.log('Attempting to rejoin room:', this.currentRoomId);

// Wait for main connection to be restored
const checkConnection = () => {
if (ConnectionManager.getState() === ConnectionState.CONNECTED) {
// Try to rejoin the room
MoitribeSDK('my-game', 'joinstandardroominvcode', {
invitationcode: this.currentRoomId!
}, (result) => {
this.isReconnecting = false;

if (result.success) {
console.log('Successfully rejoined room:', this.currentRoomId);
} else {
console.error('Failed to rejoin room:', result.message);
// Could show UI to user asking them to manually rejoin
}
});
} else {
// Check again in 1 second
setTimeout(checkConnection, 1000);
}
};

checkConnection();
}

static leaveRoom(): void {
this.currentRoomId = null;
this.isReconnecting = false;
}
}

Testing Connection Handling​

Connection Simulation​

class ConnectionSimulator {
static simulateConnectionLoss(duration: number = 5000): void {
console.log('Simulating connection loss...');

// Trigger connection lost callback
const callbacks = EnhancedConnectionCallbacks.create('my-game');
callbacks.onConnectionLost?.(-1, 'Simulated connection loss');

// Simulate reconnection after duration
setTimeout(() => {
console.log('Simulating reconnection...');
callbacks.onConnected?.(true, 'ws:// simulated-server');
}, duration);
}

static simulateConnectionFailure(): void {
console.log('Simulating connection failure...');

const callbacks = EnhancedConnectionCallbacks.create('my-game');
callbacks.onFailureConnect?.();
}

static testReconnectLogic(): void {
console.log('Testing reconnect logic...');

// Simulate multiple connection losses
this.simulateConnectionLoss(2000);
setTimeout(() => this.simulateConnectionLoss(3000), 8000);
setTimeout(() => this.simulateConnectionLoss(2000), 15000);
}
}

// Enable simulation in development
if (process.env.NODE_ENV === 'development') {
(window as any).connectionSimulator = ConnectionSimulator;
console.log('Connection simulator available at: window.connectionSimulator');
}

Best Practices​

1. Always Handle Connection States​

// ✓ Good - handle all connection states
const callbacks = {
onConnectionLost: (code, msg) => handleDisconnection(code, msg),
onConnected: (isReconnect, uri) => handleConnection(isReconnect, uri),
onSuccessConnect: () => handleSuccess(),
onFailureConnect: () => handleFailure()
};

// ✗ Bad - only handling success
const callbacks = {
onSuccessConnect: () => console.log('Connected')
};

2. Provide User Feedback​

// ✓ Good - user-friendly messages
const showConnectionMessage = (message: string, type: 'info' | 'warning' | 'error') => {
// Show toast, banner, or other UI element
showToast(message, type);
};

// ✗ Bad - silent failures
const callbacks = {
onConnectionLost: () => {} // No user feedback
};

3. Implement Exponential Backoff​

// ✓ Good - exponential backoff for reconnection
const getReconnectDelay = (attempt: number) => {
return Math.min(1000 * Math.pow(2, attempt), 30000); // Max 30 seconds
};

// ✗ Bad - fixed interval
const getReconnectDelay = () => 1000; // Always 1 second

4. Handle Room Reconnection​

// ✓ Good - handle room reconnection after main connection
const handleReconnected = () => {
if (currentRoomId) {
rejoinRoom(currentRoomId);
}
refreshGameData();
};

// ✗ Bad - only handle main connection
const handleReconnected = () => {
console.log('Reconnected'); // No room handling
};

Next Steps​

warning

Always test your connection handling logic with network simulations to ensure it works reliably in poor network conditions.