@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,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