react-native-stallion 2.3.0-alpha.4 → 2.3.0-alpha.5

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 (97) hide show
  1. package/android/build.gradle +25 -0
  2. package/android/src/main/cpp/CMakeLists.txt +19 -0
  3. package/android/src/main/cpp/stallion_signal_handler.cpp +62 -0
  4. package/android/src/main/java/com/stallion/Stallion.java +0 -2
  5. package/android/src/main/java/com/stallion/StallionModule.java +37 -1
  6. package/android/src/main/java/com/stallion/events/StallionEventConstants.java +1 -0
  7. package/android/src/main/java/com/stallion/events/StallionEventManager.java +23 -15
  8. package/android/src/main/java/com/stallion/networkmanager/StallionFileDownloader.java +8 -5
  9. package/android/src/main/java/com/stallion/networkmanager/StallionSyncHandler.java +18 -1
  10. package/android/src/main/java/com/stallion/storage/StallionMeta.java +52 -1
  11. package/android/src/main/java/com/stallion/storage/StallionStateManager.java +22 -1
  12. package/android/src/main/java/com/stallion/utils/StallionDeviceInfo.java +83 -0
  13. package/android/src/main/java/com/stallion/utils/StallionExceptionHandler.java +84 -7
  14. package/ios/Stallion.xcodeproj/project.pbxproj +6 -0
  15. package/ios/main/Stallion.swift +20 -0
  16. package/ios/main/StallionConstants.swift +1 -0
  17. package/ios/main/StallionDeviceInfo.swift +105 -0
  18. package/ios/main/StallionEventHandler.m +3 -1
  19. package/ios/main/StallionExceptionHandler.h +1 -0
  20. package/ios/main/StallionExceptionHandler.mm +293 -0
  21. package/ios/main/StallionFileDownloader.swift +9 -1
  22. package/ios/main/StallionMeta.h +11 -0
  23. package/ios/main/StallionMeta.m +76 -1
  24. package/ios/main/StallionSlotManager.m +1 -1
  25. package/ios/main/StallionSyncHandler.swift +14 -2
  26. package/package.json +1 -1
  27. package/react-native-stallion.podspec +22 -0
  28. package/src/main/components/modules/listing/components/BundleCardInfoSection.js +1 -1
  29. package/src/main/components/modules/listing/components/styles/index.js +18 -13
  30. package/src/main/components/modules/listing/components/styles/index.js.map +1 -1
  31. package/src/main/components/modules/listing/index.js +15 -15
  32. package/src/main/components/modules/listing/index.js.map +1 -1
  33. package/src/main/components/modules/listing/styles.js +2 -1
  34. package/src/main/components/modules/listing/styles.js.map +1 -1
  35. package/src/main/components/modules/login/index.js +8 -5
  36. package/src/main/components/modules/login/index.js.map +1 -1
  37. package/src/main/components/modules/login/styles/index.js +10 -4
  38. package/src/main/components/modules/login/styles/index.js.map +1 -1
  39. package/src/main/components/modules/modal/StallionModal.js +2 -2
  40. package/src/main/components/modules/modal/StallionModal.js.map +1 -1
  41. package/src/main/components/modules/prod/prod.js +5 -4
  42. package/src/main/components/modules/prod/prod.js.map +1 -1
  43. package/src/main/components/modules/prod/styles/index.js +7 -4
  44. package/src/main/components/modules/prod/styles/index.js.map +1 -1
  45. package/src/main/constants/appConstants.js +1 -0
  46. package/src/main/constants/appConstants.js.map +1 -1
  47. package/src/main/state/actionCreators/useUpdateMetaActions.js +2 -2
  48. package/src/main/state/actionCreators/useUpdateMetaActions.js.map +1 -1
  49. package/src/main/state/index.js +5 -3
  50. package/src/main/state/index.js.map +1 -1
  51. package/src/main/state/reducers/updateMetaReducer.js +8 -0
  52. package/src/main/state/reducers/updateMetaReducer.js.map +1 -1
  53. package/src/main/state/useStallionEvents.js +27 -4
  54. package/src/main/state/useStallionEvents.js.map +1 -1
  55. package/src/main/utils/ErrorBoundary.js +35 -9
  56. package/src/main/utils/ErrorBoundary.js.map +1 -1
  57. package/src/main/utils/crashState.js +16 -0
  58. package/src/main/utils/crashState.js.map +1 -0
  59. package/src/main/utils/useStallionUpdate.js +2 -2
  60. package/src/main/utils/useStallionUpdate.js.map +1 -1
  61. package/src/main/utils/withStallion.js +4 -2
  62. package/src/main/utils/withStallion.js.map +1 -1
  63. package/src/types/updateMeta.types.js +1 -0
  64. package/src/types/updateMeta.types.js.map +1 -1
  65. package/types/main/components/modules/listing/components/styles/index.d.ts +9 -4
  66. package/types/main/components/modules/listing/components/styles/index.d.ts.map +1 -1
  67. package/types/main/components/modules/listing/index.d.ts.map +1 -1
  68. package/types/main/components/modules/listing/styles.d.ts +1 -0
  69. package/types/main/components/modules/listing/styles.d.ts.map +1 -1
  70. package/types/main/components/modules/login/index.d.ts.map +1 -1
  71. package/types/main/components/modules/login/styles/index.d.ts +6 -0
  72. package/types/main/components/modules/login/styles/index.d.ts.map +1 -1
  73. package/types/main/components/modules/prod/prod.d.ts.map +1 -1
  74. package/types/main/components/modules/prod/styles/index.d.ts +5 -2
  75. package/types/main/components/modules/prod/styles/index.d.ts.map +1 -1
  76. package/types/main/constants/appConstants.d.ts +2 -0
  77. package/types/main/constants/appConstants.d.ts.map +1 -1
  78. package/types/main/index.d.ts +1 -1
  79. package/types/main/state/actionCreators/useUpdateMetaActions.d.ts.map +1 -1
  80. package/types/main/state/index.d.ts +4 -1
  81. package/types/main/state/index.d.ts.map +1 -1
  82. package/types/main/state/reducers/updateMetaReducer.d.ts +1 -0
  83. package/types/main/state/reducers/updateMetaReducer.d.ts.map +1 -1
  84. package/types/main/state/useStallionEvents.d.ts +4 -2
  85. package/types/main/state/useStallionEvents.d.ts.map +1 -1
  86. package/types/main/utils/ErrorBoundary.d.ts +1 -1
  87. package/types/main/utils/ErrorBoundary.d.ts.map +1 -1
  88. package/types/main/utils/crashState.d.ts +4 -0
  89. package/types/main/utils/crashState.d.ts.map +1 -0
  90. package/types/main/utils/useStallionUpdate.d.ts.map +1 -1
  91. package/types/main/utils/withStallion.d.ts +2 -1
  92. package/types/main/utils/withStallion.d.ts.map +1 -1
  93. package/types/types/updateMeta.types.d.ts +7 -2
  94. package/types/types/updateMeta.types.d.ts.map +1 -1
  95. package/types/types/utils.types.d.ts +1 -1
  96. package/types/types/utils.types.d.ts.map +1 -1
  97. package/ios/main/StallionExceptionHandler.m +0 -184
