@rejourneyco/react-native 1.0.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.kts +135 -0
- package/android/consumer-rules.pro +10 -0
- package/android/proguard-rules.pro +1 -0
- package/android/src/main/AndroidManifest.xml +15 -0
- package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +2981 -0
- package/android/src/main/java/com/rejourney/capture/ANRHandler.kt +206 -0
- package/android/src/main/java/com/rejourney/capture/ActivityTracker.kt +98 -0
- package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +1553 -0
- package/android/src/main/java/com/rejourney/capture/CaptureHeuristics.kt +375 -0
- package/android/src/main/java/com/rejourney/capture/CrashHandler.kt +153 -0
- package/android/src/main/java/com/rejourney/capture/MotionEvent.kt +215 -0
- package/android/src/main/java/com/rejourney/capture/SegmentUploader.kt +512 -0
- package/android/src/main/java/com/rejourney/capture/VideoEncoder.kt +773 -0
- package/android/src/main/java/com/rejourney/capture/ViewHierarchyScanner.kt +633 -0
- package/android/src/main/java/com/rejourney/capture/ViewSerializer.kt +286 -0
- package/android/src/main/java/com/rejourney/core/Constants.kt +117 -0
- package/android/src/main/java/com/rejourney/core/Logger.kt +93 -0
- package/android/src/main/java/com/rejourney/core/Types.kt +124 -0
- package/android/src/main/java/com/rejourney/lifecycle/SessionLifecycleService.kt +162 -0
- package/android/src/main/java/com/rejourney/network/DeviceAuthManager.kt +747 -0
- package/android/src/main/java/com/rejourney/network/HttpClientProvider.kt +16 -0
- package/android/src/main/java/com/rejourney/network/NetworkMonitor.kt +272 -0
- package/android/src/main/java/com/rejourney/network/UploadManager.kt +1363 -0
- package/android/src/main/java/com/rejourney/network/UploadWorker.kt +492 -0
- package/android/src/main/java/com/rejourney/privacy/PrivacyMask.kt +645 -0
- package/android/src/main/java/com/rejourney/touch/GestureClassifier.kt +233 -0
- package/android/src/main/java/com/rejourney/touch/KeyboardTracker.kt +158 -0
- package/android/src/main/java/com/rejourney/touch/TextInputTracker.kt +181 -0
- package/android/src/main/java/com/rejourney/touch/TouchInterceptor.kt +591 -0
- package/android/src/main/java/com/rejourney/utils/EventBuffer.kt +284 -0
- package/android/src/main/java/com/rejourney/utils/OEMDetector.kt +154 -0
- package/android/src/main/java/com/rejourney/utils/PerfTiming.kt +235 -0
- package/android/src/main/java/com/rejourney/utils/Telemetry.kt +297 -0
- package/android/src/main/java/com/rejourney/utils/WindowUtils.kt +84 -0
- package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +187 -0
- package/android/src/newarch/java/com/rejourney/RejourneyPackage.kt +40 -0
- package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +218 -0
- package/android/src/oldarch/java/com/rejourney/RejourneyPackage.kt +23 -0
- package/ios/Capture/RJANRHandler.h +42 -0
- package/ios/Capture/RJANRHandler.m +328 -0
- package/ios/Capture/RJCaptureEngine.h +275 -0
- package/ios/Capture/RJCaptureEngine.m +2062 -0
- package/ios/Capture/RJCaptureHeuristics.h +80 -0
- package/ios/Capture/RJCaptureHeuristics.m +903 -0
- package/ios/Capture/RJCrashHandler.h +46 -0
- package/ios/Capture/RJCrashHandler.m +313 -0
- package/ios/Capture/RJMotionEvent.h +183 -0
- package/ios/Capture/RJMotionEvent.m +183 -0
- package/ios/Capture/RJPerformanceManager.h +100 -0
- package/ios/Capture/RJPerformanceManager.m +373 -0
- package/ios/Capture/RJPixelBufferDownscaler.h +42 -0
- package/ios/Capture/RJPixelBufferDownscaler.m +85 -0
- package/ios/Capture/RJSegmentUploader.h +146 -0
- package/ios/Capture/RJSegmentUploader.m +778 -0
- package/ios/Capture/RJVideoEncoder.h +247 -0
- package/ios/Capture/RJVideoEncoder.m +1036 -0
- package/ios/Capture/RJViewControllerTracker.h +73 -0
- package/ios/Capture/RJViewControllerTracker.m +508 -0
- package/ios/Capture/RJViewHierarchyScanner.h +215 -0
- package/ios/Capture/RJViewHierarchyScanner.m +1464 -0
- package/ios/Capture/RJViewSerializer.h +119 -0
- package/ios/Capture/RJViewSerializer.m +498 -0
- package/ios/Core/RJConstants.h +124 -0
- package/ios/Core/RJConstants.m +88 -0
- package/ios/Core/RJLifecycleManager.h +85 -0
- package/ios/Core/RJLifecycleManager.m +308 -0
- package/ios/Core/RJLogger.h +61 -0
- package/ios/Core/RJLogger.m +211 -0
- package/ios/Core/RJTypes.h +176 -0
- package/ios/Core/RJTypes.m +66 -0
- package/ios/Core/Rejourney.h +64 -0
- package/ios/Core/Rejourney.mm +2495 -0
- package/ios/Network/RJDeviceAuthManager.h +94 -0
- package/ios/Network/RJDeviceAuthManager.m +967 -0
- package/ios/Network/RJNetworkMonitor.h +68 -0
- package/ios/Network/RJNetworkMonitor.m +267 -0
- package/ios/Network/RJRetryManager.h +73 -0
- package/ios/Network/RJRetryManager.m +325 -0
- package/ios/Network/RJUploadManager.h +267 -0
- package/ios/Network/RJUploadManager.m +2296 -0
- package/ios/Privacy/RJPrivacyMask.h +163 -0
- package/ios/Privacy/RJPrivacyMask.m +922 -0
- package/ios/Rejourney.h +63 -0
- package/ios/Touch/RJGestureClassifier.h +130 -0
- package/ios/Touch/RJGestureClassifier.m +333 -0
- package/ios/Touch/RJTouchInterceptor.h +169 -0
- package/ios/Touch/RJTouchInterceptor.m +772 -0
- package/ios/Utils/RJEventBuffer.h +112 -0
- package/ios/Utils/RJEventBuffer.m +358 -0
- package/ios/Utils/RJGzipUtils.h +33 -0
- package/ios/Utils/RJGzipUtils.m +89 -0
- package/ios/Utils/RJKeychainManager.h +48 -0
- package/ios/Utils/RJKeychainManager.m +111 -0
- package/ios/Utils/RJPerfTiming.h +209 -0
- package/ios/Utils/RJPerfTiming.m +264 -0
- package/ios/Utils/RJTelemetry.h +92 -0
- package/ios/Utils/RJTelemetry.m +320 -0
- package/ios/Utils/RJWindowUtils.h +66 -0
- package/ios/Utils/RJWindowUtils.m +133 -0
- package/lib/commonjs/NativeRejourney.js +40 -0
- package/lib/commonjs/components/Mask.js +79 -0
- package/lib/commonjs/index.js +1381 -0
- package/lib/commonjs/sdk/autoTracking.js +1259 -0
- package/lib/commonjs/sdk/constants.js +151 -0
- package/lib/commonjs/sdk/errorTracking.js +199 -0
- package/lib/commonjs/sdk/index.js +50 -0
- package/lib/commonjs/sdk/metricsTracking.js +204 -0
- package/lib/commonjs/sdk/navigation.js +151 -0
- package/lib/commonjs/sdk/networkInterceptor.js +412 -0
- package/lib/commonjs/sdk/utils.js +363 -0
- package/lib/commonjs/types/expo-router.d.js +2 -0
- package/lib/commonjs/types/index.js +2 -0
- package/lib/module/NativeRejourney.js +38 -0
- package/lib/module/components/Mask.js +72 -0
- package/lib/module/index.js +1284 -0
- package/lib/module/sdk/autoTracking.js +1233 -0
- package/lib/module/sdk/constants.js +145 -0
- package/lib/module/sdk/errorTracking.js +189 -0
- package/lib/module/sdk/index.js +12 -0
- package/lib/module/sdk/metricsTracking.js +187 -0
- package/lib/module/sdk/navigation.js +143 -0
- package/lib/module/sdk/networkInterceptor.js +401 -0
- package/lib/module/sdk/utils.js +342 -0
- package/lib/module/types/expo-router.d.js +2 -0
- package/lib/module/types/index.js +2 -0
- package/lib/typescript/NativeRejourney.d.ts +147 -0
- package/lib/typescript/components/Mask.d.ts +39 -0
- package/lib/typescript/index.d.ts +117 -0
- package/lib/typescript/sdk/autoTracking.d.ts +204 -0
- package/lib/typescript/sdk/constants.d.ts +120 -0
- package/lib/typescript/sdk/errorTracking.d.ts +32 -0
- package/lib/typescript/sdk/index.d.ts +9 -0
- package/lib/typescript/sdk/metricsTracking.d.ts +58 -0
- package/lib/typescript/sdk/navigation.d.ts +33 -0
- package/lib/typescript/sdk/networkInterceptor.d.ts +47 -0
- package/lib/typescript/sdk/utils.d.ts +148 -0
- package/lib/typescript/types/index.d.ts +624 -0
- package/package.json +102 -0
- package/rejourney.podspec +21 -0
- package/src/NativeRejourney.ts +165 -0
- package/src/components/Mask.tsx +80 -0
- package/src/index.ts +1459 -0
- package/src/sdk/autoTracking.ts +1373 -0
- package/src/sdk/constants.ts +134 -0
- package/src/sdk/errorTracking.ts +231 -0
- package/src/sdk/index.ts +11 -0
- package/src/sdk/metricsTracking.ts +232 -0
- package/src/sdk/navigation.ts +157 -0
- package/src/sdk/networkInterceptor.ts +440 -0
- package/src/sdk/utils.ts +369 -0
- package/src/types/expo-router.d.ts +7 -0
- package/src/types/index.ts +739 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//
|
|
2
|
+
// RJViewControllerTracker.h
|
|
3
|
+
// Rejourney
|
|
4
|
+
//
|
|
5
|
+
// Automatic ViewController lifecycle tracking via method swizzling.
|
|
6
|
+
// Detects navigation changes, tab switches, and significant UI transitions.
|
|
7
|
+
//
|
|
8
|
+
// Copyright (c) 2026 Rejourney
|
|
9
|
+
//
|
|
10
|
+
|
|
11
|
+
#import <UIKit/UIKit.h>
|
|
12
|
+
|
|
13
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
14
|
+
|
|
15
|
+
@protocol RJViewControllerTrackerDelegate <NSObject>
|
|
16
|
+
@optional
|
|
17
|
+
/// Called when a new screen appears
|
|
18
|
+
- (void)viewControllerDidAppear:(UIViewController *)viewController
|
|
19
|
+
screenName:(NSString *)screenName;
|
|
20
|
+
/// Called when view controller will disappear
|
|
21
|
+
- (void)viewControllerWillDisappear:(UIViewController *)viewController
|
|
22
|
+
screenName:(NSString *)screenName;
|
|
23
|
+
/// Called when a tab bar selection changes
|
|
24
|
+
- (void)tabBarDidSelectIndex:(NSInteger)index
|
|
25
|
+
fromIndex:(NSInteger)previousIndex;
|
|
26
|
+
@end
|
|
27
|
+
|
|
28
|
+
@interface RJViewControllerTracker : NSObject
|
|
29
|
+
|
|
30
|
+
/// Shared instance
|
|
31
|
+
+ (instancetype)sharedInstance;
|
|
32
|
+
|
|
33
|
+
/// The delegate that receives navigation events
|
|
34
|
+
@property(nonatomic, weak, nullable) id<RJViewControllerTrackerDelegate>
|
|
35
|
+
delegate;
|
|
36
|
+
|
|
37
|
+
/// Whether tracking is currently enabled
|
|
38
|
+
@property(nonatomic, assign, readonly) BOOL isEnabled;
|
|
39
|
+
|
|
40
|
+
/// Enable automatic ViewController tracking (swizzles
|
|
41
|
+
/// viewDidAppear/viewWillDisappear)
|
|
42
|
+
- (void)enableTracking;
|
|
43
|
+
|
|
44
|
+
/// Disable tracking and restore original implementations
|
|
45
|
+
- (void)disableTracking;
|
|
46
|
+
|
|
47
|
+
/// Get a human-readable name for a view controller
|
|
48
|
+
+ (NSString *)screenNameForViewController:(UIViewController *)viewController;
|
|
49
|
+
|
|
50
|
+
/// Set the authoritative screen name from JavaScript
|
|
51
|
+
/// This takes priority over native auto-detection
|
|
52
|
+
+ (void)setAuthoritativeScreenName:(NSString *)screenName;
|
|
53
|
+
|
|
54
|
+
/// Get the current authoritative screen name (if any)
|
|
55
|
+
+ (nullable NSString *)authoritativeScreenName;
|
|
56
|
+
|
|
57
|
+
/// Clear the authoritative screen name
|
|
58
|
+
+ (void)clearAuthoritativeScreenName;
|
|
59
|
+
|
|
60
|
+
@end
|
|
61
|
+
|
|
62
|
+
#pragma mark - UIViewController Tracking Category
|
|
63
|
+
|
|
64
|
+
/// Category on UIViewController for tracking purposes
|
|
65
|
+
@interface UIViewController (RJTracking)
|
|
66
|
+
|
|
67
|
+
/// Returns whether this view controller should be skipped for tracking
|
|
68
|
+
/// (internal VCs, containers, system VCs, etc.)
|
|
69
|
+
- (BOOL)rj_shouldSkipTracking;
|
|
70
|
+
|
|
71
|
+
@end
|
|
72
|
+
|
|
73
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
//
|
|
2
|
+
// RJViewControllerTracker.m
|
|
3
|
+
// Rejourney
|
|
4
|
+
//
|
|
5
|
+
// Automatic ViewController lifecycle tracking via method swizzling.
|
|
6
|
+
// Detects navigation changes, tab switches, and significant UI transitions.
|
|
7
|
+
//
|
|
8
|
+
// Copyright (c) 2026 Rejourney
|
|
9
|
+
//
|
|
10
|
+
|
|
11
|
+
#import "RJViewControllerTracker.h"
|
|
12
|
+
#import "../Core/RJLogger.h"
|
|
13
|
+
#import <objc/runtime.h>
|
|
14
|
+
|
|
15
|
+
static IMP _originalViewDidAppear = NULL;
|
|
16
|
+
static IMP _originalViewWillDisappear = NULL;
|
|
17
|
+
static IMP _originalTabBarDidSelect = NULL;
|
|
18
|
+
|
|
19
|
+
static NSString *_lastScreenName = nil;
|
|
20
|
+
static NSInteger _lastTabIndex = -1;
|
|
21
|
+
|
|
22
|
+
static NSString *_authoritativeScreenName = nil;
|
|
23
|
+
static NSTimeInterval _authoritativeScreenNameTime = 0;
|
|
24
|
+
|
|
25
|
+
static const NSTimeInterval kAuthoritativeNameTimeout = 0.5;
|
|
26
|
+
|
|
27
|
+
#pragma mark - Swizzled Method Implementations
|
|
28
|
+
|
|
29
|
+
static void RJ_swizzled_viewDidAppear(UIViewController *self, SEL _cmd,
|
|
30
|
+
BOOL animated) {
|
|
31
|
+
|
|
32
|
+
if (_originalViewDidAppear) {
|
|
33
|
+
((void (*)(id, SEL, BOOL))_originalViewDidAppear)(self, _cmd, animated);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if ([self rj_shouldSkipTracking]) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
NSString *screenName =
|
|
43
|
+
[RJViewControllerTracker screenNameForViewController:self];
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if (!screenName || screenName.length == 0) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
static dispatch_queue_t _screenNameQueue;
|
|
53
|
+
static dispatch_once_t onceToken;
|
|
54
|
+
dispatch_once(&onceToken, ^{
|
|
55
|
+
_screenNameQueue = dispatch_queue_create("com.rejourney.screenname",
|
|
56
|
+
DISPATCH_QUEUE_SERIAL);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
__block BOOL shouldNotify = NO;
|
|
60
|
+
dispatch_sync(_screenNameQueue, ^{
|
|
61
|
+
if (![screenName isEqualToString:_lastScreenName]) {
|
|
62
|
+
_lastScreenName = [screenName copy];
|
|
63
|
+
shouldNotify = YES;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (shouldNotify) {
|
|
68
|
+
RJViewControllerTracker *tracker = [RJViewControllerTracker sharedInstance];
|
|
69
|
+
if (tracker.isEnabled && tracker.delegate) {
|
|
70
|
+
|
|
71
|
+
__weak UIViewController *weakVC = self;
|
|
72
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
73
|
+
UIViewController *strongVC = weakVC;
|
|
74
|
+
if (strongVC && [tracker.delegate respondsToSelector:@selector
|
|
75
|
+
(viewControllerDidAppear:
|
|
76
|
+
screenName:)]) {
|
|
77
|
+
[tracker.delegate viewControllerDidAppear:strongVC
|
|
78
|
+
screenName:screenName];
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static void RJ_swizzled_viewWillDisappear(UIViewController *self, SEL _cmd,
|
|
86
|
+
BOOL animated) {
|
|
87
|
+
|
|
88
|
+
if (_originalViewWillDisappear) {
|
|
89
|
+
((void (*)(id, SEL, BOOL))_originalViewWillDisappear)(self, _cmd, animated);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if ([self rj_shouldSkipTracking]) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
NSString *screenName =
|
|
98
|
+
[RJViewControllerTracker screenNameForViewController:self];
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
if (!screenName || screenName.length == 0) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
RJViewControllerTracker *tracker = [RJViewControllerTracker sharedInstance];
|
|
106
|
+
if (tracker.isEnabled && tracker.delegate) {
|
|
107
|
+
|
|
108
|
+
__weak UIViewController *weakVC = self;
|
|
109
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
110
|
+
UIViewController *strongVC = weakVC;
|
|
111
|
+
if (strongVC && [tracker.delegate respondsToSelector:@selector
|
|
112
|
+
(viewControllerWillDisappear:
|
|
113
|
+
screenName:)]) {
|
|
114
|
+
[tracker.delegate viewControllerWillDisappear:strongVC
|
|
115
|
+
screenName:screenName];
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#pragma mark - UIViewController Category for Skip Logic
|
|
122
|
+
|
|
123
|
+
@implementation UIViewController (RJTracking)
|
|
124
|
+
|
|
125
|
+
- (BOOL)rj_shouldSkipTracking {
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
static NSSet *_skipPrefixes;
|
|
129
|
+
static NSSet *_skipContains;
|
|
130
|
+
static dispatch_once_t onceToken;
|
|
131
|
+
dispatch_once(&onceToken, ^{
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
_skipPrefixes = [[NSSet alloc] initWithArray:@[ @"_", @"UI" ]];
|
|
135
|
+
_skipContains = [[NSSet alloc] initWithArray:@[
|
|
136
|
+
@"Navigation", @"Container", @"Keyboard", @"StatusBar", @"InputAccessory",
|
|
137
|
+
@"FabricModal", @"ModalHost", @"Fabric", @"HostingView", @"SafeArea",
|
|
138
|
+
@"ScrollViewContent", @"RCTRootContentView", @"ReactNative"
|
|
139
|
+
]];
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
NSString *className = NSStringFromClass([self class]);
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
for (NSString *prefix in _skipPrefixes) {
|
|
146
|
+
if ([className hasPrefix:prefix]) {
|
|
147
|
+
return YES;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
for (NSString *substring in _skipContains) {
|
|
153
|
+
if ([className containsString:substring]) {
|
|
154
|
+
return YES;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if (!self.isViewLoaded || !self.view.window) {
|
|
160
|
+
return YES;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if (self.parentViewController &&
|
|
165
|
+
![self.parentViewController
|
|
166
|
+
isKindOfClass:[UINavigationController class]] &&
|
|
167
|
+
![self.parentViewController isKindOfClass:[UITabBarController class]]) {
|
|
168
|
+
return YES;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return NO;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@end
|
|
175
|
+
|
|
176
|
+
#pragma mark - RJViewControllerTracker Implementation
|
|
177
|
+
|
|
178
|
+
@interface RJViewControllerTracker ()
|
|
179
|
+
@property(nonatomic, assign, readwrite) BOOL isEnabled;
|
|
180
|
+
@property(nonatomic, assign) BOOL hasSwizzled;
|
|
181
|
+
@end
|
|
182
|
+
|
|
183
|
+
@implementation RJViewControllerTracker
|
|
184
|
+
|
|
185
|
+
+ (instancetype)sharedInstance {
|
|
186
|
+
static RJViewControllerTracker *instance = nil;
|
|
187
|
+
static dispatch_once_t onceToken;
|
|
188
|
+
dispatch_once(&onceToken, ^{
|
|
189
|
+
instance = [[RJViewControllerTracker alloc] init];
|
|
190
|
+
});
|
|
191
|
+
return instance;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
- (instancetype)init {
|
|
195
|
+
self = [super init];
|
|
196
|
+
if (self) {
|
|
197
|
+
_isEnabled = NO;
|
|
198
|
+
_hasSwizzled = NO;
|
|
199
|
+
}
|
|
200
|
+
return self;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
- (void)enableTracking {
|
|
204
|
+
if (self.isEnabled)
|
|
205
|
+
return;
|
|
206
|
+
|
|
207
|
+
@synchronized(self) {
|
|
208
|
+
if (self.hasSwizzled) {
|
|
209
|
+
self.isEnabled = YES;
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
@try {
|
|
214
|
+
|
|
215
|
+
Method originalDidAppear = class_getInstanceMethod(
|
|
216
|
+
[UIViewController class], @selector(viewDidAppear:));
|
|
217
|
+
if (originalDidAppear) {
|
|
218
|
+
_originalViewDidAppear = method_getImplementation(originalDidAppear);
|
|
219
|
+
method_setImplementation(originalDidAppear,
|
|
220
|
+
(IMP)RJ_swizzled_viewDidAppear);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
Method originalWillDisappear = class_getInstanceMethod(
|
|
225
|
+
[UIViewController class], @selector(viewWillDisappear:));
|
|
226
|
+
if (originalWillDisappear) {
|
|
227
|
+
_originalViewWillDisappear =
|
|
228
|
+
method_getImplementation(originalWillDisappear);
|
|
229
|
+
method_setImplementation(originalWillDisappear,
|
|
230
|
+
(IMP)RJ_swizzled_viewWillDisappear);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
[[NSNotificationCenter defaultCenter]
|
|
235
|
+
addObserver:self
|
|
236
|
+
selector:@selector(handleTabBarSelectionChange:)
|
|
237
|
+
name:@"UITabBarControllerDidSelectViewControllerNotification"
|
|
238
|
+
object:nil];
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
[[NSNotificationCenter defaultCenter]
|
|
242
|
+
addObserver:self
|
|
243
|
+
selector:@selector(handleWindowChange:)
|
|
244
|
+
name:UIWindowDidBecomeKeyNotification
|
|
245
|
+
object:nil];
|
|
246
|
+
|
|
247
|
+
self.hasSwizzled = YES;
|
|
248
|
+
self.isEnabled = YES;
|
|
249
|
+
|
|
250
|
+
RJLogDebug(@"ViewController tracking enabled");
|
|
251
|
+
} @catch (NSException *exception) {
|
|
252
|
+
RJLogError(@"Failed to enable VC tracking: %@", exception);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
- (void)disableTracking {
|
|
258
|
+
self.isEnabled = NO;
|
|
259
|
+
RJLogDebug(@"ViewController tracking disabled");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
- (void)handleTabBarSelectionChange:(NSNotification *)notification {
|
|
263
|
+
if (!self.isEnabled || !self.delegate)
|
|
264
|
+
return;
|
|
265
|
+
|
|
266
|
+
UITabBarController *tabBar = notification.object;
|
|
267
|
+
if ([tabBar isKindOfClass:[UITabBarController class]]) {
|
|
268
|
+
NSInteger newIndex = tabBar.selectedIndex;
|
|
269
|
+
NSInteger previousIndex = _lastTabIndex;
|
|
270
|
+
|
|
271
|
+
if (newIndex != previousIndex) {
|
|
272
|
+
_lastTabIndex = newIndex;
|
|
273
|
+
|
|
274
|
+
if ([self.delegate respondsToSelector:@selector(tabBarDidSelectIndex:
|
|
275
|
+
fromIndex:)]) {
|
|
276
|
+
[self.delegate tabBarDidSelectIndex:newIndex fromIndex:previousIndex];
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
- (void)handleWindowChange:(NSNotification *)notification {
|
|
283
|
+
if (!self.isEnabled || !self.delegate)
|
|
284
|
+
return;
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
UIWindow *window = notification.object;
|
|
288
|
+
if ([window isKindOfClass:[UIWindow class]] && window.rootViewController) {
|
|
289
|
+
|
|
290
|
+
UIViewController *topVC =
|
|
291
|
+
[self topViewControllerFromViewController:window.rootViewController];
|
|
292
|
+
if (topVC) {
|
|
293
|
+
NSString *screenName =
|
|
294
|
+
[RJViewControllerTracker screenNameForViewController:topVC];
|
|
295
|
+
if (![screenName isEqualToString:_lastScreenName]) {
|
|
296
|
+
_lastScreenName = [screenName copy];
|
|
297
|
+
|
|
298
|
+
if ([self.delegate respondsToSelector:@selector
|
|
299
|
+
(viewControllerDidAppear:screenName:)]) {
|
|
300
|
+
[self.delegate viewControllerDidAppear:topVC screenName:screenName];
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
- (UIViewController *)topViewControllerFromViewController:
|
|
308
|
+
(UIViewController *)viewController {
|
|
309
|
+
if ([viewController isKindOfClass:[UINavigationController class]]) {
|
|
310
|
+
UINavigationController *nav = (UINavigationController *)viewController;
|
|
311
|
+
return [self topViewControllerFromViewController:nav.visibleViewController];
|
|
312
|
+
}
|
|
313
|
+
if ([viewController isKindOfClass:[UITabBarController class]]) {
|
|
314
|
+
UITabBarController *tab = (UITabBarController *)viewController;
|
|
315
|
+
return
|
|
316
|
+
[self topViewControllerFromViewController:tab.selectedViewController];
|
|
317
|
+
}
|
|
318
|
+
if (viewController.presentedViewController) {
|
|
319
|
+
return
|
|
320
|
+
[self topViewControllerFromViewController:viewController
|
|
321
|
+
.presentedViewController];
|
|
322
|
+
}
|
|
323
|
+
return viewController;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
+ (void)setAuthoritativeScreenName:(NSString *)screenName {
|
|
327
|
+
@synchronized(self) {
|
|
328
|
+
_authoritativeScreenName = [screenName copy];
|
|
329
|
+
_authoritativeScreenNameTime = [[NSDate date] timeIntervalSince1970];
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
+ (nullable NSString *)authoritativeScreenName {
|
|
334
|
+
@synchronized(self) {
|
|
335
|
+
if (!_authoritativeScreenName)
|
|
336
|
+
return nil;
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
|
|
340
|
+
if (now - _authoritativeScreenNameTime > kAuthoritativeNameTimeout) {
|
|
341
|
+
_authoritativeScreenName = nil;
|
|
342
|
+
return nil;
|
|
343
|
+
}
|
|
344
|
+
return _authoritativeScreenName;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
+ (void)clearAuthoritativeScreenName {
|
|
349
|
+
@synchronized(self) {
|
|
350
|
+
_authoritativeScreenName = nil;
|
|
351
|
+
_authoritativeScreenNameTime = 0;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
+ (NSString *)screenNameForViewController:(UIViewController *)viewController {
|
|
356
|
+
if (!viewController)
|
|
357
|
+
return @"Unknown";
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
NSString *authoritativeName = [self authoritativeScreenName];
|
|
361
|
+
if (authoritativeName.length > 0) {
|
|
362
|
+
return authoritativeName;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
if (viewController.title.length > 0) {
|
|
369
|
+
return [self normalizeScreenName:viewController.title];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
if (viewController.navigationItem.title.length > 0) {
|
|
374
|
+
return [self normalizeScreenName:viewController.navigationItem.title];
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
if (viewController.tabBarItem.title.length > 0) {
|
|
379
|
+
return [self normalizeScreenName:viewController.tabBarItem.title];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
if (viewController.view.accessibilityIdentifier.length > 0) {
|
|
386
|
+
NSString *identifier = viewController.view.accessibilityIdentifier;
|
|
387
|
+
|
|
388
|
+
if (![identifier hasPrefix:@"RCT"] && ![identifier hasPrefix:@"RNS"] &&
|
|
389
|
+
![identifier containsString:@"ContentView"]) {
|
|
390
|
+
return [self normalizeScreenName:identifier];
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
if (viewController.view.accessibilityLabel.length > 0 &&
|
|
397
|
+
viewController.view.accessibilityLabel.length < 40) {
|
|
398
|
+
return [self normalizeScreenName:viewController.view.accessibilityLabel];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
if (viewController.navigationController) {
|
|
406
|
+
UINavigationItem *navItem =
|
|
407
|
+
viewController.navigationController.navigationBar.topItem;
|
|
408
|
+
if (navItem.title.length > 0) {
|
|
409
|
+
return [self normalizeScreenName:navItem.title];
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
return [self normalizeClassName:NSStringFromClass([viewController class])];
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
+ (NSString *)normalizeScreenName:(NSString *)name {
|
|
418
|
+
if (!name || name.length == 0)
|
|
419
|
+
return @"Unknown";
|
|
420
|
+
|
|
421
|
+
NSString *result = name;
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
NSArray *suffixes = @[ @"Screen", @"Page", @"View", @"Controller", @"VC" ];
|
|
425
|
+
for (NSString *suffix in suffixes) {
|
|
426
|
+
if ([result hasSuffix:suffix] && result.length > suffix.length) {
|
|
427
|
+
result = [result substringToIndex:result.length - suffix.length];
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
result = [result
|
|
434
|
+
stringByTrimmingCharactersInSet:[NSCharacterSet
|
|
435
|
+
whitespaceAndNewlineCharacterSet]];
|
|
436
|
+
|
|
437
|
+
return result.length > 0 ? result : @"Unknown";
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
+ (NSString *)normalizeClassName:(NSString *)className {
|
|
441
|
+
if (!className || className.length == 0)
|
|
442
|
+
return nil;
|
|
443
|
+
|
|
444
|
+
NSString *result = className;
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
static NSSet *_noiseNames;
|
|
448
|
+
static dispatch_once_t onceToken;
|
|
449
|
+
dispatch_once(&onceToken, ^{
|
|
450
|
+
_noiseNames = [[NSSet alloc] initWithArray:@[
|
|
451
|
+
@"Screen", @"View", @"Controller", @"Host", @"Modal", @"Wrapper",
|
|
452
|
+
@"Content", @"Root", @"Main", @"Base", @"Default", @"Generic",
|
|
453
|
+
@"Fabric Modal Host", @"Fabric", @"ModalHost"
|
|
454
|
+
]];
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
NSArray *prefixesToRemove = @[ @"RNS", @"RCT", @"RN", @"UI", @"Fabric" ];
|
|
459
|
+
for (NSString *prefix in prefixesToRemove) {
|
|
460
|
+
if ([result hasPrefix:prefix] && result.length > prefix.length + 2) {
|
|
461
|
+
result = [result substringFromIndex:prefix.length];
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
NSArray *suffixes = @[
|
|
468
|
+
@"ViewController", @"Controller", @"VC", @"Screen", @"View",
|
|
469
|
+
@"StackHeaderConfig", @"ContentView", @"ScreenView", @"ScreenContainer",
|
|
470
|
+
@"Host", @"Wrapper", @"HostingController", @"ModalHost"
|
|
471
|
+
];
|
|
472
|
+
for (NSString *suffix in suffixes) {
|
|
473
|
+
if ([result hasSuffix:suffix] && result.length > suffix.length) {
|
|
474
|
+
result = [result substringToIndex:result.length - suffix.length];
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
if (result.length < 2) {
|
|
481
|
+
return nil;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
NSMutableString *spaced = [NSMutableString string];
|
|
486
|
+
for (NSUInteger i = 0; i < result.length; i++) {
|
|
487
|
+
unichar c = [result characterAtIndex:i];
|
|
488
|
+
if (i > 0 &&
|
|
489
|
+
[[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:c]) {
|
|
490
|
+
[spaced appendString:@" "];
|
|
491
|
+
}
|
|
492
|
+
[spaced appendFormat:@"%c", c];
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
NSString *finalName = spaced.length > 0 ? spaced : nil;
|
|
497
|
+
if (finalName && [_noiseNames containsObject:finalName]) {
|
|
498
|
+
return nil;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return finalName;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
- (void)dealloc {
|
|
505
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
@end
|