feat(SDK-2185): FDv2 streaming base, initializer, and synchronizer#267
Draft
kinyoklion wants to merge 3 commits intomainfrom
Draft
feat(SDK-2185): FDv2 streaming base, initializer, and synchronizer#267kinyoklion wants to merge 3 commits intomainfrom
kinyoklion wants to merge 3 commits intomainfrom
Conversation
97eca5b to
6a9d5ec
Compare
6a9d5ec to
42d879a
Compare
kinyoklion
commented
May 7, 2026
Member
Author
There was a problem hiding this comment.
I am undecided if we should support this. If we don't then we can combine the base into the synchronizer and discard it.
Flutter does run on web, but it is inherently a single page app. So I don't think one-shot mode is nearly as important as it is for js.
Lets test code assert that the SUT correctly tears the SSE connection down. Test-only addition; the production SSEClient implementations do not expose this state, and the TestSseClient class is already documented as test-only with no semver guarantee.
- streaming_base.dart: wraps an SSEClient. Single-subscription StreamController<FDv2SourceResult>. On subscribe, opens the SSE stream and creates a fresh FDv2ProtocolHandler. Each named SSE event is parsed as JSON, wrapped in an FDv2Event, and fed to the handler. ActionPayload becomes a ChangeSetResult (persist: true); ActionGoodbye becomes a goodbye StatusResult and closes the connection; ActionServerError / ActionError become interrupted StatusResults; ActionNone does not emit. Legacy `ping` events invoke the injected PingHandler and forward its result. The `x-ld-fd-fallback` header on the OpenEvent emits terminalError with fdv1Fallback=true and closes. SSE transport errors surface as interrupted; the SSE client's built-in backoff handles reconnect. Closed-ness is tracked via a single Completer<void> _stoppedSignal matching the polling synchronizer. - streaming_initializer.dart: implements Initializer. Subscribes to the base, completes run() with the first emission, then closes the connection. close() before the first emission yields a shutdown StatusResult. - streaming_synchronizer.dart: implements Synchronizer. Thin adapter forwarding the base's stream so the orchestrator can treat polling and streaming uniformly. Tests cover: lifecycle (open on subscribe, cancel teardown, close+shutdown, idempotency), event handling (xfer-full payload, environmentId from header, goodbye, malformed data, non-object data, transport error), FDv1 fallback header (true / case- insensitive / false ignored), legacy ping bridge (forwards result, handles thrower), and synchronizer forwarding. The orchestrator (SDK-2186) wires the SSEClient with the right URL and auth strategy based on SSEClient.hasCapability(requestHeaders); the streaming source consumes whatever client it's given.
Drops the per-file FakeSseClient stub and replaces it with the shared TestSseClient from launchdarkly_event_source_client, driven via the existing emitEvent / emitError API plus the new isClosed observer. Net: ~50 lines of duplicated stub code removed across three test files.
96f16d0 to
24264af
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #266 (SSE capability surface). Mark this PR ready for review only after #266 merges and the base is rebased onto main.
Jira: SDK-2185
Scope
Phase B1 from the parallel plan — the streaming half of FDv2 sources, mirroring the polling vertical that landed in SDK-2183 / SDK-2184.
Three new files in
packages/common_client/lib/src/data_sources/fdv2/:streaming_base.dart—FDv2StreamingBaseWraps an
SSEClientwith FDv2 protocol semantics. Single-subscriptionStreamController<FDv2SourceResult>:onListen→ builds a freshFDv2ProtocolHandler, subscribes to the SSE client's stream.onCancel→ tears down without emitting shutdown (subscriber-initiated).close()→ emits shutdown then closes; idempotent.Per event:
FDv2Event, fed to the handler.ActionPayload→ChangeSetResultwithpersist: true.ActionGoodbye→ goodbyeStatusResult; closes the connection (server told us to disconnect).ActionServerError/ActionError→ interruptedStatusResult; SSE client's built-in backoff handles reconnect.ActionNone→ no emission.pingevents → injectedPingHandler(one-shot poll); result is forwarded.x-ld-fd-fallback: trueon theOpenEvent→ terminalError withfdv1Fallback: true; closes.Closed-ness tracked via a single
Completer<void> _stoppedSignal(matches the polling synchronizer pattern from SDK-2184).streaming_initializer.dart—FDv2StreamingInitializerImplements
Initializer. Subscribes to the base, completesrun()with the first emission, then closes the connection.close()before the first emission yields a shutdown result.streaming_synchronizer.dart—FDv2StreamingSynchronizerImplements
Synchronizer. Thin adapter forwarding the base's stream andclose(), so the orchestrator can treat polling and streaming uniformly.Auth wiring (deferred to orchestrator)
FDv2StreamingBasedoes not build URLs or set headers. The orchestrator (SDK-2186) constructs theSSEClient, deciding viasseClient.hasCapability(SSECapability.requestHeaders)whether to set the credential in theAuthorizationheader or as theauthURL query parameter. The streaming source consumes whatever client it's handed.Verification
dart analyze lib testclean incommon_client/.melos run analyzeandmelos run testboth pass workspace-wide.Follow-up (out of scope)
FDv2StreamingBaseinto the SDK. This PR delivers the building blocks.