@@ -0,0 +1,293 @@
1
+ //
2
+ // StallionExceptionHandler.mm
3
+ // react-native-stallion
4
+ //
5
+ // Created by Thor963 on 29/01/25.
6
+ //
7
+
8
+ #import "StallionExceptionHandler.h"
9
+ #import "StallionStateManager.h"
10
+ #import "StallionEventHandler.h"
11
+ #import "StallionSlotManager.h"
12
+ #import "StallionMeta.h"
13
+ #import "StallionMetaConstants.h"
14
+ #import "StallionObjConstants.h"
15
+ #import <signal.h>
16
+ #import <execinfo.h>
17
+ #import <React/RCTLog.h>
18
+ #import <React/RCTUtils.h>
19
+ #include <exception>
20
+ #include <cxxabi.h>
21
+ #include <string>
22
+
23
+ // Forward declarations
24
+ void handleException(NSException *exception);
25
+ void handleSignal(int signal, siginfo_t *info, void *context);
26
+ static void performStallionRollback(NSString *errorString);
27
+ static void setupSignalHandlers(void);
28
+ void handleCppTerminate(void);
29
+
30
+ @implementation StallionExceptionHandler
31
+
32
+ NSUncaughtExceptionHandler *_defaultExceptionHandler;
33
+ std::terminate_handler _defaultTerminateHandler;
34
+ BOOL exceptionAlertDismissed = FALSE;
35
+ static BOOL rollbackPerformed = FALSE;
36
+ static BOOL isInitialized = FALSE;
37
+ static struct sigaction _previousHandlers[32]; // Store previous handlers for chaining
38
+
39
+ + (void)initExceptionHandler {
40
+ // Prevent multiple initializations
41
+ if (isInitialized) {
42
+ return;
43
+ }
44
+ isInitialized = TRUE;
45
+
46
+ // Reset rollback flag when initializing exception handler
47
+ [self resetRollbackFlag];
48
+
49
+ // Store and set Objective-C exception handler
50
+ if (!_defaultExceptionHandler) {
51
+ _defaultExceptionHandler = NSGetUncaughtExceptionHandler();
52
+ }
53
+ NSSetUncaughtExceptionHandler(&handleException);
54
+
55
+ // Store and set C++ terminate handler
56
+ if (!_defaultTerminateHandler) {
57
+ _defaultTerminateHandler = std::get_terminate();
58
+ }
59
+ std::set_terminate(handleCppTerminate);
60
+
61
+ // Setup signal handlers using sigaction with chaining
62
+ setupSignalHandlers();
63
+
64
+ // Initialize JavaScript exception handler
65
+ [self initJavaScriptExceptionHandler];
66
+ }
67
+
68
+ + (void)initJavaScriptExceptionHandler {
69
+ // Handle fatal JavaScript errors that cause app crashes
70
+ RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
71
+ // Rollback only for fatal errors that crash the app
72
+ if (level >= RCTLogLevelFatal) {
73
+ NSString *errorString = [NSString stringWithFormat:@"Fatal JS Error in %@:%@ - %@", fileName, lineNumber, message];
74
+ performStallionRollback(errorString);
75
+ }
76
+ });
77
+ }
78
+
79
+ + (void)resetRollbackFlag {
80
+ rollbackPerformed = FALSE;
81
+ }
82
+
83
+ @end
84
+
85
+ #pragma mark - Shared Rollback Logic
86
+
87
+ static void performStallionRollback(NSString *errorString) {
88
+
89
+ StallionStateManager *stateManager = [StallionStateManager sharedInstance];
90
+ StallionMeta *meta = stateManager.stallionMeta;
91
+ BOOL isAutoRollback = !stateManager.isMounted;
92
+
93
+
94
+ // Only prevent multiple executions for auto rollback cases
95
+ // Launch crashes (when mounted) can continue to be registered
96
+ if (rollbackPerformed && isAutoRollback) {
97
+ NSLog(@"Auto rollback already performed, skipping duplicate rollback attempt");
98
+ return;
99
+ }
100
+
101
+ // Set rollback flag only for auto rollback cases
102
+ if (isAutoRollback) {
103
+ rollbackPerformed = TRUE;
104
+ }
105
+
106
+ if (errorString.length > 900) {
107
+ errorString = [errorString substringToIndex:900];
108
+ }
109
+
110
+ if (meta.switchState == SwitchStateProd) {
111
+ NSString *currentHash = [meta getActiveReleaseHash] ?: @"";
112
+
113
+ [[StallionEventHandler sharedInstance] cacheEvent:StallionObjConstants.exception_prod_event
114
+ eventPayload:@{
115
+ @"meta": errorString,
116
+ StallionObjConstants.release_hash_key: currentHash,
117
+ StallionObjConstants.is_auto_rollback_key: isAutoRollback ? @"true" : @"false"
118
+ }];
119
+ if (isAutoRollback) {
120
+ [StallionSlotManager rollbackProdWithAutoRollback:YES errorString:errorString];
121
+ }
122
+
123
+ } else if (meta.switchState == SwitchStateStage) {
124
+
125
+ if(isAutoRollback) {
126
+ [StallionSlotManager rollbackStage];
127
+ }
128
+
129
+ [[StallionEventHandler sharedInstance] cacheEvent:StallionObjConstants.exception_stage_event
130
+ eventPayload:@{
131
+ @"meta": errorString,
132
+ StallionObjConstants.release_hash_key: meta.stageNewHash,
133
+ StallionObjConstants.is_auto_rollback_key: isAutoRollback ? @"true" : @"false"
134
+ }];
135
+
136
+ if(!exceptionAlertDismissed) {
137
+ UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Stallion Exception Handler"
138
+ message:[NSString stringWithFormat:@"%@\n%@",
139
+ @"A crash occurred in the app. Build was rolled back. Check crash report below. Continue crash to invoke other exception handlers. \n \n",
140
+ errorString]
141
+ preferredStyle:UIAlertControllerStyleAlert];
142
+
143
+ [alert addAction:[UIAlertAction actionWithTitle:@"Continue Crash"
144
+ style:UIAlertActionStyleDefault
145
+ handler:^(UIAlertAction *action) {
146
+ exceptionAlertDismissed = TRUE;
147
+ }]];
148
+
149
+ UIApplication *app = [UIApplication sharedApplication];
150
+ UIViewController *rootViewController = app.delegate.window.rootViewController;
151
+
152
+ dispatch_async(dispatch_get_main_queue(), ^{
153
+ [rootViewController presentViewController:alert animated:YES completion:nil];
154
+ });
155
+
156
+ while (exceptionAlertDismissed == FALSE) {
157
+ [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
158
+ }
159
+ }
160
+ }
161
+ }
162
+
163
+ #pragma mark - Signal Handler Setup
164
+
165
+ static void setupSignalHandlers(void) {
166
+ struct sigaction action;
167
+ sigemptyset(&action.sa_mask);
168
+ action.sa_flags = SA_SIGINFO | SA_ONSTACK;
169
+ action.sa_sigaction = handleSignal;
170
+
171
+ // List of signals to catch - comprehensive coverage
172
+ int signals[] = {
173
+ SIGABRT, // Abort signal
174
+ SIGILL, // Illegal instruction
175
+ SIGSEGV, // Segmentation violation
176
+ SIGFPE, // Floating point exception
177
+ SIGBUS, // Bus error
178
+ SIGTRAP, // Trace/breakpoint trap
179
+ SIGPIPE, // Broken pipe
180
+ SIGSYS, // Bad system call
181
+ };
182
+
183
+ int signalCount = sizeof(signals) / sizeof(signals[0]);
184
+
185
+ for (int i = 0; i < signalCount; i++) {
186
+ int sig = signals[i];
187
+ // Store previous handler before installing ours (for chaining)
188
+ sigaction(sig, NULL, &_previousHandlers[sig]);
189
+ // Now install our handler
190
+ sigaction(sig, &action, NULL);
191
+ }
192
+ }
193
+
194
+ void handleException(NSException *exception) {
195
+ NSString *readableError = [exception reason] ?: @"Unknown exception";
196
+ NSString *name = [exception name] ?: @"Unknown";
197
+ NSString *callStack = [[exception callStackSymbols] componentsJoinedByString:@"\n"];
198
+ NSString *fullError = [NSString stringWithFormat:@"Exception: %@\nReason: %@\nStack:\n%@", name, readableError, callStack];
199
+
200
+ performStallionRollback(fullError);
201
+
202
+ // Chain to default exception handler if available
203
+ if (_defaultExceptionHandler) {
204
+ _defaultExceptionHandler(exception);
205
+ }
206
+ }
207
+
208
+ void handleSignal(int signalVal, siginfo_t *info, void *context) {
209
+ void *callstack[128];
210
+ int frames = backtrace(callstack, 128);
211
+ char **symbols = backtrace_symbols(callstack, frames);
212
+
213
+ NSMutableString *stackTrace = [NSMutableString stringWithFormat:@"Signal %d was raised.\n", signalVal];
214
+
215
+ // Add signal info if available
216
+ if (info) {
217
+ [stackTrace appendFormat:@"Signal Code: %d\n", info->si_code];
218
+ if (info->si_addr) {
219
+ [stackTrace appendFormat:@"Fault Address: %p\n", info->si_addr];
220
+ }
221
+ }
222
+
223
+ [stackTrace appendString:@"Stack trace:\n"];
224
+ for (int i = 0; i < frames; ++i) {
225
+ [stackTrace appendFormat:@"%s\n", symbols[i]];
226
+ }
227
+ free(symbols);
228
+
229
+ performStallionRollback(stackTrace);
230
+
231
+ // Chain to previous handler if it exists and is valid (bubble up)
232
+ if (signalVal >= 0 && signalVal < 32) {
233
+ struct sigaction *prev = &_previousHandlers[signalVal];
234
+ if (prev->sa_handler != SIG_DFL && prev->sa_handler != SIG_IGN && prev->sa_handler != NULL) {
235
+ // Prevent infinite loop - don't call ourselves
236
+ if (prev->sa_sigaction != handleSignal) {
237
+ if (prev->sa_flags & SA_SIGINFO) {
238
+ prev->sa_sigaction(signalVal, info, context);
239
+ } else if (prev->sa_handler != SIG_DFL && prev->sa_handler != SIG_IGN) {
240
+ prev->sa_handler(signalVal);
241
+ }
242
+ }
243
+ }
244
+ }
245
+
246
+ // Restore default and raise to proceed with crash
247
+ signal(signalVal, SIG_DFL);
248
+ raise(signalVal);
249
+ }
250
+
251
+ void handleCppTerminate(void) {
252
+ std::exception_ptr ex = std::current_exception();
253
+ NSString *errorString = @"C++ terminate() called";
254
+
255
+ if (ex) {
256
+ try {
257
+ std::rethrow_exception(ex);
258
+ } catch (const std::exception &e) {
259
+ const char *name = abi::__cxa_demangle(typeid(e).name(), 0, 0, 0);
260
+ errorString = [NSString stringWithFormat:@"C++ Exception: %s - %s",
261
+ name ? name : typeid(e).name(), e.what()];
262
+ if (name) free((void*)name);
263
+ } catch (const std::string &s) {
264
+ errorString = [NSString stringWithFormat:@"C++ Exception (string): %s", s.c_str()];
265
+ } catch (const char *s) {
266
+ errorString = [NSString stringWithFormat:@"C++ Exception (char*): %s", s];
267
+ } catch (...) {
268
+ errorString = @"C++ Exception: Unknown type";
269
+ }
270
+ }
271
+
272
+ // Get stack trace
273
+ void *callstack[128];
274
+ int frames = backtrace(callstack, 128);
275
+ char **symbols = backtrace_symbols(callstack, frames);
276
+
277
+ NSMutableString *fullError = [NSMutableString stringWithString:errorString];
278
+ [fullError appendString:@"\nStack trace:\n"];
279
+ for (int i = 0; i < frames; ++i) {
280
+ [fullError appendFormat:@"%s\n", symbols[i]];
281
+ }
282
+ free(symbols);
283
+
284
+ performStallionRollback(fullError);
285
+
286
+ // Chain to default terminate handler
287
+ if (_defaultTerminateHandler) {
288
+ _defaultTerminateHandler();
289
+ } else {
290
+ abort();
291
+ }
292
+ }
293
+
@@ -17,6 +17,7 @@ class StallionFileDownloader: NSObject {
17
17
  private var _reject: RCTPromiseRejectBlock?
18
18
  private var _onProgress: ((Float) -> Void)?
19
19
  private var lastSentProgress: Float = 0
20
+ private var lastProgressEmitTime: TimeInterval = 0
20
21
  private var _downloadDirectory: String?
21
22
 
22
23
  private let queue = DispatchQueue(label: "com.stallion.networkmanager", qos: .background)
@@ -116,13 +117,20 @@ class StallionFileDownloader: NSObject {
116
117
  guard bytesRead == headerSize else { return false }
117
118
  return header == [0x50, 0x4B, 0x03, 0x04] // PKZIP magic number
118
119
  }
120
+
121
+ private func shouldEmitProgress(currentTime: TimeInterval) -> Bool {
122
+ return currentTime - self.lastProgressEmitTime >= StallionConstants.PROGRESS_THROTTLE_INTERVAL_MS
123
+ }
119
124
  }
