react-native-userleap 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/userleap/reactnative/UserLeapModule.kt +10 -0
- package/index.d.ts +13 -0
- package/index.js +15 -0
- package/ios/UserLeapBindings.h +2 -1
- package/ios/UserLeapBindings.mm +45 -5
- package/package.json +1 -1
- package/react-native-userleap.podspec +1 -1
- package/src/NativeUserLeapBindings.ts +7 -0
package/android/build.gradle
CHANGED
|
@@ -85,6 +85,6 @@ dependencies {
|
|
|
85
85
|
if (findProject(':userleap-android-sdk') != null) {
|
|
86
86
|
implementation project(':userleap-android-sdk')
|
|
87
87
|
} else {
|
|
88
|
-
implementation ("com.userleap:userleap-android-sdk:2.
|
|
88
|
+
implementation ("com.userleap:userleap-android-sdk:2.28.0") // update this version on android updates
|
|
89
89
|
}
|
|
90
90
|
}
|
|
@@ -17,6 +17,7 @@ import com.userleap.SprigSurveyResult
|
|
|
17
17
|
import com.userleap.SurveyState
|
|
18
18
|
import com.userleap.UserLeap
|
|
19
19
|
import com.userleap.SprigUserInterfaceMode
|
|
20
|
+
import com.userleap.ReplaySessionRecordingFormat
|
|
20
21
|
import org.json.JSONObject
|
|
21
22
|
|
|
22
23
|
class UserLeapModule(reactContext: ReactApplicationContext) :
|
|
@@ -186,6 +187,15 @@ class UserLeapModule(reactContext: ReactApplicationContext) :
|
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
189
|
|
|
190
|
+
// Android-only: iOS's SDK has no equivalent. `format` is the ReplaySessionRecordingFormat
|
|
191
|
+
// ordinal value (0 = SCREENSHOT, 1 = WIREFRAME) passed as a Double across the RN bridge.
|
|
192
|
+
override fun setReplaySessionRecordingFormat(format: Double) {
|
|
193
|
+
val formatInt = format.toInt()
|
|
194
|
+
ReplaySessionRecordingFormat.values().firstOrNull { it.value == formatInt }?.let {
|
|
195
|
+
UserLeap.setReplaySessionRecordingFormat(it)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
189
199
|
override fun trackEvent(eventName: String, surveyResultCallback: Callback) {
|
|
190
200
|
trackEventAndIdentify(eventName, null, null, surveyResultCallback)
|
|
191
201
|
}
|
package/index.d.ts
CHANGED
|
@@ -11,6 +11,14 @@ declare namespace UserLeap {
|
|
|
11
11
|
dark: number;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
/** Android only — replay session recording formats for {@link setAndroidReplaySessionRecordingFormat}. */
|
|
15
|
+
const ReplaySessionRecordingFormat: {
|
|
16
|
+
/** Full-fidelity screenshots (the native default). */
|
|
17
|
+
screenshot: number;
|
|
18
|
+
/** Lightweight wireframe of the view hierarchy. */
|
|
19
|
+
wireframe: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
14
22
|
/** Lifecycle event names for {@link addEventListener}. */
|
|
15
23
|
const SprigEventName: {
|
|
16
24
|
sdkReady: "sdkReady";
|
|
@@ -90,6 +98,11 @@ declare namespace UserLeap {
|
|
|
90
98
|
}
|
|
91
99
|
|
|
92
100
|
function overrideUserInterfaceMode(mode: SprigUserInterfaceMode): void;
|
|
101
|
+
/**
|
|
102
|
+
* Android only — selects the replay session recording format. No-op on iOS,
|
|
103
|
+
* whose native SDK has no equivalent API.
|
|
104
|
+
*/
|
|
105
|
+
function setAndroidReplaySessionRecordingFormat(format: number): void;
|
|
93
106
|
function visitorIdentifier(): number;
|
|
94
107
|
function visitorIdentifierString(): string;
|
|
95
108
|
function sdkVersion(): string;
|
package/index.js
CHANGED
|
@@ -15,6 +15,12 @@ const SprigUserInterfaceMode = {
|
|
|
15
15
|
dark: 2,
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
// Android only: iOS's native SDK has no equivalent replay-format API.
|
|
19
|
+
const ReplaySessionRecordingFormat = {
|
|
20
|
+
screenshot: 0,
|
|
21
|
+
wireframe: 1,
|
|
22
|
+
};
|
|
23
|
+
|
|
18
24
|
const SprigEventName = {
|
|
19
25
|
sdkReady: "sdkReady",
|
|
20
26
|
visitorIdUpdated: "visitorIdUpdated",
|
|
@@ -361,6 +367,13 @@ const overrideUserInterfaceMode = (mode) => {
|
|
|
361
367
|
}
|
|
362
368
|
}
|
|
363
369
|
|
|
370
|
+
// Android only: iOS's native SDK has no equivalent, so this is a no-op on iOS.
|
|
371
|
+
const setAndroidReplaySessionRecordingFormat = (format) => {
|
|
372
|
+
if (Platform.OS === "android" && isValidPlatform()) {
|
|
373
|
+
NativeModules.UserLeapBindings.setReplaySessionRecordingFormat(format);
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
|
|
364
377
|
/**
|
|
365
378
|
* Subscribe to a native SDK lifecycle event. Returns a subscription whose
|
|
366
379
|
* `.remove()` unsubscribes only this listener.
|
|
@@ -445,6 +458,7 @@ const UserLeap = {
|
|
|
445
458
|
pauseDisplayingSurveys,
|
|
446
459
|
unpauseDisplayingSurveys,
|
|
447
460
|
overrideUserInterfaceMode,
|
|
461
|
+
setAndroidReplaySessionRecordingFormat,
|
|
448
462
|
addEventListener,
|
|
449
463
|
removeAllEventListeners,
|
|
450
464
|
getSupportedEventNames,
|
|
@@ -452,6 +466,7 @@ const UserLeap = {
|
|
|
452
466
|
|
|
453
467
|
UserLeap.SurveyState = SurveyState;
|
|
454
468
|
UserLeap.SprigUserInterfaceMode = SprigUserInterfaceMode;
|
|
469
|
+
UserLeap.ReplaySessionRecordingFormat = ReplaySessionRecordingFormat;
|
|
455
470
|
UserLeap.SprigEventName = SprigEventName;
|
|
456
471
|
|
|
457
472
|
export default UserLeap;
|
package/ios/UserLeapBindings.h
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#import <UserLeapKit/UserLeapKit.h>
|
|
2
2
|
#import <UserLeapKit/UserLeapKit-Swift.h>
|
|
3
3
|
|
|
4
|
+
#import <React/RCTInvalidating.h>
|
|
4
5
|
#import <RNUserLeapBindingsSpec/RNUserLeapBindingsSpec.h>
|
|
5
6
|
|
|
6
|
-
@interface UserLeapBindings : NativeUserLeapBindingsSpecBase <NativeUserLeapBindingsSpec, _SGRNExtractor>
|
|
7
|
+
@interface UserLeapBindings : NativeUserLeapBindingsSpecBase <NativeUserLeapBindingsSpec, _SGRNExtractor, RCTInvalidating>
|
|
7
8
|
@end
|
package/ios/UserLeapBindings.mm
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#import "UserLeapBindings.h"
|
|
2
2
|
#import <React/RCTUtils.h>
|
|
3
|
+
#import <ReactCommon/CallInvoker.h>
|
|
4
|
+
#import <os/lock.h>
|
|
3
5
|
// Legacy Paper includes gated on RCT_REMOVE_LEGACY_ARCH: these classes are removed on RN 0.85+.
|
|
4
6
|
#ifndef RCT_REMOVE_LEGACY_ARCH
|
|
5
7
|
#import <React/RCTTextView.h>
|
|
@@ -16,7 +18,9 @@
|
|
|
16
18
|
#import <RNUserLeapBindingsSpec/RNUserLeapBindingsSpec.h>
|
|
17
19
|
|
|
18
20
|
@implementation UserLeapBindings {
|
|
21
|
+
os_unfair_lock _lock; // guards _alive and _jsInvoker (shared_ptr copy isn't atomic); never held across an emit
|
|
19
22
|
BOOL _alive;
|
|
23
|
+
std::shared_ptr<facebook::react::CallInvoker> _jsInvoker;
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
+ (BOOL)requiresMainQueueSetup
|
|
@@ -27,16 +31,25 @@
|
|
|
27
31
|
- (instancetype)init
|
|
28
32
|
{
|
|
29
33
|
if (self = [super init]) {
|
|
34
|
+
_lock = OS_UNFAIR_LOCK_INIT;
|
|
30
35
|
_alive = YES;
|
|
31
|
-
// emitOnSprigEvent is a safe no-op when JS has no listener, so eager subscription is fine.
|
|
32
36
|
[self subscribeToSdkEvents];
|
|
33
37
|
}
|
|
34
38
|
return self;
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
|
|
41
|
+
// Deterministic teardown hook: stop delivering before the runtime is destroyed.
|
|
42
|
+
- (void)invalidate
|
|
38
43
|
{
|
|
44
|
+
os_unfair_lock_lock(&_lock);
|
|
39
45
|
_alive = NO;
|
|
46
|
+
os_unfair_lock_unlock(&_lock);
|
|
47
|
+
[self unsubscribeFromSdkEvents];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
- (void)dealloc
|
|
51
|
+
{
|
|
52
|
+
// Backstop; -invalidate is the real teardown path.
|
|
40
53
|
[self unsubscribeFromSdkEvents];
|
|
41
54
|
}
|
|
42
55
|
|
|
@@ -177,6 +190,14 @@ RCT_EXPORT_MODULE()
|
|
|
177
190
|
[[UserLeap shared] overrideUserInterfaceModeWithMode:(enum SprigUserInterfaceMode)(NSInteger)mode];
|
|
178
191
|
}
|
|
179
192
|
|
|
193
|
+
// Android-only API. iOS's UserLeapKit SDK has no replay recording-format selector,
|
|
194
|
+
// so this is an intentional no-op that satisfies the shared codegen spec. The JS
|
|
195
|
+
// layer guards this call to Android, so it is never invoked on iOS.
|
|
196
|
+
- (void)setReplaySessionRecordingFormat:(double)format
|
|
197
|
+
{
|
|
198
|
+
// No-op: not supported on iOS.
|
|
199
|
+
}
|
|
200
|
+
|
|
180
201
|
- (void)trackEvent:(NSString *)eventName
|
|
181
202
|
surveyResultCallback:(RCTResponseSenderBlock)surveyResultCallback
|
|
182
203
|
{
|
|
@@ -433,11 +454,26 @@ surveyStateCallback:(RCTResponseSenderBlock)surveyStateCallback
|
|
|
433
454
|
|
|
434
455
|
[sdk registerEventListenerFor:eventType listener:^(NSDictionary<NSString *,id> * _Nonnull data) {
|
|
435
456
|
__strong __typeof(weakSelf) strongSelf = weakSelf;
|
|
436
|
-
|
|
437
|
-
|
|
457
|
+
if (!strongSelf) return;
|
|
458
|
+
|
|
459
|
+
// Snapshot liveness + invoker under the lock; bail if torn down.
|
|
460
|
+
std::shared_ptr<facebook::react::CallInvoker> invoker;
|
|
461
|
+
os_unfair_lock_lock(&strongSelf->_lock);
|
|
462
|
+
if (strongSelf->_alive) { invoker = strongSelf->_jsInvoker; }
|
|
463
|
+
os_unfair_lock_unlock(&strongSelf->_lock);
|
|
464
|
+
if (!invoker) return;
|
|
465
|
+
|
|
438
466
|
NSDictionary *payload = [strongSelf normalizePayloadForEvent:name data:data];
|
|
439
467
|
NSString *json = [strongSelf jsonStringFromDictionary:payload];
|
|
440
|
-
|
|
468
|
+
|
|
469
|
+
// Emit on the JS thread so it can't race runtime teardown; dropped if already invalidated.
|
|
470
|
+
invoker->invokeAsync([strongSelf, name, json]() {
|
|
471
|
+
os_unfair_lock_lock(&strongSelf->_lock);
|
|
472
|
+
BOOL alive = strongSelf->_alive;
|
|
473
|
+
os_unfair_lock_unlock(&strongSelf->_lock);
|
|
474
|
+
if (!alive) return;
|
|
475
|
+
[strongSelf emitOnSprigEvent:@{@"name": name, @"json": json}];
|
|
476
|
+
});
|
|
441
477
|
}];
|
|
442
478
|
}
|
|
443
479
|
}
|
|
@@ -453,6 +489,10 @@ surveyStateCallback:(RCTResponseSenderBlock)surveyStateCallback
|
|
|
453
489
|
|
|
454
490
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
455
491
|
{
|
|
492
|
+
// Capture the JS CallInvoker so event callbacks can marshal emits onto the JS thread.
|
|
493
|
+
os_unfair_lock_lock(&_lock);
|
|
494
|
+
_jsInvoker = params.jsInvoker;
|
|
495
|
+
os_unfair_lock_unlock(&_lock);
|
|
456
496
|
return std::make_shared<facebook::react::NativeUserLeapBindingsSpecJSI>(params);
|
|
457
497
|
}
|
|
458
498
|
|
package/package.json
CHANGED
|
@@ -18,7 +18,7 @@ Pod::Spec.new do |s|
|
|
|
18
18
|
s.source_files = "ios/**/*.{h,c,m,mm,swift}"
|
|
19
19
|
s.requires_arc = true
|
|
20
20
|
|
|
21
|
-
s.dependency "UserLeapKit", "4.
|
|
21
|
+
s.dependency "UserLeapKit", "4.34.0"
|
|
22
22
|
|
|
23
23
|
# New Architecture: wires up the React-Codegen dependency and header search
|
|
24
24
|
# paths so the generated RNUserLeapBindingsSpec (Obj-C++) resolves.
|
|
@@ -50,6 +50,13 @@ export interface Spec extends TurboModule {
|
|
|
50
50
|
): void;
|
|
51
51
|
overrideUserInterfaceMode(mode: number): void;
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Android only. Selects the replay session recording format
|
|
55
|
+
* (0 = SCREENSHOT, 1 = WIREFRAME). No-op on iOS, whose native SDK has no
|
|
56
|
+
* equivalent API; the JS layer guards this call to Android.
|
|
57
|
+
*/
|
|
58
|
+
setReplaySessionRecordingFormat(format: number): void;
|
|
59
|
+
|
|
53
60
|
trackEvent(
|
|
54
61
|
eventName: string,
|
|
55
62
|
surveyResultCallback: (result: SurveyResult) => void,
|