@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.
Files changed (152) hide show
  1. package/android/build.gradle.kts +135 -0
  2. package/android/consumer-rules.pro +10 -0
  3. package/android/proguard-rules.pro +1 -0
  4. package/android/src/main/AndroidManifest.xml +15 -0
  5. package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +2981 -0
  6. package/android/src/main/java/com/rejourney/capture/ANRHandler.kt +206 -0
  7. package/android/src/main/java/com/rejourney/capture/ActivityTracker.kt +98 -0
  8. package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +1553 -0
  9. package/android/src/main/java/com/rejourney/capture/CaptureHeuristics.kt +375 -0
  10. package/android/src/main/java/com/rejourney/capture/CrashHandler.kt +153 -0
  11. package/android/src/main/java/com/rejourney/capture/MotionEvent.kt +215 -0
  12. package/android/src/main/java/com/rejourney/capture/SegmentUploader.kt +512 -0
  13. package/android/src/main/java/com/rejourney/capture/VideoEncoder.kt +773 -0
  14. package/android/src/main/java/com/rejourney/capture/ViewHierarchyScanner.kt +633 -0
  15. package/android/src/main/java/com/rejourney/capture/ViewSerializer.kt +286 -0
  16. package/android/src/main/java/com/rejourney/core/Constants.kt +117 -0
  17. package/android/src/main/java/com/rejourney/core/Logger.kt +93 -0
  18. package/android/src/main/java/com/rejourney/core/Types.kt +124 -0
  19. package/android/src/main/java/com/rejourney/lifecycle/SessionLifecycleService.kt +162 -0
  20. package/android/src/main/java/com/rejourney/network/DeviceAuthManager.kt +747 -0
  21. package/android/src/main/java/com/rejourney/network/HttpClientProvider.kt +16 -0
  22. package/android/src/main/java/com/rejourney/network/NetworkMonitor.kt +272 -0
  23. package/android/src/main/java/com/rejourney/network/UploadManager.kt +1363 -0
  24. package/android/src/main/java/com/rejourney/network/UploadWorker.kt +492 -0
  25. package/android/src/main/java/com/rejourney/privacy/PrivacyMask.kt +645 -0
  26. package/android/src/main/java/com/rejourney/touch/GestureClassifier.kt +233 -0
  27. package/android/src/main/java/com/rejourney/touch/KeyboardTracker.kt +158 -0
  28. package/android/src/main/java/com/rejourney/touch/TextInputTracker.kt +181 -0
  29. package/android/src/main/java/com/rejourney/touch/TouchInterceptor.kt +591 -0
  30. package/android/src/main/java/com/rejourney/utils/EventBuffer.kt +284 -0
  31. package/android/src/main/java/com/rejourney/utils/OEMDetector.kt +154 -0
  32. package/android/src/main/java/com/rejourney/utils/PerfTiming.kt +235 -0
  33. package/android/src/main/java/com/rejourney/utils/Telemetry.kt +297 -0
  34. package/android/src/main/java/com/rejourney/utils/WindowUtils.kt +84 -0
  35. package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +187 -0
  36. package/android/src/newarch/java/com/rejourney/RejourneyPackage.kt +40 -0
  37. package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +218 -0
  38. package/android/src/oldarch/java/com/rejourney/RejourneyPackage.kt +23 -0
  39. package/ios/Capture/RJANRHandler.h +42 -0
  40. package/ios/Capture/RJANRHandler.m +328 -0
  41. package/ios/Capture/RJCaptureEngine.h +275 -0
  42. package/ios/Capture/RJCaptureEngine.m +2062 -0
  43. package/ios/Capture/RJCaptureHeuristics.h +80 -0
  44. package/ios/Capture/RJCaptureHeuristics.m +903 -0
  45. package/ios/Capture/RJCrashHandler.h +46 -0
  46. package/ios/Capture/RJCrashHandler.m +313 -0
  47. package/ios/Capture/RJMotionEvent.h +183 -0
  48. package/ios/Capture/RJMotionEvent.m +183 -0
  49. package/ios/Capture/RJPerformanceManager.h +100 -0
  50. package/ios/Capture/RJPerformanceManager.m +373 -0
  51. package/ios/Capture/RJPixelBufferDownscaler.h +42 -0
  52. package/ios/Capture/RJPixelBufferDownscaler.m +85 -0
  53. package/ios/Capture/RJSegmentUploader.h +146 -0
  54. package/ios/Capture/RJSegmentUploader.m +778 -0
  55. package/ios/Capture/RJVideoEncoder.h +247 -0
  56. package/ios/Capture/RJVideoEncoder.m +1036 -0
  57. package/ios/Capture/RJViewControllerTracker.h +73 -0
  58. package/ios/Capture/RJViewControllerTracker.m +508 -0
  59. package/ios/Capture/RJViewHierarchyScanner.h +215 -0
  60. package/ios/Capture/RJViewHierarchyScanner.m +1464 -0
  61. package/ios/Capture/RJViewSerializer.h +119 -0
  62. package/ios/Capture/RJViewSerializer.m +498 -0
  63. package/ios/Core/RJConstants.h +124 -0
  64. package/ios/Core/RJConstants.m +88 -0
  65. package/ios/Core/RJLifecycleManager.h +85 -0
  66. package/ios/Core/RJLifecycleManager.m +308 -0
  67. package/ios/Core/RJLogger.h +61 -0
  68. package/ios/Core/RJLogger.m +211 -0
  69. package/ios/Core/RJTypes.h +176 -0
  70. package/ios/Core/RJTypes.m +66 -0
  71. package/ios/Core/Rejourney.h +64 -0
  72. package/ios/Core/Rejourney.mm +2495 -0
  73. package/ios/Network/RJDeviceAuthManager.h +94 -0
  74. package/ios/Network/RJDeviceAuthManager.m +967 -0
  75. package/ios/Network/RJNetworkMonitor.h +68 -0
  76. package/ios/Network/RJNetworkMonitor.m +267 -0
  77. package/ios/Network/RJRetryManager.h +73 -0
  78. package/ios/Network/RJRetryManager.m +325 -0
  79. package/ios/Network/RJUploadManager.h +267 -0
  80. package/ios/Network/RJUploadManager.m +2296 -0
  81. package/ios/Privacy/RJPrivacyMask.h +163 -0
  82. package/ios/Privacy/RJPrivacyMask.m +922 -0
  83. package/ios/Rejourney.h +63 -0
  84. package/ios/Touch/RJGestureClassifier.h +130 -0
  85. package/ios/Touch/RJGestureClassifier.m +333 -0
  86. package/ios/Touch/RJTouchInterceptor.h +169 -0
  87. package/ios/Touch/RJTouchInterceptor.m +772 -0
  88. package/ios/Utils/RJEventBuffer.h +112 -0
  89. package/ios/Utils/RJEventBuffer.m +358 -0
  90. package/ios/Utils/RJGzipUtils.h +33 -0
  91. package/ios/Utils/RJGzipUtils.m +89 -0
  92. package/ios/Utils/RJKeychainManager.h +48 -0
  93. package/ios/Utils/RJKeychainManager.m +111 -0
  94. package/ios/Utils/RJPerfTiming.h +209 -0
  95. package/ios/Utils/RJPerfTiming.m +264 -0
  96. package/ios/Utils/RJTelemetry.h +92 -0
  97. package/ios/Utils/RJTelemetry.m +320 -0
  98. package/ios/Utils/RJWindowUtils.h +66 -0
  99. package/ios/Utils/RJWindowUtils.m +133 -0
  100. package/lib/commonjs/NativeRejourney.js +40 -0
  101. package/lib/commonjs/components/Mask.js +79 -0
  102. package/lib/commonjs/index.js +1381 -0
  103. package/lib/commonjs/sdk/autoTracking.js +1259 -0
  104. package/lib/commonjs/sdk/constants.js +151 -0
  105. package/lib/commonjs/sdk/errorTracking.js +199 -0
  106. package/lib/commonjs/sdk/index.js +50 -0
  107. package/lib/commonjs/sdk/metricsTracking.js +204 -0
  108. package/lib/commonjs/sdk/navigation.js +151 -0
  109. package/lib/commonjs/sdk/networkInterceptor.js +412 -0
  110. package/lib/commonjs/sdk/utils.js +363 -0
  111. package/lib/commonjs/types/expo-router.d.js +2 -0
  112. package/lib/commonjs/types/index.js +2 -0
  113. package/lib/module/NativeRejourney.js +38 -0
  114. package/lib/module/components/Mask.js +72 -0
  115. package/lib/module/index.js +1284 -0
  116. package/lib/module/sdk/autoTracking.js +1233 -0
  117. package/lib/module/sdk/constants.js +145 -0
  118. package/lib/module/sdk/errorTracking.js +189 -0
  119. package/lib/module/sdk/index.js +12 -0
  120. package/lib/module/sdk/metricsTracking.js +187 -0
  121. package/lib/module/sdk/navigation.js +143 -0
  122. package/lib/module/sdk/networkInterceptor.js +401 -0
  123. package/lib/module/sdk/utils.js +342 -0
  124. package/lib/module/types/expo-router.d.js +2 -0
  125. package/lib/module/types/index.js +2 -0
  126. package/lib/typescript/NativeRejourney.d.ts +147 -0
  127. package/lib/typescript/components/Mask.d.ts +39 -0
  128. package/lib/typescript/index.d.ts +117 -0
  129. package/lib/typescript/sdk/autoTracking.d.ts +204 -0
  130. package/lib/typescript/sdk/constants.d.ts +120 -0
  131. package/lib/typescript/sdk/errorTracking.d.ts +32 -0
  132. package/lib/typescript/sdk/index.d.ts +9 -0
  133. package/lib/typescript/sdk/metricsTracking.d.ts +58 -0
  134. package/lib/typescript/sdk/navigation.d.ts +33 -0
  135. package/lib/typescript/sdk/networkInterceptor.d.ts +47 -0
  136. package/lib/typescript/sdk/utils.d.ts +148 -0
  137. package/lib/typescript/types/index.d.ts +624 -0
  138. package/package.json +102 -0
  139. package/rejourney.podspec +21 -0
  140. package/src/NativeRejourney.ts +165 -0
  141. package/src/components/Mask.tsx +80 -0
  142. package/src/index.ts +1459 -0
  143. package/src/sdk/autoTracking.ts +1373 -0
  144. package/src/sdk/constants.ts +134 -0
  145. package/src/sdk/errorTracking.ts +231 -0
  146. package/src/sdk/index.ts +11 -0
  147. package/src/sdk/metricsTracking.ts +232 -0
  148. package/src/sdk/navigation.ts +157 -0
  149. package/src/sdk/networkInterceptor.ts +440 -0
  150. package/src/sdk/utils.ts +369 -0
  151. package/src/types/expo-router.d.ts +7 -0
  152. package/src/types/index.ts +739 -0