120
125
 
121
126
  extension StallionFileDownloader: URLSessionDownloadDelegate {
122
127
  func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
123
128
  let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
124
- if progress - self.lastSentProgress > StallionConstants.PROGRESS_EVENT_THRESHOLD {
129
+ let currentTime = Date().timeIntervalSince1970
130
+
131
+ if shouldEmitProgress(currentTime: currentTime) {
125
132
  self.lastSentProgress = progress
133
+ self.lastProgressEmitTime = currentTime
126
134
  self._onProgress?(progress)
127
135
  }
128
136
  }
@@ -20,10 +20,21 @@
20
20
  @property (nonatomic, copy) NSString *prodNewHash;
21
21
  @property (nonatomic, copy) NSString *prodStableHash;
22
22
  @property (nonatomic, copy) NSString *lastRolledBackHash;
23
+ @property (nonatomic, assign) NSTimeInterval lastRolledBackAt;
24
+ @property (nonatomic, assign) NSInteger successfulLaunchCount;
25
+ @property (nonatomic, copy) NSString *lastSuccessfulLaunchHash;
26
+
27
+ + (NSInteger)maxSuccessLaunchThreshold;
28
+ + (NSTimeInterval)lastRolledBackTTL;
23
29
 
24
30
  - (void)reset;
25
31
  - (NSDictionary *)toDictionary;
32
+ - (NSString *)getHashAtCurrentProdSlot;
26
33
  - (NSString *)getActiveReleaseHash;
34
+ - (NSString *)getLastRolledBackHash;
35
+ - (void)setLastRolledBackHashWithTimestamp:(NSString *)lastRolledBackHash;
36
+ - (void)markSuccessfulLaunch:(NSString *)releaseHash;
37
+ - (NSInteger)getSuccessfulLaunchCount:(NSString *)releaseHash;
27
38
  + (instancetype)fromDictionary:(NSDictionary *)dict;
28
39
 
29
40
  @end
@@ -9,6 +9,14 @@
9
9
 
10
10
  @implementation StallionMeta
11
11
 
12
+ + (NSInteger)maxSuccessLaunchThreshold {
13
+ return 3;
14
+ }
15
+
16
+ + (NSTimeInterval)lastRolledBackTTL {
17
+ return 6 * 60 * 60; // 6 hours in seconds
18
+ }
19
+
12
20
  - (instancetype)init {
13
21
  self = [super init];
14
22
  if (self) [self reset];
@@ -26,6 +34,9 @@
26
34
  self.prodNewHash = @"";
27
35
  self.prodStableHash = @"";
28
36
  self.lastRolledBackHash = @"";
37
+ self.lastRolledBackAt = 0.0;
38
+ self.successfulLaunchCount = 0;
39
+ self.lastSuccessfulLaunchHash = @"";
29
40
  }
30
41
 
31
42
  - (NSDictionary *)toDictionary {
@@ -43,7 +54,10 @@
43
54
  @"stableHash": self.prodStableHash ?: @"",
44
55
  @"currentSlot": [StallionMetaConstants stringFromSlotState:self.currentProdSlot] ?: @""
45
56
  },
46
- @"lastRolledBackHash": self.lastRolledBackHash ?: @""
57
+ @"lastRolledBackHash": self.lastRolledBackHash ?: @"",
58
+ @"lastRolledBackAt": @(self.lastRolledBackAt),
59
+ @"successfulLaunchCount": @(self.successfulLaunchCount),
60
+ @"lastSuccessfulLaunchHash": self.lastSuccessfulLaunchHash ?: @""
47
61
  };
