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.