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.
Files changed (98) 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 +91 -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 +162 -29
  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 +379 -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 +2 -2
  25. package/ios/main/StallionStateManager.m +29 -0
  26. package/ios/main/StallionSyncHandler.swift +14 -2
  27. package/package.json +1 -1
  28. package/react-native-stallion.podspec +22 -0
  29. package/src/main/components/modules/listing/components/BundleCardInfoSection.js +1 -1
  30. package/src/main/components/modules/listing/components/styles/index.js +18 -13
  31. package/src/main/components/modules/listing/components/styles/index.js.map +1 -1
  32. package/src/main/components/modules/listing/index.js +15 -15
  33. package/src/main/components/modules/listing/index.js.map +1 -1
  34. package/src/main/components/modules/listing/styles.js +2 -1
  35. package/src/main/components/modules/listing/styles.js.map +1 -1
  36. package/src/main/components/modules/login/index.js +8 -5
  37. package/src/main/components/modules/login/index.js.map +1 -1
  38. package/src/main/components/modules/login/styles/index.js +10 -4
  39. package/src/main/components/modules/login/styles/index.js.map +1 -1
  40. package/src/main/components/modules/modal/StallionModal.js +2 -2
  41. package/src/main/components/modules/modal/StallionModal.js.map +1 -1
  42. package/src/main/components/modules/prod/prod.js +5 -4
  43. package/src/main/components/modules/prod/prod.js.map +1 -1
  44. package/src/main/components/modules/prod/styles/index.js +7 -4
  45. package/src/main/components/modules/prod/styles/index.js.map +1 -1
  46. package/src/main/constants/appConstants.js +1 -0
  47. package/src/main/constants/appConstants.js.map +1 -1
  48. package/src/main/state/actionCreators/useUpdateMetaActions.js +2 -2
  49. package/src/main/state/actionCreators/useUpdateMetaActions.js.map +1 -1
  50. package/src/main/state/index.js +5 -3
  51. package/src/main/state/index.js.map +1 -1
  52. package/src/main/state/reducers/updateMetaReducer.js +8 -0
  53. package/src/main/state/reducers/updateMetaReducer.js.map +1 -1
  54. package/src/main/state/useStallionEvents.js +27 -4
  55. package/src/main/state/useStallionEvents.js.map +1 -1
  56. package/src/main/utils/ErrorBoundary.js +45 -11
  57. package/src/main/utils/ErrorBoundary.js.map +1 -1
  58. package/src/main/utils/crashState.js +16 -0
  59. package/src/main/utils/crashState.js.map +1 -0
  60. package/src/main/utils/useStallionUpdate.js +2 -2
  61. package/src/main/utils/useStallionUpdate.js.map +1 -1
  62. package/src/main/utils/withStallion.js +4 -2
  63. package/src/main/utils/withStallion.js.map +1 -1
  64. package/src/types/updateMeta.types.js +1 -0
  65. package/src/types/updateMeta.types.js.map +1 -1
  66. package/types/main/components/modules/listing/components/styles/index.d.ts +9 -4
  67. package/types/main/components/modules/listing/components/styles/index.d.ts.map +1 -1
  68. package/types/main/components/modules/listing/index.d.ts.map +1 -1
  69. package/types/main/components/modules/listing/styles.d.ts +1 -0
  70. package/types/main/components/modules/listing/styles.d.ts.map +1 -1
  71. package/types/main/components/modules/login/index.d.ts.map +1 -1
  72. package/types/main/components/modules/login/styles/index.d.ts +6 -0
  73. package/types/main/components/modules/login/styles/index.d.ts.map +1 -1
  74. package/types/main/components/modules/prod/prod.d.ts.map +1 -1
  75. package/types/main/components/modules/prod/styles/index.d.ts +5 -2
  76. package/types/main/components/modules/prod/styles/index.d.ts.map +1 -1
  77. package/types/main/constants/appConstants.d.ts +2 -0
  78. package/types/main/constants/appConstants.d.ts.map +1 -1
  79. package/types/main/index.d.ts +1 -1
  80. package/types/main/state/actionCreators/useUpdateMetaActions.d.ts.map +1 -1
  81. package/types/main/state/index.d.ts +4 -1
  82. package/types/main/state/index.d.ts.map +1 -1
  83. package/types/main/state/reducers/updateMetaReducer.d.ts +1 -0
  84. package/types/main/state/reducers/updateMetaReducer.d.ts.map +1 -1
  85. package/types/main/state/useStallionEvents.d.ts +4 -2
  86. package/types/main/state/useStallionEvents.d.ts.map +1 -1
  87. package/types/main/utils/ErrorBoundary.d.ts +2 -1
  88. package/types/main/utils/ErrorBoundary.d.ts.map +1 -1
  89. package/types/main/utils/crashState.d.ts +4 -0
  90. package/types/main/utils/crashState.d.ts.map +1 -0
  91. package/types/main/utils/useStallionUpdate.d.ts.map +1 -1
  92. package/types/main/utils/withStallion.d.ts +2 -1
  93. package/types/main/utils/withStallion.d.ts.map +1 -1
  94. package/types/types/updateMeta.types.d.ts +7 -2
  95. package/types/types/updateMeta.types.d.ts.map +1 -1
  96. package/types/types/utils.types.d.ts +1 -1
  97. package/types/types/utils.types.d.ts.map +1 -1
  98. 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 boolean isErrorBoundaryInitialized = false;
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
- if (isErrorBoundaryInitialized) {
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
- isErrorBoundaryInitialized = true;
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
- // Safely trim the stack trace string
31
- String stackTraceString = Log.getStackTraceString(throwable);
32
- stackTraceString = stackTraceString.length() > 900
33
- ? stackTraceString.substring(0, 900) + "..."
34
- : stackTraceString;
35
-
36
- StallionStateManager stateManager = StallionStateManager.getInstance();
37
- StallionMetaConstants.SwitchState switchState = stateManager.stallionMeta.getSwitchState();
38
-
39
- if (switchState == StallionMetaConstants.SwitchState.PROD) {
40
- handleProdState(stackTraceString, stateManager);
41
- } else if (switchState == StallionMetaConstants.SwitchState.STAGE) {
42
- handleStageState(stackTraceString, stateManager);
43
- } else {
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
- // Emit exception event
69
- emitException(stackTraceString, currentHash, isAutoRollback, true);
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
- StallionSlotManager.rollbackProd(true, stackTraceString);
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
- // Emit exception event
84
- emitException(stackTraceString, currentStageHash, isAutoRollback, false);
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
- StallionSlotManager.rollbackStage();
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;
@@ -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 = 20;
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
  }
@@ -12,6 +12,7 @@ NS_ASSUME_NONNULL_BEGIN
12
12
  @interface StallionExceptionHandler : NSObject
13
13
 
14
14
  + (void)initExceptionHandler;
15
+ + (void)initJavaScriptExceptionHandler;
15
16
 
16
17
  @end
17
18