Skip to content

feat: [SDK-2121] implement session replay for react native android#456

Open
beekld wants to merge 12 commits intomainfrom
beeklimt/SDK-2121
Open

feat: [SDK-2121] implement session replay for react native android#456
beekld wants to merge 12 commits intomainfrom
beeklimt/SDK-2121

Conversation

@beekld
Copy link
Copy Markdown

@beekld beekld commented Apr 2, 2026

Summary

This is a fairly straightforward adaptation of the iOS version of the plugin.

How did you test this change?

There are some unit tests, but the primary test was by creating a react native test app, configuring it with my own LD key, and verifying that Android session replay worked the same as iOS session replay.

Are there any deployment considerations?

No.


Note

High Risk
Introduces Android session replay capture and initialization logic (including observability/OTel and replay start/stop), which is privacy- and data-collection-sensitive and could impact app runtime behavior on the main thread.

Overview
Adds Android support for session replay in @launchdarkly/react-native-ld-session-replay, replacing the previous Android “not supported” stubs with real configure, startSessionReplay, and stopSessionReplay implementations.

Introduces a SessionReplayClientAdapter that initializes an offline LDClient with the Observability and SessionReplay plugins, applies replay enable/disable on the main thread, and maps JS options (e.g., maskTextInputs, maskWebViews, maskLabels, maskImages, serviceName) to ReplayOptions/PrivacyProfile.

Updates the Android Gradle build to pull in LaunchDarkly Observability + Android SDK deps, adds JUnit5/MockK unit test support, and adds new unit tests for option mapping and pre-config start failure; also replaces the placeholder JS test with assertions that plugin registration calls native configure + startSessionReplay.

Reviewed by Cursor Bugbot for commit a6fc982. Bugbot is set up for automated code reviews on this repo. Configure here.


Related Jira issue: SDK-2121: Implement Session Replay for React Native Android.

@launchdarkly-upra launchdarkly-upra bot changed the title feat: implement session replay for react native android feat: [SDK-2121] implement session replay for react native android Apr 2, 2026
@beekld beekld marked this pull request as ready for review April 2, 2026 16:04
@beekld beekld requested a review from a team as a code owner April 2, 2026 16:04
mobileKey = mobileKey,
options = ObservabilityOptions(
serviceName = serviceName,
sessionBackgroundTimeout = 10.minutes,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be internal default.

For next PRs: SessionReplay inherit some settings from ObservabilityOptions:
resourceAttributes
sessionBackgroundTimeout
backendUrl

they should be transfered here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the hard-coded 10.minutes so it will use the internal default, and added a TODO to pass through the other observability options.


// The context key is a placeholder — the LDClient is offline and never sends it to
// LaunchDarkly servers. The React Native LDClient manages the real user context.
val context = LDContext.builder(ContextKind.DEFAULT, "12345").build()
Copy link
Copy Markdown
Contributor

@abelonogov-ld abelonogov-ld Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment is not completelly true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SessionReplay uses data from context to send data to server. Do you have other way of passing context currently implemented.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a little tricky. Unlike most of the other LD client SDKs, the React Native SDK does not get a context before plugins are registered. That is intended to be changed in the future, but there is not concrete timeline. However, I have added a hook so that when the context changes in JS, it will change here as well. I did this only in Android -- we can fix iOS later. So we have three options:

  1. Start SessionReplay here, and accept that it will initially have a placeholder value for the context.
  2. Wait to start SessionReplay until identify has been called. Would we lose replay data before that?
  3. Put this on hold until we refactor the LD RN SDK.

I went with option 1 for now. If you prefer 2, we could do it in this PR or a later one.

// calls lifecycle.addObserver() internally, which requires the main thread.
// Using timeout=0 so we don't block the UI — onPluginsReady() fires
// synchronously during LDClient.init() regardless of the flag-fetch timeout.
Handler(Looper.getMainLooper()).post {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why State Machine this logic exists here, I hope it wasn't here.

but if needed it has some race conditions.

  • currentState is checked outside lock
  • later state is being updated outside lock
  • state is being modified without @volatile

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Since the OTel APIs have to be called on the UI Thread anyway, it makes sense to just post these calls there and no state machine is needed.

I believe the locking behavior is now correct. It holds the lock just to grab local copies of the options, and then passes those through as captured variables in the posted lambdas.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can try to remove the iOS state machine in a future PR.

@beekld beekld requested a review from abelonogov-ld April 13, 2026 18:34
Copy link
Copy Markdown
Contributor

@abelonogov-ld abelonogov-ld left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Android launching looks good!

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 294a5d5. Configure here.

@beekld beekld enabled auto-merge (squash) April 13, 2026 22:43
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.

2 participants