@posthog/react-native-plugin 2.12.0 → 2.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -91,7 +91,7 @@ dependencies {
91
91
  //noinspection GradleDynamicVersion
92
92
  implementation "com.facebook.react:react-native:+"
93
93
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
94
- implementation "com.posthog:posthog-android:3.71.1"
94
+ implementation "com.posthog:posthog-android:3.71.2"
95
95
 
96
96
  testImplementation "junit:junit:4.13.2"
97
97
  testImplementation "org.json:json:20250517"
@@ -17,6 +17,7 @@ import com.facebook.react.common.JavascriptException
17
17
  import com.facebook.react.modules.core.DeviceEventManagerModule
18
18
  import com.posthog.PostHog
19
19
  import com.posthog.PostHogConfig
20
+ import com.posthog.PostHogEvent
20
21
  import com.posthog.android.PostHogAndroid
21
22
  import com.posthog.android.PostHogAndroidConfig
22
23
  import com.posthog.android.replay.PostHogReplayIntegration
@@ -171,6 +172,7 @@ class PosthogReactNativePluginModule(
171
172
  // React Native rethrows fatal JS errors natively as JavascriptException.
172
173
  // The JS layer already captured them, so drop the native duplicate.
173
174
  errorTrackingConfig.ignoredExceptionTypes.add(JavascriptException::class.java)
175
+ addBeforeSend { restoreJsFatalCaptureProperties(it) }
174
176
 
175
177
  // Always apply the session replay configuration so that recording started later
176
178
  // (e.g. startRecording or a linked feature flag) uses the right mode and masking;
@@ -456,12 +458,14 @@ class PosthogReactNativePluginModule(
456
458
  // ReadableMap.toHashMap() is HashMap<String, Any?>; the native API takes Map<String, Any>?.
457
459
  @Suppress("UNCHECKED_CAST")
458
460
  val nativeProperties = properties.toHashMap() as Map<String, Any>
459
- PostHog.capture(
460
- event = "\$exception",
461
- distinctId = distinctId.takeIf { it.isNotEmpty() },
462
- properties = nativeProperties,
463
- timestamp = parsedTimestamp,
464
- )
461
+ withJsFatalCaptureProperties(nativeProperties) {
462
+ PostHog.capture(
463
+ event = "\$exception",
464
+ distinctId = distinctId.takeIf { it.isNotEmpty() },
465
+ properties = nativeProperties,
466
+ timestamp = parsedTimestamp,
467
+ )
468
+ }
465
469
  promise.resolve(null)
466
470
  } catch (e: Throwable) {
467
471
  logError("captureFatalException", e)
@@ -722,6 +726,49 @@ class PosthogReactNativePluginModule(
722
726
  }
723
727
  }
724
728
 
729
+ // posthog-android computes these from its own state, which JS never syncs (the app's
730
+ // `personProfiles` mode, identify, person processing started by `group()` and friends), and
731
+ // newer versions no longer let caller properties override them. JS decided them for its fatal
732
+ // crash, so its values stand.
733
+ private val JS_OWNED_FATAL_CAPTURE_KEYS = listOf("\$process_person_profile", "\$is_identified")
734
+
735
+ // Set only while the JS layer's own fatal capture runs; posthog-android runs `beforeSend`
736
+ // on the calling thread, so nothing else it emits sees these properties.
737
+ private val jsFatalCaptureProperties = ThreadLocal<Map<String, Any>?>()
738
+
739
+ internal fun <T> withJsFatalCaptureProperties(
740
+ properties: Map<String, Any>,
741
+ block: () -> T,
742
+ ): T {
743
+ jsFatalCaptureProperties.set(properties)
744
+ try {
745
+ return block()
746
+ } finally {
747
+ jsFatalCaptureProperties.remove()
748
+ }
749
+ }
750
+
751
+ internal fun restoreJsFatalCaptureProperties(event: PostHogEvent): PostHogEvent {
752
+ val jsProperties = jsFatalCaptureProperties.get()
753
+ if (event.event != "\$exception" || jsProperties == null) {
754
+ return event
755
+ }
756
+ for (key in JS_OWNED_FATAL_CAPTURE_KEYS) {
757
+ if (!jsProperties.containsKey(key)) {
758
+ continue
759
+ }
760
+ // The JS map can hold nulls despite its type (`toHashMap()`); a null is omitted on the
761
+ // wire, so drop native's value rather than send it.
762
+ val value: Any? = jsProperties[key]
763
+ if (value == null) {
764
+ event.properties?.remove(key)
765
+ } else {
766
+ event.properties?.put(key, value)
767
+ }
768
+ }
769
+ return event
770
+ }
771
+
725
772
  // The JS layer sends an ISO-8601 UTC timestamp (`Date#toISOString`). Parse it explicitly
726
773
  // rather than trusting a locale-dependent default, and treat anything unparseable as a
727
774
  // caller error: silently substituting "now" would attribute the crash to the wrong instant.
@@ -274,9 +274,9 @@ public class PosthogReactNativePlugin: RCTEventEmitter {
274
274
  // `buildProperties` keeps its own value on a key clash (`{ current, _ in
275
275
  // current }`), so native's enrichment silently wins over what JS decided —
276
276
  // `$process_person_profile` from `personProfiles: 'never'`, or any value the
277
- // app's `before_send` rewrote. Put the JS-approved values back. Android has
278
- // the opposite precedence (`putAll` after its own context), so this keeps the
279
- // two platforms agreeing.
277
+ // app's `before_send` rewrote. Put the JS-approved values back. On Android,
278
+ // caller properties already win apart from SDK debug properties, so the
279
+ // module there only restores `$process_person_profile` and `$is_identified`.
280
280
  for (key, value) in jsProperties {
281
281
  event.properties[key] = value
282
282
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/react-native-plugin",
3
- "version": "2.12.0",
3
+ "version": "2.12.2",
4
4
  "description": "PostHog React Native plugin for iOS and Android integrations",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/commonjs/index.js",