5.1 KiB
5.1 KiB
title, impact, impactDescription, tags
| title | impact | impactDescription | tags |
|---|---|---|---|
| Handle Realtime Connection Issues | MEDIUM | Reliable realtime even with network interruptions | realtime, reconnection, resilience, offline |
Handle Realtime Connection Issues
Realtime connections can disconnect due to network issues or server restarts. Implement proper reconnection handling and state synchronization.
Incorrect (ignoring connection issues):
// No reconnection handling - stale data after disconnect
pb.collection('posts').subscribe('*', (e) => {
updateUI(e.record);
});
// If connection drops, UI shows stale data indefinitely
// Assuming connection is always stable
function PostList() {
useEffect(() => {
pb.collection('posts').subscribe('*', handleChange);
}, []);
// No awareness of connection state
}
Correct (robust connection handling):
// Monitor connection state
function useRealtimeConnection() {
const [connected, setConnected] = useState(false);
const [lastSync, setLastSync] = useState(null);
useEffect(() => {
// Track connection state
const unsubConnect = pb.realtime.subscribe('PB_CONNECT', (e) => {
console.log('Connected, client ID:', e.clientId);
setConnected(true);
// Re-sync data after reconnection
if (lastSync) {
syncMissedUpdates(lastSync);
}
setLastSync(new Date());
});
// Handle disconnection
pb.realtime.onDisconnect = (activeSubscriptions) => {
console.log('Disconnected');
setConnected(false);
showOfflineIndicator();
};
return () => {
unsubConnect();
};
}, [lastSync]);
return { connected };
}
// Sync missed updates after reconnection
async function syncMissedUpdates(since) {
// Fetch records modified since last sync
const updatedPosts = await pb.collection('posts').getList(1, 100, {
filter: pb.filter('updated > {:since}', { since }),
sort: '-updated'
});
// Merge with local state
updateLocalState(updatedPosts.items);
}
// Full implementation with resilience
class RealtimeManager {
constructor(pb) {
this.pb = pb;
this.subscriptions = new Map();
this.lastSyncTimes = new Map();
this.reconnectAttempts = 0;
this.maxReconnectDelay = 30000;
this.setupConnectionHandlers();
}
setupConnectionHandlers() {
this.pb.realtime.subscribe('PB_CONNECT', () => {
console.log('Realtime connected');
this.reconnectAttempts = 0;
this.onReconnect();
});
this.pb.realtime.onDisconnect = (subs) => {
console.log('Realtime disconnected');
this.scheduleReconnect();
};
}
scheduleReconnect() {
// Exponential backoff with jitter
const delay = Math.min(
1000 * Math.pow(2, this.reconnectAttempts) + Math.random() * 1000,
this.maxReconnectDelay
);
this.reconnectAttempts++;
setTimeout(() => {
if (!this.pb.realtime.isConnected) {
this.resubscribeAll();
}
}, delay);
}
async onReconnect() {
// Sync data for each tracked collection
for (const [collection, lastSync] of this.lastSyncTimes) {
await this.syncCollection(collection, lastSync);
}
}
async syncCollection(collection, since) {
try {
const updates = await this.pb.collection(collection).getList(1, 1000, {
filter: this.pb.filter('updated > {:since}', { since }),
sort: 'updated'
});
// Notify subscribers of missed updates
const handler = this.subscriptions.get(collection);
if (handler) {
updates.items.forEach(record => {
handler({ action: 'update', record });
});
}
this.lastSyncTimes.set(collection, new Date());
} catch (error) {
console.error(`Failed to sync ${collection}:`, error);
}
}
async subscribe(collection, handler) {
this.subscriptions.set(collection, handler);
this.lastSyncTimes.set(collection, new Date());
return this.pb.collection(collection).subscribe('*', (e) => {
this.lastSyncTimes.set(collection, new Date());
handler(e);
});
}
async resubscribeAll() {
// Refresh auth token before resubscribing to ensure valid credentials
if (this.pb.authStore.isValid) {
try {
await this.pb.collection('users').authRefresh();
} catch {
this.pb.authStore.clear();
}
}
for (const [collection, handler] of this.subscriptions) {
this.pb.collection(collection).subscribe('*', handler);
}
}
}
// Usage
const realtime = new RealtimeManager(pb);
await realtime.subscribe('posts', handlePostChange);
Connection timeout handling:
// Server sends disconnect after 5 min of no messages
// SDK auto-reconnects, but you can handle it explicitly
let lastHeartbeat = Date.now();
pb.realtime.subscribe('PB_CONNECT', () => {
lastHeartbeat = Date.now();
});
// Check for stale connection
setInterval(() => {
if (Date.now() - lastHeartbeat > 6 * 60 * 1000) {
console.log('Connection may be stale, refreshing...');
pb.realtime.unsubscribe();
resubscribeAll();
}
}, 60000);
Reference: PocketBase Realtime