@mixpanel/react-native-session-replay 0.1.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.
Files changed (38) hide show
  1. package/LICENSE +20 -0
  2. package/MixpanelReactNativeSessionReplay.podspec +26 -0
  3. package/README.md +200 -0
  4. package/android/app/build/generated/source/codegen/java/com/facebook/fbreact/specs/NativeMixpanelReactNativeSessionReplaySpec.java +54 -0
  5. package/android/app/build/generated/source/codegen/jni/CMakeLists.txt +36 -0
  6. package/android/app/build/generated/source/codegen/jni/MixpanelReactNativeSessionReplaySpec-generated.cpp +56 -0
  7. package/android/app/build/generated/source/codegen/jni/MixpanelReactNativeSessionReplaySpec.h +31 -0
  8. package/android/app/build/generated/source/codegen/jni/react/renderer/components/MixpanelReactNativeSessionReplaySpec/MixpanelReactNativeSessionReplaySpecJSI-generated.cpp +54 -0
  9. package/android/app/build/generated/source/codegen/jni/react/renderer/components/MixpanelReactNativeSessionReplaySpec/MixpanelReactNativeSessionReplaySpecJSI.h +107 -0
  10. package/android/build.gradle +89 -0
  11. package/android/gradle.properties +5 -0
  12. package/android/src/main/AndroidManifest.xml +2 -0
  13. package/android/src/main/java/com/mixpanelreactnativesessionreplay/MixpanelReactNativeSessionReplayModule.kt +287 -0
  14. package/android/src/main/java/com/mixpanelreactnativesessionreplay/MixpanelReactNativeSessionReplayPackage.kt +38 -0
  15. package/android/src/main/java/com/mixpanelreactnativesessionreplay/MixpanelSessionReplayView.kt +26 -0
  16. package/android/src/main/java/com/mixpanelreactnativesessionreplay/MixpanelSessionReplayViewManager.kt +29 -0
  17. package/ios/MixpanelReactNativeSessionReplay.h +5 -0
  18. package/ios/MixpanelReactNativeSessionReplay.mm +87 -0
  19. package/ios/MixpanelSessionReplayViewManager.mm +36 -0
  20. package/ios/MixpanelSwiftSessionReplay.swift +93 -0
  21. package/lib/module/MixpanelSessionReplayView.js +5 -0
  22. package/lib/module/MixpanelSessionReplayView.js.map +1 -0
  23. package/lib/module/NativeMixpanelReactNativeSessionReplay.js +5 -0
  24. package/lib/module/NativeMixpanelReactNativeSessionReplay.js.map +1 -0
  25. package/lib/module/index.js +188 -0
  26. package/lib/module/index.js.map +1 -0
  27. package/lib/module/package.json +1 -0
  28. package/lib/typescript/package.json +1 -0
  29. package/lib/typescript/src/MixpanelSessionReplayView.d.ts +7 -0
  30. package/lib/typescript/src/MixpanelSessionReplayView.d.ts.map +1 -0
  31. package/lib/typescript/src/NativeMixpanelReactNativeSessionReplay.d.ts +11 -0
  32. package/lib/typescript/src/NativeMixpanelReactNativeSessionReplay.d.ts.map +1 -0
  33. package/lib/typescript/src/index.d.ts +117 -0
  34. package/lib/typescript/src/index.d.ts.map +1 -0
  35. package/package.json +171 -0
  36. package/src/MixpanelSessionReplayView.tsx +10 -0
  37. package/src/NativeMixpanelReactNativeSessionReplay.ts +19 -0
  38. package/src/index.tsx +220 -0
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,287 @@
1
+ package com.mixpanelreactnativesessionreplay
2
+
3
+ import com.facebook.react.bridge.Promise
4
+ import com.facebook.react.bridge.ReactApplicationContext
5
+ import com.facebook.react.module.annotations.ReactModule
6
+ import com.mixpanel.android.sessionreplay.MPSessionReplay
7
+ import com.mixpanel.android.sessionreplay.MPSessionReplayInstance
8
+ import com.mixpanel.android.sessionreplay.MPSessionReplayError
9
+ import com.mixpanel.android.sessionreplay.models.MPSessionReplayConfig
10
+ import com.mixpanel.android.sessionreplay.sensitive_views.AutoMaskedView
11
+ import com.mixpanel.android.sessionreplay.utils.APIConstants
12
+ import org.json.JSONObject
13
+ import org.json.JSONArray
14
+
15
+ @ReactModule(name = MixpanelReactNativeSessionReplayModule.NAME)
16
+ class MixpanelReactNativeSessionReplayModule(reactContext: ReactApplicationContext) :
17
+ NativeMixpanelReactNativeSessionReplaySpec(reactContext) {
18
+
19
+ override fun getName(): String {
20
+ return NAME
21
+ }
22
+
23
+ override fun initialize(token: String, distinctId: String, configJSON: String, promise: Promise) {
24
+ try {
25
+ if (token.isEmpty() || distinctId.isEmpty()) {
26
+ promise.reject("INVALID_CONFIG", "Token and distinctId are required")
27
+ return
28
+ }
29
+
30
+ // Parse JSON configuration
31
+ val config = if (configJSON.isNotEmpty()) {
32
+ try {
33
+ JSONObject(configJSON)
34
+ } catch (e: Exception) {
35
+ println("Mixpanel - Failed to parse config JSON: ${e.message}")
36
+ JSONObject()
37
+ }
38
+ } else {
39
+ JSONObject()
40
+ }
41
+
42
+ // Configure Mixpanel Session Replay
43
+
44
+ val replayConfig = try {
45
+ MPSessionReplayConfig.fromJson(configJSON)
46
+ } catch (e: Exception) {
47
+ println("Mixpanel - Failed to parse config JSON in fromJson: ${e.message}")
48
+ promise.reject("CONFIG_JSON_PARSE_ERROR", "Failed to parse config JSON: ${e.message}", e)
49
+ return
50
+ }
51
+ println("Mixpanel - Config variable fromJson: $replayConfig")
52
+
53
+ // Set library version and name
54
+ APIConstants.setLibVersion(LIB_VERSION)
55
+ APIConstants.setMpLib(MP_LIB)
56
+
57
+ // Initialize Mixpanel Session Replay
58
+ MPSessionReplay.initialize(
59
+ reactApplicationContext,
60
+ token,
61
+ distinctId,
62
+ replayConfig
63
+ ) { result ->
64
+ result.fold(
65
+ onSuccess = { sessionReplayInstance ->
66
+ // Successfully initialized
67
+ sessionReplayInstance?.let { instance ->
68
+ try {
69
+ // Configure AutoMaskedView categories with comprehensive React Native support
70
+ configureSensitiveClasses(instance, replayConfig)
71
+ println("Mixpanel - Session Replay initialized successfully with enhanced view masking")
72
+ promise.resolve(null)
73
+ } catch (e: Exception) {
74
+ println("Mixpanel - Error configuring sensitive classes: ${e.message}")
75
+ // Still resolve as initialization succeeded, but log the configuration error
76
+ promise.resolve(null)
77
+ }
78
+ } ?: run {
79
+ promise.reject(
80
+ "INITIALIZATION_ERROR",
81
+ "Session Replay instance is null after successful initialization"
82
+ )
83
+ }
84
+ },
85
+ onFailure = { error ->
86
+ when (error) {
87
+ is MPSessionReplayError.Disabled -> {
88
+ promise.reject("SESSION_REPLAY_DISABLED", error.reason)
89
+ }
90
+
91
+ is MPSessionReplayError.InitializationError -> {
92
+ promise.reject("INITIALIZATION_ERROR", error.cause.message, error.cause)
93
+ }
94
+
95
+ else -> {
96
+ promise.reject(
97
+ "UNKNOWN_ERROR",
98
+ "An unknown error occurred during initialization",
99
+ error
100
+ )
101
+ }
102
+ }
103
+ }
104
+ )
105
+ }
106
+ } catch (e: Exception) {
107
+ promise.reject("INITIALIZATION_ERROR", e.message, e)
108
+ }
109
+ }
110
+
111
+ override fun startRecording(recordingSessionsPercent: Double, promise: Promise) {
112
+ try {
113
+ MPSessionReplay.getInstance()?.startRecording(sessionsPercent = recordingSessionsPercent)
114
+ promise.resolve(null)
115
+ } catch (e: Exception) {
116
+ promise.reject("START_RECORDING_ERROR", e.message, e)
117
+ }
118
+ }
119
+
120
+ override fun stopRecording(promise: Promise) {
121
+ try {
122
+ MPSessionReplay.getInstance()?.stopRecording()
123
+ promise.resolve(null)
124
+ } catch (e: Exception) {
125
+ promise.reject("STOP_RECORDING_ERROR", e.message, e)
126
+ }
127
+ }
128
+
129
+ override fun isRecording(promise: Promise) {
130
+ try {
131
+ val recording = MPSessionReplay.getInstance()?.isRecording() ?: false
132
+ promise.resolve(recording)
133
+ } catch (e: Exception) {
134
+ promise.reject("IS_RECORDING_ERROR", e.message, e)
135
+ }
136
+ }
137
+
138
+ override fun identify(distinctId: String, promise: Promise) {
139
+ try {
140
+ if (distinctId.isEmpty()) {
141
+ promise.reject("INVALID_DISTINCT_ID", "distinctId is required")
142
+ return
143
+ }
144
+
145
+ MPSessionReplay.getInstance()?.identify(distinctId)
146
+ promise.resolve(null)
147
+ } catch (e: Exception) {
148
+ promise.reject("IDENTIFY_ERROR", e.message, e)
149
+ }
150
+ }
151
+
152
+
153
+ /**
154
+ * Safely configure sensitive classes for comprehensive React Native view masking
155
+ * Supports both old and new React Native architectures with crash prevention
156
+ */
157
+ private fun configureSensitiveClasses(
158
+ mpInstance: MPSessionReplayInstance,
159
+ config: MPSessionReplayConfig
160
+ ) {
161
+ // Always mask input text classes to protect user input fields
162
+ configureInputTextClasses(mpInstance)
163
+
164
+ if (config.autoMaskedViews.contains(AutoMaskedView.Text)) {
165
+ configureTextClasses(mpInstance)
166
+ }
167
+
168
+ if (config.autoMaskedViews.contains(AutoMaskedView.Image)) {
169
+ configureImageClasses(mpInstance)
170
+ }
171
+
172
+ if (config.autoMaskedViews.contains(AutoMaskedView.Web)) {
173
+ configureWebClasses(mpInstance)
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Configure text display view classes for masking
179
+ */
180
+ private fun configureTextClasses(mpInstance: MPSessionReplayInstance) {
181
+ val textClasses = listOf(
182
+ // React Native specific text display views
183
+ "com.facebook.react.views.text.ReactTextView",
184
+ "com.facebook.react.views.text.ReactTextAnchorView",
185
+
186
+ // New Architecture (Fabric) text display views
187
+ "com.facebook.react.fabric.views.text.ReactTextView"
188
+ )
189
+
190
+ textClasses.forEach { className ->
191
+ safeAddSensitiveClass(mpInstance, className, "Text")
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Configure text input view classes for masking
197
+ */
198
+ private fun configureInputTextClasses(mpInstance: MPSessionReplayInstance) {
199
+ val inputTextClasses = listOf(
200
+ // React Native specific text input views
201
+ "com.facebook.react.views.textinput.ReactEditText",
202
+
203
+ // New Architecture (Fabric) text input views
204
+ "com.facebook.react.fabric.views.textinput.ReactEditText"
205
+ )
206
+
207
+ inputTextClasses.forEach { className ->
208
+ safeAddSensitiveClass(mpInstance, className, "Input Text")
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Configure image-related view classes for masking
214
+ */
215
+ private fun configureImageClasses(mpInstance: MPSessionReplayInstance) {
216
+ val imageClasses = listOf(
217
+ // React Native specific image views
218
+ "com.facebook.react.views.image.ReactImageView",
219
+ "com.facebook.react.views.text.frescosupport.FrescoBasedReactTextInlineImageSpan",
220
+
221
+ // New Architecture (Fabric) image views
222
+ "com.facebook.react.fabric.views.image.ReactImageView",
223
+
224
+ // Common third-party image libraries
225
+ "com.facebook.drawee.view.SimpleDraweeView"
226
+ )
227
+
228
+ imageClasses.forEach { className ->
229
+ safeAddSensitiveClass(mpInstance, className, "Image")
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Configure web-related view classes for masking
235
+ */
236
+ private fun configureWebClasses(mpInstance: MPSessionReplayInstance) {
237
+ val webClasses = listOf(
238
+ // React Native WebView (community package)
239
+ "com.reactnativecommunity.webview.RNCWebView",
240
+ "com.reactnativecommunity.webview.RNCWebViewManager.RNCWebView",
241
+
242
+ // Legacy React Native WebView
243
+ "com.facebook.react.views.webview.ReactWebView",
244
+
245
+ // New Architecture (Fabric) web views
246
+ "com.facebook.react.fabric.views.webview.ReactWebView"
247
+ )
248
+
249
+ webClasses.forEach { className ->
250
+ safeAddSensitiveClass(mpInstance, className, "Web")
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Safely add a sensitive class with comprehensive error handling
256
+ * Prevents crashes from ClassNotFoundException or other reflection issues
257
+ */
258
+ private fun safeAddSensitiveClass(
259
+ mpInstance: MPSessionReplayInstance,
260
+ className: String,
261
+ category: String
262
+ ) {
263
+ try {
264
+ val clazz = Class.forName(className)
265
+ mpInstance.addSensitiveClass(clazz)
266
+ println("Mixpanel - Successfully added $category class: $className")
267
+ } catch (e: ClassNotFoundException) {
268
+ // Class not found - this is expected for architecture-specific or optional classes
269
+ println("Mixpanel - $category class not available (expected): $className")
270
+ } catch (e: NoClassDefFoundError) {
271
+ // Missing dependencies - log but don't crash
272
+ println("Mixpanel - $category class dependencies not available: $className - ${e.message}")
273
+ } catch (e: LinkageError) {
274
+ // Linking issues - log but don't crash
275
+ println("Mixpanel - $category class linking error: $className - ${e.message}")
276
+ } catch (e: Exception) {
277
+ // Any other reflection-related issues
278
+ println("Mixpanel - Unexpected error adding $category class $className: ${e.message}")
279
+ }
280
+ }
281
+
282
+ companion object {
283
+ const val NAME = "MixpanelReactNativeSessionReplay"
284
+ const val LIB_VERSION = "0.1.1"
285
+ const val MP_LIB = "react-native-sr"
286
+ }
287
+ }
@@ -0,0 +1,38 @@
1
+ package com.mixpanelreactnativesessionreplay
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+ import com.facebook.react.uimanager.ViewManager
9
+ import java.util.HashMap
10
+
11
+ class MixpanelReactNativeSessionReplayPackage : BaseReactPackage() {
12
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
13
+ return if (name == MixpanelReactNativeSessionReplayModule.NAME) {
14
+ MixpanelReactNativeSessionReplayModule(reactContext)
15
+ } else {
16
+ null
17
+ }
18
+ }
19
+
20
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
21
+ return listOf(MixpanelSessionReplayViewManager())
22
+ }
23
+
24
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
25
+ return ReactModuleInfoProvider {
26
+ val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
27
+ moduleInfos[MixpanelReactNativeSessionReplayModule.NAME] = ReactModuleInfo(
28
+ MixpanelReactNativeSessionReplayModule.NAME,
29
+ MixpanelReactNativeSessionReplayModule.NAME,
30
+ false, // canOverrideExistingModule
31
+ false, // needsEagerInit
32
+ false, // isCxxModule
33
+ true // isTurboModule
34
+ )
35
+ moduleInfos
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,26 @@
1
+ package com.mixpanelreactnativesessionreplay
2
+
3
+ import android.content.Context
4
+ import com.facebook.react.views.view.ReactViewGroup
5
+ import com.mixpanel.android.sessionreplay.MPSessionReplay
6
+
7
+ class MixpanelSessionReplayView(context: Context) : ReactViewGroup(context) {
8
+ private var isSensitive: Boolean = false
9
+
10
+ fun setSensitive(sensitive: Boolean) {
11
+ if (isSensitive == sensitive) return
12
+
13
+ isSensitive = sensitive
14
+ updateViewSensitivity()
15
+ }
16
+
17
+ private fun updateViewSensitivity() {
18
+ MPSessionReplay.getInstance()?.let { sessionReplay ->
19
+ if (isSensitive) {
20
+ sessionReplay.addSensitiveView(this)
21
+ } else {
22
+ sessionReplay.addSafeView(this)
23
+ }
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,29 @@
1
+ package com.mixpanelreactnativesessionreplay
2
+
3
+ import com.facebook.react.module.annotations.ReactModule
4
+ import com.facebook.react.uimanager.ViewGroupManager
5
+ import com.facebook.react.uimanager.ThemedReactContext
6
+ import com.facebook.react.uimanager.annotations.ReactProp
7
+
8
+ @ReactModule(name = MixpanelSessionReplayViewManager.REACT_CLASS)
9
+ class MixpanelSessionReplayViewManager : ViewGroupManager<MixpanelSessionReplayView>() {
10
+
11
+ companion object {
12
+ const val REACT_CLASS = "MixpanelSessionReplayView"
13
+ }
14
+
15
+ override fun getName(): String {
16
+ return REACT_CLASS
17
+ }
18
+
19
+ override fun createViewInstance(reactContext: ThemedReactContext): MixpanelSessionReplayView {
20
+ return MixpanelSessionReplayView(reactContext)
21
+ }
22
+
23
+ @ReactProp(name = "sensitive")
24
+ fun setSensitive(view: MixpanelSessionReplayView, sensitive: Boolean) {
25
+ // log the sensitive property change
26
+ println("MixpanelSessionReplayView - setSensitive called with value: $sensitive for view: $view")
27
+ view.setSensitive(sensitive)
28
+ }
29
+ }
@@ -0,0 +1,5 @@
1
+ #import <MixpanelReactNativeSessionReplaySpec/MixpanelReactNativeSessionReplaySpec.h>
2
+
3
+ @interface MixpanelReactNativeSessionReplay : NSObject <NativeMixpanelReactNativeSessionReplaySpec>
4
+
5
+ @end
@@ -0,0 +1,87 @@
1
+ #import "MixpanelReactNativeSessionReplay.h"
2
+ #if __has_include("MixpanelReactNativeSessionReplay-Swift.h")
3
+ #import <MixpanelReactNativeSessionReplay-Swift.h>
4
+ #else
5
+ #import <MixpanelReactNativeSessionReplay/MixpanelReactNativeSessionReplay-Swift.h>
6
+ #endif
7
+
8
+ @implementation MixpanelReactNativeSessionReplay
9
+ RCT_EXPORT_MODULE()
10
+
11
+ - (void)initialize:(nonnull NSString *)token distinctId:(nonnull NSString *)distinctId configJSON:(nonnull NSString *)configJSON resolve:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
12
+ @try {
13
+ if (!token || !distinctId) {
14
+ reject(@"INVALID_CONFIG", @"Token and distinctId are required", nil);
15
+ return;
16
+ }
17
+ [MixpanelSwiftSessionReplay initialize:token distinctId:distinctId configJSON:configJSON completion:^(BOOL success, NSError * _Nullable error) {
18
+ if (success) {
19
+ resolve(nil);
20
+ } else {
21
+ reject(@"INITIALIZATION_FAILED", error.debugDescription, error);
22
+ }
23
+ }];
24
+ }
25
+ @catch (NSException *exception) {
26
+ reject(@"INITIALIZATION_ERROR", exception.reason, nil);
27
+ }
28
+ }
29
+
30
+ - (void)startRecording:(double)recordingSessionsPercent
31
+ resolve:(RCTPromiseResolveBlock)resolve
32
+ reject:(RCTPromiseRejectBlock)reject {
33
+ @try {
34
+ [MixpanelSwiftSessionReplay startRecordingWithRecordingSessionsPercent:recordingSessionsPercent];
35
+ resolve(nil);
36
+ }
37
+ @catch (NSException *exception) {
38
+ reject(@"START_RECORDING_ERROR", exception.reason, nil);
39
+ }
40
+ }
41
+
42
+ - (void)stopRecording:(RCTPromiseResolveBlock)resolve
43
+ reject:(RCTPromiseRejectBlock)reject {
44
+ @try {
45
+ [MixpanelSwiftSessionReplay stopRecording];
46
+ resolve(nil);
47
+ }
48
+ @catch (NSException *exception) {
49
+ reject(@"STOP_RECORDING_ERROR", exception.reason, nil);
50
+ }
51
+ }
52
+
53
+ - (void)isRecording:(RCTPromiseResolveBlock)resolve
54
+ reject:(RCTPromiseRejectBlock)reject {
55
+ @try {
56
+ BOOL recording = [MixpanelSwiftSessionReplay isRecording];
57
+ resolve(@(recording));
58
+ }
59
+ @catch (NSException *exception) {
60
+ reject(@"IS_RECORDING_ERROR", exception.reason, nil);
61
+ }
62
+ }
63
+
64
+ - (void)identify:(NSString *)distinctId
65
+ resolve:(RCTPromiseResolveBlock)resolve
66
+ reject:(RCTPromiseRejectBlock)reject {
67
+ @try {
68
+ if (!distinctId || [distinctId isEqualToString: @""]) {
69
+ reject(@"INVALID_DISTINCT_ID", @"distinctId is required", nil);
70
+ return;
71
+ }
72
+
73
+ [MixpanelSwiftSessionReplay identify:distinctId];
74
+ resolve(nil);
75
+ }
76
+ @catch (NSException *exception) {
77
+ reject(@"IDENTIFY_ERROR", exception.reason, nil);
78
+ }
79
+ }
80
+
81
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
82
+ (const facebook::react::ObjCTurboModule::InitParams &)params
83
+ {
84
+ return std::make_shared<facebook::react::NativeMixpanelReactNativeSessionReplaySpecJSI>(params);
85
+ }
86
+
87
+ @end
@@ -0,0 +1,36 @@
1
+ #import <UIKit/UIKit.h>
2
+ #import <React/RCTViewManager.h>
3
+ #import <React/RCTView.h>
4
+
5
+ #if __has_include("MixpanelReactNativeSessionReplay-Swift.h")
6
+ #import <MixpanelReactNativeSessionReplay-Swift.h>
7
+ #else
8
+ #import <MixpanelReactNativeSessionReplay/MixpanelReactNativeSessionReplay-Swift.h>
9
+ #endif
10
+
11
+ @interface MixpanelSessionReplayViewManager : RCTViewManager
12
+ @end
13
+
14
+ @implementation MixpanelSessionReplayViewManager
15
+
16
+ RCT_EXPORT_MODULE(MixpanelSessionReplayView)
17
+
18
+ - (UIView *)view
19
+ {
20
+ return [[RCTView alloc] init];
21
+ }
22
+
23
+ RCT_CUSTOM_VIEW_PROPERTY(sensitive, BOOL, RCTView)
24
+ {
25
+ if (json == nil) {
26
+ return;
27
+ }
28
+
29
+ if ([json boolValue] == YES) {
30
+ [MixpanelSwiftSessionReplay setMPReplaySensitiveWithValue:YES view:view];
31
+ } else if ([json boolValue] == NO) {
32
+ [MixpanelSwiftSessionReplay setMPReplaySensitiveWithValue:NO view:view];
33
+ }
34
+ }
35
+
36
+ @end
@@ -0,0 +1,93 @@
1
+ import Foundation
2
+ import UIKit
3
+ import MixpanelSessionReplay
4
+
5
+ @objc public class MixpanelSwiftSessionReplay: NSObject {
6
+ static let libVersion = "0.1.1"
7
+ static let mpLib = "react-native-sr"
8
+
9
+ @objc public static func startRecording(recordingSessionsPercent: Double = 100.0) {
10
+ MPSessionReplay.getInstance()?.startRecording(sessionsPercent: recordingSessionsPercent)
11
+ }
12
+
13
+ @objc public static func stopRecording() {
14
+ MPSessionReplay.getInstance()?.stopRecording()
15
+ }
16
+
17
+ @objc public static func captureScreenshot() {
18
+ MPSessionReplay.getInstance()?.captureScreenshot()
19
+ }
20
+
21
+ @objc public static func initialize(_ token: String, distinctId: String, configJSON: String, completion: @escaping (Bool, NSError?) -> Void) {
22
+ guard let data = configJSON.data(using: .utf8) else {
23
+ let error = createError("Invalid config JSON string", code: 3840)
24
+ completion(false, error)
25
+ return
26
+ }
27
+
28
+ do {
29
+ APIConstants.setLibVersion(libVersion)
30
+ APIConstants.setMpLib(mpLib)
31
+ let config = try MPSessionReplayConfig.from(json: data)
32
+ MPSessionReplay.initialize(token: token, distinctId: distinctId, config: config) { result in
33
+ switch result {
34
+ case .success(_?):
35
+ setSensitiveClasses(config: config)
36
+ completion(true, nil)
37
+
38
+ case .success(nil):
39
+ completion(false, createError("Instance found nil after successful initialisation."))
40
+
41
+ case .failure(let error as MPSessionReplayError):
42
+ let message = switch error {
43
+ case .disabledByRemoteSetting(let message): message
44
+ case .failedToInitialize: "Failed to initialize the SDK: \(error)"
45
+ }
46
+ completion(false, createError(message))
47
+
48
+ case .failure(let error):
49
+ completion(false, createError("Session replay initialization failed: \(error)"))
50
+ }
51
+ }
52
+ } catch {
53
+ completion(false, error as NSError)
54
+ }
55
+ }
56
+
57
+ @objc public static func isRecording() -> Bool {
58
+ return MPSessionReplay.getInstance()?.isRecording ?? false
59
+ }
60
+
61
+ @objc public static func identify(_ distinctId: String) {
62
+ MPSessionReplay.getInstance()?.identify(distinctId: distinctId)
63
+ }
64
+
65
+ @objc public static func setMPReplaySensitive(value: Bool, view: UIView) {
66
+ view.mpReplaySensitive = value
67
+ }
68
+
69
+ private static func createError(_ message: String, code: Int = -1) -> NSError {
70
+ return NSError(domain: "MixpanelSessionReplay",
71
+ code: code,
72
+ userInfo: [NSLocalizedDescriptionKey: message])
73
+ }
74
+
75
+ private static func setSensitiveClasses(config: MPSessionReplayConfig) {
76
+ let legacyTextViewClass: AnyClass? = NSClassFromString("RCTTextView")
77
+ let fabricTextViewClass: AnyClass? = NSClassFromString("RCTParagraphTextView")
78
+ let imageViewClass: AnyClass? = NSClassFromString("RCTImageView")
79
+ let sessionReplay = MPSessionReplay.getInstance()
80
+
81
+ if let imageViewClass, config.autoMaskedViews.contains(.image) {
82
+ sessionReplay?.addSensitiveClass(imageViewClass)
83
+ }
84
+
85
+ if let fabricTextViewClass, config.autoMaskedViews.contains(.text) {
86
+ sessionReplay?.addSensitiveClass(fabricTextViewClass)
87
+ }
88
+
89
+ if let legacyTextViewClass, config.autoMaskedViews.contains(.text) {
90
+ sessionReplay?.addSensitiveClass(legacyTextViewClass)
91
+ }
92
+ }
93
+ }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ import { requireNativeComponent } from 'react-native';
4
+ export const MixpanelSessionReplayView = requireNativeComponent('MixpanelSessionReplayView');
5
+ //# sourceMappingURL=MixpanelSessionReplayView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["requireNativeComponent","MixpanelSessionReplayView"],"sourceRoot":"../../src","sources":["MixpanelSessionReplayView.tsx"],"mappings":";;AAAA,SAASA,sBAAsB,QAAwB,cAAc;AAMrE,OAAO,MAAMC,yBAAyB,GACpCD,sBAAsB,CACpB,2BACF,CAAC","ignoreList":[]}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ import { TurboModuleRegistry } from 'react-native';
4
+ export default TurboModuleRegistry.getEnforcing('MixpanelReactNativeSessionReplay');
5
+ //# sourceMappingURL=NativeMixpanelReactNativeSessionReplay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeMixpanelReactNativeSessionReplay.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;AAelD,eAAeA,mBAAmB,CAACC,YAAY,CAC7C,kCACF,CAAC","ignoreList":[]}