@rejourneyco/react-native 1.0.2 → 1.0.4
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/src/main/java/com/rejourney/RejourneyModuleImpl.kt +38 -363
- package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +11 -113
- package/android/src/main/java/com/rejourney/capture/SegmentUploader.kt +1 -15
- package/android/src/main/java/com/rejourney/capture/VideoEncoder.kt +1 -61
- package/android/src/main/java/com/rejourney/capture/ViewHierarchyScanner.kt +3 -1
- package/android/src/main/java/com/rejourney/lifecycle/SessionLifecycleService.kt +1 -22
- package/android/src/main/java/com/rejourney/network/DeviceAuthManager.kt +14 -27
- package/android/src/main/java/com/rejourney/network/NetworkMonitor.kt +0 -2
- package/android/src/main/java/com/rejourney/network/UploadManager.kt +7 -93
- package/android/src/main/java/com/rejourney/network/UploadWorker.kt +5 -41
- package/android/src/main/java/com/rejourney/privacy/PrivacyMask.kt +2 -58
- package/android/src/main/java/com/rejourney/touch/TouchInterceptor.kt +4 -4
- package/android/src/main/java/com/rejourney/utils/EventBuffer.kt +36 -7
- package/ios/Capture/RJCaptureEngine.m +9 -61
- package/ios/Capture/RJViewHierarchyScanner.m +68 -51
- package/ios/Core/RJLifecycleManager.m +0 -14
- package/ios/Core/Rejourney.mm +24 -37
- package/ios/Network/RJDeviceAuthManager.m +0 -2
- package/ios/Network/RJUploadManager.h +8 -0
- package/ios/Network/RJUploadManager.m +45 -0
- package/ios/Privacy/RJPrivacyMask.m +5 -31
- package/ios/Rejourney.h +0 -14
- package/ios/Touch/RJTouchInterceptor.m +21 -15
- package/ios/Utils/RJEventBuffer.m +57 -69
- package/ios/Utils/RJWindowUtils.m +87 -86
- package/lib/commonjs/index.js +44 -31
- package/lib/commonjs/sdk/autoTracking.js +0 -3
- package/lib/commonjs/sdk/constants.js +1 -1
- package/lib/commonjs/sdk/networkInterceptor.js +0 -11
- package/lib/commonjs/sdk/utils.js +73 -14
- package/lib/module/index.js +44 -31
- package/lib/module/sdk/autoTracking.js +0 -3
- package/lib/module/sdk/constants.js +1 -1
- package/lib/module/sdk/networkInterceptor.js +0 -11
- package/lib/module/sdk/utils.js +73 -14
- package/lib/typescript/sdk/constants.d.ts +1 -1
- package/lib/typescript/sdk/utils.d.ts +31 -1
- package/package.json +16 -4
- package/src/index.ts +42 -20
- package/src/sdk/autoTracking.ts +0 -2
- package/src/sdk/constants.ts +14 -14
- package/src/sdk/networkInterceptor.ts +0 -9
- package/src/sdk/utils.ts +76 -14
|
@@ -36,9 +36,10 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
36
36
|
if (self) {
|
|
37
37
|
_sessionId = [sessionId copy];
|
|
38
38
|
if (pendingRootPath.length == 0) {
|
|
39
|
-
NSString *defaultPath =
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
NSString *defaultPath =
|
|
40
|
+
[NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
|
|
41
|
+
NSUserDomainMask, YES)
|
|
42
|
+
.firstObject stringByAppendingPathComponent:@"rj_pending"];
|
|
42
43
|
_pendingRootPath = [defaultPath copy];
|
|
43
44
|
} else {
|
|
44
45
|
_pendingRootPath = [pendingRootPath copy];
|
|
@@ -68,7 +69,6 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
68
69
|
NSString *sessionDir =
|
|
69
70
|
[self.pendingRootPath stringByAppendingPathComponent:self.sessionId];
|
|
70
71
|
|
|
71
|
-
|
|
72
72
|
NSFileManager *fm = [NSFileManager defaultManager];
|
|
73
73
|
if (![fm fileExistsAtPath:sessionDir]) {
|
|
74
74
|
NSError *error = nil;
|
|
@@ -87,12 +87,11 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
87
87
|
|
|
88
88
|
if (![fm fileExistsAtPath:self.eventsFilePath]) {
|
|
89
89
|
NSDictionary *attrs = @{
|
|
90
|
-
NSFileProtectionKey: NSFileProtectionCompleteUntilFirstUserAuthentication
|
|
90
|
+
NSFileProtectionKey : NSFileProtectionCompleteUntilFirstUserAuthentication
|
|
91
91
|
};
|
|
92
92
|
[fm createFileAtPath:self.eventsFilePath contents:nil attributes:attrs];
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
|
|
96
95
|
NSError *error = nil;
|
|
97
96
|
self.fileHandle =
|
|
98
97
|
[NSFileHandle fileHandleForWritingAtPath:self.eventsFilePath];
|
|
@@ -102,14 +101,12 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
102
101
|
return;
|
|
103
102
|
}
|
|
104
103
|
|
|
105
|
-
|
|
106
104
|
if (@available(iOS 13.0, *)) {
|
|
107
105
|
[self.fileHandle seekToEndReturningOffset:nil error:&error];
|
|
108
106
|
} else {
|
|
109
107
|
[self.fileHandle seekToEndOfFile];
|
|
110
108
|
}
|
|
111
109
|
|
|
112
|
-
|
|
113
110
|
[self countExistingEvents];
|
|
114
111
|
|
|
115
112
|
RJLogDebug(@"Event buffer ready: %@ (%ld existing events)",
|
|
@@ -117,40 +114,57 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
117
114
|
}
|
|
118
115
|
|
|
119
116
|
- (void)countExistingEvents {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
encoding:NSUTF8StringEncoding
|
|
123
|
-
error:&error];
|
|
124
|
-
if (error || !content) {
|
|
125
|
-
self.eventCount = 0;
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
117
|
+
__block NSInteger count = 0;
|
|
118
|
+
__block NSTimeInterval lastTs = 0;
|
|
128
119
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (line.length == 0)
|
|
135
|
-
continue;
|
|
136
|
-
|
|
137
|
-
NSData *data = [line dataUsingEncoding:NSUTF8StringEncoding];
|
|
138
|
-
NSDictionary *event = [NSJSONSerialization JSONObjectWithData:data
|
|
139
|
-
options:0
|
|
140
|
-
error:nil];
|
|
141
|
-
if (event) {
|
|
142
|
-
count++;
|
|
143
|
-
NSNumber *ts = event[@"timestamp"];
|
|
144
|
-
if (ts && [ts doubleValue] > lastTs) {
|
|
145
|
-
lastTs = [ts doubleValue];
|
|
146
|
-
}
|
|
120
|
+
[self enumerateEventsWithBlock:^(NSDictionary *event, BOOL *stop) {
|
|
121
|
+
count++;
|
|
122
|
+
NSNumber *ts = event[@"timestamp"];
|
|
123
|
+
if (ts && [ts doubleValue] > lastTs) {
|
|
124
|
+
lastTs = [ts doubleValue];
|
|
147
125
|
}
|
|
148
|
-
}
|
|
126
|
+
}];
|
|
149
127
|
|
|
150
128
|
self.eventCount = count;
|
|
151
129
|
self.lastEventTimestamp = lastTs;
|
|
152
130
|
}
|
|
153
131
|
|
|
132
|
+
- (void)enumerateEventsWithBlock:(void (^)(NSDictionary *event,
|
|
133
|
+
BOOL *stop))block {
|
|
134
|
+
if (!block || !self.eventsFilePath)
|
|
135
|
+
return;
|
|
136
|
+
|
|
137
|
+
FILE *file = fopen([self.eventsFilePath UTF8String], "r");
|
|
138
|
+
if (!file)
|
|
139
|
+
return;
|
|
140
|
+
|
|
141
|
+
char *line = NULL;
|
|
142
|
+
size_t linecap = 0;
|
|
143
|
+
ssize_t linelen;
|
|
144
|
+
BOOL stop = NO;
|
|
145
|
+
|
|
146
|
+
while (!stop && (linelen = getline(&line, &linecap, file)) > 0) {
|
|
147
|
+
@autoreleasepool {
|
|
148
|
+
if (linelen <= 1)
|
|
149
|
+
continue;
|
|
150
|
+
|
|
151
|
+
NSData *data = [NSData dataWithBytesNoCopy:line
|
|
152
|
+
length:linelen
|
|
153
|
+
freeWhenDone:NO];
|
|
154
|
+
NSError *error = nil;
|
|
155
|
+
NSDictionary *event = [NSJSONSerialization JSONObjectWithData:data
|
|
156
|
+
options:0
|
|
157
|
+
error:&error];
|
|
158
|
+
if (event) {
|
|
159
|
+
block(event, &stop);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (line)
|
|
164
|
+
free(line);
|
|
165
|
+
fclose(file);
|
|
166
|
+
}
|
|
167
|
+
|
|
154
168
|
- (void)closeFileHandle {
|
|
155
169
|
if (self.fileHandle) {
|
|
156
170
|
if (@available(iOS 13.0, *)) {
|
|
@@ -222,11 +236,9 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
222
236
|
return NO;
|
|
223
237
|
}
|
|
224
238
|
|
|
225
|
-
|
|
226
239
|
NSMutableData *lineData = [jsonData mutableCopy];
|
|
227
240
|
[lineData appendData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
|
228
241
|
|
|
229
|
-
|
|
230
242
|
if (@available(iOS 13.4, *)) {
|
|
231
243
|
NSError *writeError = nil;
|
|
232
244
|
[self.fileHandle writeData:lineData error:&writeError];
|
|
@@ -243,14 +255,9 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
243
255
|
}
|
|
244
256
|
}
|
|
245
257
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
[self.fileHandle synchronizeAndReturnError:nil];
|
|
249
|
-
} else {
|
|
250
|
-
[self.fileHandle synchronizeFile];
|
|
251
|
-
}
|
|
258
|
+
// Removed redundant synchronizeFile (fsync) calls to improve performance.
|
|
259
|
+
// The serial writeQueue ensures order, and the OS will manage buffering.
|
|
252
260
|
|
|
253
|
-
|
|
254
261
|
self.eventCount++;
|
|
255
262
|
NSNumber *ts = event[@"timestamp"];
|
|
256
263
|
if (ts) {
|
|
@@ -269,27 +276,9 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
269
276
|
__block NSMutableArray<NSDictionary *> *events = [NSMutableArray new];
|
|
270
277
|
|
|
271
278
|
[self performWriteSync:^{
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
error:&error];
|
|
276
|
-
if (error || !content) {
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
NSArray<NSString *> *lines = [content componentsSeparatedByString:@"\n"];
|
|
281
|
-
for (NSString *line in lines) {
|
|
282
|
-
if (line.length == 0)
|
|
283
|
-
continue;
|
|
284
|
-
|
|
285
|
-
NSData *data = [line dataUsingEncoding:NSUTF8StringEncoding];
|
|
286
|
-
NSDictionary *event = [NSJSONSerialization JSONObjectWithData:data
|
|
287
|
-
options:0
|
|
288
|
-
error:nil];
|
|
289
|
-
if (event) {
|
|
290
|
-
[events addObject:event];
|
|
291
|
-
}
|
|
292
|
-
}
|
|
279
|
+
[self enumerateEventsWithBlock:^(NSDictionary *event, BOOL *stop) {
|
|
280
|
+
[events addObject:event];
|
|
281
|
+
}];
|
|
293
282
|
}];
|
|
294
283
|
|
|
295
284
|
return events;
|
|
@@ -297,7 +286,7 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
297
286
|
|
|
298
287
|
- (NSArray<NSDictionary *> *)readEventsAfterBatchNumber:
|
|
299
288
|
(NSInteger)afterBatchNumber {
|
|
300
|
-
|
|
289
|
+
|
|
301
290
|
NSArray<NSDictionary *> *allEvents = [self readAllEvents];
|
|
302
291
|
|
|
303
292
|
__block NSInteger uploadedCount = 0;
|
|
@@ -310,9 +299,8 @@ static void *kRJEventBufferQueueKey = &kRJEventBufferQueueKey;
|
|
|
310
299
|
return @[];
|
|
311
300
|
}
|
|
312
301
|
|
|
313
|
-
return [allEvents
|
|
314
|
-
|
|
315
|
-
allEvents.count - startIndex)];
|
|
302
|
+
return [allEvents
|
|
303
|
+
subarrayWithRange:NSMakeRange(startIndex, allEvents.count - startIndex)];
|
|
316
304
|
}
|
|
317
305
|
|
|
318
306
|
- (void)markEventsUploadedUpToIndex:(NSInteger)eventIndex {
|
|
@@ -23,110 +23,111 @@
|
|
|
23
23
|
|
|
24
24
|
@implementation RJWindowUtils
|
|
25
25
|
|
|
26
|
+
static __weak UIWindow *_cachedKeyWindow = nil;
|
|
27
|
+
static NSTimeInterval _lastCacheTime = 0;
|
|
28
|
+
static const NSTimeInterval kKeyWindowCacheTTL = 0.5; // Cache for 500ms
|
|
29
|
+
|
|
26
30
|
+ (UIWindow *)keyWindow {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
CGFloat bestKeyAppLevel = -CGFLOAT_MAX;
|
|
46
|
-
UIWindow *bestApp = nil;
|
|
47
|
-
CGFloat bestAppLevel = -CGFLOAT_MAX;
|
|
48
|
-
UIWindow *anyKey = nil;
|
|
49
|
-
|
|
50
|
-
for (UIScene *scene in [UIApplication sharedApplication].connectedScenes) {
|
|
51
|
-
if (![scene isKindOfClass:[UIWindowScene class]]) {
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
UIWindowScene *windowScene = (UIWindowScene *)scene;
|
|
55
|
-
if (windowScene.activationState != UISceneActivationStateForegroundActive) {
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
for (UIWindow *window in windowScene.windows) {
|
|
60
|
-
if (!window || window.isHidden || window.alpha <= 0.01) {
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
NSString *cls = NSStringFromClass([window class]);
|
|
65
|
-
BOOL isSystemInputWindow =
|
|
66
|
-
([cls containsString:@"Keyboard"] ||
|
|
67
|
-
[cls containsString:@"TextEffects"] ||
|
|
68
|
-
[cls containsString:@"InputWindow"] ||
|
|
69
|
-
[cls containsString:@"RemoteKeyboard"]);
|
|
70
|
-
|
|
71
|
-
BOOL hasRoot = (window.rootViewController != nil);
|
|
72
|
-
BOOL isAppCandidate = (!isSystemInputWindow && hasRoot);
|
|
73
|
-
CGFloat level = window.windowLevel;
|
|
74
|
-
|
|
75
|
-
if (window.isKeyWindow) {
|
|
76
|
-
if (!anyKey) {
|
|
77
|
-
anyKey = window;
|
|
78
|
-
}
|
|
79
|
-
if (isAppCandidate && level > bestKeyAppLevel) {
|
|
80
|
-
bestKeyAppLevel = level;
|
|
81
|
-
bestKeyApp = window;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (isAppCandidate && level > bestAppLevel) {
|
|
86
|
-
bestAppLevel = level;
|
|
87
|
-
bestApp = window;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
31
|
+
NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
|
|
32
|
+
if (_cachedKeyWindow && (now - _lastCacheTime < kKeyWindowCacheTTL)) {
|
|
33
|
+
return _cachedKeyWindow;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
UIWindow *window = [self findKeyWindowInternal];
|
|
37
|
+
_cachedKeyWindow = window;
|
|
38
|
+
_lastCacheTime = now;
|
|
39
|
+
return window;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
+ (UIWindow *)findKeyWindowInternal {
|
|
43
|
+
if (@available(iOS 13.0, *)) {
|
|
44
|
+
UIWindow *bestKeyApp = nil;
|
|
45
|
+
CGFloat bestKeyAppLevel = -CGFLOAT_MAX;
|
|
46
|
+
UIWindow *bestApp = nil;
|
|
47
|
+
CGFloat bestAppLevel = -CGFLOAT_MAX;
|
|
48
|
+
UIWindow *anyKey = nil;
|
|
91
49
|
|
|
92
|
-
|
|
93
|
-
|
|
50
|
+
for (UIScene *scene in [UIApplication sharedApplication].connectedScenes) {
|
|
51
|
+
if (![scene isKindOfClass:[UIWindowScene class]]) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
UIWindowScene *windowScene = (UIWindowScene *)scene;
|
|
55
|
+
if (windowScene.activationState !=
|
|
56
|
+
UISceneActivationStateForegroundActive) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (UIWindow *window in windowScene.windows) {
|
|
61
|
+
if (!window || window.isHidden || window.alpha <= 0.01) {
|
|
62
|
+
continue;
|
|
94
63
|
}
|
|
95
|
-
|
|
96
|
-
|
|
64
|
+
|
|
65
|
+
NSString *cls = NSStringFromClass([window class]);
|
|
66
|
+
BOOL isSystemInputWindow = ([cls containsString:@"Keyboard"] ||
|
|
67
|
+
[cls containsString:@"TextEffects"] ||
|
|
68
|
+
[cls containsString:@"InputWindow"] ||
|
|
69
|
+
[cls containsString:@"RemoteKeyboard"]);
|
|
70
|
+
|
|
71
|
+
BOOL hasRoot = (window.rootViewController != nil);
|
|
72
|
+
BOOL isAppCandidate = (!isSystemInputWindow && hasRoot);
|
|
73
|
+
CGFloat level = window.windowLevel;
|
|
74
|
+
|
|
75
|
+
if (window.isKeyWindow) {
|
|
76
|
+
if (!anyKey) {
|
|
77
|
+
anyKey = window;
|
|
78
|
+
}
|
|
79
|
+
if (isAppCandidate && level > bestKeyAppLevel) {
|
|
80
|
+
bestKeyAppLevel = level;
|
|
81
|
+
bestKeyApp = window;
|
|
82
|
+
}
|
|
97
83
|
}
|
|
98
|
-
|
|
99
|
-
|
|
84
|
+
|
|
85
|
+
if (isAppCandidate && level > bestAppLevel) {
|
|
86
|
+
bestAppLevel = level;
|
|
87
|
+
bestApp = window;
|
|
100
88
|
}
|
|
101
|
-
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (bestKeyApp) {
|
|
93
|
+
return bestKeyApp;
|
|
94
|
+
}
|
|
95
|
+
if (bestApp) {
|
|
96
|
+
return bestApp;
|
|
97
|
+
}
|
|
98
|
+
if (anyKey) {
|
|
99
|
+
return anyKey;
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
102
|
#pragma clang diagnostic push
|
|
103
103
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
104
|
-
|
|
104
|
+
return [UIApplication sharedApplication].keyWindow;
|
|
105
105
|
#pragma clang diagnostic pop
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
}
|
|
107
|
+
return nil;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
+ (NSString *)accessibilityLabelForView:(UIView *)view {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
current = current.superview;
|
|
111
|
+
UIView *current = view;
|
|
112
|
+
while (current) {
|
|
113
|
+
if (current.accessibilityLabel.length > 0) {
|
|
114
|
+
return current.accessibilityLabel;
|
|
117
115
|
}
|
|
118
|
-
|
|
116
|
+
current = current.superview;
|
|
117
|
+
}
|
|
118
|
+
return nil;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
+ (NSString *)generateSessionId {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
NSTimeInterval timestamp = [[NSDate date] timeIntervalSince1970];
|
|
123
|
+
NSString *timestampStr =
|
|
124
|
+
[NSString stringWithFormat:@"%.0f", timestamp * 1000];
|
|
125
|
+
NSString *randomHex = [NSString stringWithFormat:@"%08X", arc4random()];
|
|
126
|
+
return [NSString stringWithFormat:@"session_%@_%@", timestampStr, randomHex];
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
+ (NSTimeInterval)currentTimestampMillis {
|
|
129
|
-
|
|
130
|
+
return [[NSDate date] timeIntervalSince1970] * 1000;
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
@end
|
package/lib/commonjs/index.js
CHANGED
|
@@ -194,7 +194,12 @@ function getLogger() {
|
|
|
194
194
|
logRecordingStart: () => {},
|
|
195
195
|
logRecordingRemoteDisabled: () => {},
|
|
196
196
|
logInvalidProjectKey: () => {},
|
|
197
|
-
logPackageMismatch: () => {}
|
|
197
|
+
logPackageMismatch: () => {},
|
|
198
|
+
logNetworkRequest: () => {},
|
|
199
|
+
logFrustration: () => {},
|
|
200
|
+
logError: () => {},
|
|
201
|
+
logUploadStats: () => {},
|
|
202
|
+
logLifecycleEvent: () => {}
|
|
198
203
|
};
|
|
199
204
|
}
|
|
200
205
|
try {
|
|
@@ -219,7 +224,12 @@ function getLogger() {
|
|
|
219
224
|
logRecordingStart: () => {},
|
|
220
225
|
logRecordingRemoteDisabled: () => {},
|
|
221
226
|
logInvalidProjectKey: () => {},
|
|
222
|
-
logPackageMismatch: () => {}
|
|
227
|
+
logPackageMismatch: () => {},
|
|
228
|
+
logNetworkRequest: () => {},
|
|
229
|
+
logFrustration: () => {},
|
|
230
|
+
logError: () => {},
|
|
231
|
+
logUploadStats: () => {},
|
|
232
|
+
logLifecycleEvent: () => {}
|
|
223
233
|
};
|
|
224
234
|
}
|
|
225
235
|
}
|
|
@@ -276,6 +286,7 @@ function getAutoTracking() {
|
|
|
276
286
|
let _isInitialized = false;
|
|
277
287
|
let _isRecording = false;
|
|
278
288
|
let _initializationFailed = false;
|
|
289
|
+
let _metricsInterval = null;
|
|
279
290
|
let _appStateSubscription = null;
|
|
280
291
|
let _authErrorSubscription = null;
|
|
281
292
|
let _currentAppState = 'active'; // Default to active, will be updated on init
|
|
@@ -478,18 +489,12 @@ const Rejourney = {
|
|
|
478
489
|
const apiUrl = _storedConfig.apiUrl || 'https://api.rejourney.co';
|
|
479
490
|
const publicKey = _storedConfig.publicRouteKey || '';
|
|
480
491
|
getLogger().debug(`Calling native startSession (apiUrl=${apiUrl})`);
|
|
481
|
-
|
|
482
|
-
// Use user identity if set, otherwise use anonymous device ID
|
|
483
492
|
const deviceId = await getAutoTracking().ensurePersistentAnonymousId();
|
|
484
|
-
|
|
485
|
-
// Try to load persisted user identity if not already set in memory
|
|
486
493
|
if (!_userIdentity) {
|
|
487
494
|
_userIdentity = await loadPersistedUserIdentity();
|
|
488
495
|
}
|
|
489
496
|
const userId = _userIdentity || deviceId;
|
|
490
497
|
getLogger().debug(`userId=${userId.substring(0, 8)}...`);
|
|
491
|
-
|
|
492
|
-
// Start native session
|
|
493
498
|
const result = await nativeModule.startSession(userId, apiUrl, publicKey);
|
|
494
499
|
getLogger().debug('Native startSession returned:', JSON.stringify(result));
|
|
495
500
|
if (!result?.success) {
|
|
@@ -502,10 +507,27 @@ const Rejourney = {
|
|
|
502
507
|
}
|
|
503
508
|
_isRecording = true;
|
|
504
509
|
getLogger().debug(`✅ Session started: ${result.sessionId}`);
|
|
505
|
-
// Use lifecycle log for session start - only shown in dev builds
|
|
506
510
|
getLogger().logSessionStart(result.sessionId);
|
|
507
|
-
|
|
508
|
-
|
|
511
|
+
// Start polling for upload stats in dev mode
|
|
512
|
+
if (__DEV__) {
|
|
513
|
+
_metricsInterval = setInterval(async () => {
|
|
514
|
+
if (!_isRecording) {
|
|
515
|
+
if (_metricsInterval) clearInterval(_metricsInterval);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
try {
|
|
519
|
+
const native = getRejourneyNative();
|
|
520
|
+
if (native) {
|
|
521
|
+
const metrics = await native.getSDKMetrics();
|
|
522
|
+
if (metrics) {
|
|
523
|
+
getLogger().logUploadStats(metrics);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
} catch (e) {
|
|
527
|
+
getLogger().debug('Failed to fetch metrics:', e);
|
|
528
|
+
}
|
|
529
|
+
}, 10000); // Poll more frequently in dev (10s) for better feedback
|
|
530
|
+
}
|
|
509
531
|
getAutoTracking().initAutoTracking({
|
|
510
532
|
rageTapThreshold: _storedConfig?.rageTapThreshold ?? 3,
|
|
511
533
|
rageTapTimeWindow: _storedConfig?.rageTapTimeWindow ?? 500,
|
|
@@ -523,7 +545,7 @@ const Rejourney = {
|
|
|
523
545
|
x,
|
|
524
546
|
y
|
|
525
547
|
});
|
|
526
|
-
|
|
548
|
+
getLogger().logFrustration(`Rage tap (${count} taps)`);
|
|
527
549
|
},
|
|
528
550
|
// Error callback - log as error event
|
|
529
551
|
onError: error => {
|
|
@@ -532,17 +554,10 @@ const Rejourney = {
|
|
|
532
554
|
stack: error.stack,
|
|
533
555
|
name: error.name
|
|
534
556
|
});
|
|
535
|
-
|
|
557
|
+
getLogger().logError(error.message);
|
|
536
558
|
},
|
|
537
|
-
|
|
538
|
-
onScreen: (_screenName, _previousScreen) => {
|
|
539
|
-
// Native module already handles screen changes
|
|
540
|
-
// This is just for metrics tracking
|
|
541
|
-
// logger.debug(`Screen changed: ${previousScreen} -> ${screenName}`);
|
|
542
|
-
}
|
|
559
|
+
onScreen: (_screenName, _previousScreen) => {}
|
|
543
560
|
});
|
|
544
|
-
|
|
545
|
-
// Collect and log device info
|
|
546
561
|
if (_storedConfig?.collectDeviceInfo !== false) {
|
|
547
562
|
try {
|
|
548
563
|
const deviceInfo = await getAutoTracking().collectDeviceInfo();
|
|
@@ -551,14 +566,12 @@ const Rejourney = {
|
|
|
551
566
|
getLogger().warn('Failed to collect device info:', deviceError);
|
|
552
567
|
}
|
|
553
568
|
}
|
|
554
|
-
|
|
555
|
-
// Setup automatic network interception
|
|
556
569
|
if (_storedConfig?.autoTrackNetwork !== false) {
|
|
557
570
|
try {
|
|
558
571
|
const ignoreUrls = [apiUrl, '/api/ingest/presign', '/api/ingest/batch/complete', '/api/ingest/session/end', ...(_storedConfig?.networkIgnoreUrls || [])];
|
|
559
572
|
getNetworkInterceptor().initNetworkInterceptor(request => {
|
|
560
|
-
this.logNetworkRequest(request);
|
|
561
573
|
getAutoTracking().trackAPIRequest(request.success || false, request.statusCode, request.duration || 0, request.responseBodySize || 0);
|
|
574
|
+
Rejourney.logNetworkRequest(request);
|
|
562
575
|
}, {
|
|
563
576
|
ignoreUrls,
|
|
564
577
|
captureSizes: _storedConfig?.networkCaptureSizes !== false
|
|
@@ -589,6 +602,10 @@ const Rejourney = {
|
|
|
589
602
|
getAutoTracking().cleanupAutoTracking();
|
|
590
603
|
getAutoTracking().resetMetrics();
|
|
591
604
|
await safeNativeCall('stopSession', () => getRejourneyNative().stopSession(), undefined);
|
|
605
|
+
if (_metricsInterval) {
|
|
606
|
+
clearInterval(_metricsInterval);
|
|
607
|
+
_metricsInterval = null;
|
|
608
|
+
}
|
|
592
609
|
_isRecording = false;
|
|
593
610
|
getLogger().logSessionEnd('current');
|
|
594
611
|
} catch (error) {
|
|
@@ -605,7 +622,6 @@ const Rejourney = {
|
|
|
605
622
|
*/
|
|
606
623
|
logEvent(name, properties) {
|
|
607
624
|
safeNativeCallSync('logEvent', () => {
|
|
608
|
-
// Fire and forget - don't await
|
|
609
625
|
getRejourneyNative().logEvent(name, properties || {}).catch(() => {});
|
|
610
626
|
}, undefined);
|
|
611
627
|
},
|
|
@@ -706,7 +722,7 @@ const Rejourney = {
|
|
|
706
722
|
eventCount: 0,
|
|
707
723
|
videoSegmentCount: 0,
|
|
708
724
|
storageSize: 0,
|
|
709
|
-
sdkVersion:
|
|
725
|
+
sdkVersion: _constants.SDK_VERSION,
|
|
710
726
|
isComplete: false
|
|
711
727
|
},
|
|
712
728
|
events: []
|
|
@@ -733,7 +749,6 @@ const Rejourney = {
|
|
|
733
749
|
* @returns Path to export file (not implemented)
|
|
734
750
|
*/
|
|
735
751
|
async exportSession(_sessionId) {
|
|
736
|
-
// Return empty string - actual export should be done from dashboard server
|
|
737
752
|
getLogger().warn('exportSession not implemented - export from dashboard server');
|
|
738
753
|
return '';
|
|
739
754
|
},
|
|
@@ -955,8 +970,6 @@ const Rejourney = {
|
|
|
955
970
|
errorMessage: request.errorMessage,
|
|
956
971
|
cached: request.cached
|
|
957
972
|
};
|
|
958
|
-
|
|
959
|
-
// Fire and forget - don't await, this is low priority
|
|
960
973
|
getRejourneyNative().logEvent('network_request', networkEvent).catch(() => {});
|
|
961
974
|
}, undefined);
|
|
962
975
|
},
|
|
@@ -1053,10 +1066,10 @@ function handleAppStateChange(nextAppState) {
|
|
|
1053
1066
|
try {
|
|
1054
1067
|
if (_currentAppState.match(/active/) && nextAppState === 'background') {
|
|
1055
1068
|
// App going to background - native module handles this automatically
|
|
1056
|
-
getLogger().
|
|
1069
|
+
getLogger().logLifecycleEvent('App moving to background');
|
|
1057
1070
|
} else if (_currentAppState.match(/inactive|background/) && nextAppState === 'active') {
|
|
1058
1071
|
// App coming back to foreground
|
|
1059
|
-
getLogger().
|
|
1072
|
+
getLogger().logLifecycleEvent('App returning to foreground');
|
|
1060
1073
|
}
|
|
1061
1074
|
_currentAppState = nextAppState;
|
|
1062
1075
|
} catch (error) {
|
|
@@ -479,9 +479,6 @@ function setupNavigationTracking() {
|
|
|
479
479
|
if (__DEV__) {
|
|
480
480
|
_utils.logger.debug('Setting up navigation tracking...');
|
|
481
481
|
}
|
|
482
|
-
|
|
483
|
-
// Delay to ensure navigation is initialized - Expo Router needs more time
|
|
484
|
-
// We retry a few times with increasing delays
|
|
485
482
|
let attempts = 0;
|
|
486
483
|
const maxAttempts = 5;
|
|
487
484
|
const trySetup = () => {
|
|
@@ -8,7 +8,7 @@ exports.UPLOAD_SETTINGS = exports.STORAGE_SETTINGS = exports.SDK_VERSION = expor
|
|
|
8
8
|
* Rejourney SDK Constants
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
const SDK_VERSION = exports.SDK_VERSION = '1.0.
|
|
11
|
+
const SDK_VERSION = exports.SDK_VERSION = '1.0.3';
|
|
12
12
|
|
|
13
13
|
/** Default configuration values */
|
|
14
14
|
const DEFAULT_CONFIG = exports.DEFAULT_CONFIG = {
|
|
@@ -126,8 +126,6 @@ function queueRequest(request) {
|
|
|
126
126
|
function flushPendingRequests() {
|
|
127
127
|
flushTimer = null;
|
|
128
128
|
if (!logCallback || pendingCount === 0) return;
|
|
129
|
-
|
|
130
|
-
// Process all pending requests
|
|
131
129
|
while (pendingCount > 0) {
|
|
132
130
|
const request = pendingRequests[pendingHead];
|
|
133
131
|
pendingRequests[pendingHead] = null; // Allow GC
|
|
@@ -195,14 +193,9 @@ function interceptFetch() {
|
|
|
195
193
|
if (!shouldSampleRequest(path)) {
|
|
196
194
|
return originalFetch(input, init);
|
|
197
195
|
}
|
|
198
|
-
|
|
199
|
-
// Capture start time (only synchronous work)
|
|
200
196
|
const startTime = Date.now();
|
|
201
197
|
const method = (init?.method || 'GET').toUpperCase();
|
|
202
|
-
|
|
203
|
-
// Call original fetch
|
|
204
198
|
return originalFetch(input, init).then(response => {
|
|
205
|
-
// Success - queue the log asynchronously
|
|
206
199
|
queueRequest({
|
|
207
200
|
requestId: `f${startTime}`,
|
|
208
201
|
method,
|
|
@@ -253,8 +246,6 @@ function interceptXHR() {
|
|
|
253
246
|
if (!config.enabled || !logCallback || !data || shouldIgnoreUrl(data.u)) {
|
|
254
247
|
return originalXHRSend.call(this, body);
|
|
255
248
|
}
|
|
256
|
-
|
|
257
|
-
// Check sampling
|
|
258
249
|
const {
|
|
259
250
|
path
|
|
260
251
|
} = parseUrlFast(data.u);
|
|
@@ -303,8 +294,6 @@ function initNetworkInterceptor(callback, options) {
|
|
|
303
294
|
*/
|
|
304
295
|
function disableNetworkInterceptor() {
|
|
305
296
|
config.enabled = false;
|
|
306
|
-
|
|
307
|
-
// Flush any pending requests
|
|
308
297
|
if (flushTimer) {
|
|
309
298
|
clearTimeout(flushTimer);
|
|
310
299
|
flushTimer = null;
|