Networking & Retry
vuer-rtc tracks message acknowledgement status to enable retry on network failures.
How It Works
Every message in the journal has an ack field:
ack: false- Message hasn't been acknowledged by the serverack: true- Server has confirmed receipt
When a message is committed locally, it starts with ack: false. When the server acknowledges it (via onServerAck), it becomes ack: true.
Retry Helpers
import {
getUnackedMessages,
hasPendingMessages,
getPendingCount,
} from '@vuer-ai/vuer-rtc/client';
// Get all messages that need to be (re)sent
const messages = getUnackedMessages(state);
// Check if there's anything pending
if (hasPendingMessages(state)) {
console.log(`${getPendingCount(state)} messages pending`);
}Undo/Redo and Ack Reset
When you undo or redo an operation, two things happen:
- New undo/redo message is created with
ack: false - Target entry's ack is reset to
false
This ensures that:
- The undo/redo message itself needs to be sent
- The target entry's
deletedAtstate change is tracked for sync
// Example: After undo
journal: [
{ msg: originalMsg, ack: false, deletedAt: 123456 }, // ack reset!
{ msg: undoMsg, ack: false }, // new message
]Implementing Retry
class RTCClient {
private retryTimer: number | null = null;
private retryDelay = 1000; // Start with 1 second
private maxRetryDelay = 30000; // Max 30 seconds
// Call when connection is restored
async retryPendingMessages() {
const messages = getUnackedMessages(this.store.getState());
for (const msg of messages) {
try {
await this.sendToServer(msg);
} catch (err) {
// Schedule exponential backoff retry
this.scheduleRetry();
return;
}
}
// All sent successfully, reset delay
this.retryDelay = 1000;
}
private scheduleRetry() {
if (this.retryTimer) return;
this.retryTimer = setTimeout(() => {
this.retryTimer = null;
this.retryPendingMessages();
}, this.retryDelay);
// Exponential backoff
this.retryDelay = Math.min(this.retryDelay * 2, this.maxRetryDelay);
}
// Call when receiving ack from server
onAck(msgId: string) {
this.store.dispatch(state => onServerAck(state, msgId));
}
}Server Implementation
The server should:
-
Acknowledge each message after processing:
ws.on('message', (data) => { const msg = JSON.parse(data); // Process the message... // Send ack back to client ws.send(JSON.stringify({ type: 'ack', msgId: msg.id })); }); -
Handle duplicate messages gracefully (idempotent):
const processedIds = new Set<string>(); function processMessage(msg: CRDTMessage) { if (processedIds.has(msg.id)) { // Already processed, just send ack return; } processedIds.add(msg.id); // Process... }
Connection Status UI
You can track connection state and show sync status:
function useConnectionStatus(store: GraphStore) {
const [status, setStatus] = useState<'connected' | 'reconnecting' | 'offline'>('connected');
const pendingCount = getPendingCount(store.getState());
return {
status,
pendingCount,
isSynced: pendingCount === 0,
};
}