react-native-stallion 2.3.0-alpha.3 → 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.
- package/android/build.gradle +25 -0
- package/android/src/main/cpp/CMakeLists.txt +19 -0
- package/android/src/main/cpp/stallion_signal_handler.cpp +62 -0
- package/android/src/main/java/com/stallion/Stallion.java +0 -2
- package/android/src/main/java/com/stallion/StallionModule.java +37 -1
- package/android/src/main/java/com/stallion/events/StallionEventConstants.java +1 -0
- package/android/src/main/java/com/stallion/events/StallionEventManager.java +23 -15
- package/android/src/main/java/com/stallion/networkmanager/StallionFileDownloader.java +8 -5
- package/android/src/main/java/com/stallion/networkmanager/StallionSyncHandler.java +18 -1
- package/android/src/main/java/com/stallion/storage/StallionConfig.java +0 -2
- package/android/src/main/java/com/stallion/storage/StallionMeta.java +52 -1
- package/android/src/main/java/com/stallion/storage/StallionStateManager.java +22 -1
- package/android/src/main/java/com/stallion/utils/StallionDeviceInfo.java +83 -0
- package/android/src/main/java/com/stallion/utils/StallionExceptionHandler.java +84 -7
- package/ios/Stallion.xcodeproj/project.pbxproj +6 -0
- package/ios/main/Stallion.swift +20 -0
- package/ios/main/StallionConstants.swift +1 -0
- package/ios/main/StallionDeviceInfo.swift +105 -0
- package/ios/main/StallionEventHandler.m +3 -1
- package/ios/main/StallionExceptionHandler.h +1 -0
- package/ios/main/StallionExceptionHandler.mm +293 -0
- package/ios/main/StallionFileDownloader.swift +9 -1
- package/ios/main/StallionMeta.h +11 -0
- package/ios/main/StallionMeta.m +76 -1
- package/ios/main/StallionSlotManager.m +1 -1
- package/ios/main/StallionSyncHandler.swift +14 -2
- package/package.json +1 -1
- package/react-native-stallion.podspec +22 -0
- package/src/main/components/modules/listing/components/BundleCardInfoSection.js +1 -1
- package/src/main/components/modules/listing/components/styles/index.js +18 -13
- package/src/main/components/modules/listing/components/styles/index.js.map +1 -1
- package/src/main/components/modules/listing/index.js +15 -15
- package/src/main/components/modules/listing/index.js.map +1 -1
- package/src/main/components/modules/listing/styles.js +2 -1
- package/src/main/components/modules/listing/styles.js.map +1 -1
- package/src/main/components/modules/login/index.js +8 -5
- package/src/main/components/modules/login/index.js.map +1 -1
- package/src/main/components/modules/login/styles/index.js +10 -4
- package/src/main/components/modules/login/styles/index.js.map +1 -1
- package/src/main/components/modules/modal/StallionModal.js +2 -2
- package/src/main/components/modules/modal/StallionModal.js.map +1 -1
- package/src/main/components/modules/prod/prod.js +5 -4
- package/src/main/components/modules/prod/prod.js.map +1 -1
- package/src/main/components/modules/prod/styles/index.js +7 -4
- package/src/main/components/modules/prod/styles/index.js.map +1 -1
- package/src/main/constants/appConstants.js +1 -0
- package/src/main/constants/appConstants.js.map +1 -1
- package/src/main/state/actionCreators/useUpdateMetaActions.js +2 -2
- package/src/main/state/actionCreators/useUpdateMetaActions.js.map +1 -1
- package/src/main/state/index.js +5 -3
- package/src/main/state/index.js.map +1 -1
- package/src/main/state/reducers/updateMetaReducer.js +8 -0
- package/src/main/state/reducers/updateMetaReducer.js.map +1 -1
- package/src/main/state/useStallionEvents.js +27 -4
- package/src/main/state/useStallionEvents.js.map +1 -1
- package/src/main/utils/ErrorBoundary.js +35 -9
- package/src/main/utils/ErrorBoundary.js.map +1 -1
- package/src/main/utils/crashState.js +16 -0
- package/src/main/utils/crashState.js.map +1 -0
- package/src/main/utils/useStallionUpdate.js +2 -2
- package/src/main/utils/useStallionUpdate.js.map +1 -1
- package/src/main/utils/withStallion.js +4 -2
- package/src/main/utils/withStallion.js.map +1 -1
- package/src/types/updateMeta.types.js +1 -0
- package/src/types/updateMeta.types.js.map +1 -1
- package/types/main/components/modules/listing/components/styles/index.d.ts +9 -4
- package/types/main/components/modules/listing/components/styles/index.d.ts.map +1 -1
- package/types/main/components/modules/listing/index.d.ts.map +1 -1
- package/types/main/components/modules/listing/styles.d.ts +1 -0
- package/types/main/components/modules/listing/styles.d.ts.map +1 -1
- package/types/main/components/modules/login/index.d.ts.map +1 -1
- package/types/main/components/modules/login/styles/index.d.ts +6 -0
- package/types/main/components/modules/login/styles/index.d.ts.map +1 -1
- package/types/main/components/modules/prod/prod.d.ts.map +1 -1
- package/types/main/components/modules/prod/styles/index.d.ts +5 -2
- package/types/main/components/modules/prod/styles/index.d.ts.map +1 -1
- package/types/main/constants/appConstants.d.ts +2 -0
- package/types/main/constants/appConstants.d.ts.map +1 -1
- package/types/main/index.d.ts +1 -1
- package/types/main/state/actionCreators/useUpdateMetaActions.d.ts.map +1 -1
- package/types/main/state/index.d.ts +4 -1
- package/types/main/state/index.d.ts.map +1 -1
- package/types/main/state/reducers/updateMetaReducer.d.ts +1 -0
- package/types/main/state/reducers/updateMetaReducer.d.ts.map +1 -1
- package/types/main/state/useStallionEvents.d.ts +4 -2
- package/types/main/state/useStallionEvents.d.ts.map +1 -1
- package/types/main/utils/ErrorBoundary.d.ts +1 -1
- package/types/main/utils/ErrorBoundary.d.ts.map +1 -1
- package/types/main/utils/crashState.d.ts +4 -0
- package/types/main/utils/crashState.d.ts.map +1 -0
- package/types/main/utils/useStallionUpdate.d.ts.map +1 -1
- package/types/main/utils/withStallion.d.ts +2 -1
- package/types/main/utils/withStallion.d.ts.map +1 -1
- package/types/types/updateMeta.types.d.ts +7 -2
- package/types/types/updateMeta.types.d.ts.map +1 -1
- package/types/types/utils.types.d.ts +1 -1
- package/types/types/utils.types.d.ts.map +1 -1
- 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
|
-
|
|
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
|
}
|
package/ios/main/StallionMeta.h
CHANGED
|
@@ -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
|
package/ios/main/StallionMeta.m
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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?.
|
|
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
|
-
|
|
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
|
@@ -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:
|
|
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:
|
|
9
|
+
borderRadius: STD_MARGIN,
|
|
9
10
|
shadowColor: COLORS.black,
|
|
10
11
|
shadowOffset: {
|
|
11
|
-
height:
|
|
12
|
-
width:
|
|
12
|
+
height: 2,
|
|
13
|
+
width: 0
|
|
13
14
|
},
|
|
14
|
-
shadowOpacity: 0.
|
|
15
|
-
elevation:
|
|
16
|
-
shadowRadius:
|
|
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
|
-
|
|
70
|
-
|
|
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:
|
|
92
|
-
fontWeight: '
|
|
93
|
-
color: COLORS.
|
|
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: '
|
|
105
|
+
alignItems: 'flex-start',
|
|
104
106
|
padding: STD_MARGIN,
|
|
105
|
-
|
|
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","
|
|
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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"}
|