feat: offline sync queue - reconcile TEMP messages on restart, auto-r…#6964
feat: offline sync queue - reconcile TEMP messages on restart, auto-r…#6964deepak0x wants to merge 1 commit intoRocketChat:developfrom
Conversation
…etry ERROR on reconnect - Export changeMessageStatus from sendMessage for reconciliation - Add messageSync.ts: reconcileTempMessages (TEMP older than 5min) and retryErrorMessages - Run reconcileTempMessages on login success (handleLoginSuccess) - Add messageSync saga: retry ERROR messages on METEOR.SUCCESS when user logged in - Register messageSync saga in root
WalkthroughThe changes introduce message synchronization utilities to handle reconciliation of temporary messages and retry of failed message sends. A new messageSync module provides two public functions, while new sagas integrate these operations into the app's state management flow—one running after login and another on successful meteor events. Changes
Sequence DiagramsequenceDiagram
actor User
participant Redux as Redux/Sagas
participant LocalDB as Local Database
participant Server as Server API
User->>Redux: Login Success
activate Redux
Redux->>LocalDB: reconcileTempMessagesSaga triggered
activate LocalDB
LocalDB->>LocalDB: Query TEMP messages (>5min old)
loop For each TEMP message
LocalDB->>Server: getSingleMessage(id)
alt Server message exists
Server-->>LocalDB: Message found
LocalDB->>LocalDB: changeMessageStatus → SENT
else Server message not found
Server-->>LocalDB: Not found
LocalDB->>Server: resendMessage()
end
end
deactivate LocalDB
deactivate Redux
User->>Redux: METEOR.SUCCESS event
activate Redux
Redux->>LocalDB: retryErrorMessagesSaga triggered
activate LocalDB
LocalDB->>LocalDB: Query ERROR status messages
loop For each ERROR message
LocalDB->>Server: resendMessage()
Server-->>LocalDB: Send result
end
deactivate LocalDB
deactivate Redux
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Important Action Needed: IP Allowlist UpdateIf your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:
Reviews will stop working after February 8, 2026 if the new IP is not added to your allowlist. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@app/lib/methods/messageSync.ts`:
- Around line 33-49: The outer catch around getSingleMessage treats any error as
"message missing" and triggers resendMessage, which can create duplicates on
transient failures; update the error handling in the try/catch that surrounds
getSingleMessage (and the subsequent changeMessageStatus call) to inspect the
thrown error (e.g. check err.response?.status, err.status, or err.code) and only
call resendMessage(record, record.tmid ?? undefined) when the error indicates
the message is truly not found (e.g. 404 or specific "not found" code); for
other errors (network timeouts, 5xx, auth failures) either rethrow or log and
abort without resending so resendMessage is not invoked erroneously. Ensure you
reference getSingleMessage, changeMessageStatus, resendMessage, messagesStatus
and preserve the inner catch(e) { log(e) } behavior for the actual resend path.
🧹 Nitpick comments (2)
app/sagas/messageSync.js (1)
21-23: Consider usingtakeLatestinstead oftakeEveryto prevent concurrent retries.
METEOR.SUCCESScan fire multiple times during reconnection scenarios. UsingtakeEverycould spawn multiple concurrentretryErrorMessagesSagainstances, potentially causing the same ERROR messages to be retried simultaneously, leading to duplicate sends or race conditions.♻️ Proposed fix
-const root = function* root() { - yield takeEvery(METEOR.SUCCESS, retryErrorMessagesSaga); -}; +import { call, select, takeLatest } from 'redux-saga/effects'; + +const root = function* root() { + yield takeLatest(METEOR.SUCCESS, retryErrorMessagesSaga); +};app/sagas/login.js (1)
251-251: Note: Reconciliation may be cancelled after 2 seconds.The
handleLoginSuccesstask is cancelled after a 2-second timeout (see Lines 426-430). IfreconcileTempMessagesis processing many stuck messages, it may be interrupted mid-execution. This follows the existing pattern for other forked tasks, but consider whether the reconciliation should complete independently.If uninterrupted execution is important, consider spawning instead of forking:
💡 Alternative using spawn for detached execution
- yield fork(reconcileTempMessagesSaga); + yield spawn(reconcileTempMessagesSaga);Note:
spawncreates a detached task that won't be cancelled when the parent is cancelled. You'd need to importspawnfromredux-saga/effects.
| try { | ||
| const serverMessage = await getSingleMessage(record.id); | ||
| if (serverMessage) { | ||
| await changeMessageStatus( | ||
| record.id, | ||
| messagesStatus.SENT, | ||
| record.tmid ?? undefined, | ||
| serverMessage | ||
| ); | ||
| } | ||
| } catch { | ||
| try { | ||
| await resendMessage(record, record.tmid ?? undefined); | ||
| } catch (e) { | ||
| log(e); | ||
| } | ||
| } |
There was a problem hiding this comment.
Catch-all may cause unintended resends on transient failures.
The outer catch block (Lines 43-48) treats any getSingleMessage failure as "message doesn't exist on server" and triggers a resend. However, transient network errors, authentication failures, or server errors would also fall into this path, potentially causing duplicate messages.
Consider checking the error type or response status before deciding to resend:
🛡️ Proposed approach
for (const record of tempMessages as TMessageModel[]) {
try {
const serverMessage = await getSingleMessage(record.id);
if (serverMessage) {
await changeMessageStatus(
record.id,
messagesStatus.SENT,
record.tmid ?? undefined,
serverMessage
);
+ } else {
+ // Message confirmed not on server, safe to resend
+ await resendMessage(record, record.tmid ?? undefined);
}
- } catch {
+ } catch (e: any) {
+ // Only resend if we got a 404/not-found response
+ // Otherwise log and skip to avoid duplicates on transient errors
+ if (e?.data?.error === 'error-invalid-message' || e?.response?.status === 404) {
try {
await resendMessage(record, record.tmid ?? undefined);
} catch (e) {
log(e);
}
+ } else {
+ log(e);
+ }
}
}🤖 Prompt for AI Agents
In `@app/lib/methods/messageSync.ts` around lines 33 - 49, The outer catch around
getSingleMessage treats any error as "message missing" and triggers
resendMessage, which can create duplicates on transient failures; update the
error handling in the try/catch that surrounds getSingleMessage (and the
subsequent changeMessageStatus call) to inspect the thrown error (e.g. check
err.response?.status, err.status, or err.code) and only call
resendMessage(record, record.tmid ?? undefined) when the error indicates the
message is truly not found (e.g. 404 or specific "not found" code); for other
errors (network timeouts, 5xx, auth failures) either rethrow or log and abort
without resending so resendMessage is not invoked erroneously. Ensure you
reference getSingleMessage, changeMessageStatus, resendMessage, messagesStatus
and preserve the inner catch(e) { log(e) } behavior for the actual resend path.
This PR adds a offline/sync flow to ensure messages don’t remain stuck after app restarts, crashes, or network changes.
Stuck TEMP messages: On login, messages that have been in
TEMPstate for more than 5 minutes are reconciled:chat.getMessage), its status is updated toSENT.ERROR.Auto-retry ERROR messages: When the app reconnects (
METEOR.SUCCESS), all messages inERRORstate are automatically retried so users don’t need to manually tap Resend after going back online.** Details**:
messageSync.tswithreconcileTempMessages()andretryErrorMessages().changeMessageStatusfromsendMessage.tsfor reuse.messageSyncsaga that retries ERROR messages on connection success when the user is logged in.Issue(s)
Closes #6928
TEMPremained in a perpetual Sending… state after app crash or force kill.ERRORstate were never retried automatically after network reconnection.How to Test / Reproduce
Stuck TEMP Messages
Expected result:
The message is reconciled:
SENTif it exists on the server, orERRORif resend fails.Auto-retry ERROR Messages
ERROR).result:
Screenshots
Types of Changes
Checklist
Summary by CodeRabbit