48
62
  } @catch (NSException *exception) {
49
63
  NSLog(@"Error in toDictionary: %@", exception.reason);
@@ -87,6 +101,9 @@
87
101
  meta.currentProdSlot = [StallionMetaConstants slotStateFromString:prodSlot[@"currentSlot"] ?: @"default_slot"];
88
102
 
89
103
  meta.lastRolledBackHash = dict[@"lastRolledBackHash"] ?: @"";
104
+ meta.lastRolledBackAt = [dict[@"lastRolledBackAt"] doubleValue];
105
+ meta.successfulLaunchCount = [dict[@"successfulLaunchCount"] integerValue];
106
+ meta.lastSuccessfulLaunchHash = dict[@"lastSuccessfulLaunchHash"] ?: @"";
90
107
 
91
108
  return meta;
92
109
  } @catch (NSException *exception) {
@@ -95,4 +112,62 @@
95
112
  }
96
113
  }
97
114
 
115
+ - (NSString *)getHashAtCurrentProdSlot {
116
+ switch (self.currentProdSlot) {
117
+ case SlotStateNewSlot:
118
+ return self.prodNewHash;
119
+ case SlotStateStableSlot:
120
+ return self.prodStableHash;
121
+ default:
122
+ return @"";
123
+ }
124
+ }
125
+
126
+ - (NSString *)getLastRolledBackHash {
127
+ [self enforceLastRolledBackExpiry];
128
+ return self.lastRolledBackHash;
129
+ }
130
+
131
+ - (void)setLastRolledBackHashWithTimestamp:(NSString *)lastRolledBackHash {
132
+ NSString *hashValue = lastRolledBackHash ?: @"";
133
+ self.lastRolledBackHash = hashValue;
134
+ self.lastRolledBackAt = [hashValue isEqualToString:@""] ? 0.0 : [[NSDate date] timeIntervalSince1970];
135
+ }
136
+
137
+ - (void)enforceLastRolledBackExpiry {
138
+ if (!self.lastRolledBackHash || [self.lastRolledBackHash isEqualToString:@""]) {
139
+ return;
140
+ }
141
+ if (self.lastRolledBackAt <= 0.0) {
142
+ return;
143
+ }
144
+ NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
145
+ if (now - self.lastRolledBackAt >= [StallionMeta lastRolledBackTTL]) {
146
+ self.lastRolledBackHash = @"";
147
+ self.lastRolledBackAt = 0.0;
148
+ }
149
+ }
150
+
151
+ - (void)markSuccessfulLaunch:(NSString *)releaseHash {
152
+ if (!releaseHash || [releaseHash isEqualToString:@""]) {
153
+ return;
154
+ }
155
+ if (![releaseHash isEqualToString:self.lastSuccessfulLaunchHash]) {
156
+ self.successfulLaunchCount = 0;
157
+ self.lastSuccessfulLaunchHash = releaseHash;
158
+ }
159
+ if (self.successfulLaunchCount < 3) { // MAX_SUCCESS_LAUNCH_THRESHOLD
160
+ self.successfulLaunchCount += 1;
161
+ }
162
+ }
163
+
164
+ - (NSInteger)getSuccessfulLaunchCount:(NSString *)releaseHash {
165
+ NSString *currentHash = releaseHash ?: @"";
166
+ if (![currentHash isEqualToString:self.lastSuccessfulLaunchHash]) {
167
+ return 0;
168
+ } else {
169
+ return self.successfulLaunchCount;
170
+ }
171
+ }
172
+
98
173
  @end
