Skip to content

fix(sync): show device-approval prompt after reconnect (replay pending transfers)#8

Merged
khokonm merged 1 commit into
mainfrom
fix/sync-transfer-approval-replay
Jun 24, 2026
Merged

fix(sync): show device-approval prompt after reconnect (replay pending transfers)#8
khokonm merged 1 commit into
mainfrom
fix/sync-transfer-approval-replay

Conversation

@khokonm

@khokonm khokonm commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Problem

When a new device joins a sync chain, it asks an existing device to send it the notes (peer transfer with approval). The other devices were never showing the approval prompt — not while they were active, and not when they reopened the app later.

Root cause

The approval request was delivered fire-and-forget:

  1. New device → request-transfer-from → the server creates a pending transfer row and calls sendToDevice(target, { type: 'transfer-requested' }).
  2. sendToDevice only delivers if the target has a live WebSocket at that exact moment; its boolean return is ignored and a missed message is lost.
  3. The join/welcome handler never replays pending transfer requests when a device (re)connects.

On mobile PWAs the WebSocket drops whenever the app is backgrounded or the screen locks, so an "active" phone usually isn't holding a live socket — the request is dropped, and because nothing re-delivers it, reopening the app ("returning") shows nothing either. db.ts already had getPendingTransfers() written for this case, but it was dead code — never imported or called.

Fix

Wire getPendingTransfers() into the join handler. When an established device (re)connects, the server replays any still-pending transfer requests in its room, so the approval prompt appears as soon as the device has a live connection.

Guards:

  • Only status === 'pending' transfers (already-approved/in-flight ones are skipped).
  • The requester is skipped (a device is never asked to approve its own request).
  • Only replayed when the requester is currently online, so we don't prompt for a transfer whose new device has gone away.
  • Brand-new (needsTransfer) devices are not asked to approve — they have no notes to send.

The existing immediate sendToDevice path is unchanged; this purely adds durable re-delivery on connect. Replaying a request the approver already saw is harmless (same transferId; the server still validates status === 'pending' on approve).

Files changed

  • server/src/index.ts — import getPendingTransfers; replay pending transfer requests in the join handler.

Verification

  • Reviewed against db.ts (getPendingTransfers returns rows with status IN ('pending','approved','in_progress'); TransferRecord.status includes 'pending') and the client handler (case 'transfer-requested' sets pendingTransfer, rendered by the global modal in App.tsx and the panel card in SettingsPanel.tsx).
  • Not run locally: tsc/build/lint — the server's dependencies aren't installed in the sandbox and package installs are blocked (registry 403). The change is additive and uses only already-imported/in-scope symbols (getDevice, room, WebSocket).

Manual test plan

  1. Device A (with notes) signed into a sync chain; Device B is new.
  2. On B, enter the sync code and pick A as the source.
  3. Active: with A's app open, confirm the approval prompt appears on A; approve → B receives notes.
  4. Backgrounded/returns: trigger the request from B while A's app is closed/backgrounded, then open A → confirm the approval prompt now appears on A (previously it never did) → approve → B receives notes.
  5. Deny path still works; approving/denying twice is a no-op (transfer no longer pending).

…es reconnect

New-device join approvals were delivered fire-and-forget via sendToDevice and
lost whenever the approver had no live WebSocket at that instant (e.g. a mobile
PWA whose socket drops when backgrounded). The join handler never re-delivered
them, so the approval prompt never appeared — 'not even when they return'.
getPendingTransfers() existed for exactly this but was dead code.

Wire it into the join handler: when an established device (re)connects, replay
any still-pending transfer requests in its room whose requester is currently
online, so it sees the approval prompt. New (needsTransfer) devices are skipped
since they have no notes to send.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant