Real-time Match Tracking
Build live match trackers with real-time updates
This guide shows you how to build a real-time match tracker that updates as games progress. We'll cover both polling and webhook approaches.
Approach 1: Polling
Poll the API at regular intervals to get the latest match data:
match-tracker.js
class MatchTracker {
constructor(apiKey, matchId) {
this.cito = new CitoAPI(apiKey);
this.matchId = matchId;
this.lastUpdate = null;
this.listeners = [];
}
subscribe(callback) {
this.listeners.push(callback);
return () => {
this.listeners = this.listeners.filter(l => l !== callback);
};
}
emit(event, data) {
this.listeners.forEach(cb => cb(event, data));
}
async start() {
this.interval = setInterval(async () => {
try {
const { data: match } = await this.cito.fortnite.matches.get(this.matchId);
if (this.hasChanged(match)) {
this.emit('update', match);
this.lastUpdate = match;
}
} catch (error) {
this.emit('error', error);
}
}, 5000); // Poll every 5 seconds
}
hasChanged(newData) {
if (!this.lastUpdate) return true;
return JSON.stringify(newData) !== JSON.stringify(this.lastUpdate);
}
stop() {
clearInterval(this.interval);
}
}
// Usage
const tracker = new MatchTracker('sk_live_...', 'match_12345');
tracker.subscribe((event, data) => {
if (event === 'update') {
console.log('Match updated:', data);
updateUI(data);
}
});
tracker.start();Approach 2: Webhooks (Recommended)
Use webhooks for instant updates without polling:
// Register webhook for match events
const webhook = await cito.webhooks.create({
url: 'https://yourapp.com/webhook',
events: ['match.updated', 'match.ended', 'player.elimination']
});
// Handle incoming webhooks
app.post('/webhook', (req, res) => {
const { event, data } = req.body;
// Broadcast to connected clients via WebSocket
io.emit('match-update', { event, data });
res.sendStatus(200);
});React Integration
useMatchTracker.ts
import { useState, useEffect } from 'react';
import { io } from 'socket.io-client';
export function useMatchTracker(matchId: string) {
const [match, setMatch] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// Initial fetch
fetch(`/api/matches/${matchId}`)
.then(res => res.json())
.then(data => {
setMatch(data);
setLoading(false);
})
.catch(setError);
// Subscribe to real-time updates
const socket = io();
socket.on('match-update', ({ event, data }) => {
if (data.match_id === matchId) {
setMatch(prev => ({ ...prev, ...data }));
}
});
return () => socket.disconnect();
}, [matchId]);
return { match, loading, error };
}
// Usage in component
function MatchView({ matchId }) {
const { match, loading, error } = useMatchTracker(matchId);
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return (
<div>
<h1>{match.tournament}</h1>
<div>Game {match.current_game} of {match.total_games}</div>
<Leaderboard players={match.players} />
</div>
);
}Best Practices
Cache aggressively
Store match data locally and only update changed fields
Use diff updates
Only re-render components that have changed data
Handle reconnection
Implement reconnection logic for WebSocket disconnects