Skip to content

feat: real-time streaming sync over WebSocket (opt-in) - #5

Merged
alexheidl merged 1 commit into
mainfrom
feat/streaming-sync
Jun 9, 2026
Merged

feat: real-time streaming sync over WebSocket (opt-in)#5
alexheidl merged 1 commit into
mainfrom
feat/streaming-sync

Conversation

@alexheidl

Copy link
Copy Markdown
Contributor

Adds opt-in WebSocket streaming as an alternative to polling, so flag changes arrive in real time. Polling stays the default; this is purely additive (the DataSynchronizer abstraction was built for it).

Lineage note

The SDK started as a port of FeatBit's .NET client SDK (polling, store, evaluator, tracker, insights, API). The .NET client SDK is polling-only, so streaming is modeled on FeatBit's JS/RN SDK and the evaluation server's /streaming wire protocol (read from featbit/featbit). README/intro updated to reflect this.

What's added

  • StreamingDataSynchronizer — OkHttp WebSocket to /streaming?type=client&version=2&token=…:
    • sends data-sync with the user + last timestamp; applies full/patch flag pushes into the same in-memory store (so evaluation, FlagTracker, and flagChanges are identical to polling)
    • application-level ping heartbeat + exponential-backoff reconnect (fresh token per connect; server expires tokens in ~30s)
  • ConnectionToken — port of FeatBit's token scheme (secret + timestamp), verified by a round-trip unit test against a faithful port of the server's decoder
  • FBOptions.streaming(wsUri) + DataSyncMode.Streaming; FBClientImpl wires the new mode
  • Rethrow CancellationException in TrackInsight → clean shutdown (no spurious error logs)

Usage

val options = FBOptions.Builder(secret)
    .streaming("wss://app-eval.featbit.co")
    .build()

Verification (against a real FeatBit server via Testcontainers)

FeatBitStreamingE2ETest (gated by FEATBIT_E2E=1): a real streaming FBClient connects, evaluates a seeded flag (true), and receives a server-side toggle as a real-time push (test body ~0.5s).

FEATBIT_E2E=1 ./gradlew :featbit-client:testDebugUnitTest --tests "co.featbit.client.e2e.*"   # green (polling + streaming)
./gradlew :featbit-client:testDebugUnitTest    # 32 tests, 2 E2E skipped, 0 failures

Mobile note

WebSockets get killed in background/doze. README recommends streaming while foregrounded + reconnect on resume/network; polling remains available as a fallback. A lifecycle-aware auto-switch could be a future addition.

🤖 Generated with Claude Code

Adds opt-in WebSocket streaming as an alternative to polling, modeled on FeatBit's
JS/RN SDK and the evaluation server's /streaming wire protocol. Polling remains the default.

- StreamingDataSynchronizer: OkHttp WebSocket to /streaming?type=client&version=2&token=…;
  sends data-sync(user,timestamp), applies full/patch flag pushes into the shared store,
  app-level ping heartbeat, exponential-backoff reconnect
- ConnectionToken: port of FeatBit's token scheme (secret+timestamp), verified by a
  round-trip unit test against a faithful port of the server decoder
- FBOptions.streaming(wsUri) + DataSyncMode.Streaming; FBClientImpl wires the new mode
- Reuses store/evaluator/FlagTracker/flagChanges unchanged
- Rethrow CancellationException in TrackInsight (clean shutdown, no spurious error logs)
- README: document both modes; clarify lineage (.NET-based; streaming from JS/RN protocol)

Verified end-to-end against a real FeatBit server (Testcontainers): streaming client
connects, evaluates a seeded flag, and receives a server-side toggle as a real-time push.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@alexheidl
alexheidl merged commit 62cffdf into main Jun 9, 2026
2 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.

1 participant