@@ -96,7 +96,7 @@
96
96
 
97
97
  if (isAutoRollback) {
98
98
  StallionStateManager *stateManager = [StallionStateManager sharedInstance];
99
- [stateManager.stallionMeta setLastRolledBackHash:rolledBackReleaseHash];
99
+ [stateManager.stallionMeta setLastRolledBackHashWithTimestamp:rolledBackReleaseHash];
100
100
  [stateManager syncStallionMeta];
101
101
  }
102
102
 
@@ -46,6 +46,7 @@ class StallionSyncHandler {
46
46
  "platform": StallionConstants.PlatformValue,
47
47
  "projectId": projectId,
48
48
  "appliedBundleHash": appliedBundleHash,
49
+ "deviceMeta": StallionDeviceInfo.getDeviceMetaJson(config)
49
50
  ]
50
51
 
51
52
  // Make API call using URLSession
@@ -151,7 +152,7 @@ class StallionSyncHandler {
151
152
  !newReleaseHash.isEmpty else { return }
152
153
 
153
154
  let stateManager = StallionStateManager.sharedInstance()
154
- let lastRolledBackHash = stateManager?.stallionMeta?.lastRolledBackHash ?? ""
155
+ let lastRolledBackHash = stateManager?.stallionMeta?.getLastRolledBackHash() ?? ""
155
156
  let lastUnverifiedHash = stateManager?.stallionConfig?.lastUnverifiedHash ?? ""
156
157
 
157
158
  if newReleaseHash != lastRolledBackHash && newReleaseHash != lastUnverifiedHash {
@@ -190,7 +191,7 @@ class StallionSyncHandler {
190
191
  url: fromUrl,
191
192
  downloadDirectory: downloadPath,
192
193
  onProgress: { progress in
193
- // Handle progress updates if necessary
194
+ emitDownloadProgress(releaseHash: newReleaseHash, progress: progress)
194
195
  },
195
196
  resolve: { _ in
196
197
  completeDownload()
@@ -278,5 +279,16 @@ class StallionSyncHandler {
278
279
  shouldCache: true
279
280
  )
280
281
  }
282
+
283
+ private static func emitDownloadProgress(releaseHash: String, progress: Float) {
284
+ let progressPayload: NSDictionary = [
285
+ "releaseHash": releaseHash,
286
+ "progress": "\(progress)"
287
+ ]
288
+ Stallion.sendEventToRn(eventName: StallionConstants.NativeEventTypesProd.DOWNLOAD_PROGRESS_PROD,
289
+ eventBody: progressPayload,
290
+ shouldCache: false
291
+ )
292
+ }
281
293
  }
282
294
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-stallion",
3
- "version": "2.3.0-alpha.4",
3
+ "version": "2.3.0-alpha.5",
4
4
  "description": "Offical React Native SDK for Stallion",
5
5
  "main": "index",
6
6
  "types": "types/index.d.ts",
@@ -3,6 +3,28 @@ require "json"
3
3
  package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
4
  folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
5
5
 
6
+ # Generate StallionVersion.h during pod install
7
+ version_header_path = File.join(__dir__, "ios/main/StallionVersion.h")
8
+ version_header_content = <<~HEADER
9
+ //
10
+ // StallionVersion.h
11
+ // react-native-stallion
12
+ //
13
+ // Auto-generated from package.json during pod install
14
+ // Do not edit this file manually
15
+ //
16
+
17
+ #ifndef StallionVersion_h
18
+ #define StallionVersion_h
19
+
20
+ #define STALLION_SDK_VERSION @"#{package["version"]}"
21
+
22
+ #endif /* StallionVersion_h */
23
+ HEADER
24
+
25
+ File.write(version_header_path, version_header_content)
26
+ puts "Stallion: Generated StallionVersion.h with version #{package["version"]}"
27
+
6
28
  Pod::Spec.new do |s|
7
29
  s.name = "react-native-stallion"
8
30
  s.version = package["version"]
@@ -59,7 +59,7 @@ const BundleCardInfoSection = _ref => {
59
59
  }, /*#__PURE__*/React.createElement(Text, {
60
60
  style: styles.releaseNoteText
61
61
  }, BUNDLE_CARD_RELEASE_NOTE), /*#__PURE__*/React.createElement(Text, {
62
- numberOfLines: 1,
62
+ numberOfLines: 2,
63
63
  style: styles.releaseNoteDescriptionText
64
64
  }, description)) : /*#__PURE__*/React.createElement(Text, {
65
65
  style: styles.releaseNoteText
@@ -4,16 +4,17 @@ import { COLORS } from '../../../../../constants/colors';
4
4
  const styles = StyleSheet.create({
5
5
  cardContainer: {
6
6
  margin: STD_MARGIN,
7
+ marginBottom: STD_MARGIN * 1.5,
7
8
  backgroundColor: COLORS.white,
8
- borderRadius: 8,
9
+ borderRadius: STD_MARGIN,
9
10
  shadowColor: COLORS.black,
10
11
  shadowOffset: {
11
- height: 4,
12
- width: 4
12
+ height: 2,
13
+ width: 0
13
14
  },
14
- shadowOpacity: 0.1,
15
- elevation: 2,
16
- shadowRadius: 8,
15
+ shadowOpacity: 0.08,
16
+ elevation: 3,
17
+ shadowRadius: 6,
17
18
  borderWidth: 0.5,
18
19
  borderColor: COLORS.black2
19
20
  },
@@ -66,8 +67,9 @@ const styles = StyleSheet.create({
66
67
  },
67
68
  divider: {
68
69
  borderBottomWidth: 0.5,
69
- opacity: 0.2,
70
- marginBottom: STD_MARGIN
70
+ borderColor: COLORS.black2,
71
+ marginVertical: STD_MARGIN,
72
+ marginHorizontal: STD_MARGIN
71
73
  },
72
74
  subText: {
73
75
  fontSize: HEADER_SLAB_HEIGHT / 3,
@@ -88,9 +90,9 @@ const styles = StyleSheet.create({
88
90
  fontWeight: 'bold'
89
91
  },
90
92
  releaseNoteText: {
91
- fontSize: 12,
92
- fontWeight: '500',
93
- color: COLORS.text_major
93
+ fontSize: 14,
94
+ fontWeight: 'bold',
95
+ color: COLORS.black7
94
96
  },
95
97
  releaseNoteDescriptionText: {
96
98
  fontSize: 14,
@@ -100,9 +102,12 @@ const styles = StyleSheet.create({
100
102
  },
101
103
  descContainer: {
102
104
  flexDirection: 'row',
103
- alignItems: 'center',
105
+ alignItems: 'flex-start',
104
106
  padding: STD_MARGIN,
105
- backgroundColor: COLORS.black1
107
+ marginHorizontal: STD_MARGIN,
108
+ marginBottom: STD_MARGIN / 2,
109
+ backgroundColor: COLORS.black1,
110
+ borderRadius: STD_MARGIN / 2
106
111
  },
107
112
  rightBorder: {
108
113
  borderRightWidth: 1,
@@ -1 +1 @@
1
- {"version":3,"names":["StyleSheet","HEADER_SLAB_HEIGHT","STD_MARGIN","COLORS","styles","create","cardContainer","margin","backgroundColor","white","borderRadius","shadowColor","black","shadowOffset","height","width","shadowOpacity","elevation","shadowRadius","borderWidth","borderColor","black2","infoSection","actionSection","flex","alignItems","justifyCenter","justifyContent","rowFlex","flexDirection","rowContainer","padding","colContainer","downloadButton","configCardContainer","paddingHorizontal","marginVertical","appliedText","color","green","fontWeight","fontSize","container","metaConainer","divider","borderBottomWidth","opacity","marginBottom","subText","text_major","titleText","subTitleText","textAlign","black7","bold","releaseNoteText","releaseNoteDescriptionText","marginLeft","descContainer","black1","rightBorder","borderRightWidth"],"sourceRoot":"../../../../../../../../src","sources":["main/components/modules/listing/components/styles/index.ts"],"mappings":"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"}
1
+ {"version":3,"names":["StyleSheet","HEADER_SLAB_HEIGHT","STD_MARGIN","COLORS","styles","create","cardContainer","margin","marginBottom","backgroundColor","white","borderRadius","shadowColor","black","shadowOffset","height","width","shadowOpacity","elevation","shadowRadius","borderWidth","borderColor","black2","infoSection","actionSection","flex","alignItems","justifyCenter","justifyContent","rowFlex","flexDirection","rowContainer","padding","colContainer","downloadButton","configCardContainer","paddingHorizontal","marginVertical","appliedText","color","green","fontWeight","fontSize","container","metaConainer","divider","borderBottomWidth","marginHorizontal","subText","text_major","titleText","subTitleText","textAlign","black7","bold","releaseNoteText","releaseNoteDescriptionText","marginLeft","descContainer","black1","rightBorder","borderRightWidth"],"sourceRoot":"../../../../../../../../src","sources":["main/components/modules/listing/components/styles/index.ts"],"mappings":"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"}