react-native-userleap 3.3.0 → 4.1.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 +53 -44
- package/android/src/main/java/com/userleap/reactnative/UserLeapModule.kt +336 -0
- package/index.d.ts +90 -0
- package/index.js +157 -6
- package/ios/UserLeapBindings.h +5 -4
- package/ios/UserLeapBindings.mm +459 -0
- package/package.json +14 -5
- package/react-native-userleap.podspec +10 -3
- package/src/NativeUserLeapBindings.ts +95 -0
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/src/main/java/com/userleap/reactnative/UserLeapModule.java +0 -387
- package/ios/UserLeapBindings.m +0 -330
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Platform, NativeModules } from "react-native";
|
|
1
|
+
import { Platform, NativeModules, TurboModuleRegistry } from "react-native";
|
|
2
2
|
|
|
3
3
|
import { version } from "./package.json";
|
|
4
4
|
|
|
@@ -15,6 +15,100 @@ const SprigUserInterfaceMode = {
|
|
|
15
15
|
dark: 2,
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
const SprigEventName = {
|
|
19
|
+
sdkReady: "sdkReady",
|
|
20
|
+
visitorIdUpdated: "visitorIdUpdated",
|
|
21
|
+
surveyHeight: "surveyHeight",
|
|
22
|
+
surveyStateReturned: "surveyStateReturned",
|
|
23
|
+
surveyWillPresent: "surveyWillPresent",
|
|
24
|
+
surveyPresented: "surveyPresented",
|
|
25
|
+
surveyAppeared: "surveyAppeared",
|
|
26
|
+
questionAnswered: "questionAnswered",
|
|
27
|
+
surveyCloseRequested: "surveyCloseRequested",
|
|
28
|
+
surveyWillClose: "surveyWillClose",
|
|
29
|
+
surveyClosed: "surveyClosed",
|
|
30
|
+
surveyCompleted: "surveyCompleted",
|
|
31
|
+
replayCapture: "replayCapture",
|
|
32
|
+
replayCaptureStarted: "replayCaptureStarted",
|
|
33
|
+
replayCaptureStopped: "replayCaptureStopped",
|
|
34
|
+
replayCaptureCompleted: "replayCaptureCompleted",
|
|
35
|
+
replayRenderingCompleted: "replayRenderingCompleted",
|
|
36
|
+
replayUploadCompleted: "replayUploadCompleted",
|
|
37
|
+
replayEventsUploadCompleted: "replayEventsUploadCompleted",
|
|
38
|
+
loggingEvent: "loggingEvent",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Resolved lazily; needed for the onSprigEvent channel (a JSI EventEmitter the legacy NativeModules interop doesn't expose).
|
|
42
|
+
let _module = null;
|
|
43
|
+
const getModule = () => {
|
|
44
|
+
if (_module) return _module;
|
|
45
|
+
_module =
|
|
46
|
+
(TurboModuleRegistry && TurboModuleRegistry.get
|
|
47
|
+
? TurboModuleRegistry.get("UserLeapBindings")
|
|
48
|
+
: null) ||
|
|
49
|
+
NativeModules.UserLeapBindings ||
|
|
50
|
+
null;
|
|
51
|
+
return _module;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// All native events arrive on one onSprigEvent channel as a {name,json} JSON-string envelope
|
|
55
|
+
// (payloads are heterogeneous); we subscribe once and fan out to per-event listeners.
|
|
56
|
+
const _listenersByEvent = new Map();
|
|
57
|
+
let _channelSub = null;
|
|
58
|
+
|
|
59
|
+
const ensureChannelSubscribed = () => {
|
|
60
|
+
if (_channelSub) return;
|
|
61
|
+
const mod = getModule();
|
|
62
|
+
if (!mod || typeof mod.onSprigEvent !== "function") return;
|
|
63
|
+
_channelSub = mod.onSprigEvent((envelope) => {
|
|
64
|
+
if (!envelope || !envelope.name) return;
|
|
65
|
+
const set = _listenersByEvent.get(envelope.name);
|
|
66
|
+
if (!set || set.size === 0) return;
|
|
67
|
+
|
|
68
|
+
let payload;
|
|
69
|
+
try {
|
|
70
|
+
payload = envelope.json ? JSON.parse(envelope.json) : {};
|
|
71
|
+
} catch (e) {
|
|
72
|
+
payload = {};
|
|
73
|
+
}
|
|
74
|
+
if (payload && typeof payload === "object") {
|
|
75
|
+
payload.type = envelope.name;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Copy so a listener (un)subscribing during dispatch can't mutate the set we're walking.
|
|
79
|
+
for (const cb of Array.from(set)) {
|
|
80
|
+
try {
|
|
81
|
+
cb(payload);
|
|
82
|
+
} catch (err) {
|
|
83
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
84
|
+
console.warn(
|
|
85
|
+
'[Sprig] listener for "' + envelope.name + '" threw:',
|
|
86
|
+
err
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const NOOP_SUBSCRIPTION = { remove: () => {} };
|
|
95
|
+
|
|
96
|
+
let _supportedEventNamesCache = null;
|
|
97
|
+
const getSupportedEventNames = () => {
|
|
98
|
+
if (_supportedEventNamesCache !== null) return _supportedEventNamesCache;
|
|
99
|
+
if (!isValidPlatform()) {
|
|
100
|
+
_supportedEventNamesCache = [];
|
|
101
|
+
return _supportedEventNamesCache;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const result = NativeModules.UserLeapBindings.getSupportedEventNames();
|
|
105
|
+
_supportedEventNamesCache = Array.isArray(result) ? result : [];
|
|
106
|
+
} catch (err) {
|
|
107
|
+
_supportedEventNamesCache = [];
|
|
108
|
+
}
|
|
109
|
+
return _supportedEventNamesCache;
|
|
110
|
+
};
|
|
111
|
+
|
|
18
112
|
|
|
19
113
|
const stringifyAttributes = (attributes) => {
|
|
20
114
|
if (!(attributes instanceof Object && attributes.constructor === Object))
|
|
@@ -42,11 +136,7 @@ const visitorIdentifier = () => {
|
|
|
42
136
|
};
|
|
43
137
|
|
|
44
138
|
const sdkVersion = () => {
|
|
45
|
-
if (
|
|
46
|
-
if (String(Platform.Version) >= "15.0") {
|
|
47
|
-
return NativeModules.UserLeapBindings.sdkVersion();
|
|
48
|
-
}
|
|
49
|
-
} else {
|
|
139
|
+
if (isValidPlatform()) {
|
|
50
140
|
return NativeModules.UserLeapBindings.getSdkVersion();
|
|
51
141
|
}
|
|
52
142
|
return "0.0.0";
|
|
@@ -271,6 +361,63 @@ const overrideUserInterfaceMode = (mode) => {
|
|
|
271
361
|
}
|
|
272
362
|
}
|
|
273
363
|
|
|
364
|
+
/**
|
|
365
|
+
* Subscribe to a native SDK lifecycle event. Returns a subscription whose
|
|
366
|
+
* `.remove()` unsubscribes only this listener.
|
|
367
|
+
* @param {string} eventName
|
|
368
|
+
* @param {(data: object) => void} callback
|
|
369
|
+
* @returns {{ remove: () => void }} subscription
|
|
370
|
+
*/
|
|
371
|
+
const addEventListener = (eventName, callback) => {
|
|
372
|
+
if (!isValidPlatform()) return NOOP_SUBSCRIPTION;
|
|
373
|
+
|
|
374
|
+
const supported = getSupportedEventNames();
|
|
375
|
+
if (supported.length > 0 && !supported.includes(eventName)) {
|
|
376
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
377
|
+
console.warn(
|
|
378
|
+
'[Sprig] "' + eventName +
|
|
379
|
+
'" is not emitted on this platform (' +
|
|
380
|
+
Platform.OS + '); subscription is a no-op.'
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
return NOOP_SUBSCRIPTION;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
ensureChannelSubscribed();
|
|
387
|
+
|
|
388
|
+
let set = _listenersByEvent.get(eventName);
|
|
389
|
+
if (!set) {
|
|
390
|
+
set = new Set();
|
|
391
|
+
_listenersByEvent.set(eventName, set);
|
|
392
|
+
}
|
|
393
|
+
set.add(callback);
|
|
394
|
+
|
|
395
|
+
let removed = false;
|
|
396
|
+
return {
|
|
397
|
+
remove: () => {
|
|
398
|
+
if (removed) return;
|
|
399
|
+
removed = true;
|
|
400
|
+
const s = _listenersByEvent.get(eventName);
|
|
401
|
+
if (s) {
|
|
402
|
+
s.delete(callback);
|
|
403
|
+
if (s.size === 0) _listenersByEvent.delete(eventName);
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Remove every listener for the given event name, or all listeners if no name is given.
|
|
411
|
+
* @param {string} [eventName]
|
|
412
|
+
*/
|
|
413
|
+
const removeAllEventListeners = (eventName) => {
|
|
414
|
+
if (eventName) {
|
|
415
|
+
_listenersByEvent.delete(eventName);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
_listenersByEvent.clear();
|
|
419
|
+
};
|
|
420
|
+
|
|
274
421
|
|
|
275
422
|
const UserLeap = {
|
|
276
423
|
visitorIdentifier,
|
|
@@ -298,9 +445,13 @@ const UserLeap = {
|
|
|
298
445
|
pauseDisplayingSurveys,
|
|
299
446
|
unpauseDisplayingSurveys,
|
|
300
447
|
overrideUserInterfaceMode,
|
|
448
|
+
addEventListener,
|
|
449
|
+
removeAllEventListeners,
|
|
450
|
+
getSupportedEventNames,
|
|
301
451
|
};
|
|
302
452
|
|
|
303
453
|
UserLeap.SurveyState = SurveyState;
|
|
304
454
|
UserLeap.SprigUserInterfaceMode = SprigUserInterfaceMode;
|
|
455
|
+
UserLeap.SprigEventName = SprigEventName;
|
|
305
456
|
|
|
306
457
|
export default UserLeap;
|
package/ios/UserLeapBindings.h
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
#import <
|
|
2
|
-
|
|
1
|
+
#import <UserLeapKit/UserLeapKit.h>
|
|
2
|
+
#import <UserLeapKit/UserLeapKit-Swift.h>
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
#import <RNUserLeapBindingsSpec/RNUserLeapBindingsSpec.h>
|
|
5
|
+
|
|
6
|
+
@interface UserLeapBindings : NativeUserLeapBindingsSpecBase <NativeUserLeapBindingsSpec, _SGRNExtractor>
|
|
6
7
|
@end
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
#import "UserLeapBindings.h"
|
|
2
|
+
#import <React/RCTUtils.h>
|
|
3
|
+
// Legacy Paper includes gated on RCT_REMOVE_LEGACY_ARCH: these classes are removed on RN 0.85+.
|
|
4
|
+
#ifndef RCT_REMOVE_LEGACY_ARCH
|
|
5
|
+
#import <React/RCTTextView.h>
|
|
6
|
+
#import <React/RCTUIManager.h>
|
|
7
|
+
#import <React/RCTTextShadowView.h>
|
|
8
|
+
#import <React/RCTShadowView.h>
|
|
9
|
+
#import <React/RCTRawTextShadowView.h>
|
|
10
|
+
#import <React/RCTVirtualTextShadowView.h>
|
|
11
|
+
#import <React/RCTUIManagerUtils.h>
|
|
12
|
+
#endif
|
|
13
|
+
#import <React/RCTView.h>
|
|
14
|
+
|
|
15
|
+
// The codegen spec header is Obj-C++ only, which is why this file is `.mm`.
|
|
16
|
+
#import <RNUserLeapBindingsSpec/RNUserLeapBindingsSpec.h>
|
|
17
|
+
|
|
18
|
+
@implementation UserLeapBindings {
|
|
19
|
+
BOOL _alive;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
+ (BOOL)requiresMainQueueSetup
|
|
23
|
+
{
|
|
24
|
+
return YES;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
- (instancetype)init
|
|
28
|
+
{
|
|
29
|
+
if (self = [super init]) {
|
|
30
|
+
_alive = YES;
|
|
31
|
+
// emitOnSprigEvent is a safe no-op when JS has no listener, so eager subscription is fine.
|
|
32
|
+
[self subscribeToSdkEvents];
|
|
33
|
+
}
|
|
34
|
+
return self;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
- (void)dealloc
|
|
38
|
+
{
|
|
39
|
+
_alive = NO;
|
|
40
|
+
[self unsubscribeFromSdkEvents];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
- (dispatch_queue_t)methodQueue
|
|
44
|
+
{
|
|
45
|
+
return dispatch_get_main_queue();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
- (NSDictionary *)parseSurveyResult:(SprigSurveyResult *)result {
|
|
49
|
+
SurveyState state = [result surveyState];
|
|
50
|
+
NSString *surveyStateString = [self getSurveyState:state];
|
|
51
|
+
NSInteger surveyId = [result surveyId];
|
|
52
|
+
return @{@"surveyState": surveyStateString, @"surveyId": @(surveyId)};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
- (NSString *)getSurveyState:(SurveyState)surveyState
|
|
56
|
+
{
|
|
57
|
+
NSString *surveyStateBinding = @"NO_SURVEY";
|
|
58
|
+
switch(surveyState) {
|
|
59
|
+
case SurveyStateDisabled:
|
|
60
|
+
surveyStateBinding = @"DISABLED";
|
|
61
|
+
break;
|
|
62
|
+
case SurveyStateReady:
|
|
63
|
+
surveyStateBinding = @"READY";
|
|
64
|
+
break;
|
|
65
|
+
case SurveyStateNoSurvey:
|
|
66
|
+
surveyStateBinding = @"NO_SURVEY";
|
|
67
|
+
break;
|
|
68
|
+
case SurveyStatePreviousSurveyReady:
|
|
69
|
+
surveyStateBinding = @"PREVIOUS_SURVEY_READY";
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
return surveyStateBinding;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
RCT_EXPORT_MODULE()
|
|
76
|
+
|
|
77
|
+
- (NSNumber *)visitorIdentifier
|
|
78
|
+
{
|
|
79
|
+
// No @() boxing: @() expects a primitive C type, and the SDK already returns NSNumber *.
|
|
80
|
+
return [[UserLeap shared] visitorIdentifier];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
- (NSString *)visitorIdentifierString
|
|
84
|
+
{
|
|
85
|
+
return [[UserLeap shared] visitorIdentifierString];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
- (NSString *)getSdkVersion
|
|
89
|
+
{
|
|
90
|
+
// Renamed from `sdkVersion` to `getSdkVersion` so iOS and Android share one spec method name.
|
|
91
|
+
return [[UserLeap shared] sdkVersion];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
- (void)configure:(NSString *)environmentId configuration:(NSDictionary *)configuration
|
|
95
|
+
{
|
|
96
|
+
[[UserLeap shared] configureWithEnvironment:environmentId configuration:configuration];
|
|
97
|
+
[[UserLeap shared] _passWithRnExtractor:self];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
- (void)setPreviewKey:(NSString *)previewKey
|
|
101
|
+
{
|
|
102
|
+
[[UserLeap shared] setPreviewKey:previewKey];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
- (void)setUserIdentifier:(NSString *)identifier
|
|
106
|
+
{
|
|
107
|
+
[[UserLeap shared] setUserIdentifier:identifier];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
- (void)setEmailAddress:(NSString *)emailAddress
|
|
111
|
+
{
|
|
112
|
+
[[UserLeap shared] setEmailAddress:emailAddress];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
- (void)logout
|
|
116
|
+
{
|
|
117
|
+
[[UserLeap shared] logout];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
- (void)setVisitorAttribute:(NSString *)key value:(NSString *)value
|
|
121
|
+
{
|
|
122
|
+
[[UserLeap shared] setVisitorAttributeWithKey:key value:value];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
- (void)setVisitorAttributes:(NSDictionary *)attributes
|
|
126
|
+
{
|
|
127
|
+
[[UserLeap shared] setVisitorAttributes:attributes];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
- (void)removeVisitorAttributes:(NSArray *)keys
|
|
131
|
+
{
|
|
132
|
+
[[UserLeap shared] removeVisitorAttributes:keys];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
- (void)setVisitorAttributesAndIdentify:(NSDictionary *)attributes
|
|
136
|
+
userId:(NSString *)userId
|
|
137
|
+
partnerAnonymousId:(NSString *)partnerAnonymousId
|
|
138
|
+
{
|
|
139
|
+
[[UserLeap shared] setVisitorAttributes:attributes userId:userId partnerAnonymousId:partnerAnonymousId];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
- (void)presentSurvey
|
|
143
|
+
{
|
|
144
|
+
[[UserLeap shared] presentSurveyFrom:RCTPresentedViewController()];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
- (void)dismissActiveSurvey
|
|
148
|
+
{
|
|
149
|
+
[[UserLeap shared] dismissActiveSurvey];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
- (void)pauseDisplayingSurveys
|
|
153
|
+
{
|
|
154
|
+
[[UserLeap shared] pauseDisplayingSurveys];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
- (void)unpauseDisplayingSurveys
|
|
158
|
+
{
|
|
159
|
+
[[UserLeap shared] unpauseDisplayingSurveys];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
- (void)trackAndPresent:(NSString *)eventName
|
|
163
|
+
{
|
|
164
|
+
[[UserLeap shared] trackAndPresentWithEventName:eventName from:RCTPresentedViewController()];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
- (void)trackIdentifyAndPresent:(NSString *)eventName
|
|
168
|
+
userId:(NSString *)userId
|
|
169
|
+
partnerAnonymousId:(NSString *)partnerAnonymousId
|
|
170
|
+
{
|
|
171
|
+
[[UserLeap shared] trackAndPresentWithEventName:eventName userId:userId partnerAnonymousId:partnerAnonymousId from:RCTPresentedViewController()];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
- (void)overrideUserInterfaceMode:(double)mode
|
|
175
|
+
{
|
|
176
|
+
// Explicit enum cast required: Obj-C++ won't implicitly convert numeric → enum.
|
|
177
|
+
[[UserLeap shared] overrideUserInterfaceModeWithMode:(enum SprigUserInterfaceMode)(NSInteger)mode];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
- (void)trackEvent:(NSString *)eventName
|
|
181
|
+
surveyResultCallback:(RCTResponseSenderBlock)surveyResultCallback
|
|
182
|
+
{
|
|
183
|
+
EventPayload *payload = [[EventPayload alloc] initWithEventName:eventName
|
|
184
|
+
userId:nil
|
|
185
|
+
partnerAnonymousId:nil
|
|
186
|
+
properties:nil
|
|
187
|
+
handler:nil
|
|
188
|
+
resultHandler:^(SprigSurveyResult * result) {
|
|
189
|
+
if (surveyResultCallback != nil) {
|
|
190
|
+
surveyResultCallback(@[[self parseSurveyResult:result]]);
|
|
191
|
+
}
|
|
192
|
+
}];
|
|
193
|
+
[[UserLeap shared] trackWithPayload:payload];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
- (void)trackEventWithProperties:(NSString *)eventName
|
|
197
|
+
userId:(NSString *)userId
|
|
198
|
+
partnerAnonymousId:(NSString *)partnerAnonymousId
|
|
199
|
+
properties:(NSDictionary *)properties
|
|
200
|
+
surveyResultCallback:(RCTResponseSenderBlock)surveyResultCallback
|
|
201
|
+
{
|
|
202
|
+
EventPayload *payload = [[EventPayload alloc] initWithEventName:eventName
|
|
203
|
+
userId:userId
|
|
204
|
+
partnerAnonymousId:partnerAnonymousId
|
|
205
|
+
properties:properties
|
|
206
|
+
handler:nil
|
|
207
|
+
resultHandler:^(SprigSurveyResult * result) {
|
|
208
|
+
if (surveyResultCallback != nil) {
|
|
209
|
+
surveyResultCallback(@[[self parseSurveyResult:result]]);
|
|
210
|
+
}
|
|
211
|
+
}];
|
|
212
|
+
[[UserLeap shared] trackWithPayload:payload];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
- (void)trackEventAndIdentify:(NSString *)eventName
|
|
216
|
+
userId:(NSString *)userId
|
|
217
|
+
partnerAnonymousId:(NSString *)partnerAnonymousId
|
|
218
|
+
surveyResultCallback:(RCTResponseSenderBlock)surveyResultCallback
|
|
219
|
+
{
|
|
220
|
+
EventPayload *payload = [[EventPayload alloc] initWithEventName:eventName
|
|
221
|
+
userId:userId
|
|
222
|
+
partnerAnonymousId:partnerAnonymousId
|
|
223
|
+
properties:nil
|
|
224
|
+
handler:nil
|
|
225
|
+
resultHandler:^(SprigSurveyResult * result) {
|
|
226
|
+
if (surveyResultCallback != nil) {
|
|
227
|
+
surveyResultCallback(@[[self parseSurveyResult:result]]);
|
|
228
|
+
}
|
|
229
|
+
}];
|
|
230
|
+
[[UserLeap shared] trackWithPayload:payload];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
- (void)track:(NSString *)eventName
|
|
234
|
+
surveyStateCallback:(RCTResponseSenderBlock)surveyStateCallback
|
|
235
|
+
{
|
|
236
|
+
if (surveyStateCallback != nil) {
|
|
237
|
+
[[UserLeap shared] trackWithEventName:eventName handler:^(enum SurveyState surveyState) {
|
|
238
|
+
surveyStateCallback(@[[self getSurveyState:surveyState]]);
|
|
239
|
+
}];
|
|
240
|
+
} else {
|
|
241
|
+
[[UserLeap shared] trackWithEventName:eventName handler:nil];
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
- (void)trackWithProperties:(NSString *)eventName
|
|
246
|
+
userId:(NSString *)userId
|
|
247
|
+
partnerAnonymousId:(NSString *)partnerAnonymousId
|
|
248
|
+
properties:(NSDictionary *)properties
|
|
249
|
+
surveyStateCallback:(RCTResponseSenderBlock)surveyStateCallback
|
|
250
|
+
{
|
|
251
|
+
[[UserLeap shared] trackWithEventName:eventName
|
|
252
|
+
userId:userId
|
|
253
|
+
partnerAnonymousId:partnerAnonymousId
|
|
254
|
+
properties:properties
|
|
255
|
+
handler:^(enum SurveyState surveyState) {
|
|
256
|
+
if (surveyStateCallback != nil) {
|
|
257
|
+
surveyStateCallback(@[[self getSurveyState:surveyState]]);
|
|
258
|
+
}
|
|
259
|
+
}];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
- (void)trackAndIdentify:(NSString *)eventName
|
|
263
|
+
userId:(NSString *)userId
|
|
264
|
+
partnerAnonymousId:(NSString *)partnerAnonymousId
|
|
265
|
+
surveyStateCallback:(RCTResponseSenderBlock)surveyStateCallback
|
|
266
|
+
{
|
|
267
|
+
if (surveyStateCallback != nil) {
|
|
268
|
+
[[UserLeap shared] trackWithEventName:eventName
|
|
269
|
+
userId:userId
|
|
270
|
+
partnerAnonymousId:partnerAnonymousId
|
|
271
|
+
handler:^(enum SurveyState surveyState) {
|
|
272
|
+
surveyStateCallback(@[[self getSurveyState:surveyState]]);
|
|
273
|
+
}];
|
|
274
|
+
} else {
|
|
275
|
+
[[UserLeap shared] trackWithEventName:eventName
|
|
276
|
+
userId:userId
|
|
277
|
+
partnerAnonymousId:partnerAnonymousId
|
|
278
|
+
handler:nil];
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
- (_SGRNTextProperties *)textPropertiesFromView:(UIView * _Nonnull)passedView {
|
|
283
|
+
|
|
284
|
+
#ifndef RCT_REMOVE_LEGACY_ARCH
|
|
285
|
+
if ([passedView isKindOfClass:[RCTTextView class]]) {
|
|
286
|
+
RCTUIManager* uiManager = [self.bridge moduleForClass:[RCTUIManager class]];
|
|
287
|
+
RCTTextView *textView = (RCTTextView *)passedView;
|
|
288
|
+
__block _SGRNTextProperties *textProperties = [[_SGRNTextProperties alloc] init];
|
|
289
|
+
dispatch_sync(RCTGetUIManagerQueue(), ^{
|
|
290
|
+
RCTShadowView *shadowView = [uiManager shadowViewForReactTag:textView.reactTag];
|
|
291
|
+
if ([shadowView isKindOfClass:[RCTTextShadowView class]]) {
|
|
292
|
+
RCTTextShadowView *textShadowView = (RCTTextShadowView *)shadowView;
|
|
293
|
+
NSString *text = [self extractText: textShadowView.reactSubviews];
|
|
294
|
+
if (text.length == 0) return;
|
|
295
|
+
textProperties.text = text;
|
|
296
|
+
textProperties.color = textShadowView.textAttributes.foregroundColor;
|
|
297
|
+
textProperties.alignment = textShadowView.textAttributes.alignment;
|
|
298
|
+
textProperties.font = textShadowView.textAttributes.effectiveFont;
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
if (textProperties.text.length == 0) return nil;
|
|
302
|
+
return textProperties;
|
|
303
|
+
}
|
|
304
|
+
#endif
|
|
305
|
+
|
|
306
|
+
Class ParagraphComponentView = NSClassFromString(@"RCTParagraphComponentView");
|
|
307
|
+
if (ParagraphComponentView && [passedView isKindOfClass:ParagraphComponentView]) {
|
|
308
|
+
NSString *text = [self extractTextFromParagraphComponentView:passedView];
|
|
309
|
+
if (text.length == 0) return nil;
|
|
310
|
+
_SGRNTextProperties *textProperties = [[_SGRNTextProperties alloc] init];
|
|
311
|
+
textProperties.text = text;
|
|
312
|
+
if ([passedView respondsToSelector:@selector(attributedText)]) {
|
|
313
|
+
NSAttributedString *attrText = [passedView performSelector:@selector(attributedText)];
|
|
314
|
+
if (attrText.length > 0) {
|
|
315
|
+
NSDictionary *attrs = [attrText attributesAtIndex:0 effectiveRange:nil];
|
|
316
|
+
if (!textProperties.color && attrs[NSForegroundColorAttributeName]) {
|
|
317
|
+
textProperties.color = attrs[NSForegroundColorAttributeName];
|
|
318
|
+
}
|
|
319
|
+
if (!textProperties.font && attrs[NSFontAttributeName]) {
|
|
320
|
+
textProperties.font = attrs[NSFontAttributeName];
|
|
321
|
+
}
|
|
322
|
+
NSParagraphStyle *style = attrs[NSParagraphStyleAttributeName];
|
|
323
|
+
if (style) {
|
|
324
|
+
textProperties.alignment = style.alignment;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return textProperties;
|
|
329
|
+
}
|
|
330
|
+
return nil;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
- (_SGRNViewProperties * _Nullable)propertiesFromView:(UIView * _Nonnull)passedView {
|
|
334
|
+
if ([passedView isKindOfClass:[RCTView class]]) {
|
|
335
|
+
RCTView *rView = (RCTView *) passedView;
|
|
336
|
+
if (rView.borderWidth > 0) {
|
|
337
|
+
_SGRNViewProperties *props = [[_SGRNViewProperties alloc] init];
|
|
338
|
+
props.borderColor = rView.borderColor;
|
|
339
|
+
props.borderWidth = rView.borderWidth;
|
|
340
|
+
return props;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return nil;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
#ifndef RCT_REMOVE_LEGACY_ARCH
|
|
347
|
+
- (NSString * _Nullable)extractText:(NSArray<RCTShadowView *> * _Nonnull) subviews {
|
|
348
|
+
NSMutableString *text = [[NSMutableString alloc] init];
|
|
349
|
+
for (RCTShadowView *subview in subviews) {
|
|
350
|
+
if ([subview isKindOfClass:[RCTRawTextShadowView class]]) {
|
|
351
|
+
[text appendString:((RCTRawTextShadowView *)subview).text];
|
|
352
|
+
}
|
|
353
|
+
if ([subview isKindOfClass:[RCTVirtualTextShadowView class]]) {
|
|
354
|
+
// Append, don't early-return: returning here would drop text already collected.
|
|
355
|
+
NSString *nested = [self extractText: ((RCTVirtualTextShadowView *)subview).reactSubviews];
|
|
356
|
+
if (nested) [text appendString:nested];
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return text;
|
|
360
|
+
}
|
|
361
|
+
#endif
|
|
362
|
+
|
|
363
|
+
- (NSString *)extractTextFromParagraphComponentView:(UIView *)view {
|
|
364
|
+
if ([view respondsToSelector:@selector(attributedText)]) {
|
|
365
|
+
NSAttributedString *attrText = [view performSelector:@selector(attributedText)];
|
|
366
|
+
return attrText.string;
|
|
367
|
+
}
|
|
368
|
+
if ([view respondsToSelector:@selector(text)]) {
|
|
369
|
+
NSString *text = [view performSelector:@selector(text)];
|
|
370
|
+
return text;
|
|
371
|
+
}
|
|
372
|
+
NSMutableString *result = [NSMutableString string];
|
|
373
|
+
for (UIView *subview in view.subviews) {
|
|
374
|
+
NSString *subText = [self extractTextFromParagraphComponentView:subview];
|
|
375
|
+
if (subText) [result appendString:subText];
|
|
376
|
+
}
|
|
377
|
+
return result;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
- (NSArray<NSString *> *)getSupportedEventNames
|
|
381
|
+
{
|
|
382
|
+
NSMutableArray<NSString *> *events = [NSMutableArray array];
|
|
383
|
+
for (NSString *name in [LifecycleEventUtil allNameStrings]) {
|
|
384
|
+
if ([name isEqualToString:@"unknown"]) continue;
|
|
385
|
+
[events addObject:name];
|
|
386
|
+
}
|
|
387
|
+
return events;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
- (NSDictionary *)normalizePayloadForEvent:(NSString *)eventName
|
|
391
|
+
data:(NSDictionary<NSString *, id> *)data
|
|
392
|
+
{
|
|
393
|
+
if (![eventName isEqualToString:@"loggingEvent"]) {
|
|
394
|
+
return data ?: @{};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
NSString *raw = data[@"message"];
|
|
398
|
+
NSString *message = @"";
|
|
399
|
+
if ([raw isKindOfClass:[NSString class]]) {
|
|
400
|
+
NSString *prefix = @"Sprig: ";
|
|
401
|
+
message = [raw hasPrefix:prefix] ? [raw substringFromIndex:prefix.length] : raw;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return @{
|
|
405
|
+
@"type": @"loggingEvent",
|
|
406
|
+
@"message": message,
|
|
407
|
+
@"level": @"info",
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
- (NSString *)jsonStringFromDictionary:(NSDictionary *)dict
|
|
412
|
+
{
|
|
413
|
+
if (![NSJSONSerialization isValidJSONObject:dict]) {
|
|
414
|
+
return @"{}";
|
|
415
|
+
}
|
|
416
|
+
NSError *error = nil;
|
|
417
|
+
NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:0 error:&error];
|
|
418
|
+
if (!data || error) {
|
|
419
|
+
return @"{}";
|
|
420
|
+
}
|
|
421
|
+
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ?: @"{}";
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
- (void)subscribeToSdkEvents
|
|
425
|
+
{
|
|
426
|
+
__weak __typeof(self) weakSelf = self;
|
|
427
|
+
UserLeap *sdk = [UserLeap shared];
|
|
428
|
+
|
|
429
|
+
for (NSNumber *raw in [LifecycleEventUtil all]) {
|
|
430
|
+
LifecycleEvent eventType = (LifecycleEvent)raw.integerValue;
|
|
431
|
+
NSString *name = [LifecycleEventUtil stringName:eventType];
|
|
432
|
+
if ([name isEqualToString:@"unknown"]) continue;
|
|
433
|
+
|
|
434
|
+
[sdk registerEventListenerFor:eventType listener:^(NSDictionary<NSString *,id> * _Nonnull data) {
|
|
435
|
+
__strong __typeof(weakSelf) strongSelf = weakSelf;
|
|
436
|
+
// Weak capture + _alive guard: a callback firing during teardown must not touch a dead module.
|
|
437
|
+
if (!strongSelf || !strongSelf->_alive) return;
|
|
438
|
+
NSDictionary *payload = [strongSelf normalizePayloadForEvent:name data:data];
|
|
439
|
+
NSString *json = [strongSelf jsonStringFromDictionary:payload];
|
|
440
|
+
[strongSelf emitOnSprigEvent:@{@"name": name, @"json": json}];
|
|
441
|
+
}];
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
- (void)unsubscribeFromSdkEvents
|
|
446
|
+
{
|
|
447
|
+
UserLeap *sdk = [UserLeap shared];
|
|
448
|
+
for (NSNumber *raw in [LifecycleEventUtil all]) {
|
|
449
|
+
LifecycleEvent eventType = (LifecycleEvent)raw.integerValue;
|
|
450
|
+
[sdk unregisterAllEventListenersFor:eventType];
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
455
|
+
{
|
|
456
|
+
return std::make_shared<facebook::react::NativeUserLeapBindingsSpecJSI>(params);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
@end
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-userleap",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "React Native module for UserLeap",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"index.js",
|
|
13
13
|
"index.d.ts",
|
|
14
14
|
"ios",
|
|
15
|
-
"react-native-userleap.podspec"
|
|
15
|
+
"react-native-userleap.podspec",
|
|
16
|
+
"src"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
18
19
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -33,12 +34,20 @@
|
|
|
33
34
|
"babel-jest": "29.7.0",
|
|
34
35
|
"eslint": "7.32.0",
|
|
35
36
|
"jest": "29.7.0",
|
|
36
|
-
"@react-native/babel-preset": "^0.
|
|
37
|
-
"react": "19.2.
|
|
38
|
-
"react-native": "0.
|
|
37
|
+
"@react-native/babel-preset": "^0.85.0",
|
|
38
|
+
"react": "19.2.3",
|
|
39
|
+
"react-native": "0.85.0",
|
|
39
40
|
"react-test-renderer": "18.2.0"
|
|
40
41
|
},
|
|
41
42
|
"overrides": {
|
|
42
43
|
"js-yaml": "3.14.2"
|
|
44
|
+
},
|
|
45
|
+
"codegenConfig": {
|
|
46
|
+
"name": "RNUserLeapBindingsSpec",
|
|
47
|
+
"type": "modules",
|
|
48
|
+
"jsSrcsDir": "./src",
|
|
49
|
+
"android": {
|
|
50
|
+
"javaPackageName": "com.userleap.reactnative"
|
|
51
|
+
}
|
|
43
52
|
}
|
|
44
53
|
}
|