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
package/android/build.gradle
CHANGED
|
@@ -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
|
}
|
|
@@ -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 =
|
|
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(
|
|
29
|
-
|
|
28
|
+
private StallionEventManager() {
|
|
29
|
+
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
// Singleton initialization method
|
|
33
|
-
public static synchronized void init(
|
|
33
|
+
public static synchronized void init() {
|
|
34
34
|
if (instance == null) {
|
|
35
|
-
instance = new StallionEventManager(
|
|
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
|
-
|
|
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
|
-
|
|
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 &&
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
149
|
-
|
|
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 {
|
|
@@ -119,12 +119,10 @@ public class StallionConfig {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
public void updateSdkToken(String newApiKey) {
|
|
122
|
-
if(!newApiKey.isEmpty()) {
|
|
123
122
|
this.sdkToken = newApiKey;
|
|
124
123
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
|
125
124
|
editor.putString(StallionConfigConstants.API_KEY_IDENTIFIER, this.sdkToken);
|
|
126
125
|
editor.apply();
|
|
127
|
-
}
|
|
128
126
|
}
|
|
129
127
|
|
|
130
128
|
public String getAppToken() {
|
|
@@ -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
|
}
|