@posthog/react-native-plugin 2.2.3 → 2.3.0
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/android/build.gradle +1 -1
- package/android/src/main/java/com/posthogreactnativeplugin/PosthogReactNativePluginModule.kt +299 -4
- package/ios/PosthogReactNativePlugin-Bridging-Header.h +1 -0
- package/ios/PosthogReactNativePlugin.mm +28 -1
- package/ios/PosthogReactNativePlugin.swift +168 -4
- package/lib/commonjs/index.js +79 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +74 -2
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +47 -0
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +47 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/package.json +1 -2
- package/posthog-react-native-plugin.podspec +1 -1
- package/src/index.tsx +107 -1
package/android/build.gradle
CHANGED
|
@@ -85,6 +85,6 @@ dependencies {
|
|
|
85
85
|
//noinspection GradleDynamicVersion
|
|
86
86
|
implementation "com.facebook.react:react-native:+"
|
|
87
87
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
88
|
-
implementation "com.posthog:posthog-android:3.
|
|
88
|
+
implementation "com.posthog:posthog-android:3.58.0"
|
|
89
89
|
}
|
|
90
90
|
|
package/android/src/main/java/com/posthogreactnativeplugin/PosthogReactNativePluginModule.kt
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
package com.posthogreactnativeplugin
|
|
2
2
|
|
|
3
|
+
import android.content.Intent
|
|
3
4
|
import android.util.Log
|
|
5
|
+
import com.facebook.react.bridge.Arguments
|
|
4
6
|
import com.facebook.react.bridge.Promise
|
|
5
7
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
8
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
@@ -8,6 +10,7 @@ import com.facebook.react.bridge.ReactMethod
|
|
|
8
10
|
import com.facebook.react.bridge.ReadableMap
|
|
9
11
|
import com.facebook.react.bridge.UiThreadUtil
|
|
10
12
|
import com.facebook.react.common.JavascriptException
|
|
13
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
11
14
|
import com.posthog.PostHog
|
|
12
15
|
import com.posthog.PostHogConfig
|
|
13
16
|
import com.posthog.android.PostHogAndroid
|
|
@@ -42,6 +45,7 @@ class PosthogReactNativePluginModule(
|
|
|
42
45
|
decideReplayConfig = getMap(sessionReplayConfig, "decideReplayConfig"),
|
|
43
46
|
nativeErrorTrackingAutocapture = getBoolean(errorTrackingConfig, "nativeAutocapture", false),
|
|
44
47
|
exceptionStepsConfig = getMap(errorTrackingConfig, "exceptionSteps"),
|
|
48
|
+
pushConfig = getMap(pluginConfig, "push"),
|
|
45
49
|
promise = promise,
|
|
46
50
|
)
|
|
47
51
|
}
|
|
@@ -63,6 +67,7 @@ class PosthogReactNativePluginModule(
|
|
|
63
67
|
decideReplayConfig = decideReplayConfig,
|
|
64
68
|
nativeErrorTrackingAutocapture = false,
|
|
65
69
|
exceptionStepsConfig = null,
|
|
70
|
+
pushConfig = null,
|
|
66
71
|
promise = promise,
|
|
67
72
|
)
|
|
68
73
|
}
|
|
@@ -76,6 +81,7 @@ class PosthogReactNativePluginModule(
|
|
|
76
81
|
decideReplayConfig: ReadableMap?,
|
|
77
82
|
nativeErrorTrackingAutocapture: Boolean,
|
|
78
83
|
exceptionStepsConfig: ReadableMap?,
|
|
84
|
+
pushConfig: ReadableMap?,
|
|
79
85
|
promise: Promise,
|
|
80
86
|
) {
|
|
81
87
|
val initRunnable =
|
|
@@ -92,17 +98,23 @@ class PosthogReactNativePluginModule(
|
|
|
92
98
|
val anonymousId = getString(sdkOptions, "anonymousId", "")
|
|
93
99
|
val theSdkVersion = getString(sdkOptions, "sdkVersion", "")
|
|
94
100
|
val theFlushAt = getInt(sdkOptions, "flushAt", DEFAULT_FLUSH_AT)
|
|
101
|
+
val theOptOut = getBoolean(sdkOptions, "optOut", false)
|
|
102
|
+
// Default true: an older JS layer that never sends this keeps native's own fetch.
|
|
103
|
+
val thePreloadFeatureFlags = getBoolean(sdkOptions, "preloadFeatureFlags", true)
|
|
95
104
|
|
|
96
105
|
// Forward custom headers (e.g. Authorization for a reverse proxy) so the native SDK
|
|
97
106
|
// attaches them to the requests it sends directly (session replay, crash uploads).
|
|
98
107
|
val theRequestHeaders =
|
|
99
|
-
getMap(sdkOptions, "requestHeaders")
|
|
108
|
+
getMap(sdkOptions, "requestHeaders")
|
|
109
|
+
?.toHashMap()
|
|
100
110
|
?.filterValues { it is String }
|
|
101
111
|
?.mapValues { it.value as String }
|
|
102
112
|
|
|
103
113
|
val config =
|
|
104
114
|
PostHogAndroidConfig(apiKey, host).apply {
|
|
105
115
|
debug = debugValue
|
|
116
|
+
optOut = theOptOut
|
|
117
|
+
preloadFeatureFlags = thePreloadFeatureFlags
|
|
106
118
|
captureDeepLinks = false
|
|
107
119
|
captureApplicationLifecycleEvents = false
|
|
108
120
|
captureScreenViews = false
|
|
@@ -112,8 +124,10 @@ class PosthogReactNativePluginModule(
|
|
|
112
124
|
|
|
113
125
|
// Keep the native exception-steps buffer aligned with the JS layer (one logical buffer).
|
|
114
126
|
// Absent keys fall back to the native defaults the helpers receive.
|
|
115
|
-
errorTrackingConfig.exceptionSteps.enabled =
|
|
116
|
-
|
|
127
|
+
errorTrackingConfig.exceptionSteps.enabled =
|
|
128
|
+
getBoolean(exceptionStepsConfig, "enabled", errorTrackingConfig.exceptionSteps.enabled)
|
|
129
|
+
errorTrackingConfig.exceptionSteps.maxBytes =
|
|
130
|
+
getInt(exceptionStepsConfig, "maxBytes", errorTrackingConfig.exceptionSteps.maxBytes)
|
|
117
131
|
|
|
118
132
|
// React Native rethrows fatal JS errors natively as JavascriptException.
|
|
119
133
|
// The JS layer already captured them, so drop the native duplicate.
|
|
@@ -130,7 +144,10 @@ class PosthogReactNativePluginModule(
|
|
|
130
144
|
val throttleDelayMs =
|
|
131
145
|
when {
|
|
132
146
|
hasKey(sdkReplayConfig, "throttleDelayMs") -> getInt(sdkReplayConfig, "throttleDelayMs", DEFAULT_THROTTLE_DELAY_MS)
|
|
133
|
-
hasKey(
|
|
147
|
+
hasKey(
|
|
148
|
+
sdkReplayConfig,
|
|
149
|
+
"androidDebouncerDelayMs",
|
|
150
|
+
) -> getInt(sdkReplayConfig, "androidDebouncerDelayMs", DEFAULT_THROTTLE_DELAY_MS)
|
|
134
151
|
else -> DEFAULT_THROTTLE_DELAY_MS
|
|
135
152
|
}
|
|
136
153
|
|
|
@@ -147,6 +164,25 @@ class PosthogReactNativePluginModule(
|
|
|
147
164
|
snapshotEndpoint = endpoint
|
|
148
165
|
}
|
|
149
166
|
|
|
167
|
+
// Only set when present: the legacy start() path predates push, and there the
|
|
168
|
+
// native defaults (both true) must win, matching posthog-android on its own.
|
|
169
|
+
if (hasKey(pushConfig, "capturePushNotificationSubscriptions")) {
|
|
170
|
+
capturePushNotificationSubscriptions =
|
|
171
|
+
getBoolean(pushConfig, "capturePushNotificationSubscriptions", true)
|
|
172
|
+
}
|
|
173
|
+
if (hasKey(pushConfig, "capturePushNotificationOpened")) {
|
|
174
|
+
capturePushNotificationOpened = getBoolean(pushConfig, "capturePushNotificationOpened", true)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Installed only when JS asked for it: an uninvited bridging provider would
|
|
178
|
+
// change how the native SDK handles a 401 on the subscription call.
|
|
179
|
+
if (getBoolean(pushConfig, "pushIdentityProviderEnabled", false)) {
|
|
180
|
+
pushModule = this@PosthogReactNativePluginModule
|
|
181
|
+
pushIdentityProvider = { distinctId, appId, completion ->
|
|
182
|
+
requestPushIdentityToken(distinctId, appId, completion)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
150
186
|
if (theSdkVersion.isNotEmpty()) {
|
|
151
187
|
sdkName = "posthog-react-native"
|
|
152
188
|
sdkVersion = theSdkVersion
|
|
@@ -155,6 +191,8 @@ class PosthogReactNativePluginModule(
|
|
|
155
191
|
PostHogAndroid.setup(context, config)
|
|
156
192
|
|
|
157
193
|
setIdentify(config.cachePreferences, distinctId, anonymousId)
|
|
194
|
+
|
|
195
|
+
captureColdStartPushOpenIfNeeded(config)
|
|
158
196
|
} catch (e: Throwable) {
|
|
159
197
|
logError(method, e)
|
|
160
198
|
} finally {
|
|
@@ -222,6 +260,50 @@ class PosthogReactNativePluginModule(
|
|
|
222
260
|
}
|
|
223
261
|
}
|
|
224
262
|
|
|
263
|
+
// Calls the native SDK rather than writing preferences like identify() does: reset() is what
|
|
264
|
+
// unregisters the logged-out user's push subscription and re-registers under the new identity.
|
|
265
|
+
@ReactMethod
|
|
266
|
+
fun reset(
|
|
267
|
+
distinctId: String,
|
|
268
|
+
anonymousId: String,
|
|
269
|
+
promise: Promise,
|
|
270
|
+
) {
|
|
271
|
+
try {
|
|
272
|
+
PostHog.reset()
|
|
273
|
+
// Native reset() mints its own anonymous id; overwrite it with the JS one so the two
|
|
274
|
+
// SDKs stay on the same identity. Must run after reset(), which needs the pre-reset
|
|
275
|
+
// distinctId to know which subscription to unregister. Known gap (as on iOS): native's
|
|
276
|
+
// async push re-registration can read the identity before this write lands and register
|
|
277
|
+
// under a throwaway id; retryPending() converges it on the next flush.
|
|
278
|
+
setIdentify(PostHog.getConfig<PostHogConfig>()?.cachePreferences, distinctId, anonymousId)
|
|
279
|
+
} catch (e: Throwable) {
|
|
280
|
+
logError("reset", e)
|
|
281
|
+
} finally {
|
|
282
|
+
promise.resolve(null)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Runtime consent changes must reach native: it persists its own opt-out flag and only reads
|
|
287
|
+
// the JS value at setup(), so a refreshed FCM token could otherwise auto-register after the
|
|
288
|
+
// user opted out. optIn() also resumes deferred push work on the next flush.
|
|
289
|
+
@ReactMethod
|
|
290
|
+
fun setOptOut(
|
|
291
|
+
optOut: Boolean,
|
|
292
|
+
promise: Promise,
|
|
293
|
+
) {
|
|
294
|
+
try {
|
|
295
|
+
if (optOut) {
|
|
296
|
+
PostHog.optOut()
|
|
297
|
+
} else {
|
|
298
|
+
PostHog.optIn()
|
|
299
|
+
}
|
|
300
|
+
} catch (e: Throwable) {
|
|
301
|
+
logError("setOptOut", e)
|
|
302
|
+
} finally {
|
|
303
|
+
promise.resolve(null)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
225
307
|
private fun setIdentify(
|
|
226
308
|
cachePreferences: PostHogPreferences?,
|
|
227
309
|
distinctId: String,
|
|
@@ -327,15 +409,228 @@ class PosthogReactNativePluginModule(
|
|
|
327
409
|
Log.println(Log.ERROR, POSTHOG_TAG, "Method $method, error: $error")
|
|
328
410
|
}
|
|
329
411
|
|
|
412
|
+
// These reject instead of this module's usual swallow-and-resolve convention: a failed
|
|
413
|
+
// registration is a distinct signal, not a silent success. The JS layer's public methods
|
|
414
|
+
// never throw, so the rejection is what gives it something to log.
|
|
415
|
+
@ReactMethod
|
|
416
|
+
fun registerPushNotificationToken(
|
|
417
|
+
deviceToken: String?,
|
|
418
|
+
appId: String?,
|
|
419
|
+
promise: Promise,
|
|
420
|
+
) {
|
|
421
|
+
try {
|
|
422
|
+
if (deviceToken.isNullOrBlank()) {
|
|
423
|
+
// A blank token is dropped silently by the native SDK, so surface it here
|
|
424
|
+
// instead of reporting false success.
|
|
425
|
+
promise.reject(
|
|
426
|
+
PUSH_ERROR_CODE,
|
|
427
|
+
"registerPushNotificationToken: deviceToken is blank; token not registered.",
|
|
428
|
+
)
|
|
429
|
+
return
|
|
430
|
+
}
|
|
431
|
+
val resolvedAppId = appId?.trim()?.takeIf { it.isNotEmpty() } ?: firebaseProjectId
|
|
432
|
+
if (resolvedAppId.isNullOrEmpty()) {
|
|
433
|
+
val message =
|
|
434
|
+
"registerPushNotificationToken: no appId provided and no Firebase project id " +
|
|
435
|
+
"could be resolved, skipping. Pass appId explicitly if this app does not use Firebase."
|
|
436
|
+
Log.w(POSTHOG_TAG, message)
|
|
437
|
+
promise.reject(PUSH_ERROR_CODE, message)
|
|
438
|
+
return
|
|
439
|
+
}
|
|
440
|
+
PostHog.registerPushNotificationToken(deviceToken, resolvedAppId)
|
|
441
|
+
promise.resolve(null)
|
|
442
|
+
} catch (e: Throwable) {
|
|
443
|
+
logError("registerPushNotificationToken", e)
|
|
444
|
+
promise.reject(PUSH_ERROR_CODE, e)
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
@ReactMethod
|
|
449
|
+
fun unregisterPushNotificationToken(promise: Promise) {
|
|
450
|
+
try {
|
|
451
|
+
PostHog.unregisterPushNotificationToken()
|
|
452
|
+
promise.resolve(null)
|
|
453
|
+
} catch (e: Throwable) {
|
|
454
|
+
logError("unregisterPushNotificationToken", e)
|
|
455
|
+
promise.reject(PUSH_ERROR_CODE, e)
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
@ReactMethod
|
|
460
|
+
fun capturePushNotificationOpened(
|
|
461
|
+
properties: ReadableMap,
|
|
462
|
+
promise: Promise,
|
|
463
|
+
) {
|
|
464
|
+
try {
|
|
465
|
+
// subtitle is iOS-only; posthog-android's capturePushNotificationOpened has no
|
|
466
|
+
// subtitle parameter, so JS's value is dropped here.
|
|
467
|
+
val title = if (hasKey(properties, "title")) properties.getString("title") else null
|
|
468
|
+
val body = if (hasKey(properties, "body")) properties.getString("body") else null
|
|
469
|
+
val payload = if (hasKey(properties, "payload")) properties.getMap("payload")?.toHashMap() else null
|
|
470
|
+
val action = if (hasKey(properties, "action")) properties.getString("action") else null
|
|
471
|
+
PostHog.capturePushNotificationOpened(title, body, payload, action)
|
|
472
|
+
promise.resolve(null)
|
|
473
|
+
} catch (e: Throwable) {
|
|
474
|
+
logError("capturePushNotificationOpened", e)
|
|
475
|
+
promise.reject(PUSH_ERROR_CODE, e)
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// posthog-android's open-capture integration registers ActivityLifecycleCallbacks during
|
|
480
|
+
// setup(), which the bridge reaches only after the launch Activity was created — so the
|
|
481
|
+
// cold-start tray tap it exists for is the one creation it can never observe here. Read
|
|
482
|
+
// the launch intent directly, then strip the marker so the integration (or a re-run)
|
|
483
|
+
// can't capture the same tap again from this intent object.
|
|
484
|
+
private fun captureColdStartPushOpenIfNeeded(config: PostHogAndroidConfig) {
|
|
485
|
+
if (!config.capturePushNotificationOpened) {
|
|
486
|
+
return
|
|
487
|
+
}
|
|
488
|
+
val intent = currentActivity?.intent ?: return
|
|
489
|
+
// A relaunch from recents redelivers the original tray intent to a fresh activity;
|
|
490
|
+
// capturing it would count a days-old tap as a new open.
|
|
491
|
+
if (intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY != 0) {
|
|
492
|
+
return
|
|
493
|
+
}
|
|
494
|
+
try {
|
|
495
|
+
intent.getStringExtra(GOOGLE_MESSAGE_ID) ?: return
|
|
496
|
+
// Unmarshalling extras throws BadParcelableException on a Parcelable class this
|
|
497
|
+
// classloader lacks; read before stripping the marker so a failed read leaves the
|
|
498
|
+
// intent as the native integration expects it.
|
|
499
|
+
val payload =
|
|
500
|
+
intent.extras?.let { bundle ->
|
|
501
|
+
bundle.keySet().associateWith { key ->
|
|
502
|
+
@Suppress("DEPRECATION")
|
|
503
|
+
bundle.get(key)
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
intent.removeExtra(GOOGLE_MESSAGE_ID)
|
|
507
|
+
PostHog.capturePushNotificationOpened(null, null, payload, null)
|
|
508
|
+
} catch (e: Throwable) {
|
|
509
|
+
logError("capturePushNotificationOpened", e)
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
@ReactMethod
|
|
514
|
+
fun providePushIdentityToken(
|
|
515
|
+
requestId: String?,
|
|
516
|
+
token: String?,
|
|
517
|
+
promise: Promise,
|
|
518
|
+
) {
|
|
519
|
+
try {
|
|
520
|
+
if (requestId != null) {
|
|
521
|
+
pushIdentityCompletions.remove(requestId)?.invoke(token)
|
|
522
|
+
}
|
|
523
|
+
} catch (e: Throwable) {
|
|
524
|
+
logError("providePushIdentityToken", e)
|
|
525
|
+
} finally {
|
|
526
|
+
promise.resolve(null)
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Required by NativeEventEmitter on the old architecture; events are broadcast via
|
|
531
|
+
// RCTDeviceEventEmitter, so there is nothing to do here.
|
|
532
|
+
@ReactMethod
|
|
533
|
+
fun addListener(eventName: String?) = Unit
|
|
534
|
+
|
|
535
|
+
@ReactMethod
|
|
536
|
+
fun removeListeners(count: Int) = Unit
|
|
537
|
+
|
|
538
|
+
override fun invalidate() {
|
|
539
|
+
if (pushModule === this) {
|
|
540
|
+
// Decline mints fast after teardown instead of stalling the native 10s watchdog.
|
|
541
|
+
pushModule = null
|
|
542
|
+
}
|
|
543
|
+
super.invalidate()
|
|
544
|
+
}
|
|
545
|
+
|
|
330
546
|
companion object {
|
|
331
547
|
const val NAME = "PosthogReactNativePlugin"
|
|
332
548
|
const val POSTHOG_TAG = "PostHog"
|
|
333
549
|
|
|
550
|
+
// FCM stamps this extra on the tray-tap launch intent; mirrors posthog-android's
|
|
551
|
+
// PostHogActivityLifecycleCallbackIntegration.
|
|
552
|
+
private const val GOOGLE_MESSAGE_ID = "google.message_id"
|
|
553
|
+
|
|
334
554
|
// Default session replay configuration values
|
|
335
555
|
const val DEFAULT_MASK_ALL_TEXT_INPUTS = true
|
|
336
556
|
const val DEFAULT_MASK_ALL_IMAGES = true
|
|
337
557
|
const val DEFAULT_CAPTURE_LOG = true
|
|
338
558
|
const val DEFAULT_FLUSH_AT = 20
|
|
339
559
|
const val DEFAULT_THROTTLE_DELAY_MS = 1000
|
|
560
|
+
|
|
561
|
+
private const val PUSH_ERROR_CODE = "PosthogReactNativePluginError"
|
|
562
|
+
private const val PUSH_IDENTITY_EVENT = "PostHogPushIdentityRequest"
|
|
563
|
+
private const val PUSH_IDENTITY_REPLY_TTL_MS = 15_000L
|
|
564
|
+
|
|
565
|
+
// Modules die on every bridge reload, so the provider closure resolves the live
|
|
566
|
+
// module through this static reference at call time, not a captured setup-time one.
|
|
567
|
+
@Volatile
|
|
568
|
+
private var pushModule: PosthogReactNativePluginModule? = null
|
|
569
|
+
|
|
570
|
+
private val pushIdentityCompletions = java.util.concurrent.ConcurrentHashMap<String, (String?) -> Unit>()
|
|
571
|
+
|
|
572
|
+
private fun requestPushIdentityToken(
|
|
573
|
+
distinctId: String,
|
|
574
|
+
appId: String,
|
|
575
|
+
completion: (String?) -> Unit,
|
|
576
|
+
) {
|
|
577
|
+
val module = pushModule
|
|
578
|
+
val context = module?.reactApplicationContext
|
|
579
|
+
if (module == null || context == null || !context.hasActiveReactInstance()) {
|
|
580
|
+
declinePushIdentity(completion, "no React instance attached")
|
|
581
|
+
return
|
|
582
|
+
}
|
|
583
|
+
val requestId = UUID.randomUUID().toString()
|
|
584
|
+
pushIdentityCompletions[requestId] = completion
|
|
585
|
+
try {
|
|
586
|
+
val params =
|
|
587
|
+
Arguments.createMap().apply {
|
|
588
|
+
putString("requestId", requestId)
|
|
589
|
+
putString("distinctId", distinctId)
|
|
590
|
+
putString("appId", appId)
|
|
591
|
+
}
|
|
592
|
+
context
|
|
593
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
594
|
+
.emit(PUSH_IDENTITY_EVENT, params)
|
|
595
|
+
} catch (e: Throwable) {
|
|
596
|
+
pushIdentityCompletions.remove(requestId)
|
|
597
|
+
declinePushIdentity(completion, "failed to reach JS: ${e.message}")
|
|
598
|
+
return
|
|
599
|
+
}
|
|
600
|
+
// The native SDK's own 10s mint watchdog handles the fallback; this only drops the
|
|
601
|
+
// entry so a late JS reply is ignored and the completion doesn't leak.
|
|
602
|
+
UiThreadUtil.runOnUiThread({ pushIdentityCompletions.remove(requestId) }, PUSH_IDENTITY_REPLY_TTL_MS)
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// A null identity token sends the request unauthenticated, which a project requiring
|
|
606
|
+
// identity verification rejects server-side. Log the reason so that failure is
|
|
607
|
+
// greppable and distinct from a host that deliberately returned null.
|
|
608
|
+
private fun declinePushIdentity(
|
|
609
|
+
completion: (String?) -> Unit,
|
|
610
|
+
reason: String,
|
|
611
|
+
) {
|
|
612
|
+
Log.w(POSTHOG_TAG, "Push subscription will be sent unauthenticated: $reason")
|
|
613
|
+
completion(null)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
@Volatile private var cachedFirebaseProjectId: String? = null
|
|
617
|
+
|
|
618
|
+
// No Firebase dependency here, so the project id is looked up reflectively and any
|
|
619
|
+
// failure (class missing, Firebase not initialized) is swallowed. Only a successful lookup
|
|
620
|
+
// is cached: getInstance() throws until Firebase initializes, so memoizing the null would
|
|
621
|
+
// strand every later token-refresh call with no resolvable appId for the process lifetime.
|
|
622
|
+
private val firebaseProjectId: String?
|
|
623
|
+
get() =
|
|
624
|
+
cachedFirebaseProjectId
|
|
625
|
+
?: try {
|
|
626
|
+
val firebaseAppClass = Class.forName("com.google.firebase.FirebaseApp")
|
|
627
|
+
val firebaseApp = firebaseAppClass.getMethod("getInstance").invoke(null)
|
|
628
|
+
val options = firebaseAppClass.getMethod("getOptions").invoke(firebaseApp)
|
|
629
|
+
(options?.javaClass?.getMethod("getProjectId")?.invoke(options) as? String)?.also {
|
|
630
|
+
cachedFirebaseProjectId = it
|
|
631
|
+
}
|
|
632
|
+
} catch (e: Throwable) {
|
|
633
|
+
null
|
|
634
|
+
}
|
|
340
635
|
}
|
|
341
636
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#import <React/RCTBridgeModule.h>
|
|
2
|
+
#import <React/RCTEventEmitter.h>
|
|
2
3
|
|
|
3
|
-
@interface RCT_EXTERN_MODULE(PosthogReactNativePlugin,
|
|
4
|
+
@interface RCT_EXTERN_MODULE(PosthogReactNativePlugin, RCTEventEmitter)
|
|
4
5
|
|
|
5
6
|
RCT_EXTERN_METHOD(setup:(NSString)sessionId
|
|
6
7
|
withSdkOptions:(NSDictionary)sdkOptions
|
|
@@ -42,6 +43,32 @@ RCT_EXTERN_METHOD(addExceptionStep:(NSString)message
|
|
|
42
43
|
withResolver:(RCTPromiseResolveBlock)resolve
|
|
43
44
|
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
44
45
|
|
|
46
|
+
RCT_EXTERN_METHOD(reset:(NSString)distinctId
|
|
47
|
+
withAnonymousId:(NSString)anonymousId
|
|
48
|
+
withResolver:(RCTPromiseResolveBlock)resolve
|
|
49
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
50
|
+
|
|
51
|
+
RCT_EXTERN_METHOD(registerPushNotificationToken:(NSString)deviceToken
|
|
52
|
+
withAppId:(NSString)appId
|
|
53
|
+
withResolver:(RCTPromiseResolveBlock)resolve
|
|
54
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
55
|
+
|
|
56
|
+
RCT_EXTERN_METHOD(unregisterPushNotificationToken:(RCTPromiseResolveBlock)resolve
|
|
57
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
58
|
+
|
|
59
|
+
RCT_EXTERN_METHOD(setOptOut:(BOOL)optOut
|
|
60
|
+
withResolver:(RCTPromiseResolveBlock)resolve
|
|
61
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
62
|
+
|
|
63
|
+
RCT_EXTERN_METHOD(capturePushNotificationOpened:(NSDictionary)properties
|
|
64
|
+
withResolver:(RCTPromiseResolveBlock)resolve
|
|
65
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
66
|
+
|
|
67
|
+
RCT_EXTERN_METHOD(providePushIdentityToken:(NSString)requestId
|
|
68
|
+
withToken:(NSString)token
|
|
69
|
+
withResolver:(RCTPromiseResolveBlock)resolve
|
|
70
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
71
|
+
|
|
45
72
|
+ (BOOL)requiresMainQueueSetup
|
|
46
73
|
{
|
|
47
74
|
return NO;
|