txlemetry-react-native-session-replay 1.6.1 → 2.0.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 +4 -4
- package/android/gradle.properties +5 -5
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/java/com/{posthogreactnativesessionreplay/PosthogReactNativeSessionReplayModule.kt → txlemetryreactnativesessionreplay/TxlemetryReactNativeSessionReplayModule.kt} +232 -232
- package/android/src/main/java/com/{posthogreactnativesessionreplay/PosthogReactNativeSessionReplayPackage.kt → txlemetryreactnativesessionreplay/TxlemetryReactNativeSessionReplayPackage.kt} +3 -3
- package/ios/{PosthogReactNativeSessionReplay.mm → TxlemetryReactNativeSessionReplay.mm} +1 -1
- package/ios/{PosthogReactNativeSessionReplay.swift → TxlemetryReactNativeSessionReplay.swift} +183 -183
- package/lib/commonjs/index.js +1 -1
- package/lib/module/index.js +1 -1
- package/package.json +159 -159
- package/src/index.tsx +2 -2
- package/txlemetry-react-native-session-replay.podspec +6 -6
- /package/ios/{PosthogReactNativeSessionReplay-Bridging-Header.h → TxlemetryReactNativeSessionReplay-Bridging-Header.h} +0 -0
package/android/build.gradle
CHANGED
|
@@ -15,11 +15,11 @@ if (isNewArchitectureEnabled()) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
def getExtOrDefault(name) {
|
|
18
|
-
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["
|
|
18
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["TxlemetryReactNativeSessionReplay_" + name]
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
def getExtOrIntegerDefault(name) {
|
|
22
|
-
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["
|
|
22
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["TxlemetryReactNativeSessionReplay_" + name]).toInteger()
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
def supportsNamespace() {
|
|
@@ -33,7 +33,7 @@ def supportsNamespace() {
|
|
|
33
33
|
|
|
34
34
|
android {
|
|
35
35
|
if (supportsNamespace()) {
|
|
36
|
-
namespace "com.
|
|
36
|
+
namespace "com.txlemetryreactnativesessionreplay"
|
|
37
37
|
|
|
38
38
|
sourceSets {
|
|
39
39
|
main {
|
|
@@ -85,5 +85,5 @@ 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.
|
|
88
|
+
implementation "com.txlemetry:txlemetry-android:4.0.0"
|
|
89
89
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
TxlemetryReactNativeSessionReplay_kotlinVersion=2.0.21
|
|
2
|
+
TxlemetryReactNativeSessionReplay_minSdkVersion=23
|
|
3
|
+
TxlemetryReactNativeSessionReplay_targetSdkVersion=36
|
|
4
|
+
TxlemetryReactNativeSessionReplay_compileSdkVersion=36
|
|
5
|
+
TxlemetryReactNativeSessionReplay_ndkversion=25.1.8937393
|
|
@@ -1,232 +1,232 @@
|
|
|
1
|
-
package com.
|
|
2
|
-
|
|
3
|
-
import android.util.Log
|
|
4
|
-
import com.facebook.react.bridge.Promise
|
|
5
|
-
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
-
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
7
|
-
import com.facebook.react.bridge.ReactMethod
|
|
8
|
-
import com.facebook.react.bridge.ReadableMap
|
|
9
|
-
import com.facebook.react.bridge.UiThreadUtil
|
|
10
|
-
import com.
|
|
11
|
-
import com.
|
|
12
|
-
import com.
|
|
13
|
-
import com.
|
|
14
|
-
import com.
|
|
15
|
-
import com.
|
|
16
|
-
import com.
|
|
17
|
-
import com.
|
|
18
|
-
import java.util.UUID
|
|
19
|
-
|
|
20
|
-
class
|
|
21
|
-
reactContext: ReactApplicationContext,
|
|
22
|
-
) : ReactContextBaseJavaModule(reactContext) {
|
|
23
|
-
override fun getName(): String = NAME
|
|
24
|
-
|
|
25
|
-
@ReactMethod
|
|
26
|
-
fun start(
|
|
27
|
-
sessionId: String,
|
|
28
|
-
sdkOptions: ReadableMap,
|
|
29
|
-
sdkReplayConfig: ReadableMap,
|
|
30
|
-
decideReplayConfig: ReadableMap,
|
|
31
|
-
promise: Promise,
|
|
32
|
-
) {
|
|
33
|
-
val initRunnable =
|
|
34
|
-
Runnable {
|
|
35
|
-
try {
|
|
36
|
-
val uuid = UUID.fromString(sessionId)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
val context = this.reactApplicationContext
|
|
40
|
-
val apiKey = runCatching { sdkOptions.getString("apiKey") }.getOrNull() ?: ""
|
|
41
|
-
val host = runCatching { sdkOptions.getString("host") }.getOrNull() ?:
|
|
42
|
-
val debugValue = runCatching { sdkOptions.getBoolean("debug") }.getOrNull() ?: false
|
|
43
|
-
|
|
44
|
-
val maskAllTextInputs = runCatching { sdkReplayConfig.getBoolean("maskAllTextInputs") }.getOrNull() ?: DEFAULT_MASK_ALL_TEXT_INPUTS
|
|
45
|
-
val maskAllImages = runCatching { sdkReplayConfig.getBoolean("maskAllImages") }.getOrNull() ?: DEFAULT_MASK_ALL_IMAGES
|
|
46
|
-
val captureLog = runCatching { sdkReplayConfig.getBoolean("captureLog") }.getOrNull() ?: DEFAULT_CAPTURE_LOG
|
|
47
|
-
|
|
48
|
-
// read throttleDelayMs and use androidDebouncerDelayMs as a fallback for back compatibility
|
|
49
|
-
val throttleDelayMs =
|
|
50
|
-
if (sdkReplayConfig.hasKey("throttleDelayMs")) {
|
|
51
|
-
sdkReplayConfig.getInt("throttleDelayMs")
|
|
52
|
-
} else if (sdkReplayConfig.hasKey("androidDebouncerDelayMs")) {
|
|
53
|
-
sdkReplayConfig.getInt("androidDebouncerDelayMs")
|
|
54
|
-
} else {
|
|
55
|
-
DEFAULT_THROTTLE_DELAY_MS
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
val sampleRate = runCatching { sdkReplayConfig.getDouble("sampleRate") }.getOrNull()
|
|
59
|
-
|
|
60
|
-
val endpoint = decideReplayConfig.getString("endpoint")
|
|
61
|
-
|
|
62
|
-
val distinctId =
|
|
63
|
-
try {
|
|
64
|
-
sdkOptions.getString("distinctId") ?: ""
|
|
65
|
-
} catch (e: Throwable) {
|
|
66
|
-
logError("parse distinctId", e)
|
|
67
|
-
""
|
|
68
|
-
}
|
|
69
|
-
val anonymousId =
|
|
70
|
-
try {
|
|
71
|
-
sdkOptions.getString("anonymousId") ?: ""
|
|
72
|
-
} catch (e: Throwable) {
|
|
73
|
-
logError("parse anonymousId", e)
|
|
74
|
-
""
|
|
75
|
-
}
|
|
76
|
-
val theSdkVersion = runCatching { sdkOptions.getString("sdkVersion") }.getOrNull()
|
|
77
|
-
|
|
78
|
-
val theFlushAt = runCatching { sdkOptions.getInt("flushAt") }.getOrNull() ?: DEFAULT_FLUSH_AT
|
|
79
|
-
|
|
80
|
-
val config =
|
|
81
|
-
|
|
82
|
-
debug = debugValue
|
|
83
|
-
captureDeepLinks = false
|
|
84
|
-
captureApplicationLifecycleEvents = false
|
|
85
|
-
captureScreenViews = false
|
|
86
|
-
flushAt = theFlushAt
|
|
87
|
-
sessionReplay = true
|
|
88
|
-
sessionReplayConfig.screenshot = true
|
|
89
|
-
sessionReplayConfig.captureLogcat = captureLog
|
|
90
|
-
sessionReplayConfig.throttleDelayMs = throttleDelayMs.toLong()
|
|
91
|
-
sessionReplayConfig.maskAllImages = maskAllImages
|
|
92
|
-
sessionReplayConfig.maskAllTextInputs = maskAllTextInputs
|
|
93
|
-
sessionReplayConfig.sampleRate = sampleRate
|
|
94
|
-
|
|
95
|
-
if (!endpoint.isNullOrEmpty()) {
|
|
96
|
-
snapshotEndpoint = endpoint
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (!theSdkVersion.isNullOrEmpty()) {
|
|
100
|
-
sdkName = "posthog-react-native"
|
|
101
|
-
sdkVersion = theSdkVersion
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
setIdentify(config.cachePreferences, distinctId, anonymousId)
|
|
107
|
-
} catch (e: Throwable) {
|
|
108
|
-
logError("start", e)
|
|
109
|
-
} finally {
|
|
110
|
-
promise.resolve(null)
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// forces the SDK to be initialized on the main thread
|
|
115
|
-
if (UiThreadUtil.isOnUiThread()) {
|
|
116
|
-
initRunnable.run()
|
|
117
|
-
} else {
|
|
118
|
-
UiThreadUtil.runOnUiThread(initRunnable)
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
@ReactMethod
|
|
123
|
-
fun startSession(
|
|
124
|
-
sessionId: String,
|
|
125
|
-
promise: Promise,
|
|
126
|
-
) {
|
|
127
|
-
try {
|
|
128
|
-
val uuid = UUID.fromString(sessionId)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
} catch (e: Throwable) {
|
|
132
|
-
logError("startSession", e)
|
|
133
|
-
} finally {
|
|
134
|
-
promise.resolve(null)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
@ReactMethod
|
|
139
|
-
fun isEnabled(promise: Promise) {
|
|
140
|
-
try {
|
|
141
|
-
promise.resolve(
|
|
142
|
-
} catch (e: Throwable) {
|
|
143
|
-
logError("isEnabled", e)
|
|
144
|
-
promise.resolve(false)
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
@ReactMethod
|
|
149
|
-
fun endSession(promise: Promise) {
|
|
150
|
-
try {
|
|
151
|
-
|
|
152
|
-
} catch (e: Throwable) {
|
|
153
|
-
logError("endSession", e)
|
|
154
|
-
} finally {
|
|
155
|
-
promise.resolve(null)
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
@ReactMethod
|
|
160
|
-
fun identify(
|
|
161
|
-
distinctId: String,
|
|
162
|
-
anonymousId: String,
|
|
163
|
-
promise: Promise,
|
|
164
|
-
) {
|
|
165
|
-
try {
|
|
166
|
-
setIdentify(
|
|
167
|
-
} catch (e: Throwable) {
|
|
168
|
-
logError("identify", e)
|
|
169
|
-
} finally {
|
|
170
|
-
promise.resolve(null)
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
private fun setIdentify(
|
|
175
|
-
cachePreferences:
|
|
176
|
-
distinctId: String,
|
|
177
|
-
anonymousId: String,
|
|
178
|
-
) {
|
|
179
|
-
cachePreferences?.let { preferences ->
|
|
180
|
-
if (anonymousId.isNotEmpty()) {
|
|
181
|
-
preferences.setValue(ANONYMOUS_ID, anonymousId)
|
|
182
|
-
}
|
|
183
|
-
if (distinctId.isNotEmpty()) {
|
|
184
|
-
preferences.setValue(DISTINCT_ID, distinctId)
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
@ReactMethod
|
|
190
|
-
fun startRecording(
|
|
191
|
-
resumeCurrent: Boolean,
|
|
192
|
-
promise: Promise,
|
|
193
|
-
) {
|
|
194
|
-
try {
|
|
195
|
-
|
|
196
|
-
} catch (e: Throwable) {
|
|
197
|
-
logError("startRecording", e)
|
|
198
|
-
} finally {
|
|
199
|
-
promise.resolve(null)
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
@ReactMethod
|
|
204
|
-
fun stopRecording(promise: Promise) {
|
|
205
|
-
try {
|
|
206
|
-
|
|
207
|
-
} catch (e: Throwable) {
|
|
208
|
-
logError("stopRecording", e)
|
|
209
|
-
} finally {
|
|
210
|
-
promise.resolve(null)
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
private fun logError(
|
|
215
|
-
method: String,
|
|
216
|
-
error: Throwable,
|
|
217
|
-
) {
|
|
218
|
-
Log.println(Log.ERROR,
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
companion object {
|
|
222
|
-
const val NAME = "
|
|
223
|
-
const val
|
|
224
|
-
|
|
225
|
-
// Default session replay configuration values
|
|
226
|
-
const val DEFAULT_MASK_ALL_TEXT_INPUTS = true
|
|
227
|
-
const val DEFAULT_MASK_ALL_IMAGES = true
|
|
228
|
-
const val DEFAULT_CAPTURE_LOG = true
|
|
229
|
-
const val DEFAULT_FLUSH_AT = 20
|
|
230
|
-
const val DEFAULT_THROTTLE_DELAY_MS = 1000
|
|
231
|
-
}
|
|
232
|
-
}
|
|
1
|
+
package com.txlemetryreactnativesessionreplay
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.facebook.react.bridge.Promise
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
7
|
+
import com.facebook.react.bridge.ReactMethod
|
|
8
|
+
import com.facebook.react.bridge.ReadableMap
|
|
9
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
10
|
+
import com.txlemetry.Txlemetry
|
|
11
|
+
import com.txlemetry.TxlemetryConfig
|
|
12
|
+
import com.txlemetry.android.TxlemetryAndroid
|
|
13
|
+
import com.txlemetry.android.TxlemetryAndroidConfig
|
|
14
|
+
import com.txlemetry.internal.TxlemetryPreferences
|
|
15
|
+
import com.txlemetry.internal.TxlemetryPreferences.Companion.ANONYMOUS_ID
|
|
16
|
+
import com.txlemetry.internal.TxlemetryPreferences.Companion.DISTINCT_ID
|
|
17
|
+
import com.txlemetry.internal.TxlemetrySessionManager
|
|
18
|
+
import java.util.UUID
|
|
19
|
+
|
|
20
|
+
class TxlemetryReactNativeSessionReplayModule(
|
|
21
|
+
reactContext: ReactApplicationContext,
|
|
22
|
+
) : ReactContextBaseJavaModule(reactContext) {
|
|
23
|
+
override fun getName(): String = NAME
|
|
24
|
+
|
|
25
|
+
@ReactMethod
|
|
26
|
+
fun start(
|
|
27
|
+
sessionId: String,
|
|
28
|
+
sdkOptions: ReadableMap,
|
|
29
|
+
sdkReplayConfig: ReadableMap,
|
|
30
|
+
decideReplayConfig: ReadableMap,
|
|
31
|
+
promise: Promise,
|
|
32
|
+
) {
|
|
33
|
+
val initRunnable =
|
|
34
|
+
Runnable {
|
|
35
|
+
try {
|
|
36
|
+
val uuid = UUID.fromString(sessionId)
|
|
37
|
+
TxlemetrySessionManager.setSessionId(uuid)
|
|
38
|
+
|
|
39
|
+
val context = this.reactApplicationContext
|
|
40
|
+
val apiKey = runCatching { sdkOptions.getString("apiKey") }.getOrNull() ?: ""
|
|
41
|
+
val host = runCatching { sdkOptions.getString("host") }.getOrNull() ?: TxlemetryConfig.DEFAULT_HOST
|
|
42
|
+
val debugValue = runCatching { sdkOptions.getBoolean("debug") }.getOrNull() ?: false
|
|
43
|
+
|
|
44
|
+
val maskAllTextInputs = runCatching { sdkReplayConfig.getBoolean("maskAllTextInputs") }.getOrNull() ?: DEFAULT_MASK_ALL_TEXT_INPUTS
|
|
45
|
+
val maskAllImages = runCatching { sdkReplayConfig.getBoolean("maskAllImages") }.getOrNull() ?: DEFAULT_MASK_ALL_IMAGES
|
|
46
|
+
val captureLog = runCatching { sdkReplayConfig.getBoolean("captureLog") }.getOrNull() ?: DEFAULT_CAPTURE_LOG
|
|
47
|
+
|
|
48
|
+
// read throttleDelayMs and use androidDebouncerDelayMs as a fallback for back compatibility
|
|
49
|
+
val throttleDelayMs =
|
|
50
|
+
if (sdkReplayConfig.hasKey("throttleDelayMs")) {
|
|
51
|
+
sdkReplayConfig.getInt("throttleDelayMs")
|
|
52
|
+
} else if (sdkReplayConfig.hasKey("androidDebouncerDelayMs")) {
|
|
53
|
+
sdkReplayConfig.getInt("androidDebouncerDelayMs")
|
|
54
|
+
} else {
|
|
55
|
+
DEFAULT_THROTTLE_DELAY_MS
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
val sampleRate = runCatching { sdkReplayConfig.getDouble("sampleRate") }.getOrNull()
|
|
59
|
+
|
|
60
|
+
val endpoint = decideReplayConfig.getString("endpoint")
|
|
61
|
+
|
|
62
|
+
val distinctId =
|
|
63
|
+
try {
|
|
64
|
+
sdkOptions.getString("distinctId") ?: ""
|
|
65
|
+
} catch (e: Throwable) {
|
|
66
|
+
logError("parse distinctId", e)
|
|
67
|
+
""
|
|
68
|
+
}
|
|
69
|
+
val anonymousId =
|
|
70
|
+
try {
|
|
71
|
+
sdkOptions.getString("anonymousId") ?: ""
|
|
72
|
+
} catch (e: Throwable) {
|
|
73
|
+
logError("parse anonymousId", e)
|
|
74
|
+
""
|
|
75
|
+
}
|
|
76
|
+
val theSdkVersion = runCatching { sdkOptions.getString("sdkVersion") }.getOrNull()
|
|
77
|
+
|
|
78
|
+
val theFlushAt = runCatching { sdkOptions.getInt("flushAt") }.getOrNull() ?: DEFAULT_FLUSH_AT
|
|
79
|
+
|
|
80
|
+
val config =
|
|
81
|
+
TxlemetryAndroidConfig(apiKey, host).apply {
|
|
82
|
+
debug = debugValue
|
|
83
|
+
captureDeepLinks = false
|
|
84
|
+
captureApplicationLifecycleEvents = false
|
|
85
|
+
captureScreenViews = false
|
|
86
|
+
flushAt = theFlushAt
|
|
87
|
+
sessionReplay = true
|
|
88
|
+
sessionReplayConfig.screenshot = true
|
|
89
|
+
sessionReplayConfig.captureLogcat = captureLog
|
|
90
|
+
sessionReplayConfig.throttleDelayMs = throttleDelayMs.toLong()
|
|
91
|
+
sessionReplayConfig.maskAllImages = maskAllImages
|
|
92
|
+
sessionReplayConfig.maskAllTextInputs = maskAllTextInputs
|
|
93
|
+
sessionReplayConfig.sampleRate = sampleRate
|
|
94
|
+
|
|
95
|
+
if (!endpoint.isNullOrEmpty()) {
|
|
96
|
+
snapshotEndpoint = endpoint
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!theSdkVersion.isNullOrEmpty()) {
|
|
100
|
+
sdkName = "posthog-react-native"
|
|
101
|
+
sdkVersion = theSdkVersion
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
TxlemetryAndroid.setup(context, config)
|
|
105
|
+
|
|
106
|
+
setIdentify(config.cachePreferences, distinctId, anonymousId)
|
|
107
|
+
} catch (e: Throwable) {
|
|
108
|
+
logError("start", e)
|
|
109
|
+
} finally {
|
|
110
|
+
promise.resolve(null)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// forces the SDK to be initialized on the main thread
|
|
115
|
+
if (UiThreadUtil.isOnUiThread()) {
|
|
116
|
+
initRunnable.run()
|
|
117
|
+
} else {
|
|
118
|
+
UiThreadUtil.runOnUiThread(initRunnable)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@ReactMethod
|
|
123
|
+
fun startSession(
|
|
124
|
+
sessionId: String,
|
|
125
|
+
promise: Promise,
|
|
126
|
+
) {
|
|
127
|
+
try {
|
|
128
|
+
val uuid = UUID.fromString(sessionId)
|
|
129
|
+
TxlemetrySessionManager.setSessionId(uuid)
|
|
130
|
+
Txlemetry.startSession()
|
|
131
|
+
} catch (e: Throwable) {
|
|
132
|
+
logError("startSession", e)
|
|
133
|
+
} finally {
|
|
134
|
+
promise.resolve(null)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@ReactMethod
|
|
139
|
+
fun isEnabled(promise: Promise) {
|
|
140
|
+
try {
|
|
141
|
+
promise.resolve(Txlemetry.isSessionReplayActive())
|
|
142
|
+
} catch (e: Throwable) {
|
|
143
|
+
logError("isEnabled", e)
|
|
144
|
+
promise.resolve(false)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@ReactMethod
|
|
149
|
+
fun endSession(promise: Promise) {
|
|
150
|
+
try {
|
|
151
|
+
Txlemetry.endSession()
|
|
152
|
+
} catch (e: Throwable) {
|
|
153
|
+
logError("endSession", e)
|
|
154
|
+
} finally {
|
|
155
|
+
promise.resolve(null)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@ReactMethod
|
|
160
|
+
fun identify(
|
|
161
|
+
distinctId: String,
|
|
162
|
+
anonymousId: String,
|
|
163
|
+
promise: Promise,
|
|
164
|
+
) {
|
|
165
|
+
try {
|
|
166
|
+
setIdentify(Txlemetry.getConfig<TxlemetryConfig>()?.cachePreferences, distinctId, anonymousId)
|
|
167
|
+
} catch (e: Throwable) {
|
|
168
|
+
logError("identify", e)
|
|
169
|
+
} finally {
|
|
170
|
+
promise.resolve(null)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private fun setIdentify(
|
|
175
|
+
cachePreferences: TxlemetryPreferences?,
|
|
176
|
+
distinctId: String,
|
|
177
|
+
anonymousId: String,
|
|
178
|
+
) {
|
|
179
|
+
cachePreferences?.let { preferences ->
|
|
180
|
+
if (anonymousId.isNotEmpty()) {
|
|
181
|
+
preferences.setValue(ANONYMOUS_ID, anonymousId)
|
|
182
|
+
}
|
|
183
|
+
if (distinctId.isNotEmpty()) {
|
|
184
|
+
preferences.setValue(DISTINCT_ID, distinctId)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@ReactMethod
|
|
190
|
+
fun startRecording(
|
|
191
|
+
resumeCurrent: Boolean,
|
|
192
|
+
promise: Promise,
|
|
193
|
+
) {
|
|
194
|
+
try {
|
|
195
|
+
Txlemetry.startSessionReplay(resumeCurrent)
|
|
196
|
+
} catch (e: Throwable) {
|
|
197
|
+
logError("startRecording", e)
|
|
198
|
+
} finally {
|
|
199
|
+
promise.resolve(null)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@ReactMethod
|
|
204
|
+
fun stopRecording(promise: Promise) {
|
|
205
|
+
try {
|
|
206
|
+
Txlemetry.stopSessionReplay()
|
|
207
|
+
} catch (e: Throwable) {
|
|
208
|
+
logError("stopRecording", e)
|
|
209
|
+
} finally {
|
|
210
|
+
promise.resolve(null)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private fun logError(
|
|
215
|
+
method: String,
|
|
216
|
+
error: Throwable,
|
|
217
|
+
) {
|
|
218
|
+
Log.println(Log.ERROR, TXLEMETRY_TAG, "Method $method, error: $error")
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
companion object {
|
|
222
|
+
const val NAME = "TxlemetryReactNativeSessionReplay"
|
|
223
|
+
const val TXLEMETRY_TAG = "Txlemetry"
|
|
224
|
+
|
|
225
|
+
// Default session replay configuration values
|
|
226
|
+
const val DEFAULT_MASK_ALL_TEXT_INPUTS = true
|
|
227
|
+
const val DEFAULT_MASK_ALL_IMAGES = true
|
|
228
|
+
const val DEFAULT_CAPTURE_LOG = true
|
|
229
|
+
const val DEFAULT_FLUSH_AT = 20
|
|
230
|
+
const val DEFAULT_THROTTLE_DELAY_MS = 1000
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
package com.
|
|
1
|
+
package com.txlemetryreactnativesessionreplay
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.ReactPackage
|
|
4
4
|
import com.facebook.react.bridge.NativeModule
|
|
5
5
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
6
|
import com.facebook.react.uimanager.ViewManager
|
|
7
7
|
|
|
8
|
-
class
|
|
8
|
+
class TxlemetryReactNativeSessionReplayPackage : ReactPackage {
|
|
9
9
|
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
|
|
10
|
-
listOf(
|
|
10
|
+
listOf(TxlemetryReactNativeSessionReplayModule(reactContext))
|
|
11
11
|
|
|
12
12
|
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> = emptyList()
|
|
13
13
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#import <React/RCTBridgeModule.h>
|
|
2
2
|
|
|
3
|
-
@interface RCT_EXTERN_MODULE(
|
|
3
|
+
@interface RCT_EXTERN_MODULE(TxlemetryReactNativeSessionReplay, NSObject)
|
|
4
4
|
|
|
5
5
|
RCT_EXTERN_METHOD(start:(NSString)sessionId
|
|
6
6
|
withSdkOptions:(NSDictionary)sdkOptions
|
package/ios/{PosthogReactNativeSessionReplay.swift → TxlemetryReactNativeSessionReplay.swift}
RENAMED
|
@@ -1,183 +1,183 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
/// Meant for internally logging
|
|
4
|
-
private func hedgeLog(_ message: String) {
|
|
5
|
-
print("[
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
@objc(
|
|
9
|
-
class
|
|
10
|
-
private var config:
|
|
11
|
-
|
|
12
|
-
@objc(start:withSdkOptions:withSdkReplayConfig:withDecideReplayConfig:withResolver:withRejecter:)
|
|
13
|
-
func start(
|
|
14
|
-
sessionId: String, sdkOptions: [String: Any], sdkReplayConfig: [String: Any],
|
|
15
|
-
decideReplayConfig: [String: Any], resolve: RCTPromiseResolveBlock,
|
|
16
|
-
reject _: RCTPromiseRejectBlock
|
|
17
|
-
) {
|
|
18
|
-
// we've seen cases where distinctId and anonymousId are not strings, so we need to check and convert them
|
|
19
|
-
guard let sessionIdStr = sessionId as? String else {
|
|
20
|
-
hedgeLog("Invalid sessionId provided: \(sessionId). Expected a string.")
|
|
21
|
-
resolve(nil)
|
|
22
|
-
return
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
let projectToken =
|
|
26
|
-
(sdkOptions["projectToken"] as? String)
|
|
27
|
-
?? (sdkOptions["apiKey"] as? String)
|
|
28
|
-
?? ""
|
|
29
|
-
let host = sdkOptions["host"] as? String ??
|
|
30
|
-
let debug = sdkOptions["debug"] as? Bool ?? false
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
let config =
|
|
35
|
-
config.sessionReplay = true
|
|
36
|
-
config.captureApplicationLifecycleEvents = false
|
|
37
|
-
config.captureScreenViews = false
|
|
38
|
-
config.debug = debug
|
|
39
|
-
config.sessionReplayConfig.screenshotMode = true
|
|
40
|
-
|
|
41
|
-
if #available(iOS 15.0, *) {
|
|
42
|
-
config.surveys = false
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
let maskAllTextInputs = sdkReplayConfig["maskAllTextInputs"] as? Bool ?? true
|
|
46
|
-
config.sessionReplayConfig.maskAllTextInputs = maskAllTextInputs
|
|
47
|
-
|
|
48
|
-
let maskAllImages = sdkReplayConfig["maskAllImages"] as? Bool ?? true
|
|
49
|
-
config.sessionReplayConfig.maskAllImages = maskAllImages
|
|
50
|
-
|
|
51
|
-
let maskAllSandboxedViews = sdkReplayConfig["maskAllSandboxedViews"] as? Bool ?? true
|
|
52
|
-
config.sessionReplayConfig.maskAllSandboxedViews = maskAllSandboxedViews
|
|
53
|
-
|
|
54
|
-
// read throttleDelayMs and use iOSdebouncerDelayMs as a fallback for back compatibility
|
|
55
|
-
let throttleDelayMs =
|
|
56
|
-
(sdkReplayConfig["throttleDelayMs"] as? Int)
|
|
57
|
-
?? (sdkReplayConfig["iOSdebouncerDelayMs"] as? Int)
|
|
58
|
-
?? 1000
|
|
59
|
-
|
|
60
|
-
let timeInterval: TimeInterval = Double(throttleDelayMs) / 1000.0
|
|
61
|
-
config.sessionReplayConfig.throttleDelay = timeInterval
|
|
62
|
-
|
|
63
|
-
let captureNetworkTelemetry = sdkReplayConfig["captureNetworkTelemetry"] as? Bool ?? true
|
|
64
|
-
config.sessionReplayConfig.captureNetworkTelemetry = captureNetworkTelemetry
|
|
65
|
-
|
|
66
|
-
let captureLog = sdkReplayConfig["captureLog"] as? Bool ?? true
|
|
67
|
-
config.sessionReplayConfig.captureLogs = captureLog
|
|
68
|
-
|
|
69
|
-
config.sessionReplayConfig.sampleRate = sdkReplayConfig["sampleRate"] as? NSNumber
|
|
70
|
-
|
|
71
|
-
let screenshotModeBackgroundCapture = sdkReplayConfig["screenshotModeBackgroundCapture"] as? Bool ?? false
|
|
72
|
-
config.sessionReplayConfig.screenshotModeBackgroundCapture = screenshotModeBackgroundCapture
|
|
73
|
-
|
|
74
|
-
let endpoint = decideReplayConfig["endpoint"] as? String ?? ""
|
|
75
|
-
if !endpoint.isEmpty {
|
|
76
|
-
config.snapshotEndpoint = endpoint
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
let distinctId = sdkOptions["distinctId"] as? String ?? ""
|
|
80
|
-
let anonymousId = sdkOptions["anonymousId"] as? String ?? ""
|
|
81
|
-
|
|
82
|
-
let sdkVersion = sdkOptions["sdkVersion"] as? String ?? ""
|
|
83
|
-
|
|
84
|
-
let flushAt = sdkOptions["flushAt"] as? Int ?? 20
|
|
85
|
-
config.flushAt = flushAt
|
|
86
|
-
|
|
87
|
-
if !sdkVersion.isEmpty {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
self.config = config
|
|
95
|
-
|
|
96
|
-
guard let storageManager = self.config?.storageManager else {
|
|
97
|
-
hedgeLog("Storage manager is not available in the config.")
|
|
98
|
-
resolve(nil)
|
|
99
|
-
return
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
setIdentify(storageManager, distinctId: distinctId, anonymousId: anonymousId)
|
|
103
|
-
|
|
104
|
-
resolve(nil)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
@objc(startSession:withResolver:withRejecter:)
|
|
108
|
-
func startSession(
|
|
109
|
-
sessionId: String, resolve: RCTPromiseResolveBlock, reject _: RCTPromiseRejectBlock
|
|
110
|
-
) {
|
|
111
|
-
// we've seen cases where distinctId and anonymousId are not strings, so we need to check and convert them
|
|
112
|
-
guard let sessionIdStr = sessionId as? String else {
|
|
113
|
-
hedgeLog("Invalid sessionId provided: \(sessionId). Expected a string.")
|
|
114
|
-
resolve(nil)
|
|
115
|
-
return
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
resolve(nil)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
@objc(isEnabled:withRejecter:)
|
|
123
|
-
func isEnabled(resolve: RCTPromiseResolveBlock, reject _: RCTPromiseRejectBlock) {
|
|
124
|
-
let isEnabled =
|
|
125
|
-
resolve(isEnabled)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
@objc(endSession:withRejecter:)
|
|
129
|
-
func endSession(resolve: RCTPromiseResolveBlock, reject _: RCTPromiseRejectBlock) {
|
|
130
|
-
|
|
131
|
-
resolve(nil)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
@objc(identify:withAnonymousId:withResolver:withRejecter:)
|
|
135
|
-
func identify(
|
|
136
|
-
distinctId: String, anonymousId: String, resolve: RCTPromiseResolveBlock,
|
|
137
|
-
reject _: RCTPromiseRejectBlock
|
|
138
|
-
) {
|
|
139
|
-
// we've seen cases where distinctId and anonymousId are not strings, so we need to check and convert them
|
|
140
|
-
guard let distinctIdStr = distinctId as? String,
|
|
141
|
-
let anonymousIdStr = anonymousId as? String
|
|
142
|
-
else {
|
|
143
|
-
hedgeLog(
|
|
144
|
-
"Invalid distinctId: \(distinctId) or anonymousId: \(anonymousId) provided. Expected strings."
|
|
145
|
-
)
|
|
146
|
-
resolve(nil)
|
|
147
|
-
return
|
|
148
|
-
}
|
|
149
|
-
guard let storageManager = config?.storageManager else {
|
|
150
|
-
hedgeLog("Storage manager is not available in the config.")
|
|
151
|
-
resolve(nil)
|
|
152
|
-
return
|
|
153
|
-
}
|
|
154
|
-
setIdentify(storageManager, distinctId: distinctIdStr, anonymousId: anonymousIdStr)
|
|
155
|
-
|
|
156
|
-
resolve(nil)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
private func setIdentify(
|
|
160
|
-
_ storageManager:
|
|
161
|
-
) {
|
|
162
|
-
if !anonymousId.isEmpty {
|
|
163
|
-
storageManager.setAnonymousId(anonymousId)
|
|
164
|
-
}
|
|
165
|
-
if !distinctId.isEmpty {
|
|
166
|
-
storageManager.setDistinctId(distinctId)
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
@objc(startRecording:withResolver:withRejecter:)
|
|
171
|
-
func startRecording(
|
|
172
|
-
resumeCurrent: Bool, resolve: RCTPromiseResolveBlock, reject _: RCTPromiseRejectBlock
|
|
173
|
-
) {
|
|
174
|
-
|
|
175
|
-
resolve(nil)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
@objc(stopRecording:withRejecter:)
|
|
179
|
-
func stopRecording(resolve: RCTPromiseResolveBlock, reject _: RCTPromiseRejectBlock) {
|
|
180
|
-
|
|
181
|
-
resolve(nil)
|
|
182
|
-
}
|
|
183
|
-
}
|
|
1
|
+
import Txlemetry
|
|
2
|
+
|
|
3
|
+
/// Meant for internally logging Txlemetry related things
|
|
4
|
+
private func hedgeLog(_ message: String) {
|
|
5
|
+
print("[Txlemetry] \(message)")
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@objc(TxlemetryReactNativeSessionReplay)
|
|
9
|
+
class TxlemetryReactNativeSessionReplay: NSObject {
|
|
10
|
+
private var config: TxlemetryConfig?
|
|
11
|
+
|
|
12
|
+
@objc(start:withSdkOptions:withSdkReplayConfig:withDecideReplayConfig:withResolver:withRejecter:)
|
|
13
|
+
func start(
|
|
14
|
+
sessionId: String, sdkOptions: [String: Any], sdkReplayConfig: [String: Any],
|
|
15
|
+
decideReplayConfig: [String: Any], resolve: RCTPromiseResolveBlock,
|
|
16
|
+
reject _: RCTPromiseRejectBlock
|
|
17
|
+
) {
|
|
18
|
+
// we've seen cases where distinctId and anonymousId are not strings, so we need to check and convert them
|
|
19
|
+
guard let sessionIdStr = sessionId as? String else {
|
|
20
|
+
hedgeLog("Invalid sessionId provided: \(sessionId). Expected a string.")
|
|
21
|
+
resolve(nil)
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let projectToken =
|
|
26
|
+
(sdkOptions["projectToken"] as? String)
|
|
27
|
+
?? (sdkOptions["apiKey"] as? String)
|
|
28
|
+
?? ""
|
|
29
|
+
let host = sdkOptions["host"] as? String ?? TxlemetryConfig.defaultHost
|
|
30
|
+
let debug = sdkOptions["debug"] as? Bool ?? false
|
|
31
|
+
|
|
32
|
+
TxlemetrySessionManager.shared.setSessionId(sessionIdStr)
|
|
33
|
+
|
|
34
|
+
let config = TxlemetryConfig(projectToken: projectToken, host: host)
|
|
35
|
+
config.sessionReplay = true
|
|
36
|
+
config.captureApplicationLifecycleEvents = false
|
|
37
|
+
config.captureScreenViews = false
|
|
38
|
+
config.debug = debug
|
|
39
|
+
config.sessionReplayConfig.screenshotMode = true
|
|
40
|
+
|
|
41
|
+
if #available(iOS 15.0, *) {
|
|
42
|
+
config.surveys = false
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let maskAllTextInputs = sdkReplayConfig["maskAllTextInputs"] as? Bool ?? true
|
|
46
|
+
config.sessionReplayConfig.maskAllTextInputs = maskAllTextInputs
|
|
47
|
+
|
|
48
|
+
let maskAllImages = sdkReplayConfig["maskAllImages"] as? Bool ?? true
|
|
49
|
+
config.sessionReplayConfig.maskAllImages = maskAllImages
|
|
50
|
+
|
|
51
|
+
let maskAllSandboxedViews = sdkReplayConfig["maskAllSandboxedViews"] as? Bool ?? true
|
|
52
|
+
config.sessionReplayConfig.maskAllSandboxedViews = maskAllSandboxedViews
|
|
53
|
+
|
|
54
|
+
// read throttleDelayMs and use iOSdebouncerDelayMs as a fallback for back compatibility
|
|
55
|
+
let throttleDelayMs =
|
|
56
|
+
(sdkReplayConfig["throttleDelayMs"] as? Int)
|
|
57
|
+
?? (sdkReplayConfig["iOSdebouncerDelayMs"] as? Int)
|
|
58
|
+
?? 1000
|
|
59
|
+
|
|
60
|
+
let timeInterval: TimeInterval = Double(throttleDelayMs) / 1000.0
|
|
61
|
+
config.sessionReplayConfig.throttleDelay = timeInterval
|
|
62
|
+
|
|
63
|
+
let captureNetworkTelemetry = sdkReplayConfig["captureNetworkTelemetry"] as? Bool ?? true
|
|
64
|
+
config.sessionReplayConfig.captureNetworkTelemetry = captureNetworkTelemetry
|
|
65
|
+
|
|
66
|
+
let captureLog = sdkReplayConfig["captureLog"] as? Bool ?? true
|
|
67
|
+
config.sessionReplayConfig.captureLogs = captureLog
|
|
68
|
+
|
|
69
|
+
config.sessionReplayConfig.sampleRate = sdkReplayConfig["sampleRate"] as? NSNumber
|
|
70
|
+
|
|
71
|
+
let screenshotModeBackgroundCapture = sdkReplayConfig["screenshotModeBackgroundCapture"] as? Bool ?? false
|
|
72
|
+
config.sessionReplayConfig.screenshotModeBackgroundCapture = screenshotModeBackgroundCapture
|
|
73
|
+
|
|
74
|
+
let endpoint = decideReplayConfig["endpoint"] as? String ?? ""
|
|
75
|
+
if !endpoint.isEmpty {
|
|
76
|
+
config.snapshotEndpoint = endpoint
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let distinctId = sdkOptions["distinctId"] as? String ?? ""
|
|
80
|
+
let anonymousId = sdkOptions["anonymousId"] as? String ?? ""
|
|
81
|
+
|
|
82
|
+
let sdkVersion = sdkOptions["sdkVersion"] as? String ?? ""
|
|
83
|
+
|
|
84
|
+
let flushAt = sdkOptions["flushAt"] as? Int ?? 20
|
|
85
|
+
config.flushAt = flushAt
|
|
86
|
+
|
|
87
|
+
if !sdkVersion.isEmpty {
|
|
88
|
+
txlemetrySdkName = "posthog-react-native"
|
|
89
|
+
txlemetryVersion = sdkVersion
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
TxlemetrySDK.shared.setup(config)
|
|
93
|
+
|
|
94
|
+
self.config = config
|
|
95
|
+
|
|
96
|
+
guard let storageManager = self.config?.storageManager else {
|
|
97
|
+
hedgeLog("Storage manager is not available in the config.")
|
|
98
|
+
resolve(nil)
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
setIdentify(storageManager, distinctId: distinctId, anonymousId: anonymousId)
|
|
103
|
+
|
|
104
|
+
resolve(nil)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@objc(startSession:withResolver:withRejecter:)
|
|
108
|
+
func startSession(
|
|
109
|
+
sessionId: String, resolve: RCTPromiseResolveBlock, reject _: RCTPromiseRejectBlock
|
|
110
|
+
) {
|
|
111
|
+
// we've seen cases where distinctId and anonymousId are not strings, so we need to check and convert them
|
|
112
|
+
guard let sessionIdStr = sessionId as? String else {
|
|
113
|
+
hedgeLog("Invalid sessionId provided: \(sessionId). Expected a string.")
|
|
114
|
+
resolve(nil)
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
TxlemetrySessionManager.shared.setSessionId(sessionIdStr)
|
|
118
|
+
TxlemetrySDK.shared.startSession()
|
|
119
|
+
resolve(nil)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@objc(isEnabled:withRejecter:)
|
|
123
|
+
func isEnabled(resolve: RCTPromiseResolveBlock, reject _: RCTPromiseRejectBlock) {
|
|
124
|
+
let isEnabled = TxlemetrySDK.shared.isSessionReplayActive()
|
|
125
|
+
resolve(isEnabled)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@objc(endSession:withRejecter:)
|
|
129
|
+
func endSession(resolve: RCTPromiseResolveBlock, reject _: RCTPromiseRejectBlock) {
|
|
130
|
+
TxlemetrySDK.shared.endSession()
|
|
131
|
+
resolve(nil)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@objc(identify:withAnonymousId:withResolver:withRejecter:)
|
|
135
|
+
func identify(
|
|
136
|
+
distinctId: String, anonymousId: String, resolve: RCTPromiseResolveBlock,
|
|
137
|
+
reject _: RCTPromiseRejectBlock
|
|
138
|
+
) {
|
|
139
|
+
// we've seen cases where distinctId and anonymousId are not strings, so we need to check and convert them
|
|
140
|
+
guard let distinctIdStr = distinctId as? String,
|
|
141
|
+
let anonymousIdStr = anonymousId as? String
|
|
142
|
+
else {
|
|
143
|
+
hedgeLog(
|
|
144
|
+
"Invalid distinctId: \(distinctId) or anonymousId: \(anonymousId) provided. Expected strings."
|
|
145
|
+
)
|
|
146
|
+
resolve(nil)
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
guard let storageManager = config?.storageManager else {
|
|
150
|
+
hedgeLog("Storage manager is not available in the config.")
|
|
151
|
+
resolve(nil)
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
setIdentify(storageManager, distinctId: distinctIdStr, anonymousId: anonymousIdStr)
|
|
155
|
+
|
|
156
|
+
resolve(nil)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private func setIdentify(
|
|
160
|
+
_ storageManager: TxlemetryStorageManager, distinctId: String, anonymousId: String
|
|
161
|
+
) {
|
|
162
|
+
if !anonymousId.isEmpty {
|
|
163
|
+
storageManager.setAnonymousId(anonymousId)
|
|
164
|
+
}
|
|
165
|
+
if !distinctId.isEmpty {
|
|
166
|
+
storageManager.setDistinctId(distinctId)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@objc(startRecording:withResolver:withRejecter:)
|
|
171
|
+
func startRecording(
|
|
172
|
+
resumeCurrent: Bool, resolve: RCTPromiseResolveBlock, reject _: RCTPromiseRejectBlock
|
|
173
|
+
) {
|
|
174
|
+
TxlemetrySDK.shared.startSessionRecording(resumeCurrent: resumeCurrent)
|
|
175
|
+
resolve(nil)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@objc(stopRecording:withRejecter:)
|
|
179
|
+
func stopRecording(resolve: RCTPromiseResolveBlock, reject _: RCTPromiseRejectBlock) {
|
|
180
|
+
TxlemetrySDK.shared.stopSessionRecording()
|
|
181
|
+
resolve(nil)
|
|
182
|
+
}
|
|
183
|
+
}
|
package/lib/commonjs/index.js
CHANGED
|
@@ -16,7 +16,7 @@ const LINKING_ERROR = `The package 'txlemetry-react-native-session-replay' doesn
|
|
|
16
16
|
ios: "- You have run 'pod install'\n",
|
|
17
17
|
default: ''
|
|
18
18
|
}) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo Go\n';
|
|
19
|
-
const nativeSessionReplay = _reactNative.NativeModules.TxlemetryReactNativeSessionReplay || _reactNative.NativeModules.
|
|
19
|
+
const nativeSessionReplay = _reactNative.NativeModules.TxlemetryReactNativeSessionReplay || _reactNative.NativeModules.TxlemetryReactNativeSessionReplay ? _reactNative.NativeModules.TxlemetryReactNativeSessionReplay || _reactNative.NativeModules.TxlemetryReactNativeSessionReplay : new Proxy({}, {
|
|
20
20
|
get() {
|
|
21
21
|
throw new Error(LINKING_ERROR);
|
|
22
22
|
}
|
package/lib/module/index.js
CHANGED
|
@@ -5,7 +5,7 @@ const LINKING_ERROR = `The package 'txlemetry-react-native-session-replay' doesn
|
|
|
5
5
|
ios: "- You have run 'pod install'\n",
|
|
6
6
|
default: ''
|
|
7
7
|
}) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo Go\n';
|
|
8
|
-
const nativeSessionReplay = NativeModules.TxlemetryReactNativeSessionReplay
|
|
8
|
+
const nativeSessionReplay = NativeModules.TxlemetryReactNativeSessionReplay ? NativeModules.TxlemetryReactNativeSessionReplay : new Proxy({}, {
|
|
9
9
|
get() {
|
|
10
10
|
throw new Error(LINKING_ERROR);
|
|
11
11
|
}
|
package/package.json
CHANGED
|
@@ -1,159 +1,159 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "txlemetry-react-native-session-replay",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Txlemetry session replay for React Native",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"android",
|
|
7
|
-
"ios",
|
|
8
|
-
"react-native"
|
|
9
|
-
],
|
|
10
|
-
"homepage": "https://txlemetry.com",
|
|
11
|
-
"bugs": {
|
|
12
|
-
"url": "https://github.com/hectorvss/Txlemetry/issues"
|
|
13
|
-
},
|
|
14
|
-
"license": "MIT",
|
|
15
|
-
"author": "Txlemetry",
|
|
16
|
-
"repository": {
|
|
17
|
-
"type": "git",
|
|
18
|
-
"url": "https://github.com/hectorvss/Txlemetry.git",
|
|
19
|
-
"directory": "txlemetry-sdk/react-native-session-replay"
|
|
20
|
-
},
|
|
21
|
-
"source": "./src/index.tsx",
|
|
22
|
-
"files": [
|
|
23
|
-
"src",
|
|
24
|
-
"lib",
|
|
25
|
-
"android",
|
|
26
|
-
"ios",
|
|
27
|
-
"cpp",
|
|
28
|
-
"txlemetry-react-native-session-replay.podspec",
|
|
29
|
-
"!ios/build",
|
|
30
|
-
"!android/build",
|
|
31
|
-
"!android/gradle",
|
|
32
|
-
"!android/gradlew",
|
|
33
|
-
"!android/gradlew.bat",
|
|
34
|
-
"!android/local.properties",
|
|
35
|
-
"!**/__tests__",
|
|
36
|
-
"!**/__fixtures__",
|
|
37
|
-
"!**/__mocks__",
|
|
38
|
-
"!**/.*",
|
|
39
|
-
"!**/*.map"
|
|
40
|
-
],
|
|
41
|
-
"main": "./lib/commonjs/index.js",
|
|
42
|
-
"module": "./lib/module/index.js",
|
|
43
|
-
"types": "./lib/typescript/module/src/index.d.ts",
|
|
44
|
-
"exports": {
|
|
45
|
-
".": {
|
|
46
|
-
"import": {
|
|
47
|
-
"types": "./lib/typescript/module/src/index.d.ts",
|
|
48
|
-
"default": "./lib/module/index.js"
|
|
49
|
-
},
|
|
50
|
-
"require": {
|
|
51
|
-
"types": "./lib/typescript/commonjs/src/index.d.ts",
|
|
52
|
-
"default": "./lib/commonjs/index.js"
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
"publishConfig": {
|
|
57
|
-
"access": "public",
|
|
58
|
-
"registry": "https://registry.npmjs.org/"
|
|
59
|
-
},
|
|
60
|
-
"scripts": {
|
|
61
|
-
"example": "pnpm --filter txlemetry-react-native-session-replay-example",
|
|
62
|
-
"test": "jest",
|
|
63
|
-
"typecheck": "tsc",
|
|
64
|
-
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
65
|
-
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
|
|
66
|
-
"changeset": "changeset"
|
|
67
|
-
},
|
|
68
|
-
"devDependencies": {
|
|
69
|
-
"@changesets/cli": "^2.27.0",
|
|
70
|
-
"@evilmartians/lefthook": "^1.5.0",
|
|
71
|
-
"@react-native-community/cli": "20.0.0",
|
|
72
|
-
"@react-native/eslint-config": "^0.73.1",
|
|
73
|
-
"@types/jest": "^29.5.5",
|
|
74
|
-
"@types/react": "^18.2.44",
|
|
75
|
-
"del-cli": "^5.1.0",
|
|
76
|
-
"eslint": "^8.51.0",
|
|
77
|
-
"eslint-config-prettier": "^9.0.0",
|
|
78
|
-
"eslint-plugin-prettier": "^5.0.1",
|
|
79
|
-
"jest": "^29.7.0",
|
|
80
|
-
"prettier": "^3.0.3",
|
|
81
|
-
"react": "18.3.1",
|
|
82
|
-
"react-native": "0.75.3",
|
|
83
|
-
"react-native-builder-bob": "^0.30.2",
|
|
84
|
-
"turbo": "^2.0.0",
|
|
85
|
-
"typescript": "^5.2.2"
|
|
86
|
-
},
|
|
87
|
-
"peerDependencies": {
|
|
88
|
-
"react": "*",
|
|
89
|
-
"react-native": "*"
|
|
90
|
-
},
|
|
91
|
-
"prettier": {
|
|
92
|
-
"quoteProps": "consistent",
|
|
93
|
-
"singleQuote": true,
|
|
94
|
-
"tabWidth": 2,
|
|
95
|
-
"trailingComma": "es5",
|
|
96
|
-
"useTabs": false
|
|
97
|
-
},
|
|
98
|
-
"eslintConfig": {
|
|
99
|
-
"extends": [
|
|
100
|
-
"@react-native",
|
|
101
|
-
"prettier"
|
|
102
|
-
],
|
|
103
|
-
"root": true,
|
|
104
|
-
"rules": {
|
|
105
|
-
"prettier/prettier": [
|
|
106
|
-
"error",
|
|
107
|
-
{
|
|
108
|
-
"quoteProps": "consistent",
|
|
109
|
-
"singleQuote": true,
|
|
110
|
-
"tabWidth": 2,
|
|
111
|
-
"trailingComma": "es5",
|
|
112
|
-
"useTabs": false
|
|
113
|
-
}
|
|
114
|
-
],
|
|
115
|
-
"react/react-in-jsx-scope": "off"
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
"eslintIgnore": [
|
|
119
|
-
"node_modules/",
|
|
120
|
-
"lib/"
|
|
121
|
-
],
|
|
122
|
-
"jest": {
|
|
123
|
-
"modulePathIgnorePatterns": [
|
|
124
|
-
"<rootDir>/example/node_modules",
|
|
125
|
-
"<rootDir>/lib/"
|
|
126
|
-
],
|
|
127
|
-
"preset": "react-native"
|
|
128
|
-
},
|
|
129
|
-
"create-react-native-library": {
|
|
130
|
-
"type": "module-legacy",
|
|
131
|
-
"languages": "kotlin-swift",
|
|
132
|
-
"version": "0.41.2"
|
|
133
|
-
},
|
|
134
|
-
"react-native-builder-bob": {
|
|
135
|
-
"source": "src",
|
|
136
|
-
"output": "lib",
|
|
137
|
-
"targets": [
|
|
138
|
-
[
|
|
139
|
-
"commonjs",
|
|
140
|
-
{
|
|
141
|
-
"esm": true
|
|
142
|
-
}
|
|
143
|
-
],
|
|
144
|
-
[
|
|
145
|
-
"module",
|
|
146
|
-
{
|
|
147
|
-
"esm": true
|
|
148
|
-
}
|
|
149
|
-
],
|
|
150
|
-
[
|
|
151
|
-
"typescript",
|
|
152
|
-
{
|
|
153
|
-
"project": "tsconfig.build.json",
|
|
154
|
-
"esm": true
|
|
155
|
-
}
|
|
156
|
-
]
|
|
157
|
-
]
|
|
158
|
-
}
|
|
159
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "txlemetry-react-native-session-replay",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Txlemetry session replay for React Native",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"android",
|
|
7
|
+
"ios",
|
|
8
|
+
"react-native"
|
|
9
|
+
],
|
|
10
|
+
"homepage": "https://txlemetry.com",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/hectorvss/Txlemetry/issues"
|
|
13
|
+
},
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Txlemetry",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/hectorvss/Txlemetry.git",
|
|
19
|
+
"directory": "txlemetry-sdk/react-native-session-replay"
|
|
20
|
+
},
|
|
21
|
+
"source": "./src/index.tsx",
|
|
22
|
+
"files": [
|
|
23
|
+
"src",
|
|
24
|
+
"lib",
|
|
25
|
+
"android",
|
|
26
|
+
"ios",
|
|
27
|
+
"cpp",
|
|
28
|
+
"txlemetry-react-native-session-replay.podspec",
|
|
29
|
+
"!ios/build",
|
|
30
|
+
"!android/build",
|
|
31
|
+
"!android/gradle",
|
|
32
|
+
"!android/gradlew",
|
|
33
|
+
"!android/gradlew.bat",
|
|
34
|
+
"!android/local.properties",
|
|
35
|
+
"!**/__tests__",
|
|
36
|
+
"!**/__fixtures__",
|
|
37
|
+
"!**/__mocks__",
|
|
38
|
+
"!**/.*",
|
|
39
|
+
"!**/*.map"
|
|
40
|
+
],
|
|
41
|
+
"main": "./lib/commonjs/index.js",
|
|
42
|
+
"module": "./lib/module/index.js",
|
|
43
|
+
"types": "./lib/typescript/module/src/index.d.ts",
|
|
44
|
+
"exports": {
|
|
45
|
+
".": {
|
|
46
|
+
"import": {
|
|
47
|
+
"types": "./lib/typescript/module/src/index.d.ts",
|
|
48
|
+
"default": "./lib/module/index.js"
|
|
49
|
+
},
|
|
50
|
+
"require": {
|
|
51
|
+
"types": "./lib/typescript/commonjs/src/index.d.ts",
|
|
52
|
+
"default": "./lib/commonjs/index.js"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public",
|
|
58
|
+
"registry": "https://registry.npmjs.org/"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"example": "pnpm --filter txlemetry-react-native-session-replay-example",
|
|
62
|
+
"test": "jest",
|
|
63
|
+
"typecheck": "tsc",
|
|
64
|
+
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
65
|
+
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
|
|
66
|
+
"changeset": "changeset"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@changesets/cli": "^2.27.0",
|
|
70
|
+
"@evilmartians/lefthook": "^1.5.0",
|
|
71
|
+
"@react-native-community/cli": "20.0.0",
|
|
72
|
+
"@react-native/eslint-config": "^0.73.1",
|
|
73
|
+
"@types/jest": "^29.5.5",
|
|
74
|
+
"@types/react": "^18.2.44",
|
|
75
|
+
"del-cli": "^5.1.0",
|
|
76
|
+
"eslint": "^8.51.0",
|
|
77
|
+
"eslint-config-prettier": "^9.0.0",
|
|
78
|
+
"eslint-plugin-prettier": "^5.0.1",
|
|
79
|
+
"jest": "^29.7.0",
|
|
80
|
+
"prettier": "^3.0.3",
|
|
81
|
+
"react": "18.3.1",
|
|
82
|
+
"react-native": "0.75.3",
|
|
83
|
+
"react-native-builder-bob": "^0.30.2",
|
|
84
|
+
"turbo": "^2.0.0",
|
|
85
|
+
"typescript": "^5.2.2"
|
|
86
|
+
},
|
|
87
|
+
"peerDependencies": {
|
|
88
|
+
"react": "*",
|
|
89
|
+
"react-native": "*"
|
|
90
|
+
},
|
|
91
|
+
"prettier": {
|
|
92
|
+
"quoteProps": "consistent",
|
|
93
|
+
"singleQuote": true,
|
|
94
|
+
"tabWidth": 2,
|
|
95
|
+
"trailingComma": "es5",
|
|
96
|
+
"useTabs": false
|
|
97
|
+
},
|
|
98
|
+
"eslintConfig": {
|
|
99
|
+
"extends": [
|
|
100
|
+
"@react-native",
|
|
101
|
+
"prettier"
|
|
102
|
+
],
|
|
103
|
+
"root": true,
|
|
104
|
+
"rules": {
|
|
105
|
+
"prettier/prettier": [
|
|
106
|
+
"error",
|
|
107
|
+
{
|
|
108
|
+
"quoteProps": "consistent",
|
|
109
|
+
"singleQuote": true,
|
|
110
|
+
"tabWidth": 2,
|
|
111
|
+
"trailingComma": "es5",
|
|
112
|
+
"useTabs": false
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
"react/react-in-jsx-scope": "off"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"eslintIgnore": [
|
|
119
|
+
"node_modules/",
|
|
120
|
+
"lib/"
|
|
121
|
+
],
|
|
122
|
+
"jest": {
|
|
123
|
+
"modulePathIgnorePatterns": [
|
|
124
|
+
"<rootDir>/example/node_modules",
|
|
125
|
+
"<rootDir>/lib/"
|
|
126
|
+
],
|
|
127
|
+
"preset": "react-native"
|
|
128
|
+
},
|
|
129
|
+
"create-react-native-library": {
|
|
130
|
+
"type": "module-legacy",
|
|
131
|
+
"languages": "kotlin-swift",
|
|
132
|
+
"version": "0.41.2"
|
|
133
|
+
},
|
|
134
|
+
"react-native-builder-bob": {
|
|
135
|
+
"source": "src",
|
|
136
|
+
"output": "lib",
|
|
137
|
+
"targets": [
|
|
138
|
+
[
|
|
139
|
+
"commonjs",
|
|
140
|
+
{
|
|
141
|
+
"esm": true
|
|
142
|
+
}
|
|
143
|
+
],
|
|
144
|
+
[
|
|
145
|
+
"module",
|
|
146
|
+
{
|
|
147
|
+
"esm": true
|
|
148
|
+
}
|
|
149
|
+
],
|
|
150
|
+
[
|
|
151
|
+
"typescript",
|
|
152
|
+
{
|
|
153
|
+
"project": "tsconfig.build.json",
|
|
154
|
+
"esm": true
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -7,8 +7,8 @@ const LINKING_ERROR =
|
|
|
7
7
|
'- You are not using Expo Go\n';
|
|
8
8
|
|
|
9
9
|
const nativeSessionReplay =
|
|
10
|
-
NativeModules.TxlemetryReactNativeSessionReplay
|
|
11
|
-
? NativeModules.TxlemetryReactNativeSessionReplay
|
|
10
|
+
NativeModules.TxlemetryReactNativeSessionReplay
|
|
11
|
+
? NativeModules.TxlemetryReactNativeSessionReplay
|
|
12
12
|
: new Proxy(
|
|
13
13
|
{},
|
|
14
14
|
{
|
|
@@ -6,7 +6,7 @@ folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1
|
|
|
6
6
|
# Single source of truth for the native dependency version.
|
|
7
7
|
# Used by both the SPM and CocoaPods resolution paths below; bump this
|
|
8
8
|
# line when picking up a new native SDK release.
|
|
9
|
-
|
|
9
|
+
txlemetry_ios_version = '4.0.0'
|
|
10
10
|
|
|
11
11
|
Pod::Spec.new do |s|
|
|
12
12
|
s.name = "txlemetry-react-native-session-replay"
|
|
@@ -17,7 +17,7 @@ Pod::Spec.new do |s|
|
|
|
17
17
|
s.authors = package["author"]
|
|
18
18
|
|
|
19
19
|
s.platforms = { :ios => min_ios_version_supported }
|
|
20
|
-
s.source = { :
|
|
20
|
+
s.source = { :http => "https://app.txlemetry.com/sdk-artifacts/react-native/react-native-session-replay/#{s.version}/source.zip" }
|
|
21
21
|
|
|
22
22
|
s.source_files = "ios/**/*.{swift,h,hpp,m,mm,c,cpp}"
|
|
23
23
|
|
|
@@ -32,12 +32,12 @@ Pod::Spec.new do |s|
|
|
|
32
32
|
|
|
33
33
|
if txlemetry_use_spm && respond_to?(:spm_dependency, true)
|
|
34
34
|
spm_dependency(s,
|
|
35
|
-
url: 'https://github.com/
|
|
36
|
-
requirement: { kind: 'upToNextMinorVersion', minimumVersion:
|
|
37
|
-
products: ['
|
|
35
|
+
url: ENV.fetch('TXLEMETRY_SWIFT_PACKAGE_URL', 'https://github.com/hectorvss/txlemetry-ios.git'),
|
|
36
|
+
requirement: { kind: 'upToNextMinorVersion', minimumVersion: txlemetry_ios_version },
|
|
37
|
+
products: ['Txlemetry']
|
|
38
38
|
)
|
|
39
39
|
else
|
|
40
|
-
s.dependency '
|
|
40
|
+
s.dependency 'Txlemetry', "~> #{txlemetry_ios_version}"
|
|
41
41
|
end
|
|
42
42
|
s.ios.deployment_target = '13.0'
|
|
43
43
|
s.swift_versions = "5.3"
|