@txlemetry/react-native-plugin 2.5.1

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.
package/src/index.tsx ADDED
@@ -0,0 +1,238 @@
1
+ import { NativeEventEmitter, NativeModules, Platform } from 'react-native'
2
+ import type { EmitterSubscription } from 'react-native'
3
+
4
+ const LINKING_ERROR =
5
+ `The package '@txlemetry/react-native-plugin' doesn't seem to be linked. Make sure: \n\n` +
6
+ Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
7
+ '- You rebuilt the app after installing the package\n' +
8
+ '- You are not using Expo Go\n'
9
+
10
+ const nativeModule = NativeModules.TxlemetryReactNativePlugin || NativeModules.PosthogReactNativePlugin
11
+ ? NativeModules.TxlemetryReactNativePlugin || NativeModules.PosthogReactNativePlugin
12
+ : new Proxy(
13
+ {},
14
+ {
15
+ get() {
16
+ throw new Error(LINKING_ERROR)
17
+ },
18
+ }
19
+ )
20
+
21
+ export type TxlemetryReactNativePluginMap = { [key: string]: any }
22
+
23
+ export interface TxlemetryReactNativePluginSessionReplayConfig {
24
+ enabled?: boolean
25
+ sdkReplayConfig?: TxlemetryReactNativePluginMap
26
+ decideReplayConfig?: TxlemetryReactNativePluginMap
27
+ }
28
+
29
+ export interface TxlemetryReactNativePluginExceptionStepsConfig {
30
+ enabled?: boolean
31
+ maxBytes?: number
32
+ }
33
+
34
+ export interface TxlemetryReactNativePluginErrorTrackingConfig {
35
+ nativeAutocapture?: boolean
36
+ exceptionSteps?: TxlemetryReactNativePluginExceptionStepsConfig
37
+ }
38
+
39
+ export interface TxlemetryReactNativePluginPushConfig {
40
+ capturePushNotificationSubscriptions?: boolean
41
+ capturePushNotificationOpened?: boolean
42
+ /**
43
+ * A provider callback can't cross the bridge, so this only tells native whether to
44
+ * install the bridging provider at all — installing one the host didn't ask for
45
+ * would change how the native SDK handles a 401 on the subscription call.
46
+ * Pair with {@link setPushIdentityProvider} before calling setup().
47
+ */
48
+ pushIdentityProviderEnabled?: boolean
49
+ }
50
+
51
+ export interface TxlemetryReactNativePluginConfig {
52
+ sessionReplay?: TxlemetryReactNativePluginSessionReplayConfig
53
+ errorTracking?: TxlemetryReactNativePluginErrorTrackingConfig
54
+ push?: TxlemetryReactNativePluginPushConfig
55
+ }
56
+
57
+ export function setup(
58
+ sessionId: string,
59
+ sdkOptions: TxlemetryReactNativePluginMap,
60
+ pluginConfig: TxlemetryReactNativePluginConfig = {}
61
+ ): Promise<void> {
62
+ return nativeModule.setup(sessionId, sdkOptions, pluginConfig)
63
+ }
64
+
65
+ export function start(
66
+ sessionId: string,
67
+ sdkOptions: TxlemetryReactNativePluginMap,
68
+ sdkReplayConfig: TxlemetryReactNativePluginMap,
69
+ decideReplayConfig: TxlemetryReactNativePluginMap
70
+ ): Promise<void> {
71
+ return nativeModule.start(sessionId, sdkOptions, sdkReplayConfig, decideReplayConfig)
72
+ }
73
+
74
+ export function startSession(sessionId: string): Promise<void> {
75
+ return nativeModule.startSession(sessionId)
76
+ }
77
+
78
+ export function endSession(): Promise<void> {
79
+ return nativeModule.endSession()
80
+ }
81
+
82
+ export function isEnabled(): Promise<boolean> {
83
+ return nativeModule.isEnabled()
84
+ }
85
+
86
+ export function identify(distinctId: string, anonymousId: string): Promise<void> {
87
+ return nativeModule.identify(distinctId, anonymousId)
88
+ }
89
+
90
+ /**
91
+ * Resets the native SDK's identity on logout. Unlike {@link identify}, this calls the native
92
+ * SDK's own `reset()`, which unregisters the logged-out user's push subscription and
93
+ * re-registers it under the new anonymous id.
94
+ */
95
+ export function reset(distinctId: string, anonymousId: string): Promise<void> {
96
+ return nativeModule.reset(distinctId, anonymousId)
97
+ }
98
+
99
+ export function startRecording(resumeCurrent: boolean): Promise<void> {
100
+ return nativeModule.startRecording(resumeCurrent)
101
+ }
102
+
103
+ export function stopRecording(): Promise<void> {
104
+ return nativeModule.stopRecording()
105
+ }
106
+
107
+ export function addExceptionStep(message: string, properties?: TxlemetryReactNativePluginMap): Promise<void> {
108
+ return nativeModule.addExceptionStep(message, properties ?? {})
109
+ }
110
+
111
+ export function registerPushNotificationToken(deviceToken: string, appId: string | null): Promise<void> {
112
+ return nativeModule.registerPushNotificationToken(deviceToken, appId)
113
+ }
114
+
115
+ export function unregisterPushNotificationToken(): Promise<void> {
116
+ return nativeModule.unregisterPushNotificationToken()
117
+ }
118
+
119
+ /**
120
+ * Propagates a runtime consent change to the native SDK, which otherwise only reads the JS
121
+ * opt-out flag at setup() and could auto-register a refreshed push token after optOut().
122
+ */
123
+ export function setOptOut(optOut: boolean): Promise<void> {
124
+ return nativeModule.setOptOut(optOut)
125
+ }
126
+
127
+ export function capturePushNotificationOpened(properties: TxlemetryReactNativePluginMap): Promise<void> {
128
+ return nativeModule.capturePushNotificationOpened(properties)
129
+ }
130
+
131
+ /**
132
+ * Mints a signed identity-verification token for a push subscription request.
133
+ * Return null to send the request without an identity token.
134
+ */
135
+ export type TxlemetryPushIdentityProvider = (distinctId: string, appId: string) => Promise<string | null>
136
+
137
+ const PUSH_IDENTITY_EVENT = 'PostHogPushIdentityRequest'
138
+
139
+ let pushIdentitySubscription: EmitterSubscription | undefined
140
+
141
+ /**
142
+ * Installs the JS side of the push identity-provider bridge. The native SDK asks for a
143
+ * token via the native bridge event; the reply is routed back with
144
+ * `providePushIdentityToken`, keyed by the request id so late replies are ignored.
145
+ *
146
+ * Install before setup() with `push.pushIdentityProviderEnabled` set, so the native
147
+ * config gets its bridging provider at SDK initialization. Any provider failure
148
+ * degrades to a null token — an unauthenticated request — never a stalled mint.
149
+ */
150
+ export function setPushIdentityProvider(provider: TxlemetryPushIdentityProvider): void {
151
+ pushIdentitySubscription?.remove()
152
+ // Via the proxy, not raw NativeModules: an unlinked module would build an emitter over
153
+ // undefined and throw a generic RN error inside the SDK's init try, taking replay down with it.
154
+ const emitter = new NativeEventEmitter(nativeModule)
155
+ pushIdentitySubscription = emitter.addListener(
156
+ PUSH_IDENTITY_EVENT,
157
+ async (request: { requestId: string; distinctId: string; appId: string }) => {
158
+ let token: string | null = null
159
+ try {
160
+ const minted = await provider(request.distinctId, request.appId)
161
+ token = typeof minted === 'string' ? minted : null
162
+ } catch (e) {
163
+ // eslint-disable-next-line no-console
164
+ console.warn(`[Txlemetry] pushIdentityProvider threw: ${e}. Push subscription will be sent unauthenticated.`)
165
+ }
166
+ try {
167
+ await nativeModule.providePushIdentityToken(request.requestId, token)
168
+ } catch (e) {
169
+ // eslint-disable-next-line no-console
170
+ console.warn(`[Txlemetry] Failed to deliver push identity token to native: ${e}`)
171
+ }
172
+ }
173
+ )
174
+ }
175
+
176
+ export interface TxlemetryReactNativePluginModule {
177
+ setup: (
178
+ sessionId: string,
179
+ sdkOptions: TxlemetryReactNativePluginMap,
180
+ pluginConfig?: TxlemetryReactNativePluginConfig
181
+ ) => Promise<void>
182
+
183
+ /**
184
+ * Legacy session replay setup entrypoint. Prefer setup() for new native features.
185
+ */
186
+ start: (
187
+ sessionId: string,
188
+ sdkOptions: TxlemetryReactNativePluginMap,
189
+ sdkReplayConfig: TxlemetryReactNativePluginMap,
190
+ decideReplayConfig: TxlemetryReactNativePluginMap
191
+ ) => Promise<void>
192
+
193
+ startSession: (sessionId: string) => Promise<void>
194
+
195
+ endSession: () => Promise<void>
196
+
197
+ isEnabled: () => Promise<boolean>
198
+
199
+ identify: (distinctId: string, anonymousId: string) => Promise<void>
200
+
201
+ reset: (distinctId: string, anonymousId: string) => Promise<void>
202
+
203
+ startRecording: (resumeCurrent: boolean) => Promise<void>
204
+
205
+ stopRecording: () => Promise<void>
206
+
207
+ addExceptionStep: (message: string, properties?: TxlemetryReactNativePluginMap) => Promise<void>
208
+
209
+ registerPushNotificationToken: (deviceToken: string, appId: string | null) => Promise<void>
210
+
211
+ unregisterPushNotificationToken: () => Promise<void>
212
+
213
+ setOptOut: (optOut: boolean) => Promise<void>
214
+
215
+ capturePushNotificationOpened: (properties: TxlemetryReactNativePluginMap) => Promise<void>
216
+
217
+ setPushIdentityProvider: (provider: TxlemetryPushIdentityProvider) => void
218
+ }
219
+
220
+ const TxlemetryReactNativePlugin: TxlemetryReactNativePluginModule = {
221
+ setup,
222
+ start,
223
+ startSession,
224
+ endSession,
225
+ isEnabled,
226
+ identify,
227
+ reset,
228
+ startRecording,
229
+ stopRecording,
230
+ addExceptionStep,
231
+ registerPushNotificationToken,
232
+ unregisterPushNotificationToken,
233
+ setOptOut,
234
+ capturePushNotificationOpened,
235
+ setPushIdentityProvider,
236
+ }
237
+
238
+ export default TxlemetryReactNativePlugin
@@ -0,0 +1,69 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+ folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
5
+
6
+ # Single source of truth for the native dependency version.
7
+ # Used by both the SPM and CocoaPods resolution paths below; bump this
8
+ # line when picking up a new native SDK release.
9
+ posthog_ios_version = '3.69.10'
10
+
11
+ Pod::Spec.new do |s|
12
+ s.name = "txlemetry-react-native-plugin"
13
+ s.version = package["version"]
14
+ s.summary = package["description"]
15
+ s.homepage = package["homepage"]
16
+ s.license = package["license"]
17
+ s.authors = package["author"]
18
+
19
+ s.platforms = { :ios => min_ios_version_supported, :osx => '10.15' }
20
+ s.source = { :git => "https://github.com/hectorvss/Txlemetry.git", :tag => "@txlemetry/react-native-plugin@#{s.version}" }
21
+
22
+ s.source_files = "ios/PosthogReactNativePlugin.{swift,h,hpp,m,mm,c,cpp}"
23
+
24
+ # Default: resolve the native SDK via CocoaPods trunk.
25
+ # Opt-in: set `txlemetry.useSpm` to `"true"` in the consumer's
26
+ # `ios/Podfile.properties.json` to resolve the native SDK via Swift Package
27
+ # Manager using the React Native `spm_dependency` helper (RN >= 0.75).
28
+ # The SPM path requires `use_frameworks! :linkage => :dynamic` in the Podfile.
29
+ podfile_properties_path = File.join(Pod::Config.instance.installation_root.to_s, 'Podfile.properties.json')
30
+ podfile_properties = File.exist?(podfile_properties_path) ? (JSON.parse(File.read(podfile_properties_path)) rescue {}) : {}
31
+ txlemetry_use_spm = podfile_properties['txlemetry.useSpm'].to_s == 'true' || podfile_properties['posthog.useSpm'].to_s == 'true'
32
+
33
+ if txlemetry_use_spm && respond_to?(:spm_dependency, true)
34
+ spm_dependency(s,
35
+ url: 'https://github.com/PostHog/posthog-ios.git',
36
+ requirement: { kind: 'upToNextMinorVersion', minimumVersion: posthog_ios_version },
37
+ products: ['PostHog']
38
+ )
39
+ else
40
+ s.dependency 'PostHog', "~> #{posthog_ios_version}"
41
+ end
42
+ s.ios.deployment_target = '13.0'
43
+ s.osx.deployment_target = '10.15'
44
+ s.swift_versions = "5.3"
45
+
46
+
47
+ # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
48
+ # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
49
+ if respond_to?(:install_modules_dependencies, true)
50
+ install_modules_dependencies(s)
51
+ else
52
+ s.dependency "React-Core"
53
+
54
+ # Don't install the dependencies when we run `pod install` in the old architecture.
55
+ if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
56
+ s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
57
+ s.pod_target_xcconfig = {
58
+ "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
59
+ "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
60
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
61
+ }
62
+ s.dependency "React-Codegen"
63
+ s.dependency "RCT-Folly"
64
+ s.dependency "RCTRequired"
65
+ s.dependency "RCTTypeSafety"
66
+ s.dependency "ReactCommon/turbomodule/core"
67
+ end
68
+ end
69
+ end