revert(session-replay-react-native): remove SRMaskView / AmpMask / AmpUnmask stack#1885
Conversation
size-limit report 📦
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Invalidate races native setup
- Added invalidation flag with thread-safe checks in both iOS and Android to serialize setup and invalidate, preventing race conditions during Fast Refresh or bridge teardown.
Or push these changes by commenting:
@cursor push 9ce08b488f
Preview (9ce08b488f)
diff --git a/packages/session-replay-react-native/android/src/main/java/com/amplitude/sessionreplayreactnative/SessionReplayReactNativeModule.kt b/packages/session-replay-react-native/android/src/main/java/com/amplitude/sessionreplayreactnative/SessionReplayReactNativeModule.kt
--- a/packages/session-replay-react-native/android/src/main/java/com/amplitude/sessionreplayreactnative/SessionReplayReactNativeModule.kt
+++ b/packages/session-replay-react-native/android/src/main/java/com/amplitude/sessionreplayreactnative/SessionReplayReactNativeModule.kt
@@ -18,6 +18,7 @@
class SessionReplayReactNativeModule(private val reactContext: ReactApplicationContext) :
SessionReplayReactNativeSpec(reactContext) {
private lateinit var sessionReplay: SessionReplay
+ @Volatile private var invalidated = false
override fun getName(): String {
return NAME
@@ -82,6 +83,12 @@
privacyConfig = PrivacyConfig(maskLevel = maskLevel),
)
+ if (invalidated) {
+ sessionReplay.shutdown()
+ promise.reject("MODULE_INVALIDATED", "Module was invalidated during setup")
+ return
+ }
+
promise.resolve(null)
} catch (e: Exception) {
promise.reject("SETUP_ERROR", e.message, e)
@@ -177,6 +184,7 @@
}
override fun invalidate() {
+ invalidated = true
if (::sessionReplay.isInitialized) {
sessionReplay.shutdown()
}
diff --git a/packages/session-replay-react-native/ios/NativeSessionReplay.swift b/packages/session-replay-react-native/ios/NativeSessionReplay.swift
--- a/packages/session-replay-react-native/ios/NativeSessionReplay.swift
+++ b/packages/session-replay-react-native/ios/NativeSessionReplay.swift
@@ -10,13 +10,14 @@
var sessionReplay: SessionReplay!
var logger: CoreLogger!
+ private var invalidated = false
override init() {
print("NativeSessionReplay init")
}
@objc(setup:resolve:reject:)
- func setup(_ config: NSDictionary, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
+ func setup(_ config: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
guard let apiKey = config["apiKey"] as? String,
let sessionId = config["sessionId"] as? NSNumber,
let serverZone = config["serverZone"] as? String,
@@ -62,10 +63,17 @@
enableRemoteConfig: enableRemoteConfig
)
- if (autoStart) {
- sessionReplay.start()
+ DispatchQueue.main.async { [weak self] in
+ guard let self = self, !self.invalidated else {
+ reject("MODULE_INVALIDATED", "Module was invalidated during setup", nil)
+ return
+ }
+
+ if (autoStart) {
+ self.sessionReplay?.start()
+ }
+ resolve(nil)
}
- resolve(nil)
}
@objc(setSessionId:resolve:reject:)
@@ -128,6 +136,7 @@
@objc(invalidate)
func invalidate() {
print("invalidate")
+ invalidated = true
// could be nil here
sessionReplay?.stop()
sessionReplay = nilYou can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 12e2c16. Configure here.
There was a problem hiding this comment.
Pull request overview
Reverts the experimental layout-transparent masking stack from @amplitude/session-replay-react-native (Fabric SRMaskView, SRMaskingRegistry seam, and the public <AmpMask>/<AmpUnmask> API), returning the package to the legacy AmpMaskView-only approach.
Changes:
- Removes the public
AmpMask/AmpUnmaskAPI and associated JS/Fabric/native implementations. - Removes the masking registry/primitive seam and associated native tests/canaries.
- Simplifies codegen and build wiring back to TurboModule-only (
codegenConfig.type = "modules"), and updates docs/example accordingly.
Reviewed changes
Copilot reviewed 37 out of 39 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/session-replay-react-native/test/specs/SRMaskViewNativeComponent.test.ts | Removes codegen-component registration test for SRMaskView. |
| packages/session-replay-react-native/test/paper/MaskPaper.test.tsx | Removes old-arch fallback behavior tests for AmpMask/AmpUnmask. |
| packages/session-replay-react-native/test/Mask.types.test.ts | Removes tests for experimental mask prop/types exports. |
| packages/session-replay-react-native/test/index.arch.test.tsx | Removes tests for index new-arch vs old-arch AmpMask selection/export behavior. |
| packages/session-replay-react-native/test/fabric/MaskFabric.test.tsx | Removes Fabric masking component behavior tests and availability probing tests. |
| packages/session-replay-react-native/test/mocks/react-native.ts | Simplifies React Native Jest mock (removes UIManager mock). |
| packages/session-replay-react-native/src/specs/SRMaskViewNativeComponent.ts | Removes SRMaskView codegen native component spec. |
| packages/session-replay-react-native/src/paper/MaskPaper.tsx | Removes old-arch AmpMask/AmpUnmask guard + fallback implementation. |
| packages/session-replay-react-native/src/Mask.types.ts | Removes experimental mask/unmask prop types and helpers. |
| packages/session-replay-react-native/src/mask-fallback.tsx | Removes shared fallback/warn-once helpers for AmpMask/AmpUnmask. |
| packages/session-replay-react-native/src/index.tsx | Stops exporting AmpMask/AmpUnmask and related types; keeps AmpMaskView export. |
| packages/session-replay-react-native/src/fabric/MaskFabric.tsx | Removes Fabric implementation and fail-closed probing logic. |
| packages/session-replay-react-native/README.md | Removes documentation for experimental layout-transparent masking. |
| packages/session-replay-react-native/package.json | Switches codegenConfig back to TurboModule-only; updates lint/fix globs. |
| packages/session-replay-react-native/ios/SRMaskingRegistry.m | Removes iOS masking registry implementation. |
| packages/session-replay-react-native/ios/SRMaskingPrimitive.h | Removes iOS masking primitive protocol + registry header. |
| packages/session-replay-react-native/ios/SRDefaultMaskingPrimitive.swift | Removes iOS default masking primitive bridge. |
| packages/session-replay-react-native/ios/NativeSessionReplay.swift | Removes deferred main-thread setup/teardown sequencing tied to masking seam. |
| packages/session-replay-react-native/ios/fabric/SRMaskView.mm | Removes iOS Fabric SRMaskView host view implementation. |
| packages/session-replay-react-native/ios/fabric/SRMaskView.h | Removes iOS Fabric SRMaskView header. |
| packages/session-replay-react-native/example/yarn.lock | Updates example lockfile dependencies after removing masking demo stack. |
| packages/session-replay-react-native/example/package.json | Loosens react-native-screens version range in the example app. |
| packages/session-replay-react-native/example/ios/Podfile.lock | Updates pods resolution in example after dependency changes. |
| packages/session-replay-react-native/example/ios/exampleTests/SRMaskViewTests.mm | Removes iOS XCTest canaries for SRMaskView + masking seam. |
| packages/session-replay-react-native/example/ios/example.xcodeproj/project.pbxproj | Removes SRMaskViewTests.mm from Xcode test target. |
| packages/session-replay-react-native/example/App.tsx | Removes the Mask demo screen and AmpMask/AmpUnmask usage from example app. |
| packages/session-replay-react-native/cpp/SRMaskViewShadowNode.h | Removes C++ ShadowNode for display:contents SRMaskView. |
| packages/session-replay-react-native/cpp/SRMaskViewShadowNode.cpp | Removes C++ ShadowNode implementation details/workarounds. |
| packages/session-replay-react-native/cpp/SRMaskViewComponentDescriptor.h | Removes custom ComponentDescriptor binding for SRMaskView shadow node. |
| packages/session-replay-react-native/android/src/newarch/java/com/amplitude/sessionreplayreactnative/SessionReplayReactNativePackage.kt | Stops registering the SRMaskView view manager in new-arch package. |
| packages/session-replay-react-native/android/src/newarch/java/com/amplitude/sessionreplayreactnative/fabric/SRMaskViewPointerEvents.java | Removes cross-version pointerEvents shim (SRMaskView removed). |
| packages/session-replay-react-native/android/src/newarch/java/com/amplitude/sessionreplayreactnative/fabric/SRMaskViewManager.kt | Removes Fabric SRMaskView manager. |
| packages/session-replay-react-native/android/src/newarch/java/com/amplitude/sessionreplayreactnative/fabric/SRMaskView.kt | Removes Fabric SRMaskView host implementation. |
| packages/session-replay-react-native/android/src/main/java/com/amplitude/sessionreplayreactnative/SRMaskingPrimitive.kt | Removes Android masking primitive + registry seam. |
| packages/session-replay-react-native/android/src/main/java/com/amplitude/sessionreplayreactnative/SRDefaultMaskingPrimitive.kt | Removes Android default masking primitive bridge. |
| packages/session-replay-react-native/android/src/main/java/com/amplitude/sessionreplayreactnative/SessionReplayReactNativeModule.kt | Removes UI-thread setup serialization and primitive registration logic. |
| packages/session-replay-react-native/android/src/androidTest/java/com/amplitude/sessionreplayreactnative/SRMaskViewTest.kt | Removes Android instrumented tests for SRMaskView + masking seam. |
| packages/session-replay-react-native/android/build.gradle | Removes RN-floor gating/patch tasks and Android test/buildConfig wiring for SRMaskView. |
| packages/session-replay-react-native/AmplitudeSessionReplayReactNative.podspec | Removes Fabric gating and cpp/ios-fabric conditional compilation; ships iOS sources only. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


Summary
Reverts the display:contents masking stack from
@amplitude/session-replay-react-native:<AmpMask>/<AmpUnmask>API, SDKRN-33)Why
Customer diagnostics on RN 0.83.6 (2026-07-15) proved a rendering defect in the SRMaskView design: the display:contents shadow node always hands the host view zero-size layout metrics, and on Fabric-recycled component views a
(0,0,0,0)metrics frame is never applied to the UIKit view — so the widened frame (including origin) from the pointer's previous incarnation persists and masks render at stale screen positions (19/19 zero-metric recycle events in the customer trace kept stale frames; e.g. hosts stuck at x=227 that should be at x=0). The shadow tree stays correct, so JS-side measurement cannot detect it.A plain-layout-node Fabric mask component tested in the same customer run self-heals in the identical situations. That replacement (a Fabric-native
AmpMaskView) lands in a follow-up PR stacked on this one.Impact
<AmpMask>/<AmpUnmask>existed only in0.1.0-beta.2; the next publish removes them (changelog note included in the follow-up PR).9c901047) except version/CHANGELOG — verified viagit diff.pnpm test(49 passing),pnpm build,pnpm lintall green.🤖 Generated with Claude Code
Note
High Risk
Breaking removal of beta public APIs and large native/build changes; setup no longer serializes masking registration with capture start, which could reintroduce subtle init races.
Overview
Reverts the experimental layout-transparent masking stack (
<AmpMask>/<AmpUnmask>, FabricSRMaskView, C++display:contentsshadow nodes, and theSRMaskingRegistryseam) from@amplitude/session-replay-react-native, returning masking to the legacyAmpMaskViewwrapper only.The public package no longer exports
AmpMask/AmpUnmaskor Fabric component codegen (codegenConfiggoes fromtype: "all"tomodulesonly). iOS/Android builds drop Fabric-specific sources, RN ≥ 0.77 floor gates, and Android codegen CMake/header patching forSRMaskView. Nativesetup()/invalidate()no longer defer work on the UI/main thread to register masking primitives beforestart().README, example app Mask demo, and the associated unit/instrumented tests for the removed APIs are deleted; example lockfile bumps
react-native-screens.Reviewed by Cursor Bugbot for commit 12e2c16. Bugbot is set up for automated code reviews on this repo. Configure here.