@@ -0,0 +1,63 @@
1
+ // Licensed under the Apache License, Version 2.0 (the "License");
2
+ // you may not use this file except in compliance with the License.
3
+ // You may obtain a copy of the License at
4
+ //
5
+ // http://www.apache.org/licenses/LICENSE-2.0
6
+ //
7
+ // Unless required by applicable law or agreed to in writing, software
8
+ // distributed under the License is distributed on an "AS IS" BASIS,
9
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ // See the License for the specific language governing permissions and
11
+ // limitations under the License.
12
+ //
13
+ // Copyright (c) 2026 Rejourney
14
+
15
+ //
16
+ //
17
+ // Rejourney.h
18
+ // Rejourney
19
+ //
20
+ // Umbrella header for the Rejourney SDK.
21
+ // Import this header to access the complete SDK.
22
+ //
23
+ // Licensed under the Apache License, Version 2.0 (the "License");
24
+ // you may not use this file except in compliance with the License.
25
+ // You may obtain a copy of the License at
26
+ //
27
+ // http://www.apache.org/licenses/LICENSE-2.0
28
+ //
29
+ // Unless required by applicable law or agreed to in writing, software
30
+ // distributed under the License is distributed on an "AS IS" BASIS,
31
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
32
+ // See the License for the specific language governing permissions and
33
+ // limitations under the License.
34
+ //
35
+ // Copyright (c) 2026 Rejourney
36
+ //
37
+
38
+ #ifndef Rejourney_h
39
+ #define Rejourney_h
40
+
41
+ // Core
42
+ #import "Core/RJConstants.h"
43
+ #import "Core/RJLogger.h"
44
+ #import "Core/RJTypes.h"
45
+ #import "Core/Rejourney.h"
46
+
47
+ // Capture
48
+ #import "Capture/RJCaptureEngine.h"
49
+ #import "Capture/RJSegmentUploader.h"
50
+ #import "Capture/RJVideoEncoder.h"
51
+ #import "Capture/RJViewSerializer.h"
52
+
53
+ // Touch
54
+ #import "Touch/RJGestureClassifier.h"
55
+ #import "Touch/RJTouchInterceptor.h"
56
+
57
+ // Network
58
+ #import "Network/RJUploadManager.h"
59
+
60
+ // Utils
61
+ #import "Utils/RJWindowUtils.h"
62
+
63
+ #endif /* Rejourney_h */
@@ -0,0 +1,130 @@
1
+ //
2
+ // RJGestureClassifier.h
3
+ // Rejourney
4
+ //
5
+ // Gesture classification from touch data.
6
+ //
7
+ // The classifier analyzes touch paths to determine gesture types
8
+ // including taps, swipes, pinches, rotations, and multi-finger gestures.
9
+ //
10
+ // Licensed under the Apache License, Version 2.0 (the "License");
11
+ // you may not use this file except in compliance with the License.
12
+ // You may obtain a copy of the License at
13
+ //
14
+ // http://www.apache.org/licenses/LICENSE-2.0
15
+ //
16
+ // Unless required by applicable law or agreed to in writing, software
17
+ // distributed under the License is distributed on an "AS IS" BASIS,
18
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
+ // See the License for the specific language governing permissions and
20
+ // limitations under the License.
21
+ //
22
+ // Copyright (c) 2026 Rejourney
23
+ //
24
+
25
+ #import <Foundation/Foundation.h>
26
+ #import <UIKit/UIKit.h>
27
+ #import "../Core/RJTypes.h"
28
+
29
+ NS_ASSUME_NONNULL_BEGIN
30
+
31
+ /**
32
+ * Represents a single touch point in a gesture path.
33
+ */
34
+ @interface RJTouchPoint : NSObject
35
+
36
+ /// X coordinate in window space
37
+ @property (nonatomic, assign) CGFloat x;
38
+
39
+ /// Y coordinate in window space
40
+ @property (nonatomic, assign) CGFloat y;
41
+
42
+ /// Timestamp in milliseconds
43
+ @property (nonatomic, assign) NSTimeInterval timestamp;
44
+
45
+ /// Touch force (0.0-1.0, where available)
46
+ @property (nonatomic, assign) CGFloat force;
47
+
48
+ /// Creates a touch point from coordinates
49
+ + (instancetype)pointWithX:(CGFloat)x y:(CGFloat)y timestamp:(NSTimeInterval)timestamp force:(CGFloat)force;
50
+
51
+ /// Converts to dictionary for logging
52
+ - (NSDictionary *)toDictionary;
53
+
54
+ @end
55
+
56
+ /**
57
+ * Classifies gestures from touch data.
58
+ *
59
+ * The classifier supports:
60
+ * - Single-finger: tap, double-tap, long-press, force-touch, swipe, scroll
61
+ * - Two-finger: pinch, rotation, pan, two-finger-tap
62
+ * - Multi-finger: three-finger gesture, multi-touch
63
+ *
64
+ * ## Usage
65
+ * ```objc
66
+ * RJGestureClassifier *classifier = [[RJGestureClassifier alloc] init];
67
+ *
68
+ * // Single-finger gesture
69
+ * NSString *gesture = [classifier classifySingleTouchPath:touches duration:500];
70
+ *
71
+ * // Multi-finger gesture
72
+ * NSString *gesture = [classifier classifyMultiTouchPaths:touchPaths
73
+ * duration:300
74
+ * touchCount:2];
75
+ * ```
76
+ */
77
+ @interface RJGestureClassifier : NSObject
78
+
79
+ #pragma mark - Double-Tap Detection State
80
+
81
+ /// Time of last tap for double-tap detection
82
+ @property (nonatomic, assign) NSTimeInterval lastTapTime;
83
+
84
+ /// Location of last tap for double-tap detection
85
+ @property (nonatomic, assign) CGPoint lastTapPoint;
86
+
87
+ /// Current tap count for multi-tap detection
88
+ @property (nonatomic, assign) NSInteger tapCount;
89
+
90
+ /// Maximum force recorded during current gesture
91
+ @property (nonatomic, assign) CGFloat maxForce;
92
+
93
+ /// Initial pinch distance for pinch detection
94
+ @property (nonatomic, assign) CGFloat initialPinchDistance;
95
+
96
+ /// Initial rotation angle for rotation detection
97
+ @property (nonatomic, assign) CGFloat initialRotationAngle;
98
+
99
+ #pragma mark - Classification
100
+
101
+ /**
102
+ * Classifies a single-finger gesture from its touch path.
103
+ *
104
+ * @param touches Array of touch point dictionaries or RJTouchPoint objects.
105
+ * @param duration Gesture duration in milliseconds.
106
+ * @return Gesture type string (see RJGestureType constants).
107
+ */
108
+ - (RJGestureType)classifySingleTouchPath:(NSArray *)touches
109
+ duration:(NSTimeInterval)duration;
110
+
111
+ /**
112
+ * Classifies a multi-finger gesture from touch paths.
113
+ *
114
+ * @param touchPaths Dictionary mapping touch IDs to arrays of touch points.
115
+ * @param duration Gesture duration in milliseconds.
116
+ * @param touchCount Number of fingers involved.
117
+ * @return Gesture type string (see RJGestureType constants).
118
+ */
119
+ - (RJGestureType)classifyMultiTouchPaths:(NSDictionary<NSNumber *, NSArray *> *)touchPaths
120
+ duration:(NSTimeInterval)duration
121
+ touchCount:(NSInteger)touchCount;
122
+
123
+ /**
124
+ * Resets the classifier state for a new gesture.
125
+ */
126
+ - (void)resetState;
127
+
128
+ @end
129
+
130
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,333 @@
1
+ //
2
+ // RJGestureClassifier.m
3
+ // Rejourney
4
+ //
5
+ // Gesture classification implementation.
6
+ //
7
+ // Licensed under the Apache License, Version 2.0 (the "License");
8
+ // you may not use this file except in compliance with the License.
9
+ // You may obtain a copy of the License at
10
+ //
11
+ // http://www.apache.org/licenses/LICENSE-2.0
12
+ //
13
+ // Unless required by applicable law or agreed to in writing, software
14
+ // distributed under the License is distributed on an "AS IS" BASIS,
15
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ // See the License for the specific language governing permissions and
17
+ // limitations under the License.
18
+ //
19
+ // Copyright (c) 2026 Rejourney
20
+ //
21
+
22
+ #import "RJGestureClassifier.h"
23
+ #import "../Core/RJConstants.h"
24
+
25
+ #pragma mark - RJTouchPoint Implementation
26
+
27
+ @implementation RJTouchPoint
28
+
29
+ + (instancetype)pointWithX:(CGFloat)x y:(CGFloat)y timestamp:(NSTimeInterval)timestamp force:(CGFloat)force {
30
+ RJTouchPoint *point = [[RJTouchPoint alloc] init];
31
+ point.x = x;
32
+ point.y = y;
33
+ point.timestamp = timestamp;
34
+ point.force = force;
35
+ return point;
36
+ }
37
+
38
+ - (NSDictionary *)toDictionary {
39
+ return @{
40
+ @"x": @(self.x),
41
+ @"y": @(self.y),
42
+ @"timestamp": @(self.timestamp),
43
+ @"force": @(self.force)
44
+ };
45
+ }
46
+
47
+ @end
48
+
49
+ #pragma mark - RJGestureClassifier Implementation
50
+
51
+ @implementation RJGestureClassifier
52
+
53
+ #pragma mark - Initialization
54
+
55
+ - (instancetype)init {
56
+ self = [super init];
57
+ if (self) {
58
+ [self resetState];
59
+ }
60
+ return self;
61
+ }
62
+
63
+ - (void)resetState {
64
+ _lastTapTime = 0;
65
+ _lastTapPoint = CGPointZero;
66
+ _tapCount = 0;
67
+ _maxForce = 0;
68
+ _initialPinchDistance = 0;
69
+ _initialRotationAngle = 0;
70
+ }
71
+
72
+ #pragma mark - Single Touch Classification
73
+
74
+ - (RJGestureType)classifySingleTouchPath:(NSArray *)touches
75
+ duration:(NSTimeInterval)duration {
76
+ if (touches.count < 2) {
77
+ return [self classifyStationaryGesture:duration];
78
+ }
79
+
80
+
81
+ NSDictionary *first = [self extractPointDict:touches.firstObject];
82
+ NSDictionary *last = [self extractPointDict:touches.lastObject];
83
+
84
+ if (!first || !last) {
85
+ return RJGestureTypeTap;
86
+ }
87
+
88
+
89
+ CGFloat dx = [last[@"x"] floatValue] - [first[@"x"] floatValue];
90
+ CGFloat dy = [last[@"y"] floatValue] - [first[@"y"] floatValue];
91
+ CGFloat distance = sqrt(dx * dx + dy * dy);
92
+
93
+
94
+ if (distance < 10) {
95
+ return [self classifyStationaryGesture:duration];
96
+ }
97
+
98
+
99
+ return [self classifyMovementGesture:dx dy:dy distance:distance];
100
+ }
101
+
102
+ - (NSDictionary *)extractPointDict:(id)point {
103
+ if ([point isKindOfClass:[NSDictionary class]]) {
104
+ return point;
105
+ } else if ([point isKindOfClass:[RJTouchPoint class]]) {
106
+ return [(RJTouchPoint *)point toDictionary];
107
+ }
108
+ return nil;
109
+ }
110
+
111
+ - (RJGestureType)classifyStationaryGesture:(NSTimeInterval)duration {
112
+
113
+ if (self.maxForce > RJForceTouchThreshold) {
114
+ return RJGestureTypeForceTouch;
115
+ }
116
+
117
+
118
+ NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970] * 1000;
119
+ if ([self checkDoubleTap:currentTime]) {
120
+ return RJGestureTypeDoubleTap;
121
+ }
122
+
123
+
124
+ return duration > RJLongPressMinDuration ? RJGestureTypeLongPress : RJGestureTypeTap;
125
+ }
126
+
127
+ - (BOOL)checkDoubleTap:(NSTimeInterval)currentTime {
128
+ if (self.lastTapTime <= 0) {
129
+
130
+ self.tapCount = 1;
131
+ self.lastTapTime = currentTime;
132
+ return NO;
133
+ }
134
+
135
+
136
+ if (currentTime - self.lastTapTime >= RJDoubleTapMaxInterval) {
137
+
138
+ self.tapCount = 1;
139
+ self.lastTapTime = currentTime;
140
+ return NO;
141
+ }
142
+
143
+
144
+ self.tapCount++;
145
+ self.lastTapTime = currentTime;
146
+
147
+ if (self.tapCount >= 2) {
148
+ self.tapCount = 0;
149
+ self.lastTapTime = 0;
150
+ return YES;
151
+ }
152
+
153
+ return NO;
154
+ }
155
+
156
+ - (RJGestureType)classifyMovementGesture:(CGFloat)dx
157
+ dy:(CGFloat)dy
158
+ distance:(CGFloat)distance {
159
+
160
+ if (fabs(dy) > fabs(dx) && fabs(dy) > RJSwipeMinDistance) {
161
+ return dy > 0 ? RJGestureTypeScrollDown : RJGestureTypeScrollUp;
162
+ }
163
+
164
+
165
+ if (fabs(dx) > fabs(dy)) {
166
+ return dx > 0 ? RJGestureTypeSwipeRight : RJGestureTypeSwipeLeft;
167
+ }
168
+
169
+
170
+ return dy > 0 ? RJGestureTypeSwipeDown : RJGestureTypeSwipeUp;
171
+ }
172
+
173
+ #pragma mark - Multi-Touch Classification
174
+
175
+ - (RJGestureType)classifyMultiTouchPaths:(NSDictionary<NSNumber *, NSArray *> *)touchPaths
176
+ duration:(NSTimeInterval)duration
177
+ touchCount:(NSInteger)touchCount {
178
+
179
+ if (!touchPaths || touchCount <= 0) {
180
+ return RJGestureTypeTap;
181
+ }
182
+
183
+ @try {
184
+
185
+ if (touchCount == 1) {
186
+ NSArray *path = [[touchPaths allValues] firstObject];
187
+ return [self classifySingleTouchPath:path duration:duration];
188
+ }
189
+
190
+
191
+ if (touchCount == 2) {
192
+ return [self classifyTwoFingerGesture:touchPaths];
193
+ }
194
+
195
+
196
+ if (touchCount == 3) {
197
+ return RJGestureTypeThreeFingerGesture;
198
+ }
199
+
200
+
201
+ return RJGestureTypeMultiTouch;
202
+ } @catch (NSException *exception) {
203
+
204
+ return RJGestureTypeTap;
205
+ }
206
+ }
207
+
208
+ - (RJGestureType)classifyTwoFingerGesture:(NSDictionary<NSNumber *, NSArray *> *)touchPaths {
209
+ @try {
210
+ NSArray *touchIds = [touchPaths allKeys];
211
+ if (touchIds.count < 2) {
212
+ return RJGestureTypeTap;
213
+ }
214
+
215
+ NSArray *path1 = touchPaths[touchIds[0]];
216
+ NSArray *path2 = touchPaths[touchIds[1]];
217
+
218
+
219
+ if (!path1 || !path2 || path1.count < 2 || path2.count < 2) {
220
+ return RJGestureTypeTwoFingerTap;
221
+ }
222
+
223
+
224
+ NSDictionary *start1 = [self extractPointDict:path1.firstObject];
225
+ NSDictionary *start2 = [self extractPointDict:path2.firstObject];
226
+ NSDictionary *end1 = [self extractPointDict:path1.lastObject];
227
+ NSDictionary *end2 = [self extractPointDict:path2.lastObject];
228
+
229
+ if (!start1 || !start2 || !end1 || !end2) {
230
+ return RJGestureTypeTwoFingerTap;
231
+ }
232
+
233
+
234
+ CGFloat startDx = [start1[@"x"] floatValue] - [start2[@"x"] floatValue];
235
+ CGFloat startDy = [start1[@"y"] floatValue] - [start2[@"y"] floatValue];
236
+
237
+
238
+ if (isnan(startDx) || isnan(startDy) || isinf(startDx) || isinf(startDy)) {
239
+ return RJGestureTypeMultiTouch;
240
+ }
241
+
242
+ CGFloat startDistance = sqrt(startDx * startDx + startDy * startDy);
243
+
244
+
245
+ if (startDistance < 1.0) {
246
+ return RJGestureTypeMultiTouch;
247
+ }
248
+
249
+ CGFloat endDx = [end1[@"x"] floatValue] - [end2[@"x"] floatValue];
250
+ CGFloat endDy = [end1[@"y"] floatValue] - [end2[@"y"] floatValue];
251
+ CGFloat endDistance = sqrt(endDx * endDx + endDy * endDy);
252
+
253
+
254
+ RJGestureType pinchGesture = [self checkPinchGesture:startDistance endDistance:endDistance];
255
+ if (pinchGesture) {
256
+ return pinchGesture;
257
+ }
258
+
259
+
260
+ RJGestureType rotationGesture = [self checkRotationGesture:startDx startDy:startDy
261
+ endDx:endDx endDy:endDy];
262
+ if (rotationGesture) {
263
+ return rotationGesture;
264
+ }
265
+
266
+
267
+ RJGestureType panGesture = [self checkPanGesture:start1 start2:start2 end1:end1 end2:end2];
268
+ if (panGesture) {
269
+ return panGesture;
270
+ }
271
+
272
+ return RJGestureTypeTwoFingerTap;
273
+ } @catch (NSException *exception) {
274
+ return RJGestureTypeTwoFingerTap;
275
+ }
276
+ }
277
+
278
+ - (RJGestureType)checkPinchGesture:(CGFloat)startDistance endDistance:(CGFloat)endDistance {
279
+ CGFloat distanceChange = endDistance - startDistance;
280
+ CGFloat distanceChangePercent = fabs(distanceChange) / startDistance;
281
+
282
+
283
+ if (distanceChangePercent > RJPinchMinChangePercent && fabs(distanceChange) > RJPinchMinDistance) {
284
+ return distanceChange > 0 ? RJGestureTypePinchOut : RJGestureTypePinchIn;
285
+ }
286
+
287
+ return nil;
288
+ }
289
+
290
+ - (RJGestureType)checkRotationGesture:(CGFloat)startDx startDy:(CGFloat)startDy
291
+ endDx:(CGFloat)endDx endDy:(CGFloat)endDy {
292
+ CGFloat startAngle = atan2(startDy, startDx);
293
+ CGFloat endAngle = atan2(endDy, endDx);
294
+ CGFloat angleDiff = endAngle - startAngle;
295
+
296
+
297
+ while (angleDiff > M_PI) angleDiff -= 2 * M_PI;
298
+ while (angleDiff < -M_PI) angleDiff += 2 * M_PI;
299
+
300
+ CGFloat rotationDegrees = angleDiff * (180.0 / M_PI);
301
+
302
+ if (fabs(rotationDegrees) > RJRotationMinAngle) {
303
+ return rotationDegrees > 0 ? RJGestureTypeRotateCCW : RJGestureTypeRotateCW;
304
+ }
305
+
306
+ return nil;
307
+ }
308
+
309
+ - (RJGestureType)checkPanGesture:(NSDictionary *)start1 start2:(NSDictionary *)start2
310
+ end1:(NSDictionary *)end1 end2:(NSDictionary *)end2 {
311
+
312
+ CGFloat centerStartX = ([start1[@"x"] floatValue] + [start2[@"x"] floatValue]) / 2.0;
313
+ CGFloat centerStartY = ([start1[@"y"] floatValue] + [start2[@"y"] floatValue]) / 2.0;
314
+ CGFloat centerEndX = ([end1[@"x"] floatValue] + [end2[@"x"] floatValue]) / 2.0;
315
+ CGFloat centerEndY = ([end1[@"y"] floatValue] + [end2[@"y"] floatValue]) / 2.0;
316
+
317
+ CGFloat centerDx = centerEndX - centerStartX;
318
+ CGFloat centerDy = centerEndY - centerStartY;
319
+ CGFloat centerDistance = sqrt(centerDx * centerDx + centerDy * centerDy);
320
+
321
+
322
+ if (centerDistance > RJPinchMinDistance) {
323
+ if (fabs(centerDy) > fabs(centerDx)) {
324
+ return centerDy > 0 ? RJGestureTypePanDown : RJGestureTypePanUp;
325
+ } else {
326
+ return centerDx > 0 ? RJGestureTypePanRight : RJGestureTypePanLeft;
327
+ }
328
+ }
329
+
330
+ return nil;
331
+ }
332
+
333
+ @end
@@ -0,0 +1,169 @@
1
+ //
2
+ // RJTouchInterceptor.h
3
+ // Rejourney
4
+ //
5
+ // Global touch event interception and gesture detection.
6
+ //
7
+ // The touch interceptor uses method swizzling to capture all touch
8
+ // events at the UIApplication level, classify gestures, and notify
9
+ // the Rejourney module.
10
+ //
11
+ // Licensed under the Apache License, Version 2.0 (the "License");
12
+ // you may not use this file except in compliance with the License.
13
+ // You may obtain a copy of the License at
14
+ //
15
+ // http://www.apache.org/licenses/LICENSE-2.0
16
+ //
17
+ // Unless required by applicable law or agreed to in writing, software
18
+ // distributed under the License is distributed on an "AS IS" BASIS,
19
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ // See the License for the specific language governing permissions and
21
+ // limitations under the License.
22
+ //
23
+ // Copyright (c) 2026 Rejourney
24
+ //
25
+
26
+ #import <Foundation/Foundation.h>
27
+ #import <UIKit/UIKit.h>
28
+
29
+ NS_ASSUME_NONNULL_BEGIN
30
+
31
+ @class RJGestureClassifier;
32
+ @class RJMotionEvent;
33
+
34
+ /**
35
+ * Delegate protocol for receiving gesture and motion notifications.
36
+ */
37
+ @protocol RJTouchInterceptorDelegate <NSObject>
38
+
39
+ /**
40
+ * Called when a gesture is recognized.
41
+ *
42
+ * @param gestureType The type of gesture (e.g., "tap", "swipe_left").
43
+ * @param touches Array of touch point dictionaries.
44
+ * @param duration Gesture duration in milliseconds.
45
+ * @param targetLabel Accessibility label of the target view (if available).
46
+ */
47
+ - (void)touchInterceptorDidRecognizeGesture:(NSString *)gestureType
48
+ touches:(NSArray<NSDictionary *> *)touches
49
+ duration:(NSTimeInterval)duration
50
+ targetLabel:(nullable NSString *)targetLabel;
51
+
52
+ @optional
53
+
54
+ /**
55
+ * Called when a gesture is recognized.
56
+ *
57
+ * @param gestureType The type of gesture (e.g., "tap", "swipe_left").
58
+ * @param touches Array of touch point dictionaries.
59
+ * @param duration Gesture duration in milliseconds.
60
+ * @param targetLabel Accessibility label of the target view (if available).
61
+ */
62
+ - (void)touchInterceptorDidRecognizeGesture:(NSString *)gestureType
63
+ touches:(NSArray<NSDictionary *> *)touches
64
+ duration:(NSTimeInterval)duration
65
+ targetLabel:(nullable NSString *)targetLabel;
66
+
67
+ @optional
68
+
69
+ /**
70
+ * Called when a motion event is captured (scroll, pan, swipe with velocity).
71
+ * Used for event-driven replay reconstruction.
72
+ *
73
+ * @param motionEvent The motion event with type, displacement, velocity, and
74
+ * curve.
75
+ */
76
+ - (void)touchInterceptorDidCaptureMotionEvent:(RJMotionEvent *)motionEvent;
77
+
78
+ @optional
79
+
80
+ /**
81
+ * Called when a touch interaction starts (TouchDown).
82
+ * Used to trigger "active state" captures to reduce replay lag.
83
+ */
84
+ - (void)touchInterceptorDidDetectInteractionStart;
85
+
86
+ @required
87
+
88
+ /**
89
+ * Whether recording is currently active.
90
+ * The interceptor will not process touches when this returns NO.
91
+ */
92
+ - (BOOL)isCurrentlyRecording;
93
+
94
+ /**
95
+ * Whether the keyboard is currently visible.
96
+ * Used to detect keyboard taps (which are logged without location for privacy).
97
+ */
98
+ - (BOOL)isKeyboardCurrentlyVisible;
99
+
100
+ /**
101
+ * The current keyboard frame.
102
+ * Used to determine if touches are on the keyboard.
103
+ */
104
+ - (CGRect)currentKeyboardFrame;
105
+
106
+ @end
107
+
108
+ /**
109
+ * Global touch interceptor that captures all touch events.
110
+ *
111
+ * This singleton intercepts UIApplication's sendEvent: method to capture
112
+ * all touch events before they're processed by the view hierarchy. It
113
+ * tracks multi-touch paths, classifies gestures, and notifies the delegate.
114
+ *
115
+ * ## Privacy
116
+ * - Keyboard touches are logged as "keyboard_tap" without location
117
+ * - No key content is captured
118
+ *
119
+ * ## Setup
120
+ * ```objc
121
+ * // In your module init
122
+ * [RJTouchInterceptor sharedInstance].delegate = self;
123
+ * [[RJTouchInterceptor sharedInstance] enableGlobalTracking];
124
+ * ```
125
+ *
126
+ * @note This class uses method swizzling which is performed once and cannot be
127
+ * undone.
128
+ */
129
+ @interface RJTouchInterceptor : NSObject
130
+
131
+ #pragma mark - Singleton
132
+
133
+ /**
134
+ * Returns the shared touch interceptor instance.
135
+ */
136
+ + (instancetype)sharedInstance;
137
+
138
+ #pragma mark - Configuration
139
+
140
+ /// Delegate to receive gesture notifications
141
+ @property(nonatomic, weak, nullable) id<RJTouchInterceptorDelegate> delegate;
142
+
143
+ /// Whether touch tracking is currently enabled
144
+ @property(nonatomic, readonly) BOOL isTrackingEnabled;
145
+
146
+ #pragma mark - Setup
147
+
148
+ /**
149
+ * Enables global touch tracking via method swizzling.
150
+ *
151
+ * This method swizzles UIApplication's sendEvent: method to intercept
152
+ * all touch events. This is called once and cannot be undone.
153
+ *
154
+ * @note Safe to call multiple times; swizzling only happens once.
155
+ */
156
+ - (void)enableGlobalTracking;
157
+
158
+ #pragma mark - Internal (called by swizzled method)
159
+
160
+ /**
161
+ * Handles a touch event. Called by the swizzled sendEvent: method.
162
+ *
163
+ * @param event The UIEvent containing touches.
164
+ */
165
+ - (void)handleTouchEvent:(UIEvent *)event;
166
+
167
+ @end
168
+
169
+ NS_ASSUME_NONNULL_END