@plushanalytics/react-native-session-replay 1.2.6 → 3.0.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.
- package/README.md +138 -3
- package/android/src/main/java/com/posthogreactnativesessionreplay/PosthogReactNativeSessionReplayModule.kt +68 -27
- package/ios/PosthogReactNativeSessionReplay.mm +3 -0
- package/ios/PosthogReactNativeSessionReplay.swift +58 -31
- package/lib/commonjs/adapters/mobileReplayAdapter.js +135 -0
- package/lib/commonjs/adapters/mobileReplayAdapter.js.map +1 -0
- package/lib/commonjs/client.js +42 -0
- package/lib/commonjs/client.js.map +1 -0
- package/lib/commonjs/index.js +31 -37
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/nativeBridge.js +50 -0
- package/lib/commonjs/nativeBridge.js.map +1 -0
- package/lib/commonjs/provider.js +45 -0
- package/lib/commonjs/provider.js.map +1 -0
- package/lib/commonjs/types.js +6 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/module/adapters/mobileReplayAdapter.js +130 -0
- package/lib/module/adapters/mobileReplayAdapter.js.map +1 -0
- package/lib/module/client.js +33 -0
- package/lib/module/client.js.map +1 -0
- package/lib/module/index.js +3 -33
- package/lib/module/index.js.map +1 -1
- package/lib/module/nativeBridge.js +40 -0
- package/lib/module/nativeBridge.js.map +1 -0
- package/lib/module/provider.js +39 -0
- package/lib/module/provider.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/commonjs/src/adapters/mobileReplayAdapter.d.ts +9 -0
- package/lib/typescript/commonjs/src/adapters/mobileReplayAdapter.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/client.d.ts +6 -0
- package/lib/typescript/commonjs/src/client.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/index.d.ts +4 -28
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/nativeBridge.d.ts +31 -0
- package/lib/typescript/commonjs/src/nativeBridge.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/provider.d.ts +12 -0
- package/lib/typescript/commonjs/src/provider.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/types.d.ts +32 -0
- package/lib/typescript/commonjs/src/types.d.ts.map +1 -0
- package/lib/typescript/module/src/adapters/mobileReplayAdapter.d.ts +9 -0
- package/lib/typescript/module/src/adapters/mobileReplayAdapter.d.ts.map +1 -0
- package/lib/typescript/module/src/client.d.ts +6 -0
- package/lib/typescript/module/src/client.d.ts.map +1 -0
- package/lib/typescript/module/src/index.d.ts +4 -28
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/nativeBridge.d.ts +31 -0
- package/lib/typescript/module/src/nativeBridge.d.ts.map +1 -0
- package/lib/typescript/module/src/provider.d.ts +12 -0
- package/lib/typescript/module/src/provider.d.ts.map +1 -0
- package/lib/typescript/module/src/types.d.ts +32 -0
- package/lib/typescript/module/src/types.d.ts.map +1 -0
- package/package.json +22 -25
- package/src/adapters/mobileReplayAdapter.ts +206 -0
- package/src/client.ts +43 -0
- package/src/index.tsx +25 -79
- package/src/nativeBridge.ts +86 -0
- package/src/provider.tsx +46 -0
- package/src/types.ts +73 -0
package/README.md
CHANGED
|
@@ -1,9 +1,144 @@
|
|
|
1
1
|
# @plushanalytics/react-native-session-replay
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React Native SDK for Plush Analytics (events + session replay) with a React Provider/hook.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
|
-
```
|
|
7
|
+
```bash
|
|
8
8
|
npm install @plushanalytics/react-native-session-replay
|
|
9
9
|
```
|
|
10
|
+
|
|
11
|
+
> [!NOTE]
|
|
12
|
+
> This package includes native code and is not compatible with Expo Go. Use development builds:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx expo run:ios
|
|
16
|
+
npx expo run:android
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Setup (Provider + Hook)
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import React from "react";
|
|
23
|
+
import { Button } from "react-native";
|
|
24
|
+
import { PlushAnalyticsProvider, usePlushAnalytics } from "@plushanalytics/react-native-session-replay";
|
|
25
|
+
|
|
26
|
+
function CheckoutButton(): JSX.Element {
|
|
27
|
+
const analytics = usePlushAnalytics();
|
|
28
|
+
return (
|
|
29
|
+
<Button
|
|
30
|
+
title="Purchase"
|
|
31
|
+
onPress={() => {
|
|
32
|
+
void analytics.track("purchase_clicked", { properties: { plan: "pro" } });
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default function App(): JSX.Element {
|
|
39
|
+
return (
|
|
40
|
+
<PlushAnalyticsProvider
|
|
41
|
+
config={{
|
|
42
|
+
apiKey: "plsh_live_...",
|
|
43
|
+
projectId: "mobile_app",
|
|
44
|
+
context: { platform: "react-native" },
|
|
45
|
+
sessionReplayConfig: {
|
|
46
|
+
enabled: true,
|
|
47
|
+
masking: {
|
|
48
|
+
textInputs: true,
|
|
49
|
+
images: true,
|
|
50
|
+
sandboxedViews: true,
|
|
51
|
+
},
|
|
52
|
+
captureNetworkTelemetry: true,
|
|
53
|
+
throttleDelayMs: 1000,
|
|
54
|
+
},
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
<CheckoutButton />
|
|
58
|
+
</PlushAnalyticsProvider>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Direct Client Usage (No React)
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { createPlushAnalyticsClient } from "@plushanalytics/react-native-session-replay";
|
|
67
|
+
|
|
68
|
+
const analytics = createPlushAnalyticsClient({
|
|
69
|
+
apiKey: "plsh_live_...",
|
|
70
|
+
projectId: "mobile_app",
|
|
71
|
+
sessionReplayConfig: {
|
|
72
|
+
enabled: true,
|
|
73
|
+
masking: {
|
|
74
|
+
textInputs: true,
|
|
75
|
+
images: true,
|
|
76
|
+
sandboxedViews: true,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await analytics.track("app_opened");
|
|
82
|
+
await analytics.shutdown();
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Session Replay Config
|
|
86
|
+
|
|
87
|
+
Replay behavior is configured locally in the SDK. It does not depend on backend remote config.
|
|
88
|
+
|
|
89
|
+
- `config.sessionReplayConfig.enabled`: defaults to `true`
|
|
90
|
+
- `config.sessionReplayConfig.masking.textInputs`: defaults to `true`
|
|
91
|
+
- `config.sessionReplayConfig.masking.images`: defaults to `true`
|
|
92
|
+
- `config.sessionReplayConfig.masking.sandboxedViews`: defaults to `true`
|
|
93
|
+
- `config.sessionReplayConfig.captureLog`: defaults to `true`
|
|
94
|
+
- `config.sessionReplayConfig.captureNetworkTelemetry`: defaults to `true`
|
|
95
|
+
- `config.sessionReplayConfig.throttleDelayMs`: defaults to `1000`
|
|
96
|
+
|
|
97
|
+
Per-session overrides are supported via `startSessionRecording({ replayConfig })` / `startReplay({ replayConfig })`.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
await analytics.startReplay({
|
|
101
|
+
replayConfig: {
|
|
102
|
+
enabled: true,
|
|
103
|
+
masking: {
|
|
104
|
+
images: false,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Legacy flat replay keys are still supported in per-session overrides for backward compatibility:
|
|
111
|
+
`maskAllTextInputs`, `maskAllImages`, `maskAllSandboxedViews`.
|
|
112
|
+
|
|
113
|
+
## Replay Authentication Headers
|
|
114
|
+
|
|
115
|
+
Replay uploads include both headers from the configured `apiKey`:
|
|
116
|
+
|
|
117
|
+
- `x-api-key`
|
|
118
|
+
- `x-plush-api-key`
|
|
119
|
+
|
|
120
|
+
## Common Usage
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
await analytics.track("cta_clicked", { properties: { plan: "pro" } });
|
|
124
|
+
await analytics.identify("user_123", { traits: { plan: "pro" } });
|
|
125
|
+
await analytics.screen("Settings", { properties: { tab: "billing" } });
|
|
126
|
+
await analytics.group("org_456", { traits: { tier: "enterprise" } });
|
|
127
|
+
|
|
128
|
+
await analytics.flush();
|
|
129
|
+
await analytics.reset();
|
|
130
|
+
|
|
131
|
+
await analytics.startSessionRecording();
|
|
132
|
+
await analytics.flushSessionRecording();
|
|
133
|
+
await analytics.stopSessionRecording();
|
|
134
|
+
|
|
135
|
+
analytics.setProject("mobile_app");
|
|
136
|
+
analytics.setContext({ build: 42 });
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## API Surface
|
|
140
|
+
|
|
141
|
+
- `PlushAnalyticsProvider`
|
|
142
|
+
- `usePlushAnalytics()`
|
|
143
|
+
- `createPlushAnalyticsClient(config)`
|
|
144
|
+
- `PlushAnalyticsClient`
|
|
@@ -15,7 +15,10 @@ import com.posthog.internal.PostHogPreferences
|
|
|
15
15
|
import com.posthog.internal.PostHogPreferences.Companion.ANONYMOUS_ID
|
|
16
16
|
import com.posthog.internal.PostHogPreferences.Companion.DISTINCT_ID
|
|
17
17
|
import com.posthog.internal.PostHogSessionManager
|
|
18
|
+
import java.net.URI
|
|
18
19
|
import java.util.UUID
|
|
20
|
+
import okhttp3.Interceptor
|
|
21
|
+
import okhttp3.OkHttpClient
|
|
19
22
|
|
|
20
23
|
class PosthogReactNativeSessionReplayModule(
|
|
21
24
|
reactContext: ReactApplicationContext,
|
|
@@ -39,13 +42,24 @@ class PosthogReactNativeSessionReplayModule(
|
|
|
39
42
|
val context = this.reactApplicationContext
|
|
40
43
|
val apiKey = runCatching { sdkOptions.getString("apiKey") }.getOrNull() ?: ""
|
|
41
44
|
val host = runCatching { sdkOptions.getString("host") }.getOrNull() ?: PostHogConfig.DEFAULT_HOST
|
|
45
|
+
if (isPosthogHost(host)) {
|
|
46
|
+
val message = "Blocked replay host '$host' because posthog.com hosts are not allowed."
|
|
47
|
+
logError("start", IllegalArgumentException(message))
|
|
48
|
+
promise.reject("PLUSH_BLOCKED_REPLAY_HOST", message)
|
|
49
|
+
return@Runnable
|
|
50
|
+
}
|
|
51
|
+
|
|
42
52
|
val debugValue = runCatching { sdkOptions.getBoolean("debug") }.getOrNull() ?: false
|
|
43
53
|
|
|
44
|
-
val maskAllTextInputs =
|
|
45
|
-
|
|
46
|
-
|
|
54
|
+
val maskAllTextInputs =
|
|
55
|
+
runCatching { sdkReplayConfig.getBoolean("maskAllTextInputs") }.getOrNull()
|
|
56
|
+
?: DEFAULT_MASK_ALL_TEXT_INPUTS
|
|
57
|
+
val maskAllImages =
|
|
58
|
+
runCatching { sdkReplayConfig.getBoolean("maskAllImages") }.getOrNull() ?: DEFAULT_MASK_ALL_IMAGES
|
|
59
|
+
val captureLog =
|
|
60
|
+
runCatching { sdkReplayConfig.getBoolean("captureLog") }.getOrNull() ?: DEFAULT_CAPTURE_LOG
|
|
47
61
|
|
|
48
|
-
//
|
|
62
|
+
// Read throttleDelayMs and use androidDebouncerDelayMs as a fallback for back compatibility.
|
|
49
63
|
val throttleDelayMs =
|
|
50
64
|
if (sdkReplayConfig.hasKey("throttleDelayMs")) {
|
|
51
65
|
sdkReplayConfig.getInt("throttleDelayMs")
|
|
@@ -56,24 +70,10 @@ class PosthogReactNativeSessionReplayModule(
|
|
|
56
70
|
}
|
|
57
71
|
|
|
58
72
|
val endpoint = decideReplayConfig.getString("endpoint")
|
|
59
|
-
|
|
60
|
-
val
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
} catch (e: Throwable) {
|
|
64
|
-
logError("parse distinctId", e)
|
|
65
|
-
""
|
|
66
|
-
}
|
|
67
|
-
val anonymousId =
|
|
68
|
-
try {
|
|
69
|
-
sdkOptions.getString("anonymousId") ?: ""
|
|
70
|
-
} catch (e: Throwable) {
|
|
71
|
-
logError("parse anonymousId", e)
|
|
72
|
-
""
|
|
73
|
-
}
|
|
74
|
-
val theSdkVersion = runCatching { sdkOptions.getString("sdkVersion") }.getOrNull()
|
|
75
|
-
|
|
76
|
-
val theFlushAt = runCatching { sdkOptions.getInt("flushAt") }.getOrNull() ?: DEFAULT_FLUSH_AT
|
|
73
|
+
val distinctId = runCatching { sdkOptions.getString("distinctId") }.getOrNull() ?: ""
|
|
74
|
+
val anonymousId = runCatching { sdkOptions.getString("anonymousId") }.getOrNull() ?: ""
|
|
75
|
+
val sdkVersion = runCatching { sdkOptions.getString("sdkVersion") }.getOrNull()
|
|
76
|
+
val flushAt = runCatching { sdkOptions.getInt("flushAt") }.getOrNull() ?: DEFAULT_FLUSH_AT
|
|
77
77
|
|
|
78
78
|
val config =
|
|
79
79
|
PostHogAndroidConfig(apiKey, host).apply {
|
|
@@ -81,25 +81,28 @@ class PosthogReactNativeSessionReplayModule(
|
|
|
81
81
|
captureDeepLinks = false
|
|
82
82
|
captureApplicationLifecycleEvents = false
|
|
83
83
|
captureScreenViews = false
|
|
84
|
-
|
|
84
|
+
preloadFeatureFlags = false
|
|
85
|
+
remoteConfig = false
|
|
86
|
+
this.flushAt = flushAt
|
|
85
87
|
sessionReplay = true
|
|
86
88
|
sessionReplayConfig.screenshot = true
|
|
87
89
|
sessionReplayConfig.captureLogcat = captureLog
|
|
88
90
|
sessionReplayConfig.throttleDelayMs = throttleDelayMs.toLong()
|
|
89
91
|
sessionReplayConfig.maskAllImages = maskAllImages
|
|
90
92
|
sessionReplayConfig.maskAllTextInputs = maskAllTextInputs
|
|
93
|
+
httpClient = buildApiKeyClient(apiKey)
|
|
91
94
|
|
|
92
95
|
if (!endpoint.isNullOrEmpty()) {
|
|
93
96
|
snapshotEndpoint = endpoint
|
|
94
97
|
}
|
|
95
98
|
|
|
96
|
-
if (!
|
|
99
|
+
if (!sdkVersion.isNullOrEmpty()) {
|
|
97
100
|
sdkName = "posthog-react-native"
|
|
98
|
-
sdkVersion =
|
|
101
|
+
this.sdkVersion = sdkVersion
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
|
-
PostHogAndroid.setup(context, config)
|
|
102
104
|
|
|
105
|
+
PostHogAndroid.setup(context, config)
|
|
103
106
|
setIdentify(config.cachePreferences, distinctId, anonymousId)
|
|
104
107
|
} catch (e: Throwable) {
|
|
105
108
|
logError("start", e)
|
|
@@ -108,7 +111,7 @@ class PosthogReactNativeSessionReplayModule(
|
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
113
|
|
|
111
|
-
//
|
|
114
|
+
// Force the SDK to be initialized on the main thread.
|
|
112
115
|
if (UiThreadUtil.isOnUiThread()) {
|
|
113
116
|
initRunnable.run()
|
|
114
117
|
} else {
|
|
@@ -153,6 +156,17 @@ class PosthogReactNativeSessionReplayModule(
|
|
|
153
156
|
}
|
|
154
157
|
}
|
|
155
158
|
|
|
159
|
+
@ReactMethod
|
|
160
|
+
fun flush(promise: Promise) {
|
|
161
|
+
try {
|
|
162
|
+
PostHog.flush()
|
|
163
|
+
} catch (e: Throwable) {
|
|
164
|
+
logError("flush", e)
|
|
165
|
+
} finally {
|
|
166
|
+
promise.resolve(null)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
156
170
|
@ReactMethod
|
|
157
171
|
fun identify(
|
|
158
172
|
distinctId: String,
|
|
@@ -183,6 +197,22 @@ class PosthogReactNativeSessionReplayModule(
|
|
|
183
197
|
}
|
|
184
198
|
}
|
|
185
199
|
|
|
200
|
+
private fun buildApiKeyClient(apiKey: String): OkHttpClient {
|
|
201
|
+
return OkHttpClient.Builder()
|
|
202
|
+
.addInterceptor(
|
|
203
|
+
Interceptor { chain ->
|
|
204
|
+
val original = chain.request()
|
|
205
|
+
val updated =
|
|
206
|
+
original
|
|
207
|
+
.newBuilder()
|
|
208
|
+
.header("x-api-key", apiKey)
|
|
209
|
+
.header("x-plush-api-key", apiKey)
|
|
210
|
+
.build()
|
|
211
|
+
chain.proceed(updated)
|
|
212
|
+
}
|
|
213
|
+
).build()
|
|
214
|
+
}
|
|
215
|
+
|
|
186
216
|
private fun logError(
|
|
187
217
|
method: String,
|
|
188
218
|
error: Throwable,
|
|
@@ -190,6 +220,17 @@ class PosthogReactNativeSessionReplayModule(
|
|
|
190
220
|
Log.println(Log.ERROR, POSTHOG_TAG, "Method $method, error: $error")
|
|
191
221
|
}
|
|
192
222
|
|
|
223
|
+
private fun isPosthogHost(value: String): Boolean {
|
|
224
|
+
val hostName =
|
|
225
|
+
try {
|
|
226
|
+
URI(value).host?.lowercase() ?: value.lowercase()
|
|
227
|
+
} catch (_: Throwable) {
|
|
228
|
+
value.lowercase()
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return hostName == "posthog.com" || hostName.endsWith(".posthog.com")
|
|
232
|
+
}
|
|
233
|
+
|
|
193
234
|
companion object {
|
|
194
235
|
const val NAME = "PosthogReactNativeSessionReplay"
|
|
195
236
|
const val POSTHOG_TAG = "PostHog"
|
|
@@ -19,6 +19,9 @@ RCT_EXTERN_METHOD(isEnabled:(RCTPromiseResolveBlock)resolve
|
|
|
19
19
|
RCT_EXTERN_METHOD(endSession:(RCTPromiseResolveBlock)resolve
|
|
20
20
|
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
21
21
|
|
|
22
|
+
RCT_EXTERN_METHOD(flush:(RCTPromiseResolveBlock)resolve
|
|
23
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
24
|
+
|
|
22
25
|
RCT_EXTERN_METHOD(identify:(NSString)distinctId
|
|
23
26
|
withAnonymousId:(NSString)anonymousId
|
|
24
27
|
withResolver:(RCTPromiseResolveBlock)resolve
|
|
@@ -1,8 +1,19 @@
|
|
|
1
|
+
import Foundation
|
|
1
2
|
import PostHog
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
private func isPosthogHost(_ value: String) -> Bool {
|
|
5
|
+
let normalizedHost: String
|
|
6
|
+
if let url = URL(string: value), let host = url.host {
|
|
7
|
+
normalizedHost = host.lowercased()
|
|
8
|
+
} else {
|
|
9
|
+
normalizedHost = value.lowercased()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return normalizedHost == "posthog.com" || normalizedHost.hasSuffix(".posthog.com")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private func logNativeError(_ message: String) {
|
|
16
|
+
NSLog("[Plush][RNReplay] %@", message)
|
|
6
17
|
}
|
|
7
18
|
|
|
8
19
|
@objc(PosthogReactNativeSessionReplay)
|
|
@@ -11,13 +22,15 @@ class PosthogReactNativeSessionReplay: NSObject {
|
|
|
11
22
|
|
|
12
23
|
@objc(start:withSdkOptions:withSdkReplayConfig:withDecideReplayConfig:withResolver:withRejecter:)
|
|
13
24
|
func start(
|
|
14
|
-
sessionId: String,
|
|
15
|
-
|
|
16
|
-
|
|
25
|
+
sessionId: String,
|
|
26
|
+
sdkOptions: [String: Any],
|
|
27
|
+
sdkReplayConfig: [String: Any],
|
|
28
|
+
decideReplayConfig: [String: Any],
|
|
29
|
+
resolve: RCTPromiseResolveBlock,
|
|
30
|
+
reject: RCTPromiseRejectBlock
|
|
17
31
|
) {
|
|
18
|
-
// we've seen cases where distinctId and anonymousId are not strings, so we need to check and convert them
|
|
19
32
|
guard let sessionIdStr = sessionId as? String else {
|
|
20
|
-
|
|
33
|
+
logNativeError("Invalid sessionId provided. Expected a string.")
|
|
21
34
|
resolve(nil)
|
|
22
35
|
return
|
|
23
36
|
}
|
|
@@ -26,15 +39,30 @@ class PosthogReactNativeSessionReplay: NSObject {
|
|
|
26
39
|
let host = sdkOptions["host"] as? String ?? PostHogConfig.defaultHost
|
|
27
40
|
let debug = sdkOptions["debug"] as? Bool ?? false
|
|
28
41
|
|
|
42
|
+
if isPosthogHost(host) {
|
|
43
|
+
let message = "Blocked replay host '\(host)' because posthog.com hosts are not allowed."
|
|
44
|
+
reject("PLUSH_BLOCKED_REPLAY_HOST", message, nil)
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
29
48
|
PostHogSessionManager.shared.setSessionId(sessionIdStr)
|
|
30
49
|
|
|
31
50
|
let config = PostHogConfig(apiKey: apiKey, host: host)
|
|
32
51
|
config.sessionReplay = true
|
|
33
52
|
config.captureApplicationLifecycleEvents = false
|
|
34
53
|
config.captureScreenViews = false
|
|
54
|
+
config.preloadFeatureFlags = false
|
|
55
|
+
config.remoteConfig = false
|
|
35
56
|
config.debug = debug
|
|
36
57
|
config.sessionReplayConfig.screenshotMode = true
|
|
37
58
|
|
|
59
|
+
let sessionConfiguration = URLSessionConfiguration.default
|
|
60
|
+
sessionConfiguration.httpAdditionalHeaders = [
|
|
61
|
+
"x-api-key": apiKey,
|
|
62
|
+
"x-plush-api-key": apiKey,
|
|
63
|
+
]
|
|
64
|
+
config.urlSessionConfiguration = sessionConfiguration
|
|
65
|
+
|
|
38
66
|
if #available(iOS 15.0, *) {
|
|
39
67
|
config.surveys = false
|
|
40
68
|
}
|
|
@@ -48,7 +76,7 @@ class PosthogReactNativeSessionReplay: NSObject {
|
|
|
48
76
|
let maskAllSandboxedViews = sdkReplayConfig["maskAllSandboxedViews"] as? Bool ?? true
|
|
49
77
|
config.sessionReplayConfig.maskAllSandboxedViews = maskAllSandboxedViews
|
|
50
78
|
|
|
51
|
-
//
|
|
79
|
+
// Read throttleDelayMs and use iOSdebouncerDelayMs as a fallback for back compatibility.
|
|
52
80
|
let throttleDelayMs =
|
|
53
81
|
(sdkReplayConfig["throttleDelayMs"] as? Int)
|
|
54
82
|
?? (sdkReplayConfig["iOSdebouncerDelayMs"] as? Int)
|
|
@@ -79,30 +107,30 @@ class PosthogReactNativeSessionReplay: NSObject {
|
|
|
79
107
|
}
|
|
80
108
|
|
|
81
109
|
PostHogSDK.shared.setup(config)
|
|
82
|
-
|
|
83
110
|
self.config = config
|
|
84
111
|
|
|
85
112
|
guard let storageManager = self.config?.storageManager else {
|
|
86
|
-
|
|
113
|
+
logNativeError("Storage manager is not available in the config.")
|
|
87
114
|
resolve(nil)
|
|
88
115
|
return
|
|
89
116
|
}
|
|
90
117
|
|
|
91
118
|
setIdentify(storageManager, distinctId: distinctId, anonymousId: anonymousId)
|
|
92
|
-
|
|
93
119
|
resolve(nil)
|
|
94
120
|
}
|
|
95
121
|
|
|
96
122
|
@objc(startSession:withResolver:withRejecter:)
|
|
97
123
|
func startSession(
|
|
98
|
-
sessionId: String,
|
|
124
|
+
sessionId: String,
|
|
125
|
+
resolve: RCTPromiseResolveBlock,
|
|
126
|
+
reject _: RCTPromiseRejectBlock
|
|
99
127
|
) {
|
|
100
|
-
// we've seen cases where distinctId and anonymousId are not strings, so we need to check and convert them
|
|
101
128
|
guard let sessionIdStr = sessionId as? String else {
|
|
102
|
-
|
|
129
|
+
logNativeError("Invalid sessionId provided. Expected a string.")
|
|
103
130
|
resolve(nil)
|
|
104
131
|
return
|
|
105
132
|
}
|
|
133
|
+
|
|
106
134
|
PostHogSessionManager.shared.setSessionId(sessionIdStr)
|
|
107
135
|
PostHogSDK.shared.startSession()
|
|
108
136
|
resolve(nil)
|
|
@@ -110,8 +138,7 @@ class PosthogReactNativeSessionReplay: NSObject {
|
|
|
110
138
|
|
|
111
139
|
@objc(isEnabled:withRejecter:)
|
|
112
140
|
func isEnabled(resolve: RCTPromiseResolveBlock, reject _: RCTPromiseRejectBlock) {
|
|
113
|
-
|
|
114
|
-
resolve(isEnabled)
|
|
141
|
+
resolve(PostHogSDK.shared.isSessionReplayActive())
|
|
115
142
|
}
|
|
116
143
|
|
|
117
144
|
@objc(endSession:withRejecter:)
|
|
@@ -120,33 +147,33 @@ class PosthogReactNativeSessionReplay: NSObject {
|
|
|
120
147
|
resolve(nil)
|
|
121
148
|
}
|
|
122
149
|
|
|
150
|
+
@objc(flush:withRejecter:)
|
|
151
|
+
func flush(resolve: RCTPromiseResolveBlock, reject _: RCTPromiseRejectBlock) {
|
|
152
|
+
PostHogSDK.shared.flush()
|
|
153
|
+
resolve(nil)
|
|
154
|
+
}
|
|
155
|
+
|
|
123
156
|
@objc(identify:withAnonymousId:withResolver:withRejecter:)
|
|
124
157
|
func identify(
|
|
125
|
-
distinctId: String,
|
|
158
|
+
distinctId: String,
|
|
159
|
+
anonymousId: String,
|
|
160
|
+
resolve: RCTPromiseResolveBlock,
|
|
126
161
|
reject _: RCTPromiseRejectBlock
|
|
127
162
|
) {
|
|
128
|
-
// we've seen cases where distinctId and anonymousId are not strings, so we need to check and convert them
|
|
129
|
-
guard let distinctIdStr = distinctId as? String,
|
|
130
|
-
let anonymousIdStr = anonymousId as? String
|
|
131
|
-
else {
|
|
132
|
-
hedgeLog(
|
|
133
|
-
"Invalid distinctId: \(distinctId) or anonymousId: \(anonymousId) provided. Expected strings."
|
|
134
|
-
)
|
|
135
|
-
resolve(nil)
|
|
136
|
-
return
|
|
137
|
-
}
|
|
138
163
|
guard let storageManager = config?.storageManager else {
|
|
139
|
-
|
|
164
|
+
logNativeError("Storage manager is not available in the config.")
|
|
140
165
|
resolve(nil)
|
|
141
166
|
return
|
|
142
167
|
}
|
|
143
|
-
setIdentify(storageManager, distinctId: distinctIdStr, anonymousId: anonymousIdStr)
|
|
144
168
|
|
|
169
|
+
setIdentify(storageManager, distinctId: distinctId, anonymousId: anonymousId)
|
|
145
170
|
resolve(nil)
|
|
146
171
|
}
|
|
147
172
|
|
|
148
173
|
private func setIdentify(
|
|
149
|
-
_ storageManager: PostHogStorageManager,
|
|
174
|
+
_ storageManager: PostHogStorageManager,
|
|
175
|
+
distinctId: String,
|
|
176
|
+
anonymousId: String
|
|
150
177
|
) {
|
|
151
178
|
if !anonymousId.isEmpty {
|
|
152
179
|
storageManager.setAnonymousId(anonymousId)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.createMobileReplayRecorderAdapter = createMobileReplayRecorderAdapter;
|
|
7
|
+
var replayModule = _interopRequireWildcard(require("../nativeBridge.js"));
|
|
8
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
9
|
+
const DEFAULT_SNAPSHOT_PATH = "/v1/events/batch";
|
|
10
|
+
const DEFAULT_THROTTLE_DELAY_MS = 1000;
|
|
11
|
+
const UUID_PATTERN = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
12
|
+
function assertNonEmpty(value, field) {
|
|
13
|
+
if (!value || !value.trim()) {
|
|
14
|
+
throw new Error(`${field} is required.`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function normalizeHost(value) {
|
|
18
|
+
return value.replace(/\/+$/, "");
|
|
19
|
+
}
|
|
20
|
+
function normalizeSnapshotPath(value) {
|
|
21
|
+
const trimmed = value?.trim();
|
|
22
|
+
if (!trimmed) return DEFAULT_SNAPSHOT_PATH;
|
|
23
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
24
|
+
}
|
|
25
|
+
function isUuid(value) {
|
|
26
|
+
return UUID_PATTERN.test(value);
|
|
27
|
+
}
|
|
28
|
+
function generateUuid() {
|
|
29
|
+
const cryptoApi = globalThis.crypto;
|
|
30
|
+
if (cryptoApi?.randomUUID) return cryptoApi.randomUUID();
|
|
31
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, char => {
|
|
32
|
+
const random = Math.floor(Math.random() * 16);
|
|
33
|
+
const resolved = char === "x" ? random : random & 0x3 | 0x8;
|
|
34
|
+
return resolved.toString(16);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function resolveSessionId(options) {
|
|
38
|
+
const requestedSessionId = options?.sessionId?.trim();
|
|
39
|
+
if (requestedSessionId && isUuid(requestedSessionId)) {
|
|
40
|
+
return requestedSessionId;
|
|
41
|
+
}
|
|
42
|
+
return generateUuid();
|
|
43
|
+
}
|
|
44
|
+
function assertNoPosthogDotCom(host) {
|
|
45
|
+
const lowered = host.toLowerCase();
|
|
46
|
+
const hostName = (() => {
|
|
47
|
+
try {
|
|
48
|
+
return new URL(host).hostname.toLowerCase();
|
|
49
|
+
} catch {
|
|
50
|
+
return lowered;
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
if (hostName === "posthog.com" || hostName.endsWith(".posthog.com")) {
|
|
54
|
+
throw new Error("replayHost must not point to posthog.com (or a subdomain).");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function resolveMaskingValue(overrideConfig, defaultConfig, maskingKey, legacyKey) {
|
|
58
|
+
const overrideMaskingValue = overrideConfig?.masking?.[maskingKey];
|
|
59
|
+
if (typeof overrideMaskingValue === "boolean") {
|
|
60
|
+
return overrideMaskingValue;
|
|
61
|
+
}
|
|
62
|
+
const overrideLegacyValue = overrideConfig?.[legacyKey];
|
|
63
|
+
if (typeof overrideLegacyValue === "boolean") {
|
|
64
|
+
return overrideLegacyValue;
|
|
65
|
+
}
|
|
66
|
+
const defaultMaskingValue = defaultConfig?.masking?.[maskingKey];
|
|
67
|
+
if (typeof defaultMaskingValue === "boolean") {
|
|
68
|
+
return defaultMaskingValue;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
function resolveReplayConfig(defaultConfig, overrideConfig) {
|
|
73
|
+
const enabled = overrideConfig?.enabled ?? defaultConfig?.enabled ?? true;
|
|
74
|
+
const captureLog = overrideConfig?.captureLog ?? defaultConfig?.captureLog ?? true;
|
|
75
|
+
const captureNetworkTelemetry = overrideConfig?.captureNetworkTelemetry ?? defaultConfig?.captureNetworkTelemetry ?? true;
|
|
76
|
+
const requestedThrottleDelayMs = overrideConfig?.throttleDelayMs ?? defaultConfig?.throttleDelayMs ?? DEFAULT_THROTTLE_DELAY_MS;
|
|
77
|
+
const throttleDelayMs = Number.isFinite(requestedThrottleDelayMs) && requestedThrottleDelayMs >= 0 ? requestedThrottleDelayMs : DEFAULT_THROTTLE_DELAY_MS;
|
|
78
|
+
return {
|
|
79
|
+
enabled,
|
|
80
|
+
maskAllTextInputs: resolveMaskingValue(overrideConfig, defaultConfig, "textInputs", "maskAllTextInputs"),
|
|
81
|
+
maskAllImages: resolveMaskingValue(overrideConfig, defaultConfig, "images", "maskAllImages"),
|
|
82
|
+
maskAllSandboxedViews: resolveMaskingValue(overrideConfig, defaultConfig, "sandboxedViews", "maskAllSandboxedViews"),
|
|
83
|
+
captureLog,
|
|
84
|
+
captureNetworkTelemetry,
|
|
85
|
+
throttleDelayMs
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function toNativeReplayConfig(config) {
|
|
89
|
+
return {
|
|
90
|
+
maskAllTextInputs: config.maskAllTextInputs,
|
|
91
|
+
maskAllImages: config.maskAllImages,
|
|
92
|
+
maskAllSandboxedViews: config.maskAllSandboxedViews,
|
|
93
|
+
captureLog: config.captureLog,
|
|
94
|
+
captureNetworkTelemetry: config.captureNetworkTelemetry,
|
|
95
|
+
throttleDelayMs: config.throttleDelayMs
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function createMobileReplayRecorderAdapter(defaults) {
|
|
99
|
+
assertNonEmpty(defaults.apiKey, "apiKey");
|
|
100
|
+
assertNonEmpty(defaults.endpoint, "endpoint");
|
|
101
|
+
return {
|
|
102
|
+
start: async args => {
|
|
103
|
+
const options = args.options;
|
|
104
|
+
const resolvedReplayConfig = resolveReplayConfig(defaults.replayConfigDefaults, options?.replayConfig);
|
|
105
|
+
if (!resolvedReplayConfig.enabled) {
|
|
106
|
+
return () => {};
|
|
107
|
+
}
|
|
108
|
+
const host = normalizeHost(options?.replayHost?.trim() || defaults.endpoint);
|
|
109
|
+
assertNoPosthogDotCom(host);
|
|
110
|
+
const snapshotPath = normalizeSnapshotPath(options?.replaySnapshotPath);
|
|
111
|
+
const activeSessionId = resolveSessionId(options);
|
|
112
|
+
const sdkOptions = {
|
|
113
|
+
apiKey: defaults.apiKey,
|
|
114
|
+
host,
|
|
115
|
+
debug: false,
|
|
116
|
+
distinctId: options?.userId?.trim() || "",
|
|
117
|
+
anonymousId: "",
|
|
118
|
+
sdkVersion: "plushanalytics-react-native",
|
|
119
|
+
flushAt: 20
|
|
120
|
+
};
|
|
121
|
+
await replayModule.start(activeSessionId, sdkOptions, toNativeReplayConfig(resolvedReplayConfig), {
|
|
122
|
+
endpoint: snapshotPath
|
|
123
|
+
});
|
|
124
|
+
await replayModule.startSession(activeSessionId);
|
|
125
|
+
args.onSessionId?.(activeSessionId);
|
|
126
|
+
return () => {
|
|
127
|
+
void replayModule.endSession();
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
flush: async () => {
|
|
131
|
+
await replayModule.flush();
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=mobileReplayAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["replayModule","_interopRequireWildcard","require","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","DEFAULT_SNAPSHOT_PATH","DEFAULT_THROTTLE_DELAY_MS","UUID_PATTERN","assertNonEmpty","value","field","trim","Error","normalizeHost","replace","normalizeSnapshotPath","trimmed","startsWith","isUuid","test","generateUuid","cryptoApi","globalThis","crypto","randomUUID","char","random","Math","floor","resolved","toString","resolveSessionId","options","requestedSessionId","sessionId","assertNoPosthogDotCom","host","lowered","toLowerCase","hostName","URL","hostname","endsWith","resolveMaskingValue","overrideConfig","defaultConfig","maskingKey","legacyKey","overrideMaskingValue","masking","overrideLegacyValue","defaultMaskingValue","resolveReplayConfig","enabled","captureLog","captureNetworkTelemetry","requestedThrottleDelayMs","throttleDelayMs","Number","isFinite","maskAllTextInputs","maskAllImages","maskAllSandboxedViews","toNativeReplayConfig","config","createMobileReplayRecorderAdapter","defaults","apiKey","endpoint","start","args","resolvedReplayConfig","replayConfigDefaults","replayConfig","replayHost","snapshotPath","replaySnapshotPath","activeSessionId","sdkOptions","debug","distinctId","userId","anonymousId","sdkVersion","flushAt","startSession","onSessionId","endSession","flush"],"sourceRoot":"../../../src","sources":["adapters/mobileReplayAdapter.ts"],"mappings":"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","ignoreList":[]}
|