react-native-stallion 2.3.0-alpha.4 → 2.3.0-alpha.6
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 +91 -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/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 +162 -29
- 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 +379 -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 +2 -2
- package/ios/main/StallionStateManager.m +29 -0
- 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 +45 -11
- 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 +2 -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,83 @@
|
|
|
1
|
+
package com.stallion.utils;
|
|
2
|
+
|
|
3
|
+
import android.os.Build;
|
|
4
|
+
|
|
5
|
+
import com.stallion.storage.StallionConfig;
|
|
6
|
+
|
|
7
|
+
import org.json.JSONObject;
|
|
8
|
+
|
|
9
|
+
import java.util.Locale;
|
|
10
|
+
import java.util.TimeZone;
|
|
11
|
+
|
|
12
|
+
public class StallionDeviceInfo {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Sample JSON contract for deviceMeta
|
|
16
|
+
* {
|
|
17
|
+
* "osName": "Android",
|
|
18
|
+
* "osVersion": "14",
|
|
19
|
+
* "sdkInt": 34,
|
|
20
|
+
* "manufacturer": "Google",
|
|
21
|
+
* "brand": "google",
|
|
22
|
+
* "model": "Pixel 7",
|
|
23
|
+
* "device": "panther",
|
|
24
|
+
* "product": "panther",
|
|
25
|
+
* "hardware": "panther",
|
|
26
|
+
* "locale": "en-US",
|
|
27
|
+
* "localeLanguage": "en",
|
|
28
|
+
* "localeCountry": "US",
|
|
29
|
+
* "timezone": "America/Los_Angeles",
|
|
30
|
+
* "timezoneOffsetMinutes": -420,
|
|
31
|
+
* "isEmulator": false,
|
|
32
|
+
* "projectId": "<project-id>",
|
|
33
|
+
* "uid": "<device-uid>",
|
|
34
|
+
* "appVersion": "1.2.3"
|
|
35
|
+
* }
|
|
36
|
+
*/
|
|
37
|
+
public static JSONObject getDeviceMetaJson(StallionConfig config) {
|
|
38
|
+
JSONObject json = new JSONObject();
|
|
39
|
+
try {
|
|
40
|
+
json.put("osName", "Android");
|
|
41
|
+
json.put("osVersion", Build.VERSION.RELEASE != null ? Build.VERSION.RELEASE : "");
|
|
42
|
+
json.put("sdkInt", Build.VERSION.SDK_INT);
|
|
43
|
+
json.put("manufacturer", Build.MANUFACTURER != null ? Build.MANUFACTURER : "");
|
|
44
|
+
json.put("brand", Build.BRAND != null ? Build.BRAND : "");
|
|
45
|
+
json.put("model", Build.MODEL != null ? Build.MODEL : "");
|
|
46
|
+
json.put("device", Build.DEVICE != null ? Build.DEVICE : "");
|
|
47
|
+
json.put("product", Build.PRODUCT != null ? Build.PRODUCT : "");
|
|
48
|
+
json.put("hardware", Build.HARDWARE != null ? Build.HARDWARE : "");
|
|
49
|
+
Locale defaultLocale = Locale.getDefault();
|
|
50
|
+
json.put("locale", defaultLocale != null ? defaultLocale.toLanguageTag() : "");
|
|
51
|
+
json.put("localeLanguage", defaultLocale != null ? defaultLocale.getLanguage() : "");
|
|
52
|
+
json.put("localeCountry", defaultLocale != null ? defaultLocale.getCountry() : "");
|
|
53
|
+
TimeZone tz = TimeZone.getDefault();
|
|
54
|
+
json.put("timezone", tz != null ? tz.getID() : "");
|
|
55
|
+
json.put("timezoneOffsetMinutes", tz != null ? (tz.getOffset(System.currentTimeMillis()) / 60000) : 0);
|
|
56
|
+
json.put("isEmulator", isProbablyEmulator());
|
|
57
|
+
if (config != null) {
|
|
58
|
+
json.put("projectId", config.getProjectId());
|
|
59
|
+
json.put("uid", config.getUid());
|
|
60
|
+
json.put("appVersion", config.getAppVersion());
|
|
61
|
+
}
|
|
62
|
+
} catch (Exception ignored) { }
|
|
63
|
+
return json;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private static boolean isProbablyEmulator() {
|
|
67
|
+
String fingerprint = Build.FINGERPRINT;
|
|
68
|
+
String model = Build.MODEL;
|
|
69
|
+
String manufacturer = Build.MANUFACTURER;
|
|
70
|
+
String brand = Build.BRAND;
|
|
71
|
+
String device = Build.DEVICE;
|
|
72
|
+
String product = Build.PRODUCT;
|
|
73
|
+
|
|
74
|
+
if (fingerprint != null && (fingerprint.startsWith("generic") || fingerprint.startsWith("unknown"))) return true;
|
|
75
|
+
if (model != null && (model.contains("google_sdk") || model.contains("Emulator") || model.contains("Android SDK built for x86"))) return true;
|
|
76
|
+
if (manufacturer != null && manufacturer.contains("Genymotion")) return true;
|
|
77
|
+
if (brand != null && brand.startsWith("generic") && device != null && device.startsWith("generic")) return true;
|
|
78
|
+
if (product != null && product.equals("google_sdk")) return true;
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
@@ -9,41 +9,64 @@ import com.stallion.storage.StallionStateManager;
|
|
|
9
9
|
|
|
10
10
|
import org.json.JSONObject;
|
|
11
11
|
|
|
12
|
+
import java.util.concurrent.atomic.AtomicBoolean;
|
|
13
|
+
|
|
12
14
|
public class StallionExceptionHandler {
|
|
13
15
|
|
|
14
16
|
private static Thread.UncaughtExceptionHandler _androidUncaughtExceptionHandler;
|
|
15
17
|
private static Thread _exceptionThread;
|
|
16
18
|
private static Throwable _exceptionThrowable;
|
|
17
|
-
private static
|
|
19
|
+
private static final AtomicBoolean isErrorBoundaryInitialized = new AtomicBoolean(false);
|
|
20
|
+
private static final AtomicBoolean isNativeSignalsInitialized = new AtomicBoolean(false);
|
|
21
|
+
private static final AtomicBoolean hasProcessedNativeCrashMarker = new AtomicBoolean(false);
|
|
22
|
+
private static final AtomicBoolean isRollbackPerformed = new AtomicBoolean(false);
|
|
18
23
|
|
|
19
24
|
public static void initErrorBoundary() {
|
|
20
|
-
|
|
25
|
+
// Use compareAndSet to atomically check and set initialization flag
|
|
26
|
+
if (!isErrorBoundaryInitialized.compareAndSet(false, true)) {
|
|
21
27
|
return; // Prevent multiple initializations
|
|
22
28
|
}
|
|
23
|
-
|
|
29
|
+
|
|
30
|
+
// Reset rollback flag when initializing exception handler
|
|
31
|
+
isRollbackPerformed.set(false);
|
|
24
32
|
|
|
25
33
|
_androidUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
|
|
26
34
|
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
|
|
27
35
|
_exceptionThread = thread;
|
|
28
36
|
_exceptionThrowable = throwable;
|
|
29
37
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
try {
|
|
39
|
+
// Safely trim the stack trace string
|
|
40
|
+
String stackTraceString = Log.getStackTraceString(throwable);
|
|
41
|
+
stackTraceString = stackTraceString.length() > 900
|
|
42
|
+
? stackTraceString.substring(0, 900) + "..."
|
|
43
|
+
: stackTraceString;
|
|
44
|
+
|
|
45
|
+
StallionStateManager stateManager = StallionStateManager.getInstance();
|
|
46
|
+
StallionMetaConstants.SwitchState switchState = stateManager.stallionMeta.getSwitchState();
|
|
47
|
+
|
|
48
|
+
if (switchState == StallionMetaConstants.SwitchState.PROD) {
|
|
49
|
+
handleProdState(stackTraceString, stateManager);
|
|
50
|
+
} else if (switchState == StallionMetaConstants.SwitchState.STAGE) {
|
|
51
|
+
handleStageState(stackTraceString, stateManager);
|
|
52
|
+
} else {
|
|
53
|
+
continueExceptionFlow();
|
|
54
|
+
}
|
|
55
|
+
} catch (Exception e) {
|
|
56
|
+
// If anything goes wrong in our handler, ensure we still chain to previous handler
|
|
44
57
|
continueExceptionFlow();
|
|
45
58
|
}
|
|
46
59
|
});
|
|
60
|
+
|
|
61
|
+
// Initialize native signal handler once and process any prior crash marker
|
|
62
|
+
try {
|
|
63
|
+
if (isNativeSignalsInitialized.compareAndSet(false, true)) {
|
|
64
|
+
System.loadLibrary("stallion-crash");
|
|
65
|
+
String filesDir = StallionStateManager.getInstance().getStallionConfig().getFilesDirectory();
|
|
66
|
+
initNativeSignalHandler(filesDir);
|
|
67
|
+
}
|
|
68
|
+
processNativeCrashMarkerIfPresent();
|
|
69
|
+
} catch (Throwable ignored) {}
|
|
47
70
|
}
|
|
48
71
|
|
|
49
72
|
private static void emitException(String stackTraceString, String releaseHash, boolean isAutoRollback, boolean isProd) {
|
|
@@ -52,27 +75,43 @@ public class StallionExceptionHandler {
|
|
|
52
75
|
syncErrorPayload.put("meta", stackTraceString);
|
|
53
76
|
syncErrorPayload.put("releaseHash", releaseHash);
|
|
54
77
|
syncErrorPayload.put("isAutoRollback", Boolean.toString(isAutoRollback));
|
|
78
|
+
StallionEventManager.getInstance().sendEvent(
|
|
79
|
+
isProd ?
|
|
80
|
+
StallionEventConstants.NativeProdEventTypes.EXCEPTION_PROD.toString()
|
|
81
|
+
: StallionEventConstants.NativeStageEventTypes.EXCEPTION_STAGE.toString(),
|
|
82
|
+
syncErrorPayload
|
|
83
|
+
);
|
|
55
84
|
} catch (Exception ignored) { }
|
|
56
|
-
StallionEventManager.getInstance().sendEvent(
|
|
57
|
-
isProd ?
|
|
58
|
-
StallionEventConstants.NativeProdEventTypes.EXCEPTION_PROD.toString()
|
|
59
|
-
: StallionEventConstants.NativeStageEventTypes.EXCEPTION_STAGE.toString(),
|
|
60
|
-
syncErrorPayload
|
|
61
|
-
);
|
|
62
85
|
}
|
|
63
86
|
|
|
64
87
|
private static void handleProdState(String stackTraceString, StallionStateManager stateManager) {
|
|
65
88
|
boolean isAutoRollback = !stateManager.getIsMounted();
|
|
66
89
|
String currentHash = stateManager.stallionMeta.getHashAtCurrentProdSlot();
|
|
67
90
|
|
|
68
|
-
//
|
|
69
|
-
|
|
91
|
+
// Only prevent multiple executions for auto rollback cases
|
|
92
|
+
// Launch crashes (when mounted) can continue to be registered
|
|
93
|
+
if (isAutoRollback) {
|
|
94
|
+
// Use compareAndSet to atomically check and set the flag
|
|
95
|
+
if (!isRollbackPerformed.compareAndSet(false, true)) {
|
|
96
|
+
// Flag was already true, skip duplicate rollback
|
|
97
|
+
continueExceptionFlow();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Emit exception event (wrap in try-catch to ensure chaining happens)
|
|
103
|
+
try {
|
|
104
|
+
emitException(stackTraceString, currentHash, isAutoRollback, true);
|
|
105
|
+
} catch (Exception ignored) { }
|
|
70
106
|
|
|
71
107
|
// Perform rollback if auto-rollback is enabled
|
|
72
108
|
if (isAutoRollback) {
|
|
73
|
-
|
|
109
|
+
try {
|
|
110
|
+
StallionSlotManager.rollbackProd(true, stackTraceString);
|
|
111
|
+
} catch (Exception ignored) { }
|
|
74
112
|
}
|
|
75
113
|
|
|
114
|
+
// Always chain to previous handler, even if rollback or emit failed
|
|
76
115
|
continueExceptionFlow();
|
|
77
116
|
}
|
|
78
117
|
|
|
@@ -80,11 +119,29 @@ public class StallionExceptionHandler {
|
|
|
80
119
|
boolean isAutoRollback = !stateManager.getIsMounted();
|
|
81
120
|
String currentStageHash = stateManager.stallionMeta.getStageNewHash();
|
|
82
121
|
|
|
83
|
-
//
|
|
84
|
-
|
|
122
|
+
// Only prevent multiple executions for auto rollback cases
|
|
123
|
+
// Launch crashes (when mounted) can continue to be registered
|
|
124
|
+
if (isAutoRollback) {
|
|
125
|
+
// Use compareAndSet to atomically check and set the flag
|
|
126
|
+
if (!isRollbackPerformed.compareAndSet(false, true)) {
|
|
127
|
+
// Flag was already true, skip duplicate rollback
|
|
128
|
+
continueExceptionFlow();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
85
132
|
|
|
86
|
-
|
|
133
|
+
// Emit exception event (wrap in try-catch to ensure chaining happens)
|
|
134
|
+
try {
|
|
135
|
+
emitException(stackTraceString, currentStageHash, isAutoRollback, false);
|
|
136
|
+
} catch (Exception ignored) { }
|
|
87
137
|
|
|
138
|
+
if(isAutoRollback) {
|
|
139
|
+
try {
|
|
140
|
+
StallionSlotManager.rollbackStage();
|
|
141
|
+
} catch (Exception ignored) { }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Always chain to previous handler, even if rollback or emit failed
|
|
88
145
|
continueExceptionFlow();
|
|
89
146
|
}
|
|
90
147
|
|
|
@@ -93,4 +150,80 @@ public class StallionExceptionHandler {
|
|
|
93
150
|
_androidUncaughtExceptionHandler.uncaughtException(_exceptionThread, _exceptionThrowable);
|
|
94
151
|
}
|
|
95
152
|
}
|
|
153
|
+
|
|
154
|
+
private static void processNativeCrashMarkerIfPresent() {
|
|
155
|
+
try {
|
|
156
|
+
if (hasProcessedNativeCrashMarker.get()) { return; }
|
|
157
|
+
String filesDir = StallionStateManager.getInstance().getStallionConfig().getFilesDirectory();
|
|
158
|
+
java.io.File marker = new java.io.File(filesDir + "/stallion_crash.marker");
|
|
159
|
+
if (marker.exists()) {
|
|
160
|
+
StringBuilder sb = new StringBuilder();
|
|
161
|
+
try (java.io.BufferedReader br = new java.io.BufferedReader(new java.io.FileReader(marker))) {
|
|
162
|
+
String line;
|
|
163
|
+
while ((line = br.readLine()) != null) {
|
|
164
|
+
sb.append(line);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
String jsonContent = sb.toString();
|
|
169
|
+
String stackTraceString = "";
|
|
170
|
+
boolean isAutoRollback = false;
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
// Parse JSON from previous crash
|
|
174
|
+
JSONObject crashMarker = new JSONObject(jsonContent);
|
|
175
|
+
stackTraceString = crashMarker.optString("crashLog", "");
|
|
176
|
+
// Use the autoRollback flag that was determined at crash time (previous session)
|
|
177
|
+
isAutoRollback = crashMarker.optBoolean("isAutoRollback", false);
|
|
178
|
+
} catch (org.json.JSONException e) {
|
|
179
|
+
// Fallback for old format (non-JSON)
|
|
180
|
+
stackTraceString = jsonContent;
|
|
181
|
+
// Default to true for old format (conservative approach)
|
|
182
|
+
isAutoRollback = true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (stackTraceString.length() > 900) {
|
|
186
|
+
stackTraceString = stackTraceString.substring(0, 900) + "...";
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
StallionStateManager stateManager = StallionStateManager.getInstance();
|
|
190
|
+
StallionMetaConstants.SwitchState switchState = stateManager.stallionMeta.getSwitchState();
|
|
191
|
+
if (switchState == StallionMetaConstants.SwitchState.PROD) {
|
|
192
|
+
String currentHash = stateManager.stallionMeta.getHashAtCurrentProdSlot();
|
|
193
|
+
// Use isAutoRollback from previous crash, not current session state
|
|
194
|
+
try {
|
|
195
|
+
emitException(stackTraceString, currentHash, isAutoRollback, true);
|
|
196
|
+
} catch (Exception ignored) { }
|
|
197
|
+
if (isAutoRollback) {
|
|
198
|
+
// Only prevent multiple executions for auto rollback cases
|
|
199
|
+
if (isRollbackPerformed.compareAndSet(false, true)) {
|
|
200
|
+
try {
|
|
201
|
+
StallionSlotManager.rollbackProd(true, stackTraceString);
|
|
202
|
+
} catch (Exception ignored) { }
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
} else if (switchState == StallionMetaConstants.SwitchState.STAGE) {
|
|
206
|
+
String currentStageHash = stateManager.stallionMeta.getStageNewHash();
|
|
207
|
+
// Use isAutoRollback from previous crash, not current session state
|
|
208
|
+
try {
|
|
209
|
+
emitException(stackTraceString, currentStageHash, isAutoRollback, false);
|
|
210
|
+
} catch (Exception ignored) { }
|
|
211
|
+
if (isAutoRollback) {
|
|
212
|
+
// Only prevent multiple executions for auto rollback cases
|
|
213
|
+
if (isRollbackPerformed.compareAndSet(false, true)) {
|
|
214
|
+
try {
|
|
215
|
+
StallionSlotManager.rollbackStage();
|
|
216
|
+
} catch (Exception ignored) { }
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// delete marker
|
|
222
|
+
StallionFileManager.deleteFileOrFolderSilently(marker);
|
|
223
|
+
hasProcessedNativeCrashMarker.set(true);
|
|
224
|
+
}
|
|
225
|
+
} catch (Exception ignored) {}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private static native void initNativeSignalHandler(String filesDir);
|
|
96
229
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
/* Begin PBXBuildFile section */
|
|
10
10
|
5E555C0D2413F4C50049A1A2 /* Stallion.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* Stallion.m */; };
|
|
11
11
|
F4FF95D7245B92E800C19C63 /* Stallion.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FF95D6245B92E800C19C63 /* Stallion.swift */; };
|
|
12
|
+
F4FF95DA245B92EB00C19C63 /* StallionDeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4FF95D9245B92EB00C19C63 /* StallionDeviceInfo.swift */; };
|
|
12
13
|
/* End PBXBuildFile section */
|
|
13
14
|
|
|
14
15
|
/* Begin PBXCopyFilesBuildPhase section */
|
|
@@ -28,6 +29,8 @@
|
|
|
28
29
|
B3E7B5891CC2AC0600A0062D /* Stallion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Stallion.m; sourceTree = "<group>"; };
|
|
29
30
|
F4FF95D5245B92E700C19C63 /* Stallion-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Stallion-Bridging-Header.h"; sourceTree = "<group>"; };
|
|
30
31
|
F4FF95D6245B92E800C19C63 /* Stallion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stallion.swift; sourceTree = "<group>"; };
|
|
32
|
+
F4FF95D7245B92E900C19C63 /* StallionVersion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "StallionVersion.h"; sourceTree = "<group>"; };
|
|
33
|
+
F4FF95D9245B92EB00C19C63 /* StallionDeviceInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StallionDeviceInfo.swift"; sourceTree = "<group>"; };
|
|
31
34
|
/* End PBXFileReference section */
|
|
32
35
|
|
|
33
36
|
/* Begin PBXFrameworksBuildPhase section */
|
|
@@ -53,8 +56,10 @@
|
|
|
53
56
|
isa = PBXGroup;
|
|
54
57
|
children = (
|
|
55
58
|
F4FF95D6245B92E800C19C63 /* Stallion.swift */,
|
|
59
|
+
F4FF95D9245B92EB00C19C63 /* StallionDeviceInfo.swift */,
|
|
56
60
|
B3E7B5891CC2AC0600A0062D /* Stallion.m */,
|
|
57
61
|
F4FF95D5245B92E700C19C63 /* Stallion-Bridging-Header.h */,
|
|
62
|
+
F4FF95D7245B92E900C19C63 /* StallionVersion.h */,
|
|
58
63
|
134814211AA4EA7D00B7C361 /* Products */,
|
|
59
64
|
);
|
|
60
65
|
sourceTree = "<group>";
|
|
@@ -117,6 +122,7 @@
|
|
|
117
122
|
buildActionMask = 2147483647;
|
|
118
123
|
files = (
|
|
119
124
|
F4FF95D7245B92E800C19C63 /* Stallion.swift in Sources */,
|
|
125
|
+
F4FF95DA245B92EB00C19C63 /* StallionDeviceInfo.swift in Sources */,
|
|
120
126
|
B3E7B58A1CC2AC0600A0062D /* Stallion.m in Sources */,
|
|
121
127
|
);
|
|
122
128
|
runOnlyForDeploymentPostprocessing = 0;
|
package/ios/main/Stallion.swift
CHANGED
|
@@ -30,6 +30,14 @@ class Stallion: RCTEventEmitter {
|
|
|
30
30
|
@objc func onLaunch(_ launchData: String) {
|
|
31
31
|
stallionStateManager.isMounted = true
|
|
32
32
|
checkPendingDownloads()
|
|
33
|
+
let currentReleaseHash = stallionStateManager.stallionMeta.getHashAtCurrentProdSlot()
|
|
34
|
+
if let currentReleaseHash = currentReleaseHash {
|
|
35
|
+
if !currentReleaseHash.isEmpty && stallionStateManager.stallionMeta.getSuccessfulLaunchCount(currentReleaseHash) == 0 {
|
|
36
|
+
emitInstallEvent(currentReleaseHash)
|
|
37
|
+
}
|
|
38
|
+
stallionStateManager.stallionMeta.markSuccessfulLaunch(currentReleaseHash)
|
|
39
|
+
stallionStateManager.syncStallionMeta()
|
|
40
|
+
}
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
private func checkPendingDownloads() {
|
|
@@ -153,5 +161,17 @@ class Stallion: RCTEventEmitter {
|
|
|
153
161
|
RCTTriggerReloadCommandListeners("Stallion: Restart")
|
|
154
162
|
}
|
|
155
163
|
}
|
|
164
|
+
|
|
165
|
+
private func emitInstallEvent(_ releaseHash: String) {
|
|
166
|
+
let eventPayload: [String: Any] = [
|
|
167
|
+
"releaseHash": releaseHash
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
Stallion.sendEventToRn(
|
|
171
|
+
eventName: StallionConstants.NativeEventTypesProd.INSTALLED_PROD,
|
|
172
|
+
eventBody: eventPayload as NSDictionary,
|
|
173
|
+
shouldCache: false
|
|
174
|
+
)
|
|
175
|
+
}
|
|
156
176
|
}
|
|
157
177
|
|
|
@@ -34,6 +34,7 @@ class StallionConstants {
|
|
|
34
34
|
static let SecretKey = "secretKey"
|
|
35
35
|
}
|
|
36
36
|
static let PROGRESS_EVENT_THRESHOLD: Float = 0.05;
|
|
37
|
+
static let PROGRESS_THROTTLE_INTERVAL_MS: TimeInterval = 0.3; // 300ms
|
|
37
38
|
|
|
38
39
|
static let PROD_DIRECTORY = "StallionProd"
|
|
39
40
|
static let STAGE_DIRECTORY = "StallionStage"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
//
|
|
2
|
+
// StallionDeviceInfo.swift
|
|
3
|
+
// react-native-stallion
|
|
4
|
+
//
|
|
5
|
+
// Created by Thor963 on 29/01/25.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import UIKit
|
|
10
|
+
|
|
11
|
+
class StallionDeviceInfo {
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Sample JSON contract for deviceMeta
|
|
15
|
+
* {
|
|
16
|
+
* "osName": "iOS",
|
|
17
|
+
* "osVersion": "17.2",
|
|
18
|
+
* "sdkInt": 17,
|
|
19
|
+
* "manufacturer": "Apple",
|
|
20
|
+
* "brand": "Apple",
|
|
21
|
+
* "model": "iPhone 15 Pro",
|
|
22
|
+
* "device": "iPhone16,1",
|
|
23
|
+
* "product": "iPhone16,1",
|
|
24
|
+
* "hardware": "iPhone16,1",
|
|
25
|
+
* "locale": "en-US",
|
|
26
|
+
* "localeLanguage": "en",
|
|
27
|
+
* "localeCountry": "US",
|
|
28
|
+
* "timezone": "America/Los_Angeles",
|
|
29
|
+
* "timezoneOffsetMinutes": -420,
|
|
30
|
+
* "isEmulator": false,
|
|
31
|
+
* "projectId": "<project-id>",
|
|
32
|
+
* "uid": "<device-uid>",
|
|
33
|
+
* "appVersion": "1.2.3"
|
|
34
|
+
* }
|
|
35
|
+
*/
|
|
36
|
+
static func getDeviceMetaJson(_ config: StallionConfig?) -> [String: Any] {
|
|
37
|
+
var json: [String: Any] = [:]
|
|
38
|
+
|
|
39
|
+
// OS Information
|
|
40
|
+
json["osName"] = "iOS"
|
|
41
|
+
json["osVersion"] = UIDevice.current.systemVersion
|
|
42
|
+
json["sdkInt"] = Int(UIDevice.current.systemVersion.components(separatedBy: ".").first ?? "0") ?? 0
|
|
43
|
+
|
|
44
|
+
// Device Information
|
|
45
|
+
json["manufacturer"] = "Apple"
|
|
46
|
+
json["brand"] = "Apple"
|
|
47
|
+
json["model"] = getDeviceModel()
|
|
48
|
+
json["device"] = getDeviceIdentifier()
|
|
49
|
+
json["product"] = getDeviceIdentifier()
|
|
50
|
+
json["hardware"] = getDeviceIdentifier()
|
|
51
|
+
|
|
52
|
+
// Locale Information
|
|
53
|
+
let locale = Locale.current
|
|
54
|
+
json["locale"] = locale.identifier
|
|
55
|
+
json["localeLanguage"] = locale.languageCode ?? ""
|
|
56
|
+
json["localeCountry"] = locale.regionCode ?? ""
|
|
57
|
+
|
|
58
|
+
// Timezone Information
|
|
59
|
+
let timezone = TimeZone.current
|
|
60
|
+
json["timezone"] = timezone.identifier
|
|
61
|
+
json["timezoneOffsetMinutes"] = timezone.secondsFromGMT() / 60
|
|
62
|
+
|
|
63
|
+
// Emulator Detection
|
|
64
|
+
json["isEmulator"] = isProbablyEmulator()
|
|
65
|
+
|
|
66
|
+
// Config Information
|
|
67
|
+
if let config = config {
|
|
68
|
+
json["projectId"] = config.projectId ?? ""
|
|
69
|
+
json["uid"] = config.uid ?? ""
|
|
70
|
+
json["appVersion"] = config.appVersion ?? ""
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return json
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private static func getDeviceModel() -> String {
|
|
77
|
+
var systemInfo = utsname()
|
|
78
|
+
uname(&systemInfo)
|
|
79
|
+
let modelCode = withUnsafePointer(to: &systemInfo.machine) {
|
|
80
|
+
$0.withMemoryRebound(to: CChar.self, capacity: 1) {
|
|
81
|
+
ptr in String.init(validatingUTF8: ptr)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return modelCode ?? "Unknown"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private static func getDeviceIdentifier() -> String {
|
|
88
|
+
var systemInfo = utsname()
|
|
89
|
+
uname(&systemInfo)
|
|
90
|
+
let identifier = withUnsafePointer(to: &systemInfo.machine) {
|
|
91
|
+
$0.withMemoryRebound(to: CChar.self, capacity: 1) {
|
|
92
|
+
ptr in String.init(validatingUTF8: ptr)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return identifier ?? "Unknown"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private static func isProbablyEmulator() -> Bool {
|
|
99
|
+
#if targetEnvironment(simulator)
|
|
100
|
+
return true
|
|
101
|
+
#else
|
|
102
|
+
return false
|
|
103
|
+
#endif
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
#import "StallionEventHandler.h"
|
|
9
9
|
#import "StallionStateManager.h"
|
|
10
|
+
#import "StallionVersion.h"
|
|
10
11
|
|
|
11
12
|
#import <React/RCTBridge.h>
|
|
12
13
|
#import <React/RCTEventDispatcher.h>
|
|
@@ -15,7 +16,7 @@
|
|
|
15
16
|
static NSString *const STALLION_NATIVE_EVENT_NAME = @"STALLION_NATIVE_EVENT";
|
|
16
17
|
static NSString *const EVENTS_KEY = @"stored_events";
|
|
17
18
|
static NSInteger const MAX_BATCH_COUNT_SIZE = 9;
|
|
18
|
-
static NSInteger const MAX_EVENT_STORAGE_LIMIT =
|
|
19
|
+
static NSInteger const MAX_EVENT_STORAGE_LIMIT = 60;
|
|
19
20
|
|
|
20
21
|
@implementation StallionEventHandler
|
|
21
22
|
|
|
@@ -45,6 +46,7 @@ static NSInteger const MAX_EVENT_STORAGE_LIMIT = 20;
|
|
|
45
46
|
mutablePayload[@"platform"] = @"ios";
|
|
46
47
|
mutablePayload[@"appVersion"] = appVersion;
|
|
47
48
|
mutablePayload[@"uid"] = uid;
|
|
49
|
+
mutablePayload[@"sdkVersion"] = STALLION_SDK_VERSION;
|
|
48
50
|
// Store event locally
|
|
49
51
|
[self storeEventLocally:uniqueId eventPayload:mutablePayload];
|
|
50
52
|
}
|