@rejourneyco/react-native 1.0.2 → 1.0.3
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 +3 -26
- 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/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 +42 -30
- package/lib/commonjs/sdk/autoTracking.js +0 -3
- package/lib/commonjs/sdk/networkInterceptor.js +0 -11
- package/lib/commonjs/sdk/utils.js +73 -14
- package/lib/module/index.js +42 -30
- package/lib/module/sdk/autoTracking.js +0 -3
- package/lib/module/sdk/networkInterceptor.js +0 -11
- package/lib/module/sdk/utils.js +73 -14
- package/lib/typescript/sdk/utils.d.ts +31 -1
- package/package.json +16 -4
- package/src/index.ts +40 -19
- package/src/sdk/autoTracking.ts +0 -2
- package/src/sdk/constants.ts +13 -13
- package/src/sdk/networkInterceptor.ts +0 -9
- package/src/sdk/utils.ts +76 -14
package/lib/module/index.js
CHANGED
|
@@ -86,7 +86,12 @@ function getLogger() {
|
|
|
86
86
|
logRecordingStart: () => {},
|
|
87
87
|
logRecordingRemoteDisabled: () => {},
|
|
88
88
|
logInvalidProjectKey: () => {},
|
|
89
|
-
logPackageMismatch: () => {}
|
|
89
|
+
logPackageMismatch: () => {},
|
|
90
|
+
logNetworkRequest: () => {},
|
|
91
|
+
logFrustration: () => {},
|
|
92
|
+
logError: () => {},
|
|
93
|
+
logUploadStats: () => {},
|
|
94
|
+
logLifecycleEvent: () => {}
|
|
90
95
|
};
|
|
91
96
|
}
|
|
92
97
|
try {
|
|
@@ -111,7 +116,12 @@ function getLogger() {
|
|
|
111
116
|
logRecordingStart: () => {},
|
|
112
117
|
logRecordingRemoteDisabled: () => {},
|
|
113
118
|
logInvalidProjectKey: () => {},
|
|
114
|
-
logPackageMismatch: () => {}
|
|
119
|
+
logPackageMismatch: () => {},
|
|
120
|
+
logNetworkRequest: () => {},
|
|
121
|
+
logFrustration: () => {},
|
|
122
|
+
logError: () => {},
|
|
123
|
+
logUploadStats: () => {},
|
|
124
|
+
logLifecycleEvent: () => {}
|
|
115
125
|
};
|
|
116
126
|
}
|
|
117
127
|
}
|
|
@@ -168,6 +178,7 @@ function getAutoTracking() {
|
|
|
168
178
|
let _isInitialized = false;
|
|
169
179
|
let _isRecording = false;
|
|
170
180
|
let _initializationFailed = false;
|
|
181
|
+
let _metricsInterval = null;
|
|
171
182
|
let _appStateSubscription = null;
|
|
172
183
|
let _authErrorSubscription = null;
|
|
173
184
|
let _currentAppState = 'active'; // Default to active, will be updated on init
|
|
@@ -370,18 +381,12 @@ const Rejourney = {
|
|
|
370
381
|
const apiUrl = _storedConfig.apiUrl || 'https://api.rejourney.co';
|
|
371
382
|
const publicKey = _storedConfig.publicRouteKey || '';
|
|
372
383
|
getLogger().debug(`Calling native startSession (apiUrl=${apiUrl})`);
|
|
373
|
-
|
|
374
|
-
// Use user identity if set, otherwise use anonymous device ID
|
|
375
384
|
const deviceId = await getAutoTracking().ensurePersistentAnonymousId();
|
|
376
|
-
|
|
377
|
-
// Try to load persisted user identity if not already set in memory
|
|
378
385
|
if (!_userIdentity) {
|
|
379
386
|
_userIdentity = await loadPersistedUserIdentity();
|
|
380
387
|
}
|
|
381
388
|
const userId = _userIdentity || deviceId;
|
|
382
389
|
getLogger().debug(`userId=${userId.substring(0, 8)}...`);
|
|
383
|
-
|
|
384
|
-
// Start native session
|
|
385
390
|
const result = await nativeModule.startSession(userId, apiUrl, publicKey);
|
|
386
391
|
getLogger().debug('Native startSession returned:', JSON.stringify(result));
|
|
387
392
|
if (!result?.success) {
|
|
@@ -394,10 +399,27 @@ const Rejourney = {
|
|
|
394
399
|
}
|
|
395
400
|
_isRecording = true;
|
|
396
401
|
getLogger().debug(`✅ Session started: ${result.sessionId}`);
|
|
397
|
-
// Use lifecycle log for session start - only shown in dev builds
|
|
398
402
|
getLogger().logSessionStart(result.sessionId);
|
|
399
|
-
|
|
400
|
-
|
|
403
|
+
// Start polling for upload stats in dev mode
|
|
404
|
+
if (__DEV__) {
|
|
405
|
+
_metricsInterval = setInterval(async () => {
|
|
406
|
+
if (!_isRecording) {
|
|
407
|
+
if (_metricsInterval) clearInterval(_metricsInterval);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
try {
|
|
411
|
+
const native = getRejourneyNative();
|
|
412
|
+
if (native) {
|
|
413
|
+
const metrics = await native.getSDKMetrics();
|
|
414
|
+
if (metrics) {
|
|
415
|
+
getLogger().logUploadStats(metrics);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
} catch (e) {
|
|
419
|
+
getLogger().debug('Failed to fetch metrics:', e);
|
|
420
|
+
}
|
|
421
|
+
}, 10000); // Poll more frequently in dev (10s) for better feedback
|
|
422
|
+
}
|
|
401
423
|
getAutoTracking().initAutoTracking({
|
|
402
424
|
rageTapThreshold: _storedConfig?.rageTapThreshold ?? 3,
|
|
403
425
|
rageTapTimeWindow: _storedConfig?.rageTapTimeWindow ?? 500,
|
|
@@ -415,7 +437,7 @@ const Rejourney = {
|
|
|
415
437
|
x,
|
|
416
438
|
y
|
|
417
439
|
});
|
|
418
|
-
|
|
440
|
+
getLogger().logFrustration(`Rage tap (${count} taps)`);
|
|
419
441
|
},
|
|
420
442
|
// Error callback - log as error event
|
|
421
443
|
onError: error => {
|
|
@@ -424,17 +446,10 @@ const Rejourney = {
|
|
|
424
446
|
stack: error.stack,
|
|
425
447
|
name: error.name
|
|
426
448
|
});
|
|
427
|
-
|
|
449
|
+
getLogger().logError(error.message);
|
|
428
450
|
},
|
|
429
|
-
|
|
430
|
-
onScreen: (_screenName, _previousScreen) => {
|
|
431
|
-
// Native module already handles screen changes
|
|
432
|
-
// This is just for metrics tracking
|
|
433
|
-
// logger.debug(`Screen changed: ${previousScreen} -> ${screenName}`);
|
|
434
|
-
}
|
|
451
|
+
onScreen: (_screenName, _previousScreen) => {}
|
|
435
452
|
});
|
|
436
|
-
|
|
437
|
-
// Collect and log device info
|
|
438
453
|
if (_storedConfig?.collectDeviceInfo !== false) {
|
|
439
454
|
try {
|
|
440
455
|
const deviceInfo = await getAutoTracking().collectDeviceInfo();
|
|
@@ -443,13 +458,10 @@ const Rejourney = {
|
|
|
443
458
|
getLogger().warn('Failed to collect device info:', deviceError);
|
|
444
459
|
}
|
|
445
460
|
}
|
|
446
|
-
|
|
447
|
-
// Setup automatic network interception
|
|
448
461
|
if (_storedConfig?.autoTrackNetwork !== false) {
|
|
449
462
|
try {
|
|
450
463
|
const ignoreUrls = [apiUrl, '/api/ingest/presign', '/api/ingest/batch/complete', '/api/ingest/session/end', ...(_storedConfig?.networkIgnoreUrls || [])];
|
|
451
464
|
getNetworkInterceptor().initNetworkInterceptor(request => {
|
|
452
|
-
this.logNetworkRequest(request);
|
|
453
465
|
getAutoTracking().trackAPIRequest(request.success || false, request.statusCode, request.duration || 0, request.responseBodySize || 0);
|
|
454
466
|
}, {
|
|
455
467
|
ignoreUrls,
|
|
@@ -481,6 +493,10 @@ const Rejourney = {
|
|
|
481
493
|
getAutoTracking().cleanupAutoTracking();
|
|
482
494
|
getAutoTracking().resetMetrics();
|
|
483
495
|
await safeNativeCall('stopSession', () => getRejourneyNative().stopSession(), undefined);
|
|
496
|
+
if (_metricsInterval) {
|
|
497
|
+
clearInterval(_metricsInterval);
|
|
498
|
+
_metricsInterval = null;
|
|
499
|
+
}
|
|
484
500
|
_isRecording = false;
|
|
485
501
|
getLogger().logSessionEnd('current');
|
|
486
502
|
} catch (error) {
|
|
@@ -497,7 +513,6 @@ const Rejourney = {
|
|
|
497
513
|
*/
|
|
498
514
|
logEvent(name, properties) {
|
|
499
515
|
safeNativeCallSync('logEvent', () => {
|
|
500
|
-
// Fire and forget - don't await
|
|
501
516
|
getRejourneyNative().logEvent(name, properties || {}).catch(() => {});
|
|
502
517
|
}, undefined);
|
|
503
518
|
},
|
|
@@ -625,7 +640,6 @@ const Rejourney = {
|
|
|
625
640
|
* @returns Path to export file (not implemented)
|
|
626
641
|
*/
|
|
627
642
|
async exportSession(_sessionId) {
|
|
628
|
-
// Return empty string - actual export should be done from dashboard server
|
|
629
643
|
getLogger().warn('exportSession not implemented - export from dashboard server');
|
|
630
644
|
return '';
|
|
631
645
|
},
|
|
@@ -847,8 +861,6 @@ const Rejourney = {
|
|
|
847
861
|
errorMessage: request.errorMessage,
|
|
848
862
|
cached: request.cached
|
|
849
863
|
};
|
|
850
|
-
|
|
851
|
-
// Fire and forget - don't await, this is low priority
|
|
852
864
|
getRejourneyNative().logEvent('network_request', networkEvent).catch(() => {});
|
|
853
865
|
}, undefined);
|
|
854
866
|
},
|
|
@@ -945,10 +957,10 @@ function handleAppStateChange(nextAppState) {
|
|
|
945
957
|
try {
|
|
946
958
|
if (_currentAppState.match(/active/) && nextAppState === 'background') {
|
|
947
959
|
// App going to background - native module handles this automatically
|
|
948
|
-
getLogger().
|
|
960
|
+
getLogger().logLifecycleEvent('App moving to background');
|
|
949
961
|
} else if (_currentAppState.match(/inactive|background/) && nextAppState === 'active') {
|
|
950
962
|
// App coming back to foreground
|
|
951
|
-
getLogger().
|
|
963
|
+
getLogger().logLifecycleEvent('App returning to foreground');
|
|
952
964
|
}
|
|
953
965
|
_currentAppState = nextAppState;
|
|
954
966
|
} catch (error) {
|
|
@@ -452,9 +452,6 @@ function setupNavigationTracking() {
|
|
|
452
452
|
if (__DEV__) {
|
|
453
453
|
logger.debug('Setting up navigation tracking...');
|
|
454
454
|
}
|
|
455
|
-
|
|
456
|
-
// Delay to ensure navigation is initialized - Expo Router needs more time
|
|
457
|
-
// We retry a few times with increasing delays
|
|
458
455
|
let attempts = 0;
|
|
459
456
|
const maxAttempts = 5;
|
|
460
457
|
const trySetup = () => {
|
|
@@ -115,8 +115,6 @@ function queueRequest(request) {
|
|
|
115
115
|
function flushPendingRequests() {
|
|
116
116
|
flushTimer = null;
|
|
117
117
|
if (!logCallback || pendingCount === 0) return;
|
|
118
|
-
|
|
119
|
-
// Process all pending requests
|
|
120
118
|
while (pendingCount > 0) {
|
|
121
119
|
const request = pendingRequests[pendingHead];
|
|
122
120
|
pendingRequests[pendingHead] = null; // Allow GC
|
|
@@ -184,14 +182,9 @@ function interceptFetch() {
|
|
|
184
182
|
if (!shouldSampleRequest(path)) {
|
|
185
183
|
return originalFetch(input, init);
|
|
186
184
|
}
|
|
187
|
-
|
|
188
|
-
// Capture start time (only synchronous work)
|
|
189
185
|
const startTime = Date.now();
|
|
190
186
|
const method = (init?.method || 'GET').toUpperCase();
|
|
191
|
-
|
|
192
|
-
// Call original fetch
|
|
193
187
|
return originalFetch(input, init).then(response => {
|
|
194
|
-
// Success - queue the log asynchronously
|
|
195
188
|
queueRequest({
|
|
196
189
|
requestId: `f${startTime}`,
|
|
197
190
|
method,
|
|
@@ -242,8 +235,6 @@ function interceptXHR() {
|
|
|
242
235
|
if (!config.enabled || !logCallback || !data || shouldIgnoreUrl(data.u)) {
|
|
243
236
|
return originalXHRSend.call(this, body);
|
|
244
237
|
}
|
|
245
|
-
|
|
246
|
-
// Check sampling
|
|
247
238
|
const {
|
|
248
239
|
path
|
|
249
240
|
} = parseUrlFast(data.u);
|
|
@@ -292,8 +283,6 @@ export function initNetworkInterceptor(callback, options) {
|
|
|
292
283
|
*/
|
|
293
284
|
export function disableNetworkInterceptor() {
|
|
294
285
|
config.enabled = false;
|
|
295
|
-
|
|
296
|
-
// Flush any pending requests
|
|
297
286
|
if (flushTimer) {
|
|
298
287
|
clearTimeout(flushTimer);
|
|
299
288
|
flushTimer = null;
|
package/lib/module/sdk/utils.js
CHANGED
|
@@ -222,7 +222,6 @@ export let LogLevel = /*#__PURE__*/function (LogLevel) {
|
|
|
222
222
|
*/
|
|
223
223
|
class Logger {
|
|
224
224
|
prefix = '[Rejourney]';
|
|
225
|
-
debugMode = false;
|
|
226
225
|
|
|
227
226
|
/**
|
|
228
227
|
* Minimum log level to display.
|
|
@@ -243,7 +242,6 @@ class Logger {
|
|
|
243
242
|
this.minimumLogLevel = level;
|
|
244
243
|
}
|
|
245
244
|
setDebugMode(enabled) {
|
|
246
|
-
this.debugMode = enabled;
|
|
247
245
|
this.minimumLogLevel = enabled ? LogLevel.DEBUG : typeof __DEV__ !== 'undefined' && __DEV__ ? LogLevel.ERROR : LogLevel.SILENT;
|
|
248
246
|
}
|
|
249
247
|
|
|
@@ -264,14 +262,26 @@ class Logger {
|
|
|
264
262
|
/** Log a warning message */
|
|
265
263
|
warn(...args) {
|
|
266
264
|
if (this.minimumLogLevel <= LogLevel.WARNING) {
|
|
267
|
-
|
|
265
|
+
if (this.minimumLogLevel <= LogLevel.DEBUG) {
|
|
266
|
+
// Explicit Debug Mode: Show YellowBox
|
|
267
|
+
console.warn(this.prefix, ...args);
|
|
268
|
+
} else {
|
|
269
|
+
// Default Dev Mode: Log to console only, avoid YellowBox
|
|
270
|
+
console.log(this.prefix, '[WARN]', ...args);
|
|
271
|
+
}
|
|
268
272
|
}
|
|
269
273
|
}
|
|
270
274
|
|
|
271
275
|
/** Log an error message */
|
|
272
276
|
error(...args) {
|
|
273
277
|
if (this.minimumLogLevel <= LogLevel.ERROR) {
|
|
274
|
-
|
|
278
|
+
if (this.minimumLogLevel <= LogLevel.DEBUG) {
|
|
279
|
+
// Explicit Debug Mode: Show RedBox
|
|
280
|
+
console.error(this.prefix, ...args);
|
|
281
|
+
} else {
|
|
282
|
+
// Default Dev Mode: Log to console only, avoid RedBox
|
|
283
|
+
console.log(this.prefix, '[ERROR]', ...args);
|
|
284
|
+
}
|
|
275
285
|
}
|
|
276
286
|
}
|
|
277
287
|
notice(...args) {
|
|
@@ -285,9 +295,7 @@ class Logger {
|
|
|
285
295
|
* Only shown in development builds - this is the minimal "SDK started" log.
|
|
286
296
|
*/
|
|
287
297
|
logInitSuccess(version) {
|
|
288
|
-
|
|
289
|
-
this.info(`✓ SDK initialized (v${version})`);
|
|
290
|
-
}
|
|
298
|
+
this.notice(`✓ SDK initialized (v${version})`);
|
|
291
299
|
}
|
|
292
300
|
|
|
293
301
|
/**
|
|
@@ -303,9 +311,7 @@ class Logger {
|
|
|
303
311
|
* Only shown in development builds.
|
|
304
312
|
*/
|
|
305
313
|
logSessionStart(sessionId) {
|
|
306
|
-
|
|
307
|
-
this.info(`Session started: ${sessionId}`);
|
|
308
|
-
}
|
|
314
|
+
this.notice(`Session started: ${sessionId}`);
|
|
309
315
|
}
|
|
310
316
|
|
|
311
317
|
/**
|
|
@@ -313,12 +319,10 @@ class Logger {
|
|
|
313
319
|
* Only shown in development builds.
|
|
314
320
|
*/
|
|
315
321
|
logSessionEnd(sessionId) {
|
|
316
|
-
|
|
317
|
-
this.info(`Session ended: ${sessionId}`);
|
|
318
|
-
}
|
|
322
|
+
this.notice(`Session ended: ${sessionId}`);
|
|
319
323
|
}
|
|
320
324
|
logObservabilityStart() {
|
|
321
|
-
this.notice('Starting Rejourney observability');
|
|
325
|
+
this.notice('💧 Starting Rejourney observability');
|
|
322
326
|
}
|
|
323
327
|
logRecordingStart() {
|
|
324
328
|
this.notice('Starting recording');
|
|
@@ -332,6 +336,61 @@ class Logger {
|
|
|
332
336
|
logPackageMismatch() {
|
|
333
337
|
this.notice('Bundle ID / package name mismatch');
|
|
334
338
|
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Log network request details
|
|
342
|
+
*/
|
|
343
|
+
logNetworkRequest(request) {
|
|
344
|
+
const statusIcon = request.error || request.statusCode && request.statusCode >= 400 ? '🔴' : '🟢';
|
|
345
|
+
const method = request.method || 'GET';
|
|
346
|
+
// Shorten URL to just path if possible
|
|
347
|
+
let url = request.url || '';
|
|
348
|
+
try {
|
|
349
|
+
if (url.startsWith('http')) {
|
|
350
|
+
const urlObj = new URL(url);
|
|
351
|
+
url = urlObj.pathname;
|
|
352
|
+
}
|
|
353
|
+
} catch {
|
|
354
|
+
// Keep full URL if parsing fails
|
|
355
|
+
}
|
|
356
|
+
const duration = request.duration ? `(${Math.round(request.duration)}ms)` : '';
|
|
357
|
+
const status = request.statusCode ? `${request.statusCode}` : 'ERR';
|
|
358
|
+
this.notice(`${statusIcon} [NET] ${status} ${method} ${url} ${duration} ${request.error ? `Error: ${request.error}` : ''}`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Log frustration event (rage taps, etc)
|
|
363
|
+
*/
|
|
364
|
+
logFrustration(kind) {
|
|
365
|
+
this.notice(`🤬 Frustration detected: ${kind}`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Log error captured by SDK
|
|
370
|
+
*/
|
|
371
|
+
logError(message) {
|
|
372
|
+
this.notice(`X Error captured: ${message}`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Log lifecycle event (Background/Foreground)
|
|
377
|
+
* Visible in development builds.
|
|
378
|
+
*/
|
|
379
|
+
logLifecycleEvent(event) {
|
|
380
|
+
this.notice(`🔄 Lifecycle: ${event}`);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Log upload statistics
|
|
385
|
+
*/
|
|
386
|
+
logUploadStats(metrics) {
|
|
387
|
+
const success = metrics.uploadSuccessCount;
|
|
388
|
+
const failed = metrics.uploadFailureCount;
|
|
389
|
+
const bytes = formatBytes(metrics.totalBytesUploaded);
|
|
390
|
+
|
|
391
|
+
// Always show in dev mode for reassurance, even if 0
|
|
392
|
+
this.notice(`📡 Upload Stats: ${success} success, ${failed} failed (${bytes} uploaded)`);
|
|
393
|
+
}
|
|
335
394
|
}
|
|
336
395
|
export const logger = new Logger();
|
|
337
396
|
//# sourceMappingURL=utils.js.map
|
|
@@ -91,7 +91,6 @@ export declare enum LogLevel {
|
|
|
91
91
|
*/
|
|
92
92
|
declare class Logger {
|
|
93
93
|
private prefix;
|
|
94
|
-
private debugMode;
|
|
95
94
|
/**
|
|
96
95
|
* Minimum log level to display.
|
|
97
96
|
*
|
|
@@ -142,6 +141,37 @@ declare class Logger {
|
|
|
142
141
|
logRecordingRemoteDisabled(): void;
|
|
143
142
|
logInvalidProjectKey(): void;
|
|
144
143
|
logPackageMismatch(): void;
|
|
144
|
+
/**
|
|
145
|
+
* Log network request details
|
|
146
|
+
*/
|
|
147
|
+
logNetworkRequest(request: {
|
|
148
|
+
method?: string;
|
|
149
|
+
url?: string;
|
|
150
|
+
statusCode?: number;
|
|
151
|
+
duration?: number;
|
|
152
|
+
error?: string;
|
|
153
|
+
}): void;
|
|
154
|
+
/**
|
|
155
|
+
* Log frustration event (rage taps, etc)
|
|
156
|
+
*/
|
|
157
|
+
logFrustration(kind: string): void;
|
|
158
|
+
/**
|
|
159
|
+
* Log error captured by SDK
|
|
160
|
+
*/
|
|
161
|
+
logError(message: string): void;
|
|
162
|
+
/**
|
|
163
|
+
* Log lifecycle event (Background/Foreground)
|
|
164
|
+
* Visible in development builds.
|
|
165
|
+
*/
|
|
166
|
+
logLifecycleEvent(event: string): void;
|
|
167
|
+
/**
|
|
168
|
+
* Log upload statistics
|
|
169
|
+
*/
|
|
170
|
+
logUploadStats(metrics: {
|
|
171
|
+
uploadSuccessCount: number;
|
|
172
|
+
uploadFailureCount: number;
|
|
173
|
+
totalBytesUploaded: number;
|
|
174
|
+
}): void;
|
|
145
175
|
}
|
|
146
176
|
export declare const logger: Logger;
|
|
147
177
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rejourneyco/react-native",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Rejourney Session Recording SDK for React Native",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -47,9 +47,21 @@
|
|
|
47
47
|
},
|
|
48
48
|
"keywords": [
|
|
49
49
|
"react-native",
|
|
50
|
-
"session-
|
|
51
|
-
"
|
|
52
|
-
"
|
|
50
|
+
"session-replay",
|
|
51
|
+
"observability",
|
|
52
|
+
"error-tracking",
|
|
53
|
+
"crash-reporting",
|
|
54
|
+
"anr-reporting",
|
|
55
|
+
"api-monitoring",
|
|
56
|
+
"alerting",
|
|
57
|
+
"mobile-analytics",
|
|
58
|
+
"mobile-observability",
|
|
59
|
+
"mobile-error-tracking",
|
|
60
|
+
"mobile-crash-reporting",
|
|
61
|
+
"mobile-anr-reporting",
|
|
62
|
+
"mobile-api-monitoring",
|
|
63
|
+
"mobile-performance-monitoring",
|
|
64
|
+
"mobile-user-behavior-analytics"
|
|
53
65
|
],
|
|
54
66
|
"repository": {
|
|
55
67
|
"type": "git",
|
package/src/index.ts
CHANGED
|
@@ -100,6 +100,11 @@ function getLogger() {
|
|
|
100
100
|
logRecordingRemoteDisabled: () => { },
|
|
101
101
|
logInvalidProjectKey: () => { },
|
|
102
102
|
logPackageMismatch: () => { },
|
|
103
|
+
logNetworkRequest: () => { },
|
|
104
|
+
logFrustration: () => { },
|
|
105
|
+
logError: () => { },
|
|
106
|
+
logUploadStats: () => { },
|
|
107
|
+
logLifecycleEvent: () => { },
|
|
103
108
|
};
|
|
104
109
|
}
|
|
105
110
|
|
|
@@ -126,6 +131,11 @@ function getLogger() {
|
|
|
126
131
|
logRecordingRemoteDisabled: () => { },
|
|
127
132
|
logInvalidProjectKey: () => { },
|
|
128
133
|
logPackageMismatch: () => { },
|
|
134
|
+
logNetworkRequest: () => { },
|
|
135
|
+
logFrustration: () => { },
|
|
136
|
+
logError: () => { },
|
|
137
|
+
logUploadStats: () => { },
|
|
138
|
+
logLifecycleEvent: () => { },
|
|
129
139
|
};
|
|
130
140
|
}
|
|
131
141
|
}
|
|
@@ -194,6 +204,7 @@ function getAutoTracking() {
|
|
|
194
204
|
let _isInitialized = false;
|
|
195
205
|
let _isRecording = false;
|
|
196
206
|
let _initializationFailed = false;
|
|
207
|
+
let _metricsInterval: ReturnType<typeof setInterval> | null = null;
|
|
197
208
|
let _appStateSubscription: { remove: () => void } | null = null;
|
|
198
209
|
let _authErrorSubscription: { remove: () => void } | null = null;
|
|
199
210
|
let _currentAppState: string = 'active'; // Default to active, will be updated on init
|
|
@@ -415,10 +426,8 @@ const Rejourney: RejourneyAPI = {
|
|
|
415
426
|
|
|
416
427
|
getLogger().debug(`Calling native startSession (apiUrl=${apiUrl})`);
|
|
417
428
|
|
|
418
|
-
// Use user identity if set, otherwise use anonymous device ID
|
|
419
429
|
const deviceId = await getAutoTracking().ensurePersistentAnonymousId();
|
|
420
430
|
|
|
421
|
-
// Try to load persisted user identity if not already set in memory
|
|
422
431
|
if (!_userIdentity) {
|
|
423
432
|
_userIdentity = await loadPersistedUserIdentity();
|
|
424
433
|
}
|
|
@@ -426,7 +435,6 @@ const Rejourney: RejourneyAPI = {
|
|
|
426
435
|
const userId = _userIdentity || deviceId;
|
|
427
436
|
getLogger().debug(`userId=${userId.substring(0, 8)}...`);
|
|
428
437
|
|
|
429
|
-
// Start native session
|
|
430
438
|
const result = await nativeModule.startSession(userId, apiUrl, publicKey);
|
|
431
439
|
getLogger().debug('Native startSession returned:', JSON.stringify(result));
|
|
432
440
|
|
|
@@ -441,10 +449,28 @@ const Rejourney: RejourneyAPI = {
|
|
|
441
449
|
|
|
442
450
|
_isRecording = true;
|
|
443
451
|
getLogger().debug(`✅ Session started: ${result.sessionId}`);
|
|
444
|
-
// Use lifecycle log for session start - only shown in dev builds
|
|
445
452
|
getLogger().logSessionStart(result.sessionId);
|
|
453
|
+
// Start polling for upload stats in dev mode
|
|
454
|
+
if (__DEV__) {
|
|
455
|
+
_metricsInterval = setInterval(async () => {
|
|
456
|
+
if (!_isRecording) {
|
|
457
|
+
if (_metricsInterval) clearInterval(_metricsInterval);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
try {
|
|
461
|
+
const native = getRejourneyNative();
|
|
462
|
+
if (native) {
|
|
463
|
+
const metrics = await native.getSDKMetrics();
|
|
464
|
+
if (metrics) {
|
|
465
|
+
getLogger().logUploadStats(metrics);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
} catch (e) {
|
|
469
|
+
getLogger().debug('Failed to fetch metrics:', e);
|
|
470
|
+
}
|
|
471
|
+
}, 10000); // Poll more frequently in dev (10s) for better feedback
|
|
472
|
+
}
|
|
446
473
|
|
|
447
|
-
// Initialize auto tracking features
|
|
448
474
|
getAutoTracking().initAutoTracking(
|
|
449
475
|
{
|
|
450
476
|
rageTapThreshold: _storedConfig?.rageTapThreshold ?? 3,
|
|
@@ -464,7 +490,7 @@ const Rejourney: RejourneyAPI = {
|
|
|
464
490
|
x,
|
|
465
491
|
y,
|
|
466
492
|
});
|
|
467
|
-
|
|
493
|
+
getLogger().logFrustration(`Rage tap (${count} taps)`);
|
|
468
494
|
},
|
|
469
495
|
// Error callback - log as error event
|
|
470
496
|
onError: (error: { message: string; stack?: string; name?: string }) => {
|
|
@@ -473,18 +499,13 @@ const Rejourney: RejourneyAPI = {
|
|
|
473
499
|
stack: error.stack,
|
|
474
500
|
name: error.name,
|
|
475
501
|
});
|
|
476
|
-
|
|
502
|
+
getLogger().logError(error.message);
|
|
477
503
|
},
|
|
478
|
-
// Screen change callback - log screen change
|
|
479
504
|
onScreen: (_screenName: string, _previousScreen?: string) => {
|
|
480
|
-
// Native module already handles screen changes
|
|
481
|
-
// This is just for metrics tracking
|
|
482
|
-
// logger.debug(`Screen changed: ${previousScreen} -> ${screenName}`);
|
|
483
505
|
},
|
|
484
506
|
}
|
|
485
507
|
);
|
|
486
508
|
|
|
487
|
-
// Collect and log device info
|
|
488
509
|
if (_storedConfig?.collectDeviceInfo !== false) {
|
|
489
510
|
try {
|
|
490
511
|
const deviceInfo = await getAutoTracking().collectDeviceInfo();
|
|
@@ -494,7 +515,6 @@ const Rejourney: RejourneyAPI = {
|
|
|
494
515
|
}
|
|
495
516
|
}
|
|
496
517
|
|
|
497
|
-
// Setup automatic network interception
|
|
498
518
|
if (_storedConfig?.autoTrackNetwork !== false) {
|
|
499
519
|
try {
|
|
500
520
|
const ignoreUrls: (string | RegExp)[] = [
|
|
@@ -507,7 +527,6 @@ const Rejourney: RejourneyAPI = {
|
|
|
507
527
|
|
|
508
528
|
getNetworkInterceptor().initNetworkInterceptor(
|
|
509
529
|
(request: NetworkRequestParams) => {
|
|
510
|
-
this.logNetworkRequest(request);
|
|
511
530
|
getAutoTracking().trackAPIRequest(
|
|
512
531
|
request.success || false,
|
|
513
532
|
request.statusCode,
|
|
@@ -554,6 +573,11 @@ const Rejourney: RejourneyAPI = {
|
|
|
554
573
|
|
|
555
574
|
await safeNativeCall('stopSession', () => getRejourneyNative()!.stopSession(), undefined);
|
|
556
575
|
|
|
576
|
+
if (_metricsInterval) {
|
|
577
|
+
clearInterval(_metricsInterval);
|
|
578
|
+
_metricsInterval = null;
|
|
579
|
+
}
|
|
580
|
+
|
|
557
581
|
_isRecording = false;
|
|
558
582
|
getLogger().logSessionEnd('current');
|
|
559
583
|
} catch (error) {
|
|
@@ -573,7 +597,6 @@ const Rejourney: RejourneyAPI = {
|
|
|
573
597
|
safeNativeCallSync(
|
|
574
598
|
'logEvent',
|
|
575
599
|
() => {
|
|
576
|
-
// Fire and forget - don't await
|
|
577
600
|
getRejourneyNative()!.logEvent(name, properties || {}).catch(() => { });
|
|
578
601
|
},
|
|
579
602
|
undefined
|
|
@@ -719,7 +742,6 @@ const Rejourney: RejourneyAPI = {
|
|
|
719
742
|
* @returns Path to export file (not implemented)
|
|
720
743
|
*/
|
|
721
744
|
async exportSession(_sessionId: string): Promise<string> {
|
|
722
|
-
// Return empty string - actual export should be done from dashboard server
|
|
723
745
|
getLogger().warn('exportSession not implemented - export from dashboard server');
|
|
724
746
|
return '';
|
|
725
747
|
},
|
|
@@ -975,7 +997,6 @@ const Rejourney: RejourneyAPI = {
|
|
|
975
997
|
cached: request.cached,
|
|
976
998
|
};
|
|
977
999
|
|
|
978
|
-
// Fire and forget - don't await, this is low priority
|
|
979
1000
|
getRejourneyNative()!.logEvent('network_request', networkEvent).catch(() => { });
|
|
980
1001
|
},
|
|
981
1002
|
undefined
|
|
@@ -1095,10 +1116,10 @@ function handleAppStateChange(nextAppState: string): void {
|
|
|
1095
1116
|
try {
|
|
1096
1117
|
if (_currentAppState.match(/active/) && nextAppState === 'background') {
|
|
1097
1118
|
// App going to background - native module handles this automatically
|
|
1098
|
-
getLogger().
|
|
1119
|
+
getLogger().logLifecycleEvent('App moving to background');
|
|
1099
1120
|
} else if (_currentAppState.match(/inactive|background/) && nextAppState === 'active') {
|
|
1100
1121
|
// App coming back to foreground
|
|
1101
|
-
getLogger().
|
|
1122
|
+
getLogger().logLifecycleEvent('App returning to foreground');
|
|
1102
1123
|
}
|
|
1103
1124
|
_currentAppState = nextAppState;
|
|
1104
1125
|
} catch (error) {
|
package/src/sdk/autoTracking.ts
CHANGED
|
@@ -572,8 +572,6 @@ function setupNavigationTracking(): void {
|
|
|
572
572
|
logger.debug('Setting up navigation tracking...');
|
|
573
573
|
}
|
|
574
574
|
|
|
575
|
-
// Delay to ensure navigation is initialized - Expo Router needs more time
|
|
576
|
-
// We retry a few times with increasing delays
|
|
577
575
|
let attempts = 0;
|
|
578
576
|
const maxAttempts = 5;
|
|
579
577
|
|
package/src/sdk/constants.ts
CHANGED
|
@@ -7,26 +7,26 @@ export const SDK_VERSION = '1.0.0';
|
|
|
7
7
|
/** Default configuration values */
|
|
8
8
|
export const DEFAULT_CONFIG = {
|
|
9
9
|
enabled: true,
|
|
10
|
-
captureFPS: 0.5,
|
|
11
|
-
captureOnEvents: true,
|
|
12
|
-
maxSessionDuration: 10 * 60 * 1000,
|
|
13
|
-
maxStorageSize: 50 * 1024 * 1024,
|
|
10
|
+
captureFPS: 0.5,
|
|
11
|
+
captureOnEvents: true,
|
|
12
|
+
maxSessionDuration: 10 * 60 * 1000,
|
|
13
|
+
maxStorageSize: 50 * 1024 * 1024,
|
|
14
14
|
autoScreenTracking: true,
|
|
15
15
|
autoGestureTracking: true,
|
|
16
16
|
privacyOcclusion: true,
|
|
17
17
|
enableCompression: true,
|
|
18
|
-
inactivityThreshold: 5000,
|
|
18
|
+
inactivityThreshold: 5000,
|
|
19
19
|
disableInDev: false,
|
|
20
20
|
detectRageTaps: true,
|
|
21
21
|
rageTapThreshold: 3,
|
|
22
|
-
rageTapTimeWindow: 1000,
|
|
22
|
+
rageTapTimeWindow: 1000,
|
|
23
23
|
debug: false,
|
|
24
24
|
autoStartRecording: true,
|
|
25
|
-
collectDeviceInfo: true,
|
|
26
|
-
collectGeoLocation: true,
|
|
27
|
-
postNavigationDelay: 300,
|
|
28
|
-
postGestureDelay: 200,
|
|
29
|
-
postModalDelay: 400,
|
|
25
|
+
collectDeviceInfo: true,
|
|
26
|
+
collectGeoLocation: true,
|
|
27
|
+
postNavigationDelay: 300,
|
|
28
|
+
postGestureDelay: 200,
|
|
29
|
+
postModalDelay: 400,
|
|
30
30
|
} as const;
|
|
31
31
|
|
|
32
32
|
/** Event type constants */
|
|
@@ -61,8 +61,8 @@ export const CAPTURE_SETTINGS = {
|
|
|
61
61
|
DEFAULT_FPS: 0.5,
|
|
62
62
|
MIN_FPS: 0.1,
|
|
63
63
|
MAX_FPS: 2,
|
|
64
|
-
CAPTURE_SCALE: 0.25,
|
|
65
|
-
MIN_CAPTURE_DELTA_TIME: 0.5,
|
|
64
|
+
CAPTURE_SCALE: 0.25,
|
|
65
|
+
MIN_CAPTURE_DELTA_TIME: 0.5,
|
|
66
66
|
} as const;
|
|
67
67
|
|
|
68
68
|
/** Memory management settings */
|