@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
package/ios/Rejourney.h
ADDED
|
@@ -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
|