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
@@ -1,3 +1,5 @@
1
+ import groovy.json.JsonSlurper
2
+
1
3
  buildscript {
2
4
  repositories {
3
5
  google()
@@ -30,6 +32,22 @@ def getExtOrIntegerDefault(name) {
30
32
  return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["Stallion_" + name]).toInteger()
31
33
  }
32
34
 
35
+ def getSdkVersionFromPackageJson() {
36
+ try {
37
+ def pkgFile = new File(projectDir.parentFile, "package.json")
38
+ if (!pkgFile.exists()) {
39
+ logger.lifecycle("Stallion: package.json not found at ${pkgFile.absolutePath}, defaulting to 0.0.0")
40
+ return "0.0.0"
41
+ }
42
+ def json = new JsonSlurper().parseText(pkgFile.text)
43
+ def version = (json?.version ?: "0.0.0").toString()
44
+ logger.lifecycle("Stallion: Resolved SDK version ${version} from ${pkgFile.absolutePath}")
45
+ return version
46
+ } catch (Throwable ignored) {
47
+ return "0.0.0"
48
+ }
49
+ }
50
+
33
51
  def stallionEnabled = true
34
52
 
35
53
  android {
@@ -48,6 +66,7 @@ android {
48
66
  minSdkVersion getExtOrIntegerDefault("minSdkVersion")
49
67
  targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
50
68
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
69
+ buildConfigField "String", "STALLION_SDK_VERSION", "\"${getSdkVersionFromPackageJson()}\""
51
70
  }
52
71
  buildTypes {
53
72
  release {
@@ -64,6 +83,12 @@ android {
64
83
  targetCompatibility JavaVersion.VERSION_1_8
65
84
  }
66
85
 
86
+ externalNativeBuild {
87
+ cmake {
88
+ path file("src/main/cpp/CMakeLists.txt")
89
+ }
90
+ }
91
+
67
92
  }
68
93
 
69
94
  repositories {
@@ -0,0 +1,19 @@
1
+ cmake_minimum_required(VERSION 3.10.2)
2
+
3
+ project(stallion_crash)
4
+
5
+ add_library(
6
+ stallion-crash
7
+ SHARED
8
+ stallion_signal_handler.cpp
9
+ )
10
+
11
+ find_library(
12
+ log-lib
13
+ log
14
+ )
15
+
16
+ target_link_libraries(
17
+ stallion-crash
18
+ ${log-lib}
19
+ )
@@ -0,0 +1,62 @@
1
+ #include <jni.h>
2
+ #include <signal.h>
3
+ #include <unistd.h>
4
+ #include <fcntl.h>
5
+ #include <string.h>
6
+ #include <stdio.h>
7
+
8
+ static char g_marker_path[512];
9
+ static char g_mount_marker_path[512];
10
+
11
+ // Async-signal-safe function to check if mount marker exists
12
+ static int is_mounted() {
13
+ int fd = open(g_mount_marker_path, O_RDONLY);
14
+ if (fd >= 0) {
15
+ close(fd);
16
+ return 1; // Mounted
17
+ }
18
+ return 0; // Not mounted
19
+ }
20
+
21
+ // Async-signal-safe JSON writing (minimal JSON for crash marker)
22
+ static void write_crash_marker_json(int signal, int mounted) {
23
+ int fd = open(g_marker_path, O_CREAT | O_WRONLY | O_TRUNC, 0600);
24
+ if (fd >= 0) {
25
+ // Write JSON: {"signal":X,"isAutoRollback":true/false,"crashLog":"signal=X\n"}
26
+ // isAutoRollback = !mounted (auto rollback if not mounted)
27
+ int autoRollback = !mounted;
28
+ char json[512];
29
+ int len = snprintf(json, sizeof(json),
30
+ "{\"signal\":%d,\"isAutoRollback\":%s,\"crashLog\":\"signal=%d\\n\"}",
31
+ signal, autoRollback ? "true" : "false", signal);
32
+ if (len > 0 && len < (int)sizeof(json)) {
33
+ (void)write(fd, json, len);
34
+ }
35
+ close(fd);
36
+ }
37
+ }
38
+
39
+ static void stallion_signal_handler(int sig) {
40
+ // Read mount state at crash time (async-signal-safe)
41
+ int mounted = is_mounted();
42
+ // Write JSON marker with crash info and autoRollback flag
43
+ write_crash_marker_json(sig, mounted);
44
+ signal(sig, SIG_DFL);
45
+ raise(sig);
46
+ }
47
+
48
+ extern "C"
49
+ JNIEXPORT void JNICALL
50
+ Java_com_stallion_utils_StallionExceptionHandler_initNativeSignalHandler(
51
+ JNIEnv* env, jclass, jstring filesDir) {
52
+ const char* path = env->GetStringUTFChars(filesDir, nullptr);
53
+ snprintf(g_marker_path, sizeof(g_marker_path), "%s/%s", path, "stallion_crash.marker");
54
+ snprintf(g_mount_marker_path, sizeof(g_mount_marker_path), "%s/%s", path, "stallion_mount.marker");
55
+ env->ReleaseStringUTFChars(filesDir, path);
56
+
57
+ signal(SIGABRT, stallion_signal_handler);
58
+ signal(SIGSEGV, stallion_signal_handler);
59
+ signal(SIGILL, stallion_signal_handler);
60
+ signal(SIGBUS, stallion_signal_handler);
61
+ signal(SIGFPE, stallion_signal_handler);
62
+ }
@@ -29,8 +29,6 @@ public class Stallion {
29
29
 
30
30
  validateAppVersion(stateManager.getStallionConfig().getAppVersion());
31
31
 
32
- StallionEventManager.init(stateManager);
33
-
34
32
  String baseFolderPath = stateManager.getStallionConfig().getFilesDirectory();
35
33
  StallionMeta stallionMeta = stateManager.stallionMeta;
36
34
  StallionMetaConstants.SwitchState switchState = stallionMeta.getSwitchState();
@@ -10,6 +10,7 @@ import com.facebook.react.bridge.ReactMethod;
10
10
  import com.facebook.react.bridge.ReadableMap;
11
11
  import com.facebook.react.module.annotations.ReactModule;
12
12
  import com.facebook.react.modules.core.DeviceEventManagerModule;
13
+ import com.stallion.events.StallionEventConstants;
13
14
  import com.stallion.events.StallionEventManager;
14
15
  import com.stallion.networkmanager.StallionStageManager;
15
16
  import com.stallion.networkmanager.StallionSyncHandler;
@@ -21,6 +22,7 @@ import com.stallion.utils.ProcessPhoenix;
21
22
 
22
23
  import org.json.JSONArray;
23
24
  import org.json.JSONException;
25
+ import org.json.JSONObject;
24
26
 
25
27
  import java.util.ArrayList;
26
28
  import java.util.List;
@@ -34,7 +36,6 @@ public class StallionModule extends ReactContextBaseJavaModule implements Lifecy
34
36
  super(reactContext);
35
37
  StallionStateManager.init(reactContext);
36
38
  this.stallionStateManager = StallionStateManager.getInstance();
37
- StallionEventManager.init(this.stallionStateManager);
38
39
  reactContext.addLifecycleEventListener(this);
39
40
  }
40
41
 
@@ -60,12 +61,23 @@ public class StallionModule extends ReactContextBaseJavaModule implements Lifecy
60
61
 
61
62
  @ReactMethod
62
63
  public void onLaunch(String launchData) {
64
+ // try {
65
+ // JSONObject launchDataJson = new JSONObject(launchData);
66
+ // } catch (Exception e) {
67
+ // e.printStackTrace();
68
+ // }
63
69
  stallionStateManager.setIsMounted(true);
64
70
  DeviceEventManagerModule.RCTDeviceEventEmitter eventEmitter = getReactApplicationContext().getJSModule(
65
71
  DeviceEventManagerModule.RCTDeviceEventEmitter.class
66
72
  );
67
73
  StallionEventManager.getInstance().setEmitter(eventEmitter);
68
74
  checkPendingDownloads();
75
+ String currentReleaseHash = stallionStateManager.stallionMeta.getHashAtCurrentProdSlot();
76
+ if(!currentReleaseHash.isEmpty() && stallionStateManager.stallionMeta.getSuccessfulLaunchCount(currentReleaseHash) == 0) {
77
+ emitInstallEvent(currentReleaseHash);
78
+ }
79
+ stallionStateManager.stallionMeta.markSuccessfulLaunch(currentReleaseHash);
80
+ stallionStateManager.syncStallionMeta();
69
81
  }
70
82
 
71
83
  private void checkPendingDownloads() {
@@ -167,4 +179,28 @@ public class StallionModule extends ReactContextBaseJavaModule implements Lifecy
167
179
  public void restart() {
168
180
  ProcessPhoenix.triggerRebirth(getReactApplicationContext());
169
181
  }
182
+
183
+ @ReactMethod
184
+ public void addListener(String eventName) {
185
+ // No-op: required for RN event emitter contract
186
+ }
187
+
188
+ @ReactMethod
189
+ public void removeListeners(double count) {
190
+ // No-op: required for RN event emitter contract
191
+ }
192
+
193
+ private void emitInstallEvent(String releaseHash) {
194
+ try {
195
+ JSONObject eventPayload = new JSONObject();
196
+ eventPayload.put("releaseHash", releaseHash);
197
+
198
+ StallionEventManager.getInstance().sendEventWithoutCaching(
199
+ StallionEventConstants.NativeProdEventTypes.INSTALLED_PROD.toString(),
200
+ eventPayload
201
+ );
202
+ } catch (Exception e) {
203
+ e.printStackTrace();
204
+ }
205
+ }
170
206
  }
@@ -3,6 +3,7 @@ package com.stallion.events;
3
3
  public class StallionEventConstants {
4
4
  public enum NativeProdEventTypes {
5
5
  DOWNLOAD_STARTED_PROD,
6
+ DOWNLOAD_PROGRESS_PROD,
6
7
  DOWNLOAD_RESUME_PROD,
7
8
  DOWNLOAD_ERROR_PROD,
8
9
  DOWNLOAD_COMPLETE_PROD,
@@ -1,6 +1,7 @@
1
1
  package com.stallion.events;
2
2
 
3
3
  import com.facebook.react.modules.core.DeviceEventManagerModule;
4
+ import com.stallion.BuildConfig;
4
5
  import com.stallion.storage.StallionConfig;
5
6
  import com.stallion.storage.StallionConfigConstants;
6
7
  import com.stallion.storage.StallionStateManager;
@@ -18,21 +19,20 @@ public class StallionEventManager {
18
19
  public static final String STALLION_NATIVE_EVENT_NAME = "STALLION_NATIVE_EVENT";
19
20
  private static final String EVENTS_KEY = "stored_events";
20
21
  private static final int MAX_BATCH_COUNT_SIZE = 9;
21
- private static final int MAX_EVENT_STORAGE_LIMIT = 20;
22
+ private static final int MAX_EVENT_STORAGE_LIMIT = 60;
22
23
 
23
24
  private static StallionEventManager instance;
24
- private final StallionStateManager stallionStateManager;
25
25
  private final AtomicReference<DeviceEventManagerModule.RCTDeviceEventEmitter> eventEmitterRef = new AtomicReference<>();
26
26
 
27
27
  // Private constructor for Singleton
28
- private StallionEventManager(StallionStateManager stateManager) {
29
- this.stallionStateManager = stateManager;
28
+ private StallionEventManager() {
29
+
30
30
  }
31
31
 
32
32
  // Singleton initialization method
33
- public static synchronized void init(StallionStateManager stateManager) {
33
+ public static synchronized void init() {
34
34
  if (instance == null) {
35
- instance = new StallionEventManager(stateManager);
35
+ instance = new StallionEventManager();
36
36
  }
37
37
  }
38
38
 
@@ -57,7 +57,8 @@ public class StallionEventManager {
57
57
  eventPayload.put("type", eventName);
58
58
  DeviceEventManagerModule.RCTDeviceEventEmitter eventEmitter = eventEmitterRef.get();
59
59
  // Emit the event to React Native
60
- if (eventEmitter != null && stallionStateManager.getIsMounted()) {
60
+ StallionStateManager stateManager = StallionStateManager.getInstance();
61
+ if (eventEmitter != null && stateManager.getIsMounted()) {
61
62
  eventEmitter.emit(STALLION_NATIVE_EVENT_NAME, eventPayload.toString());
62
63
  }
63
64
 
@@ -69,7 +70,8 @@ public class StallionEventManager {
69
70
  // Method to send an event
70
71
  public void sendEvent(String eventName, JSONObject eventPayload) {
71
72
  try {
72
- StallionConfig stallionConfig = this.stallionStateManager.getStallionConfig();
73
+ StallionStateManager stateManager = StallionStateManager.getInstance();
74
+ StallionConfig stallionConfig = stateManager.getStallionConfig();
73
75
  // Generate a unique ID for the event
74
76
  String uniqueId = UUID.randomUUID().toString();
75
77
 
@@ -77,10 +79,11 @@ public class StallionEventManager {
77
79
 
78
80
  DeviceEventManagerModule.RCTDeviceEventEmitter eventEmitter = eventEmitterRef.get();
79
81
  // Emit the event to React Native
80
- if (eventEmitter != null && stallionStateManager.getIsMounted()) {
82
+ if (eventEmitter != null && stateManager.getIsMounted()) {
81
83
  eventEmitter.emit(STALLION_NATIVE_EVENT_NAME, eventPayload.toString());
82
84
  }
83
85
 
86
+
84
87
  // change type for sending to server
85
88
  eventPayload.remove("type");
86
89
  eventPayload.put("eventType", eventName);
@@ -92,6 +95,7 @@ public class StallionEventManager {
92
95
  eventPayload.put("platform", StallionConfigConstants.PLATFORM);
93
96
  eventPayload.put("appVersion", stallionConfig.getAppVersion());
94
97
  eventPayload.put("uid", stallionConfig.getUid());
98
+ eventPayload.put("sdkVersion", BuildConfig.STALLION_SDK_VERSION);
95
99
 
96
100
  // Store the event locally
97
101
  storeEventLocally(uniqueId, eventPayload);
@@ -104,7 +108,8 @@ public class StallionEventManager {
104
108
  // Store the event locally in SharedPreferences
105
109
  private void storeEventLocally(String uniqueId, JSONObject eventPayload) {
106
110
  try {
107
- String eventsString = stallionStateManager.getString(EVENTS_KEY, "{}");
111
+ StallionStateManager stateManager = StallionStateManager.getInstance();
112
+ String eventsString = stateManager.getString(EVENTS_KEY, "{}");
108
113
  JSONObject eventsObject = new JSONObject(eventsString);
109
114
 
110
115
  // Flush all if limit reached
@@ -113,7 +118,7 @@ public class StallionEventManager {
113
118
  }
114
119
 
115
120
  eventsObject.put(uniqueId, eventPayload.toString());
116
- stallionStateManager.setString(EVENTS_KEY, eventsObject.toString());
121
+ stateManager.setString(EVENTS_KEY, eventsObject.toString());
117
122
  } catch (JSONException e) {
118
123
  cleanupEventStorage();
119
124
  e.printStackTrace();
@@ -121,13 +126,15 @@ public class StallionEventManager {
121
126
  }
122
127
 
123
128
  private void cleanupEventStorage() {
124
- stallionStateManager.setString(EVENTS_KEY, "{}");
129
+ StallionStateManager stateManager = StallionStateManager.getInstance();
130
+ stateManager.setString(EVENTS_KEY, "{}");
125
131
  }
126
132
 
127
133
  // Method to pop events as a batch
128
134
  public String popEvents() {
129
135
  try {
130
- String eventsString = stallionStateManager.getString(EVENTS_KEY, "{}");
136
+ StallionStateManager stateManager = StallionStateManager.getInstance();
137
+ String eventsString = stateManager.getString(EVENTS_KEY, "{}");
131
138
  JSONObject eventsObject = new JSONObject(eventsString);
132
139
 
133
140
  JSONArray batch = new JSONArray();
@@ -153,7 +160,8 @@ public class StallionEventManager {
153
160
  // Acknowledge events by deleting them from local storage
154
161
  public void acknowledgeEvents(List<String> eventIds) {
155
162
  try {
156
- String eventsString = stallionStateManager.getString(EVENTS_KEY, "{}");
163
+ StallionStateManager stateManager = StallionStateManager.getInstance();
164
+ String eventsString = stateManager.getString(EVENTS_KEY, "{}");
157
165
  JSONObject eventsObject = new JSONObject(eventsString);
158
166
 
159
167
  // Remove each event by its unique ID
@@ -164,7 +172,7 @@ public class StallionEventManager {
164
172
  }
165
173
 
166
174
  // Update the SharedPreferences with the modified events
167
- stallionStateManager.setString(EVENTS_KEY, eventsObject.toString());
175
+ stateManager.setString(EVENTS_KEY, eventsObject.toString());
168
176
 
169
177
  } catch (JSONException e) {
170
178
  e.printStackTrace();
@@ -3,10 +3,8 @@ package com.stallion.networkmanager;
3
3
  import android.os.StatFs;
4
4
 
5
5
  import java.io.BufferedInputStream;
6
- import java.io.BufferedOutputStream;
7
6
  import java.io.File;
8
7
  import java.io.FileInputStream;
9
- import java.io.FileOutputStream;
10
8
  import java.io.IOException;
11
9
  import java.io.RandomAccessFile;
12
10
  import java.net.HttpURLConnection;
@@ -25,6 +23,7 @@ public class StallionFileDownloader {
25
23
 
26
24
  private static final String TAG = "StallionFileDownloader";
27
25
  private static final ExecutorService executor = Executors.newSingleThreadExecutor();
26
+ private static final long DEFAULT_PROGRESS_THROTTLE_INTERVAL_MS = 300L;
28
27
 
29
28
  public static void downloadBundle(
30
29
  String downloadUrl,
@@ -125,7 +124,7 @@ public class StallionFileDownloader {
125
124
  long totalBytes = connection.getContentLength() + alreadyDownloaded;
126
125
  long receivedBytes = alreadyDownloaded;
127
126
  int bytesRead;
128
- double lastProgress = (double) receivedBytes / totalBytes;
127
+ long lastProgressEmitTimeMs = 0L;
129
128
 
130
129
  // Ensure totalBytes is valid
131
130
  if (totalBytes <= 0) {
@@ -145,8 +144,8 @@ public class StallionFileDownloader {
145
144
  return;
146
145
  }
147
146
 
148
- if (progress - lastProgress >= 0.1) {
149
- lastProgress = progress;
147
+ if (shouldEmitProgress(lastProgressEmitTimeMs, DEFAULT_PROGRESS_THROTTLE_INTERVAL_MS)) {
148
+ lastProgressEmitTimeMs = System.currentTimeMillis();
150
149
  callback.onProgress(progress);
151
150
  }
152
151
  }
@@ -194,6 +193,10 @@ public class StallionFileDownloader {
194
193
  return connection;
195
194
  }
196
195
 
196
+ private static boolean shouldEmitProgress(long lastEmitTimeMs, long intervalMs) {
197
+ return System.currentTimeMillis() - lastEmitTimeMs >= intervalMs;
198
+ }
199
+
197
200
  private static void validateAndUnzip(
198
201
  File downloadedZip,
199
202
  String destDirectory,
@@ -1,10 +1,12 @@
1
1
  package com.stallion.networkmanager;
2
2
 
3
+ import com.stallion.events.StallionEventConstants;
3
4
  import com.stallion.events.StallionEventManager;
4
5
  import com.stallion.storage.StallionConfigConstants;
5
6
  import com.stallion.storage.StallionMetaConstants;
6
7
  import com.stallion.storage.StallionStateManager;
7
8
  import com.stallion.storage.StallionConfig;
9
+ import com.stallion.utils.StallionDeviceInfo;
8
10
  import com.stallion.utils.StallionFileManager;
9
11
  import com.stallion.utils.StallionSignatureVerification;
10
12
  import com.stallion.utils.StallionSlotManager;
@@ -43,6 +45,8 @@ public class StallionSyncHandler {
43
45
  requestPayload.put("platform", "android");
44
46
  requestPayload.put("projectId", projectId);
45
47
  requestPayload.put("appliedBundleHash", appliedBundleHash);
48
+ // Attach device metadata for analytics
49
+ requestPayload.put("deviceMeta", StallionDeviceInfo.getDeviceMetaJson(config));
46
50
 
47
51
  // Make API call using StallionApiManager
48
52
  JSONObject releaseMeta = StallionApiManager.post(
@@ -52,7 +56,7 @@ public class StallionSyncHandler {
52
56
 
53
57
  // Process API response
54
58
  processReleaseMeta(releaseMeta, appVersion);
55
-
59
+ stateManager.setIsSyncSuccessful(true);
56
60
  } catch (Exception e) {
57
61
  emitSyncError(e);
58
62
  } finally {
@@ -167,6 +171,7 @@ public class StallionSyncHandler {
167
171
  @Override
168
172
  public void onProgress(double downloadFraction) {
169
173
  // Optional: Handle progress updates
174
+ emitDownloadProgressProd(newReleaseHash, downloadFraction);
170
175
  }
171
176
  }
172
177
  );
@@ -175,6 +180,18 @@ public class StallionSyncHandler {
175
180
  }
176
181
  }
177
182
 
183
+ private static void emitDownloadProgressProd(String releaseHash, double newProgress) {
184
+ JSONObject successPayload = new JSONObject();
185
+ try {
186
+ successPayload.put("releaseHash", releaseHash);
187
+ successPayload.put("progress", String.valueOf(newProgress));
188
+ } catch (Exception ignored) { }
189
+ StallionEventManager.getInstance().sendEventWithoutCaching(
190
+ StallionEventConstants.NativeProdEventTypes.DOWNLOAD_PROGRESS_PROD.toString(),
191
+ successPayload
192
+ );
193
+ }
194
+
178
195
  private static void emitSyncError(Exception e) {
179
196
  JSONObject syncErrorPayload = new JSONObject();
180
197
  try {
@@ -14,6 +14,12 @@ public class StallionMeta {
14
14
  private String prodNewHash;
15
15
  private String prodStableHash;
16
16
  private String lastRolledBackHash;
17
+ private long lastRolledBackAt;
18
+ private int successfulLaunchCount;
19
+ private String lastSuccessfulLaunchHash;
20
+
21
+ public static final int MAX_SUCCESS_LAUNCH_THRESHOLD = 3;
22
+ public static final long LAST_ROLLED_BACK_TTL_MS = 6L * 60L * 60L * 1000L; // 6 hours
17
23
 
18
24
  public StallionMeta() {
19
25
  this.reset();
@@ -29,6 +35,9 @@ public class StallionMeta {
29
35
  this.prodNewHash = "";
30
36
  this.prodStableHash = "";
31
37
  this.lastRolledBackHash = "";
38
+ this.lastRolledBackAt = 0L;
39
+ this.successfulLaunchCount = 0;
40
+ this.lastSuccessfulLaunchHash = "";
32
41
  }
33
42
 
34
43
  // Getters and Setters
@@ -121,11 +130,13 @@ public class StallionMeta {
121
130
  }
122
131
 
123
132
  public String getLastRolledBackHash() {
133
+ enforceLastRolledBackExpiry();
124
134
  return lastRolledBackHash;
125
135
  }
126
136
 
127
137
  public void setLastRolledBackHash(String lastRolledBackHash) {
128
- this.lastRolledBackHash = lastRolledBackHash;
138
+ this.lastRolledBackHash = lastRolledBackHash == null ? "" : lastRolledBackHash;
139
+ this.lastRolledBackAt = this.lastRolledBackHash.isEmpty() ? 0L : System.currentTimeMillis();
129
140
  }
130
141
 
131
142
  // Convert to JSON
@@ -148,6 +159,9 @@ public class StallionMeta {
148
159
  metaJson.put("prodSlot", prodJson);
149
160
 
150
161
  metaJson.put("lastRolledBackHash", lastRolledBackHash);
162
+ metaJson.put("lastRolledBackAt", lastRolledBackAt);
163
+ metaJson.put("successfulLaunchCount", successfulLaunchCount);
164
+ metaJson.put("lastSuccessfulLaunchHash", lastSuccessfulLaunchHash);
151
165
 
152
166
  } catch (JSONException e) {
153
167
  return new JSONObject();
@@ -169,6 +183,9 @@ public class StallionMeta {
169
183
  );
170
184
 
171
185
  stallionMeta.setLastRolledBackHash(jsonObject.optString("lastRolledBackHash", ""));
186
+ stallionMeta.lastRolledBackAt = jsonObject.optLong("lastRolledBackAt", 0L);
187
+ stallionMeta.successfulLaunchCount = jsonObject.optInt("successfulLaunchCount", 0);
188
+ stallionMeta.lastSuccessfulLaunchHash = jsonObject.optString("lastSuccessfulLaunchHash", "");
172
189
 
173
190
  JSONObject stageJson = jsonObject.optJSONObject("stageSlot");
174
191
  if(stageJson != null) {
@@ -203,4 +220,38 @@ public class StallionMeta {
203
220
  return stallionMeta;
204
221
  }
205
222
  }
223
+
224
+ public void markSuccessfulLaunch(String releaseHash) {
225
+ if(releaseHash == null || releaseHash.isEmpty()) {
226
+ return;
227
+ }
228
+ if (!releaseHash.equals(this.lastSuccessfulLaunchHash)) {
229
+ this.successfulLaunchCount = 0;
230
+ this.lastSuccessfulLaunchHash = releaseHash;
231
+ }
232
+ if (this.successfulLaunchCount < MAX_SUCCESS_LAUNCH_THRESHOLD) {
233
+ this.successfulLaunchCount += 1;
234
+ }
235
+ }
236
+
237
+ public int getSuccessfulLaunchCount(String releaseHash) {
238
+ String currentHash = releaseHash == null ? "" : releaseHash;
239
+ if (!currentHash.equals(this.lastSuccessfulLaunchHash)) {
240
+ return 0;
241
+ } else return this.successfulLaunchCount;
242
+ }
243
+
244
+ private void enforceLastRolledBackExpiry() {
245
+ if (this.lastRolledBackHash == null || this.lastRolledBackHash.isEmpty()) {
246
+ return;
247
+ }
248
+ if (this.lastRolledBackAt <= 0L) {
249
+ return;
250
+ }
251
+ long now = System.currentTimeMillis();
252
+ if (now - this.lastRolledBackAt >= LAST_ROLLED_BACK_TTL_MS) {
253
+ this.lastRolledBackHash = "";
254
+ this.lastRolledBackAt = 0L;
255
+ }
256
+ }
206
257
  }
@@ -3,6 +3,7 @@ package com.stallion.storage;
3
3
  import android.content.Context;
4
4
  import android.content.SharedPreferences;
5
5
 
6
+ import com.stallion.events.StallionEventManager;
6
7
  import com.stallion.utils.StallionExceptionHandler;
7
8
 
8
9
  import org.json.JSONException;
@@ -20,7 +21,7 @@ public class StallionStateManager {
20
21
  private boolean isMounted;
21
22
  private String pendingReleaseUrl;
22
23
  private String pendingReleaseHash;
23
-
24
+ private boolean isSyncSuccessful;
24
25
 
25
26
  private StallionStateManager(Context context) {
26
27
  this.sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
@@ -29,11 +30,15 @@ public class StallionStateManager {
29
30
  this.isMounted = false;
30
31
  this.pendingReleaseUrl = "";
31
32
  this.pendingReleaseHash = "";
33
+
34
+ // Reset mount state on initialization (ensures mount marker file is deleted for new session)
35
+ setIsMounted(false);
32
36
  }
33
37
 
34
38
  public static synchronized void init(Context context) {
35
39
  if (instance == null) {
36
40
  instance = new StallionStateManager(context);
41
+ StallionEventManager.init();
37
42
  StallionExceptionHandler.initErrorBoundary();
38
43
  }
39
44
  }
@@ -78,6 +83,18 @@ public class StallionStateManager {
78
83
 
79
84
  public void setIsMounted(Boolean isMounted) {
80
85
  this.isMounted = isMounted;
86
+ // Write mount state to a simple file that C++ can read
87
+ String filesDir = getStallionConfig().getFilesDirectory();
88
+ java.io.File mountMarker = new java.io.File(filesDir + "/stallion_mount.marker");
89
+ if (isMounted) {
90
+ try {
91
+ // Create file to indicate mounted (file existence = mounted)
92
+ mountMarker.createNewFile();
93
+ } catch (Exception ignored) {}
94
+ } else {
95
+ // Delete file to indicate not mounted (no file = not mounted)
96
+ mountMarker.delete();
97
+ }
81
98
  }
82
99
 
83
100
  public boolean getIsMounted() {
@@ -110,4 +127,8 @@ public class StallionStateManager {
110
127
  public String getPendingReleaseHash() {
111
128
  return this.pendingReleaseHash;
112
129
  }
130
+
131
+ public boolean getIsSyncSuccessful() { return this.isSyncSuccessful; }
132
+
133
+ public void setIsSyncSuccessful(boolean isSyncSuccessful) { this.isSyncSuccessful = isSyncSuccessful; }
113
134
  }