@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,325 @@
|
|
|
1
|
+
//
|
|
2
|
+
// RJRetryManager.m
|
|
3
|
+
// Rejourney
|
|
4
|
+
//
|
|
5
|
+
// Retry queue and circuit breaker for upload resilience implementation.
|
|
6
|
+
//
|
|
7
|
+
// Copyright (c) 2026 Rejourney
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
#import "RJRetryManager.h"
|
|
11
|
+
#import "../Core/RJLogger.h"
|
|
12
|
+
#import "../Utils/RJTelemetry.h"
|
|
13
|
+
|
|
14
|
+
static const NSInteger kCircuitBreakerThreshold = 5;
|
|
15
|
+
static const NSTimeInterval kCircuitBreakerTimeout = 60.0;
|
|
16
|
+
static const NSTimeInterval kMaxRetryDelay = 60.0;
|
|
17
|
+
|
|
18
|
+
#pragma mark - Private Interface
|
|
19
|
+
|
|
20
|
+
@interface RJRetryManager ()
|
|
21
|
+
|
|
22
|
+
@property(nonatomic, strong) dispatch_queue_t retryQueue;
|
|
23
|
+
|
|
24
|
+
@property(nonatomic, strong) NSMutableArray<NSDictionary *> *pendingRetries;
|
|
25
|
+
|
|
26
|
+
@property(nonatomic, assign) BOOL isRetryScheduled;
|
|
27
|
+
|
|
28
|
+
@property(nonatomic, assign) NSTimeInterval circuitOpenedTime;
|
|
29
|
+
|
|
30
|
+
@property(nonatomic, assign) BOOL circuitOpen;
|
|
31
|
+
|
|
32
|
+
@property(nonatomic, assign) NSInteger failureCount;
|
|
33
|
+
|
|
34
|
+
@end
|
|
35
|
+
|
|
36
|
+
#pragma mark - Implementation
|
|
37
|
+
|
|
38
|
+
@implementation RJRetryManager
|
|
39
|
+
|
|
40
|
+
#pragma mark - Initialization
|
|
41
|
+
|
|
42
|
+
- (instancetype)init {
|
|
43
|
+
self = [super init];
|
|
44
|
+
if (self) {
|
|
45
|
+
_retryQueue =
|
|
46
|
+
dispatch_queue_create("com.rejourney.retry", DISPATCH_QUEUE_SERIAL);
|
|
47
|
+
_pendingRetries = [NSMutableArray new];
|
|
48
|
+
_isRetryScheduled = NO;
|
|
49
|
+
_circuitOpen = NO;
|
|
50
|
+
_circuitOpenedTime = 0;
|
|
51
|
+
_failureCount = 0;
|
|
52
|
+
_isShuttingDown = NO;
|
|
53
|
+
}
|
|
54
|
+
return self;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#pragma mark - Public Properties
|
|
58
|
+
|
|
59
|
+
- (BOOL)isCircuitOpen {
|
|
60
|
+
return _circuitOpen;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
- (NSInteger)consecutiveFailureCount {
|
|
64
|
+
return _failureCount;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#pragma mark - Circuit Breaker
|
|
68
|
+
|
|
69
|
+
- (BOOL)shouldAllowRequest {
|
|
70
|
+
if (!self.circuitOpen) {
|
|
71
|
+
return YES;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
|
|
75
|
+
if (now - self.circuitOpenedTime >= kCircuitBreakerTimeout) {
|
|
76
|
+
|
|
77
|
+
RJLogDebug(@"Circuit breaker entering half-open state");
|
|
78
|
+
self.circuitOpen = NO;
|
|
79
|
+
return YES;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
RJLogDebug(@"Circuit breaker open, waiting %.0fs before retry",
|
|
83
|
+
kCircuitBreakerTimeout - (now - self.circuitOpenedTime));
|
|
84
|
+
return NO;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
- (void)recordUploadSuccess {
|
|
88
|
+
self.failureCount = 0;
|
|
89
|
+
[[RJTelemetry sharedInstance] recordEvent:RJTelemetryEventUploadSuccess];
|
|
90
|
+
if (self.circuitOpen) {
|
|
91
|
+
RJLogDebug(@"Upload succeeded, closing circuit breaker");
|
|
92
|
+
self.circuitOpen = NO;
|
|
93
|
+
[[RJTelemetry sharedInstance]
|
|
94
|
+
recordEvent:RJTelemetryEventCircuitBreakerClose];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
- (void)recordUploadFailure {
|
|
99
|
+
self.failureCount++;
|
|
100
|
+
[[RJTelemetry sharedInstance] recordEvent:RJTelemetryEventUploadFailure];
|
|
101
|
+
if (self.failureCount >= kCircuitBreakerThreshold && !self.circuitOpen) {
|
|
102
|
+
self.circuitOpen = YES;
|
|
103
|
+
self.circuitOpenedTime = [[NSDate date] timeIntervalSince1970];
|
|
104
|
+
[[RJTelemetry sharedInstance]
|
|
105
|
+
recordEvent:RJTelemetryEventCircuitBreakerOpen];
|
|
106
|
+
RJLogWarning(@"Circuit breaker opened after %ld consecutive failures",
|
|
107
|
+
(long)self.failureCount);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#pragma mark - Retry Queue
|
|
112
|
+
|
|
113
|
+
- (void)addToRetryQueueWithEvents:(NSArray<NSDictionary *> *)events {
|
|
114
|
+
if (self.isShuttingDown)
|
|
115
|
+
return;
|
|
116
|
+
|
|
117
|
+
dispatch_async(self.retryQueue, ^{
|
|
118
|
+
@try {
|
|
119
|
+
NSDictionary *retryItem = @{
|
|
120
|
+
@"events" : events ?: @[],
|
|
121
|
+
@"timestamp" : @([[NSDate date] timeIntervalSince1970]),
|
|
122
|
+
@"attemptCount" : @0
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
[self.pendingRetries addObject:retryItem];
|
|
126
|
+
RJLogDebug(@"Added batch to retry queue (queue size: %lu)",
|
|
127
|
+
(unsigned long)self.pendingRetries.count);
|
|
128
|
+
|
|
129
|
+
[self scheduleRetryIfNeeded];
|
|
130
|
+
} @catch (NSException *exception) {
|
|
131
|
+
RJLogWarning(@"Add to retry queue exception: %@", exception);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
- (void)scheduleRetryIfNeeded {
|
|
137
|
+
|
|
138
|
+
if (self.isRetryScheduled || self.pendingRetries.count == 0 ||
|
|
139
|
+
self.isShuttingDown) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (![self shouldAllowRequest]) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
self.isRetryScheduled = YES;
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
NSTimeInterval delay = MIN(pow(2.0, self.failureCount), kMaxRetryDelay);
|
|
151
|
+
|
|
152
|
+
RJLogDebug(@"Scheduling retry in %.1fs (consecutive failures: %ld)", delay,
|
|
153
|
+
(long)self.failureCount);
|
|
154
|
+
|
|
155
|
+
__weak typeof(self) weakSelf = self;
|
|
156
|
+
dispatch_after(
|
|
157
|
+
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)),
|
|
158
|
+
self.retryQueue, ^{
|
|
159
|
+
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
160
|
+
if (!strongSelf || strongSelf.isShuttingDown)
|
|
161
|
+
return;
|
|
162
|
+
|
|
163
|
+
strongSelf.isRetryScheduled = NO;
|
|
164
|
+
[strongSelf processRetryQueue];
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
- (void)processRetryQueue {
|
|
169
|
+
|
|
170
|
+
if (self.pendingRetries.count == 0 || self.isShuttingDown) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!self.uploadBlock) {
|
|
175
|
+
RJLogWarning(@"No upload block set, cannot process retry queue");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
NSDictionary *item = self.pendingRetries.firstObject;
|
|
181
|
+
NSArray *events = item[@"events"];
|
|
182
|
+
NSInteger attemptCount = [item[@"attemptCount"] integerValue];
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
[self.pendingRetries removeObjectAtIndex:0];
|
|
186
|
+
|
|
187
|
+
RJLogDebug(@"Retrying batch (attempt %ld, remaining: %lu)",
|
|
188
|
+
(long)(attemptCount + 1),
|
|
189
|
+
(unsigned long)self.pendingRetries.count);
|
|
190
|
+
|
|
191
|
+
__weak typeof(self) weakSelf = self;
|
|
192
|
+
dispatch_async(self.retryQueue, ^{
|
|
193
|
+
__strong typeof(weakSelf) strongSelf = weakSelf;
|
|
194
|
+
if (!strongSelf)
|
|
195
|
+
return;
|
|
196
|
+
|
|
197
|
+
BOOL success = NO;
|
|
198
|
+
@try {
|
|
199
|
+
success = strongSelf.uploadBlock(events);
|
|
200
|
+
} @catch (NSException *exception) {
|
|
201
|
+
RJLogWarning(@"Retry upload exception: %@", exception);
|
|
202
|
+
success = NO;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
206
|
+
if (success) {
|
|
207
|
+
|
|
208
|
+
[strongSelf recordUploadSuccess];
|
|
209
|
+
RJLogDebug(@"Retry upload succeeded");
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
if (strongSelf.pendingRetries.count > 0) {
|
|
213
|
+
dispatch_async(strongSelf.retryQueue, ^{
|
|
214
|
+
[strongSelf scheduleRetryIfNeeded];
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
|
|
219
|
+
[strongSelf recordUploadFailure];
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
if (attemptCount < 5) {
|
|
223
|
+
NSMutableDictionary *updatedItem = [item mutableCopy];
|
|
224
|
+
updatedItem[@"attemptCount"] = @(attemptCount + 1);
|
|
225
|
+
dispatch_async(strongSelf.retryQueue, ^{
|
|
226
|
+
[strongSelf.pendingRetries addObject:updatedItem];
|
|
227
|
+
RJLogDebug(@"Re-queued failed batch (attempt %ld)",
|
|
228
|
+
(long)(attemptCount + 1));
|
|
229
|
+
[strongSelf scheduleRetryIfNeeded];
|
|
230
|
+
});
|
|
231
|
+
} else {
|
|
232
|
+
RJLogWarning(@"Batch exceeded max retries, discarding");
|
|
233
|
+
dispatch_async(strongSelf.retryQueue, ^{
|
|
234
|
+
[strongSelf scheduleRetryIfNeeded];
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
#pragma mark - Persistence
|
|
243
|
+
|
|
244
|
+
- (NSString *)failedUploadsPath {
|
|
245
|
+
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
|
|
246
|
+
NSUserDomainMask, YES);
|
|
247
|
+
NSString *cacheDir = paths.firstObject;
|
|
248
|
+
return [cacheDir stringByAppendingPathComponent:@"rj_failed_uploads.plist"];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
- (void)persistPendingUploads {
|
|
252
|
+
dispatch_async(self.retryQueue, ^{
|
|
253
|
+
@try {
|
|
254
|
+
if (self.pendingRetries.count == 0) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
NSString *path = [self failedUploadsPath];
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
NSMutableArray *allPending = [NSMutableArray array];
|
|
262
|
+
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
|
263
|
+
NSArray *existing = [NSArray arrayWithContentsOfFile:path];
|
|
264
|
+
if (existing) {
|
|
265
|
+
[allPending addObjectsFromArray:existing];
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
[allPending addObjectsFromArray:self.pendingRetries];
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
if (allPending.count > 100) {
|
|
274
|
+
allPending = [[allPending
|
|
275
|
+
subarrayWithRange:NSMakeRange(allPending.count - 100, 100)]
|
|
276
|
+
mutableCopy];
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
BOOL success = [allPending writeToFile:path atomically:YES];
|
|
281
|
+
if (success) {
|
|
282
|
+
RJLogDebug(@"Persisted %lu failed uploads to disk",
|
|
283
|
+
(unsigned long)allPending.count);
|
|
284
|
+
[self.pendingRetries removeAllObjects];
|
|
285
|
+
} else {
|
|
286
|
+
RJLogWarning(@"Failed to persist uploads to disk");
|
|
287
|
+
}
|
|
288
|
+
} @catch (NSException *exception) {
|
|
289
|
+
RJLogWarning(@"Persist uploads exception: %@", exception);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
- (void)loadAndRetryPersistedUploads {
|
|
295
|
+
dispatch_async(self.retryQueue, ^{
|
|
296
|
+
@try {
|
|
297
|
+
NSString *path = [self failedUploadsPath];
|
|
298
|
+
|
|
299
|
+
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
NSArray *persisted = [NSArray arrayWithContentsOfFile:path];
|
|
304
|
+
if (!persisted || persisted.count == 0) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
RJLogDebug(@"Found %lu persisted failed uploads, queuing for retry",
|
|
309
|
+
(unsigned long)persisted.count);
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
[self.pendingRetries addObjectsFromArray:persisted];
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
[self scheduleRetryIfNeeded];
|
|
319
|
+
} @catch (NSException *exception) {
|
|
320
|
+
RJLogWarning(@"Load persisted uploads exception: %@", exception);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
@end
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
//
|
|
2
|
+
// RJUploadManager.h
|
|
3
|
+
// Rejourney
|
|
4
|
+
//
|
|
5
|
+
// Session data upload management.
|
|
6
|
+
//
|
|
7
|
+
// The upload manager handles batched uploads of session data to the
|
|
8
|
+
// dashboard server, including automatic retry and background task
|
|
9
|
+
// management.
|
|
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 "../Core/RJTypes.h"
|
|
27
|
+
#import <Foundation/Foundation.h>
|
|
28
|
+
#import <UIKit/UIKit.h>
|
|
29
|
+
|
|
30
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Manages session data uploads to the dashboard server.
|
|
34
|
+
*
|
|
35
|
+
* The upload manager provides:
|
|
36
|
+
* - Batched upload scheduling
|
|
37
|
+
* - Background task management for reliable uploads
|
|
38
|
+
* - Automatic retry on failure
|
|
39
|
+
* - Payload construction with device info
|
|
40
|
+
*
|
|
41
|
+
* ## Usage
|
|
42
|
+
* ```objc
|
|
43
|
+
* RJUploadManager *manager = [[RJUploadManager alloc]
|
|
44
|
+
* initWithApiUrl:@"https://api.rejourney.co"]; manager.sessionId =
|
|
45
|
+
* @"session_123"; manager.userId = @"user_456";
|
|
46
|
+
*
|
|
47
|
+
* [manager startBatchUploadTimer];
|
|
48
|
+
* // ... later ...
|
|
49
|
+
* [manager uploadBatchWithEvents:events isFinal:NO completion:^(BOOL success) {
|
|
50
|
+
* // Handle result
|
|
51
|
+
* }];
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @note This class is thread-safe for public methods.
|
|
55
|
+
*/
|
|
56
|
+
@interface RJUploadManager : NSObject
|
|
57
|
+
|
|
58
|
+
#pragma mark - Configuration
|
|
59
|
+
|
|
60
|
+
/// API URL for session uploads
|
|
61
|
+
@property(nonatomic, copy) NSString *apiUrl;
|
|
62
|
+
|
|
63
|
+
/// Public route key (pk_live_xxx) for SDK authentication
|
|
64
|
+
@property(nonatomic, copy, nullable) NSString *publicKey;
|
|
65
|
+
|
|
66
|
+
/// Backend project ID (UUID) for attestation/ingest
|
|
67
|
+
@property(nonatomic, copy, nullable) NSString *projectId;
|
|
68
|
+
|
|
69
|
+
/// Current session ID
|
|
70
|
+
@property(nonatomic, copy, nullable) NSString *sessionId;
|
|
71
|
+
|
|
72
|
+
/// Current user ID
|
|
73
|
+
@property(nonatomic, copy, nullable) NSString *userId;
|
|
74
|
+
|
|
75
|
+
/// Device hash for session correlation
|
|
76
|
+
@property(nonatomic, copy, nullable) NSString *deviceHash;
|
|
77
|
+
|
|
78
|
+
/// Session start timestamp
|
|
79
|
+
@property(nonatomic, assign) NSTimeInterval sessionStartTime;
|
|
80
|
+
|
|
81
|
+
/// Total background time in milliseconds (for billing exclusion)
|
|
82
|
+
@property(nonatomic, assign) NSTimeInterval totalBackgroundTimeMs;
|
|
83
|
+
|
|
84
|
+
/// Current batch number
|
|
85
|
+
@property(nonatomic, readonly) NSInteger batchNumber;
|
|
86
|
+
|
|
87
|
+
/// Whether an upload is currently in progress
|
|
88
|
+
@property(nonatomic, readonly) BOOL isUploading;
|
|
89
|
+
|
|
90
|
+
/// Max recording minutes allowed for this project
|
|
91
|
+
@property(nonatomic, assign) NSInteger maxRecordingMinutes;
|
|
92
|
+
|
|
93
|
+
/// Sample rate (0-100) for this project
|
|
94
|
+
@property(nonatomic, assign) NSInteger sampleRate;
|
|
95
|
+
|
|
96
|
+
#pragma mark - Retry & Resilience
|
|
97
|
+
|
|
98
|
+
/// Number of consecutive upload failures (for circuit breaker)
|
|
99
|
+
@property(nonatomic, readonly) NSInteger consecutiveFailureCount;
|
|
100
|
+
|
|
101
|
+
/// Whether the circuit breaker is currently open (blocking requests)
|
|
102
|
+
@property(nonatomic, readonly) BOOL isCircuitOpen;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Loads and retries any persisted failed uploads from previous sessions.
|
|
106
|
+
* Call this during session start to recover from server downtime scenarios.
|
|
107
|
+
*/
|
|
108
|
+
- (void)loadAndRetryPersistedUploads;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Persists pending uploads to disk for recovery after app restart.
|
|
112
|
+
* Call this during app termination or background expiration.
|
|
113
|
+
*/
|
|
114
|
+
- (void)persistPendingUploads;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Recovers any crash-persisted pending uploads and closes prior sessions.
|
|
118
|
+
* Safe to call after an upload token is available.
|
|
119
|
+
*/
|
|
120
|
+
- (void)recoverPendingSessionsWithCompletion:
|
|
121
|
+
(nullable RJCompletionHandler)completion;
|
|
122
|
+
|
|
123
|
+
#pragma mark - Initialization
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Creates an upload manager for the specified API URL.
|
|
127
|
+
*
|
|
128
|
+
* @param apiUrl Base URL of the API server.
|
|
129
|
+
* @return A new upload manager instance.
|
|
130
|
+
*/
|
|
131
|
+
- (instancetype)initWithApiUrl:(NSString *)apiUrl;
|
|
132
|
+
|
|
133
|
+
/// Unavailable. Use initWithApiUrl: instead.
|
|
134
|
+
- (instancetype)init NS_UNAVAILABLE;
|
|
135
|
+
|
|
136
|
+
#pragma mark - Project Configuration
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Fetches the project configuration (ID, limits, etc) from the server.
|
|
140
|
+
* This resolves the publicKey to a projectId and gets the recording rules.
|
|
141
|
+
*
|
|
142
|
+
* @param completion Called with success status and configuration dictionary.
|
|
143
|
+
*/
|
|
144
|
+
- (void)fetchProjectConfigWithCompletion:
|
|
145
|
+
(void (^)(BOOL success, NSDictionary *_Nullable config,
|
|
146
|
+
NSError *_Nullable error))completion;
|
|
147
|
+
|
|
148
|
+
#pragma mark - Timer Management
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Starts the batch upload timer.
|
|
152
|
+
* The timer fires every 30 seconds to upload accumulated data.
|
|
153
|
+
*/
|
|
154
|
+
- (void)startBatchUploadTimer;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Stops the batch upload timer.
|
|
158
|
+
*/
|
|
159
|
+
- (void)stopBatchUploadTimer;
|
|
160
|
+
|
|
161
|
+
#pragma mark - Upload Methods
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Uploads a batch of events to the dashboard.
|
|
165
|
+
*
|
|
166
|
+
* @param events Array of event dictionaries.
|
|
167
|
+
* @param isFinal Whether this is the final batch for the session.
|
|
168
|
+
* @param completion Called with upload success status.
|
|
169
|
+
*/
|
|
170
|
+
- (void)uploadBatchWithEvents:(NSArray<NSDictionary *> *)events
|
|
171
|
+
isFinal:(BOOL)isFinal
|
|
172
|
+
completion:(nullable RJCompletionHandler)completion;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Performs a synchronous upload for app termination.
|
|
176
|
+
* This is a blocking call that should only be used in willTerminate.
|
|
177
|
+
*
|
|
178
|
+
* @param events Array of event dictionaries.
|
|
179
|
+
* @return Whether the upload succeeded.
|
|
180
|
+
*/
|
|
181
|
+
- (BOOL)synchronousUploadWithEvents:(NSArray<NSDictionary *> *)events;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Uploads a crash report to the dashboard.
|
|
185
|
+
*
|
|
186
|
+
* @param report The crash report dictionary.
|
|
187
|
+
* @param completion Called with success status.
|
|
188
|
+
*/
|
|
189
|
+
- (void)uploadCrashReport:(NSDictionary *)report
|
|
190
|
+
completion:(nullable RJCompletionHandler)completion;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Uploads an ANR report to the dashboard.
|
|
194
|
+
*
|
|
195
|
+
* @param report The ANR report dictionary.
|
|
196
|
+
* @param completion Called with success status.
|
|
197
|
+
*/
|
|
198
|
+
- (void)uploadANRReport:(NSDictionary *)report
|
|
199
|
+
completion:(nullable RJCompletionHandler)completion;
|
|
200
|
+
|
|
201
|
+
#pragma mark - Background Task Management
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Begins a background task for upload during app backgrounding.
|
|
205
|
+
*
|
|
206
|
+
* @param name Task name for debugging.
|
|
207
|
+
* @return Background task identifier.
|
|
208
|
+
*/
|
|
209
|
+
- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithName:(NSString *)name;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Ends a background task.
|
|
213
|
+
*
|
|
214
|
+
* @param taskId Task identifier from beginBackgroundTaskWithName:.
|
|
215
|
+
*/
|
|
216
|
+
- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)taskId;
|
|
217
|
+
|
|
218
|
+
#pragma mark - Session End
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Sends a session end signal to the backend synchronously.
|
|
222
|
+
* This updates the session duration and status on the server.
|
|
223
|
+
*
|
|
224
|
+
* @return Whether the request succeeded.
|
|
225
|
+
*/
|
|
226
|
+
- (BOOL)endSessionSync;
|
|
227
|
+
|
|
228
|
+
#pragma mark - State Reset
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Updates the session recovery metadata with current timestamp.
|
|
232
|
+
* Call after successful uploads to ensure proper endedAt on recovery.
|
|
233
|
+
*/
|
|
234
|
+
- (void)updateSessionRecoveryMeta;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Resets the upload manager for a new session.
|
|
238
|
+
*/
|
|
239
|
+
- (void)resetForNewSession;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Shuts down the upload manager, cancelling any active uploads.
|
|
243
|
+
* Call this during module deallocation.
|
|
244
|
+
*/
|
|
245
|
+
- (void)shutdown;
|
|
246
|
+
|
|
247
|
+
#pragma mark - Replay Promotion
|
|
248
|
+
|
|
249
|
+
/// Whether this session has been promoted for replay upload.
|
|
250
|
+
/// Set by evaluateReplayPromotionWithMetrics:completion:.
|
|
251
|
+
@property(nonatomic, readonly) BOOL isReplayPromoted;
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Evaluates whether the session should be promoted for replay upload.
|
|
255
|
+
* Call this at session end.
|
|
256
|
+
*
|
|
257
|
+
* @param metrics Session metrics (crashCount, anrCount, errorCount, etc.)
|
|
258
|
+
* @param completion Called with promotion status and reason.
|
|
259
|
+
*/
|
|
260
|
+
- (void)evaluateReplayPromotionWithMetrics:(NSDictionary *)metrics
|
|
261
|
+
completion:
|
|
262
|
+
(void (^)(BOOL promoted,
|
|
263
|
+
NSString *reason))completion;
|
|
264
|
+
|
|
265
|
+
@end
|
|
266
|
+
|
|
267
|
+
NS_ASSUME_NONNULL_END
|