From 2a4e0fbaea5b8bc266b7af395fb97790551adca2 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Mon, 5 Oct 2026 12:25:31 -0400 Subject: [PATCH 1/4] feat(react-native): send $react_native_version with every event Reads Platform.constants.reactNativeVersion so we can see which React Native versions SDK users run before choosing support floors. --- .changeset/react-native-version-property.md | 5 +++ packages/react-native/src/native-deps.tsx | 7 ++- packages/react-native/src/types.ts | 2 + packages/react-native/src/utils.ts | 17 +++++++ .../test/react-native-version.spec.ts | 45 +++++++++++++++++++ 5 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 .changeset/react-native-version-property.md create mode 100644 packages/react-native/test/react-native-version.spec.ts diff --git a/.changeset/react-native-version-property.md b/.changeset/react-native-version-property.md new file mode 100644 index 0000000000..9672d929ec --- /dev/null +++ b/.changeset/react-native-version-property.md @@ -0,0 +1,5 @@ +--- +'posthog-react-native': minor +--- + +Add `$react_native_version` (for example `0.79.6`) to every captured event diff --git a/packages/react-native/src/native-deps.tsx b/packages/react-native/src/native-deps.tsx index 8cc1b4af34..67e295837c 100644 --- a/packages/react-native/src/native-deps.tsx +++ b/packages/react-native/src/native-deps.tsx @@ -1,5 +1,5 @@ import { OptionalAsyncStorage } from './optional/OptionalAsyncStorage' -import { GLOBAL_OBJ, getPlatformOS, isMacOS, isWeb, isWindows } from './utils' +import { GLOBAL_OBJ, getPlatformOS, getReactNativeVersion, isMacOS, isWeb, isWindows } from './utils' import { OptionalExpoApplication } from './optional/OptionalExpoApplication' import { OptionalExpoDevice } from './optional/OptionalExpoDevice' import { OptionalExpoFileSystem } from './optional/OptionalExpoFileSystem' @@ -39,6 +39,11 @@ export const getAppProperties = (): PostHogCustomAppProperties => { $device_type: currentDeviceType, } + const reactNativeVersion = getReactNativeVersion() + if (reactNativeVersion) { + properties.$react_native_version = reactNativeVersion + } + if (OptionalExpoApplication) { properties.$app_build = OptionalExpoApplication.nativeBuildVersion properties.$app_name = OptionalExpoApplication.applicationName diff --git a/packages/react-native/src/types.ts b/packages/react-native/src/types.ts index cc22cdcfe2..4029443153 100644 --- a/packages/react-native/src/types.ts +++ b/packages/react-native/src/types.ts @@ -154,6 +154,8 @@ export interface PostHogCustomAppProperties { $timezone?: string | null /** Whether the app is running on an emulator/simulator */ $is_emulator?: boolean | null + /** React Native version like "0.79.6" */ + $react_native_version?: string | null } export type PostHogSessionReplayConfig = { diff --git a/packages/react-native/src/utils.ts b/packages/react-native/src/utils.ts index acd597046f..43f7f5daa1 100644 --- a/packages/react-native/src/utils.ts +++ b/packages/react-native/src/utils.ts @@ -51,3 +51,20 @@ export function isWindows(): boolean { } export const isHermes = () => !!GLOBAL_OBJ.HermesInternal + +type ReactNativeVersion = { major: number; minor: number; patch: number; prerelease?: number | string | null } + +/** React Native version like "0.79.6" or "0.80.0-rc.2", or undefined where Platform.constants doesn't report it (e.g. web) */ +export function getReactNativeVersion(): string | undefined { + try { + const version = (Platform as { constants?: { reactNativeVersion?: ReactNativeVersion } }).constants + ?.reactNativeVersion + if (!version) { + return undefined + } + const { major, minor, patch, prerelease } = version + return `${major}.${minor}.${patch}${prerelease != null ? `-${prerelease}` : ''}` + } catch { + return undefined + } +} diff --git a/packages/react-native/test/react-native-version.spec.ts b/packages/react-native/test/react-native-version.spec.ts new file mode 100644 index 0000000000..d9416e9cf6 --- /dev/null +++ b/packages/react-native/test/react-native-version.spec.ts @@ -0,0 +1,45 @@ +import { Platform } from 'react-native' +import { PostHog } from '../src' +import { getReactNativeVersion } from '../src/utils' + +const platform = Platform as typeof Platform & { constants?: unknown } + +describe('$react_native_version', () => { + afterEach(() => { + delete platform.constants + }) + + it('formats Platform.constants.reactNativeVersion', () => { + platform.constants = { reactNativeVersion: { major: 0, minor: 79, patch: 6, prerelease: null } } + expect(getReactNativeVersion()).toBe('0.79.6') + }) + + it('appends the prerelease tag', () => { + platform.constants = { reactNativeVersion: { major: 0, minor: 80, patch: 0, prerelease: 'rc.2' } } + expect(getReactNativeVersion()).toBe('0.80.0-rc.2') + }) + + it('is undefined when the platform does not report a version', () => { + platform.constants = {} + expect(getReactNativeVersion()).toBeUndefined() + }) + + it('is sent with every event', async () => { + platform.constants = { reactNativeVersion: { major: 0, minor: 79, patch: 6 } } + const posthog = new PostHog('test-token', { flushInterval: 0 }) + await posthog.ready() + + expect(posthog.getCommonEventProperties().$react_native_version).toBe('0.79.6') + + await posthog.shutdown() + }) + + it('is omitted when the platform does not report a version', async () => { + const posthog = new PostHog('test-token', { flushInterval: 0 }) + await posthog.ready() + + expect(posthog.getCommonEventProperties()).not.toHaveProperty('$react_native_version') + + await posthog.shutdown() + }) +}) From 75578fbdfba7ce1845911def414bf8430c979a61 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Mon, 5 Oct 2026 12:32:21 -0400 Subject: [PATCH 2/4] chore(react-native): regenerate references and scope $react_native_version to JS events --- .changeset/react-native-version-property.md | 2 +- .../references/posthog-react-native-references-latest.json | 5 +++++ packages/react-native/src/types.ts | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.changeset/react-native-version-property.md b/.changeset/react-native-version-property.md index 9672d929ec..4354e05674 100644 --- a/.changeset/react-native-version-property.md +++ b/.changeset/react-native-version-property.md @@ -2,4 +2,4 @@ 'posthog-react-native': minor --- -Add `$react_native_version` (for example `0.79.6`) to every captured event +Add `$react_native_version` (for example `0.79.6`) to events captured from JavaScript diff --git a/packages/react-native/references/posthog-react-native-references-latest.json b/packages/react-native/references/posthog-react-native-references-latest.json index 24bb425336..39aac79625 100644 --- a/packages/react-native/references/posthog-react-native-references-latest.json +++ b/packages/react-native/references/posthog-react-native-references-latest.json @@ -3347,6 +3347,11 @@ "type": "string | null", "name": "$os_version" }, + { + "description": "React Native version like \"0.79.6\". Set on events captured from JavaScript, not on events the native SDKs capture", + "type": "string | null", + "name": "$react_native_version" + }, { "description": "Timezone of the device like \"Europe/Berlin\"", "type": "string | null", diff --git a/packages/react-native/src/types.ts b/packages/react-native/src/types.ts index 4029443153..fe5dbe266e 100644 --- a/packages/react-native/src/types.ts +++ b/packages/react-native/src/types.ts @@ -154,7 +154,7 @@ export interface PostHogCustomAppProperties { $timezone?: string | null /** Whether the app is running on an emulator/simulator */ $is_emulator?: boolean | null - /** React Native version like "0.79.6" */ + /** React Native version like "0.79.6". Set on events captured from JavaScript, not on events the native SDKs capture */ $react_native_version?: string | null } From 0c2f1eac9539a0be84c2044cc663b0568c1d991d Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Mon, 5 Oct 2026 12:35:33 -0400 Subject: [PATCH 3/4] fix(react-native): ignore malformed RN version constants and cover the throwing path --- .../posthog-react-native-references-latest.json | 2 +- packages/react-native/src/types.ts | 2 +- packages/react-native/src/utils.ts | 2 +- .../test/react-native-version.spec.ts | 15 +++++++++++++++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/react-native/references/posthog-react-native-references-latest.json b/packages/react-native/references/posthog-react-native-references-latest.json index 39aac79625..8ec178bd13 100644 --- a/packages/react-native/references/posthog-react-native-references-latest.json +++ b/packages/react-native/references/posthog-react-native-references-latest.json @@ -3348,7 +3348,7 @@ "name": "$os_version" }, { - "description": "React Native version like \"0.79.6\". Set on events captured from JavaScript, not on events the native SDKs capture", + "description": "React Native version like \"0.79.6\"", "type": "string | null", "name": "$react_native_version" }, diff --git a/packages/react-native/src/types.ts b/packages/react-native/src/types.ts index fe5dbe266e..4029443153 100644 --- a/packages/react-native/src/types.ts +++ b/packages/react-native/src/types.ts @@ -154,7 +154,7 @@ export interface PostHogCustomAppProperties { $timezone?: string | null /** Whether the app is running on an emulator/simulator */ $is_emulator?: boolean | null - /** React Native version like "0.79.6". Set on events captured from JavaScript, not on events the native SDKs capture */ + /** React Native version like "0.79.6" */ $react_native_version?: string | null } diff --git a/packages/react-native/src/utils.ts b/packages/react-native/src/utils.ts index 43f7f5daa1..91e071f107 100644 --- a/packages/react-native/src/utils.ts +++ b/packages/react-native/src/utils.ts @@ -59,7 +59,7 @@ export function getReactNativeVersion(): string | undefined { try { const version = (Platform as { constants?: { reactNativeVersion?: ReactNativeVersion } }).constants ?.reactNativeVersion - if (!version) { + if (!version || typeof version.major !== 'number') { return undefined } const { major, minor, patch, prerelease } = version diff --git a/packages/react-native/test/react-native-version.spec.ts b/packages/react-native/test/react-native-version.spec.ts index d9416e9cf6..ab4b81b1d9 100644 --- a/packages/react-native/test/react-native-version.spec.ts +++ b/packages/react-native/test/react-native-version.spec.ts @@ -24,6 +24,21 @@ describe('$react_native_version', () => { expect(getReactNativeVersion()).toBeUndefined() }) + it('is undefined when the version object has no numeric fields', () => { + platform.constants = { reactNativeVersion: {} } + expect(getReactNativeVersion()).toBeUndefined() + }) + + it('is undefined when reading the constants throws', () => { + Object.defineProperty(platform, 'constants', { + configurable: true, + get: () => { + throw new Error('getConstants failed') + }, + }) + expect(getReactNativeVersion()).toBeUndefined() + }) + it('is sent with every event', async () => { platform.constants = { reactNativeVersion: { major: 0, minor: 79, patch: 6 } } const posthog = new PostHog('test-token', { flushInterval: 0 }) From f2c8870f6dd4e2d18aa2b6898fc421e1cd710067 Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Mon, 5 Oct 2026 12:50:39 -0400 Subject: [PATCH 4/4] fix(core): keep $react_native_version on minimal $feature_flag_called events --- .changeset/core-flag-called-react-native-version.md | 5 +++++ packages/core/src/featureFlagUtils.ts | 2 ++ packages/react-native/test/react-native-version.spec.ts | 7 +++++++ 3 files changed, 14 insertions(+) create mode 100644 .changeset/core-flag-called-react-native-version.md diff --git a/.changeset/core-flag-called-react-native-version.md b/.changeset/core-flag-called-react-native-version.md new file mode 100644 index 0000000000..2e6897b160 --- /dev/null +++ b/.changeset/core-flag-called-react-native-version.md @@ -0,0 +1,5 @@ +--- +'@posthog/core': patch +--- + +Keep `$react_native_version` on minimal `$feature_flag_called` events diff --git a/packages/core/src/featureFlagUtils.ts b/packages/core/src/featureFlagUtils.ts index ef0f4bf5eb..2e3fa33f92 100644 --- a/packages/core/src/featureFlagUtils.ts +++ b/packages/core/src/featureFlagUtils.ts @@ -252,6 +252,8 @@ export const MINIMAL_FLAG_CALLED_EVENT_PROPERTIES: readonly string[] = [ '$lib_version', '$device_id', '$is_server', + // Runtime identity, kept for runtime breakdowns of flag calls + '$react_native_version', ] /** diff --git a/packages/react-native/test/react-native-version.spec.ts b/packages/react-native/test/react-native-version.spec.ts index ab4b81b1d9..756be09e8f 100644 --- a/packages/react-native/test/react-native-version.spec.ts +++ b/packages/react-native/test/react-native-version.spec.ts @@ -1,4 +1,5 @@ import { Platform } from 'react-native' +import { minimizeFlagCalledEventProperties } from '@posthog/core' import { PostHog } from '../src' import { getReactNativeVersion } from '../src/utils' @@ -49,6 +50,12 @@ describe('$react_native_version', () => { await posthog.shutdown() }) + it('survives minimal $feature_flag_called events', () => { + expect(minimizeFlagCalledEventProperties({ $react_native_version: '0.79.6', custom: 'dropped' })).toEqual({ + $react_native_version: '0.79.6', + }) + }) + it('is omitted when the platform does not report a version', async () => { const posthog = new PostHog('test-token', { flushInterval: 0 }) await posthog.ready()