MODULE 05 · LESSON 5.5
Recover chat after disconnection and prove the network boundary
Recover mobile chat across network loss, suspend and restart.
Lesson 25 of 86 · FabrCore 2.0
Overview
Delivery is ordered and at-least-once per stable client ID. Recovery may replay duplicates. Android can suspend or kill the process, so a continuously running socket is not a background delivery guarantee. Persisting a checkpoint is meaningful only if the corresponding local state was applied safely.
Replay creates duplicates by design
Delivery is at-least-once for a stable client ID. If the connection fails after the application applied a message but before the acknowledgement reached the Host, the same delivery can arrive again. A durable local transaction keyed by DeliveryId prevents duplicate bubbles or effects. Saving only the latest sequence without applying the corresponding message can lose visible content.
A gap needs reconciliation, not guesswork
The retained delivery window is bounded. If the checkpoint falls outside it, a gap tells the client that replay cannot reconstruct everything. Refresh from the application's authoritative history/state path and make the incomplete interval visible if necessary. Android suspension also means the socket may not run while the app is backgrounded; an in-process loop is not a mobile push service.
Recover after a dropped connection
Download the Operations Desk source. The README lists project setup, package prerequisites and local ports.
- Persist transcript/application state and its processed sequence consistently, then acknowledge. Implement IFabrCoreWebSocketCheckpointStore for restart recovery and isolate it by account/client identity.
- On resume, reconnect with a fresh ticket. Handle ResyncRequired by rebuilding relevant state through authorized HTTP queries; never silently skip a retention gap.
- Test Wi-Fi changes, suspend/resume, process termination and sign-out/account switching. Do not automatically resend a business mutation whose result is unknown.
// Connect the events BEFORE starting the receiver.
// resyncSignal is an application-owned channel/queue consumed by your recovery worker.
connection.ResyncRequired += (_, gap) => resyncSignal.Writer.TryWrite(gap.Gap);
await connection.ConnectAsync(lifetime.Token);
await connection.ReceiveAsync(async (delivery, ct) =>
{
// Your local store transaction deduplicates by DeliveryId and saves the sequence.
var added = await transcriptStore.ApplyOnceAsync(
accountId, delivery.DeliveryId, delivery.Sequence, delivery.Message, ct);
if (added)
await MainThread.InvokeOnMainThreadAsync(() => viewModel.Add(delivery.Message));
}, lifetime.Token);
// ChatConnection acknowledges only after this callback succeeds.
// transcriptStore, viewModel and resyncSignal are application-owned dependencies.
Disconnect around an acknowledgement
- Receive a known message and interrupt the connection around local apply/acknowledgement. Reconnect with the same account-bound client ID and checkpoint store.
- If the delivery is replayed, verify the local deduplication transaction retains one record and one visible bubble. Do not acknowledge before that transaction succeeds.
- Exercise the gap callback with a checkpoint outside retained coverage in a controlled test. Expect your recovery worker to refresh state and report any unrecoverable interval, not pretend replay was complete.
The result demonstrates recovery behavior under duplicate delivery and bounded retention. It does not establish indefinite background execution on Android.
If the result is different
A new client ID begins at the current tail. OS push notifications are a separate integration; FabrCore proactive delivery alone does not wake a suspended Android app.
Go deeper
Explore the related documentation.