Skip to content

feat: sync the event buffer to the server - #16

Merged
mikahatdichlieb merged 5 commits into
mainfrom
feat/event-sync
Oct 26, 2026
Merged

mikahatdichlieb merged 5 commits into
mainfrom
feat/event-sync

Conversation

@mikahatdichlieb

Copy link
Copy Markdown
Contributor

Closes #13

What

Uploads the local event buffer whenever the server is reachable.

  • Triggers:

    • right away when the app is opened
    • hourly in the background while any network is connected
    • after a failure, with exponential backoff starting at 10 minutes

    There is no Wi-Fi detection, so the home network, Tailscale and public servers all work the same way. An unreachable server leaves the buffer for the next attempt.

  • Each run collects new usage first, then uploads the buffer in batches of 500 events. 500 events stay far below 512 KiB.

  • Events leave the buffer once the server accounts for them: accepted, duplicate, or rejected. For rejected events only the reason code and a count are logged.

  • 429: the buffer is kept. No request is made from any path (app open, periodic run, retry) until Retry-After has passed, then a single delayed attempt runs.

  • 401: the token is dropped and the server URL is kept, so the app shows "Pair this device again". No further device requests and no timer retry; the buffer and collection are kept.

  • GET /api/v1/device/me: once per app start for the clock skew, never on a timer.

  • Connected screen: says in words when the device last synced or why it could not, how many entries are waiting, and warns when the device clock is off by more than 5 minutes.

Other changes

  • DeviceSetup is one instance per process, so pairing, sync and the 401 handling share its lock.
  • "Server URL but no token" now means "pair again". A failed token save during pairing therefore removes the server URL too.
  • The last successful sync time and the earliest allowed attempt after a 429 are stored in SharedPreferences, which are excluded from backups.

Security / privacy relevant

  • After a 401 the token is deleted; nothing is retried on a timer.
  • Logcat only ever contains reason codes and counts, never event data or tokens.
  • Without Tailscale, each attempt away from home still does a DNS lookup of the server hostname on the current network.

Tests

EventSyncTest runs against an HTTPS MockWebServer and covers:

  • no request without pairing
  • accepted, duplicate and rejected events leave the buffer, and only reason codes are logged
  • 501 events go out in two batches
  • 401 keeps the buffer, asks to pair again and sends no further requests
  • 429 with its delay, an unreachable server and a server error all keep the buffer
  • an empty buffer sends no request
  • /device/me clock skew and 401

The cross-path Retry-After gate and WorkManager scheduling depend on Android and are not unit tested. Nothing has been run on a device yet. There are no new dependencies and the Room schema is unchanged.

markUnauthenticated() drops the token after a 401 so no further device
request is made; the remaining server URL makes the app ask to pair
again. A failed token save no longer leaves a server URL behind. One
DeviceSetup per process so pairing and sync share its lock.
Events leave the buffer once the server accounts for them (accepted,
duplicate or rejected; only reason codes are logged). 401 stops and asks
to pair again, 429, network failures and server errors keep the buffer.
device/me reports the clock skew once per app start.
Hourly while a network is connected, with exponential backoff after a
failure; no Wi-Fi detection, so home networks, Tailscale and public
servers all work. After a 429 no request is made from any path until
Retry-After has passed.
The app checks the device and syncs right away when opened, then says in
words when it last synced, how many entries are waiting, and whether the
device clock is off by more than the server tolerates.
@mikahatdichlieb
mikahatdichlieb merged commit c01f1e2 into main Oct 26, 2026
6 checks passed
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.

Sync the buffer to the server

1 participant