react-native-stallion 2.0.0-alpha.3 → 2.0.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.
@@ -27,19 +27,27 @@ import java.util.List;
27
27
 
28
28
  @ReactModule(name = StallionConfigConstants.MODULE_NAME)
29
29
  public class StallionModule extends ReactContextBaseJavaModule implements LifecycleEventListener {
30
- private final ReactApplicationContext currentReactContext;
31
30
  private final StallionStateManager stallionStateManager;
32
31
 
33
32
  public StallionModule(ReactApplicationContext reactContext) {
34
33
  super(reactContext);
35
34
  StallionStateManager.init(reactContext);
36
35
  this.stallionStateManager = StallionStateManager.getInstance();
37
- this.currentReactContext = reactContext;
36
+ StallionEventManager.init(this.stallionStateManager);
38
37
  reactContext.addLifecycleEventListener(this);
39
38
  }
40
39
 
41
40
  @Override
42
41
  public void onHostResume() {
42
+ if (
43
+ StallionEventManager.getInstance().getEmitter() == null
44
+ && getReactApplicationContext().getCatalystInstance() != null
45
+ ) {
46
+ DeviceEventManagerModule.RCTDeviceEventEmitter eventEmitter = getReactApplicationContext().getJSModule(
47
+ DeviceEventManagerModule.RCTDeviceEventEmitter.class
48
+ );
49
+ StallionEventManager.getInstance().setEmitter(eventEmitter);
50
+ }
43
51
  StallionSyncHandler.sync();
44
52
  }
45
53
 
@@ -52,7 +60,8 @@ public class StallionModule extends ReactContextBaseJavaModule implements Lifecy
52
60
  @Override
53
61
  public void onCatalystInstanceDestroy() {
54
62
  super.onCatalystInstanceDestroy();
55
- this.currentReactContext.removeLifecycleEventListener(this);
63
+ stallionStateManager.setIsMounted(false);
64
+ getReactApplicationContext().removeLifecycleEventListener(this);
56
65
  }
57
66
 
58
67
  @Override
@@ -64,12 +73,6 @@ public class StallionModule extends ReactContextBaseJavaModule implements Lifecy
64
73
  @ReactMethod
65
74
  public void onLaunch(String launchData) {
66
75
  stallionStateManager.setIsMounted(true);
67
-
68
- DeviceEventManagerModule.RCTDeviceEventEmitter eventEmitter = this.currentReactContext.getJSModule(
69
- DeviceEventManagerModule.RCTDeviceEventEmitter.class
70
- );
71
- StallionEventManager.getInstance().setEmitter(eventEmitter);
72
-
73
76
  checkPendingDownloads();
74
77
  }
75
78
 
@@ -12,6 +12,7 @@ import org.json.JSONObject;
12
12
  import java.util.Iterator;
13
13
  import java.util.List;
14
14
  import java.util.UUID;
15
+ import java.util.concurrent.atomic.AtomicReference;
15
16
 
16
17
  public class StallionEventManager {
17
18
  public static final String STALLION_NATIVE_EVENT_NAME = "STALLION_NATIVE_EVENT";
@@ -20,7 +21,7 @@ public class StallionEventManager {
20
21
 
21
22
  private static StallionEventManager instance;
22
23
  private final StallionStateManager stallionStateManager;
23
- private DeviceEventManagerModule.RCTDeviceEventEmitter eventEmitter;
24
+ private final AtomicReference<DeviceEventManagerModule.RCTDeviceEventEmitter> eventEmitterRef = new AtomicReference<>();
24
25
 
25
26
  // Private constructor for Singleton
26
27
  private StallionEventManager(StallionStateManager stateManager) {
@@ -35,7 +36,11 @@ public class StallionEventManager {
35
36
  }
36
37
 
37
38
  public void setEmitter(DeviceEventManagerModule.RCTDeviceEventEmitter deviceEmitter) {
38
- eventEmitter = deviceEmitter;
39
+ eventEmitterRef.set(deviceEmitter);
40
+ }
41
+
42
+ public DeviceEventManagerModule.RCTDeviceEventEmitter getEmitter() {
43
+ return eventEmitterRef.get();
39
44
  }
40
45
 
41
46
  // Get instance method
@@ -49,9 +54,9 @@ public class StallionEventManager {
49
54
  public void sendEventWithoutCaching(String eventName, JSONObject eventPayload) {
50
55
  try {
51
56
  eventPayload.put("type", eventName);
52
-
57
+ DeviceEventManagerModule.RCTDeviceEventEmitter eventEmitter = eventEmitterRef.get();
53
58
  // Emit the event to React Native
54
- if (eventEmitter != null) {
59
+ if (eventEmitter != null && stallionStateManager.getIsMounted()) {
55
60
  eventEmitter.emit(STALLION_NATIVE_EVENT_NAME, eventPayload.toString());
56
61
  }
57
62
 
@@ -69,8 +74,9 @@ public class StallionEventManager {
69
74
 
70
75
  eventPayload.put("type", eventName);
71
76
 
77
+ DeviceEventManagerModule.RCTDeviceEventEmitter eventEmitter = eventEmitterRef.get();
72
78
  // Emit the event to React Native
73
- if (eventEmitter != null) {
79
+ if (eventEmitter != null && stallionStateManager.getIsMounted()) {
74
80
  eventEmitter.emit(STALLION_NATIVE_EVENT_NAME, eventPayload.toString());
75
81
  }
76
82
 
@@ -128,7 +128,7 @@ public class StallionFileDownloader {
128
128
 
129
129
  // Ensure totalBytes is valid
130
130
  if (totalBytes <= 0) {
131
- callback.onReject(StallionApiConstants.DOWNLOAD_ERROR_PREFIX, "Invalid content length: " + totalBytes);
131
+ callback.onReject(StallionApiConstants.DOWNLOAD_ERROR_PREFIX, "Invalid content length: ");
132
132
  return;
133
133
  }
134
134
 
@@ -158,7 +158,7 @@ public class StallionFileDownloader {
158
158
  return;
159
159
  }
160
160
  } catch (IOException e) {
161
- callback.onReject(StallionApiConstants.DOWNLOAD_ERROR_PREFIX, "IOException occurred: " + e.getMessage());
161
+ callback.onReject(StallionApiConstants.DOWNLOAD_ERROR_PREFIX, "IOException occurred: ");
162
162
  throw e;
163
163
  } finally {
164
164
  if (connection != null) {
@@ -205,8 +205,11 @@ public class StallionFileDownloader {
205
205
  callback.onReject(StallionApiConstants.DOWNLOAD_ERROR_PREFIX, StallionApiConstants.CORRUPTED_FILE_ERROR);
206
206
  }
207
207
  } catch (Exception e) {
208
- String filesystemError = e.getMessage();
209
- callback.onReject(StallionApiConstants.DOWNLOAD_ERROR_PREFIX, StallionApiConstants.DOWNLOAD_FILESYSTEM_ERROR_MESSAGE + filesystemError);
208
+ String filesystemError = e.getMessage() != null ? e.getMessage() : "Unknown filesystem error";
209
+ callback.onReject(
210
+ StallionApiConstants.DOWNLOAD_ERROR_PREFIX,
211
+ StallionApiConstants.DOWNLOAD_FILESYSTEM_ERROR_MESSAGE + filesystemError
212
+ );
210
213
  } finally {
211
214
  StallionFileManager.deleteFileOrFolderSilently(downloadedZip);
212
215
  }
@@ -14,6 +14,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
14
14
  public class StallionSyncHandler {
15
15
 
16
16
  private static final AtomicBoolean isSyncInProgress = new AtomicBoolean(false);
17
+ private static final AtomicBoolean isDownloadInProgress = new AtomicBoolean(false);
17
18
 
18
19
  public static void sync() {
19
20
  // Ensure only one sync job runs at a time
@@ -99,47 +100,58 @@ public class StallionSyncHandler {
99
100
  }
100
101
 
101
102
  public static void downloadNewRelease(String newReleaseHash, String newReleaseUrl) {
102
- StallionStateManager stateManager = StallionStateManager.getInstance();
103
- String downloadPath = stateManager.getStallionConfig().getFilesDirectory()
104
- + StallionConfigConstants.PROD_DIRECTORY
105
- + StallionConfigConstants.TEMP_FOLDER_SLOT;
106
- String projectId = stateManager.getStallionConfig().getProjectId();
107
-
108
- emitDownloadStarted(newReleaseHash);
109
-
110
- StallionFileDownloader.downloadBundle(
111
- newReleaseUrl + "?projectId=" + projectId,
112
- downloadPath,
113
- new StallionDownloadCallback() {
114
- @Override
115
- public void onReject(String prefix, String error) {
116
- emitDownloadError(newReleaseHash, prefix + error);
117
- }
103
+ // Ensure only one download job runs at a time
104
+ if (!isDownloadInProgress.compareAndSet(false, true)) {
105
+ return; // Exit if another job is already running
106
+ }
107
+ try {
108
+ StallionStateManager stateManager = StallionStateManager.getInstance();
109
+ String downloadPath = stateManager.getStallionConfig().getFilesDirectory()
110
+ + StallionConfigConstants.PROD_DIRECTORY
111
+ + StallionConfigConstants.TEMP_FOLDER_SLOT;
112
+ String projectId = stateManager.getStallionConfig().getProjectId();
113
+
114
+ emitDownloadStarted(newReleaseHash);
115
+
116
+ StallionFileDownloader.downloadBundle(
117
+ newReleaseUrl + "?projectId=" + projectId,
118
+ downloadPath,
119
+ new StallionDownloadCallback() {
120
+ @Override
121
+ public void onReject(String prefix, String error) {
122
+ isDownloadInProgress.set(false);
123
+ emitDownloadError(newReleaseHash, prefix + error);
124
+ }
118
125
 
119
- @Override
120
- public void onSuccess(String successPayload) {
121
- stateManager.stallionMeta.setCurrentProdSlot(StallionMetaConstants.SlotStates.NEW_SLOT);
122
- stateManager.stallionMeta.setProdTempHash(newReleaseHash);
123
- String currentProdNewHash = stateManager.stallionMeta.getProdNewHash();
124
- if(currentProdNewHash != null && !currentProdNewHash.isEmpty()) {
125
- StallionSlotManager.stabilizeProd();
126
+ @Override
127
+ public void onSuccess(String successPayload) {
128
+ isDownloadInProgress.set(false);
129
+ stateManager.stallionMeta.setCurrentProdSlot(StallionMetaConstants.SlotStates.NEW_SLOT);
130
+ stateManager.stallionMeta.setProdTempHash(newReleaseHash);
131
+ String currentProdNewHash = stateManager.stallionMeta.getProdNewHash();
132
+ if(currentProdNewHash != null && !currentProdNewHash.isEmpty()) {
133
+ StallionSlotManager.stabilizeProd();
134
+ }
135
+ stateManager.syncStallionMeta();
136
+ emitDownloadSuccess(newReleaseHash);
126
137
  }
127
- stateManager.syncStallionMeta();
128
- emitDownloadSuccess(newReleaseHash);
129
- }
130
138
 
131
- @Override
132
- public void onProgress(double downloadFraction) {
133
- // Optional: Handle progress updates
139
+ @Override
140
+ public void onProgress(double downloadFraction) {
141
+ // Optional: Handle progress updates
142
+ }
134
143
  }
135
- }
136
- );
144
+ );
145
+ } catch (Exception ignored) {
146
+ isDownloadInProgress.set(false);
147
+ }
137
148
  }
138
149
 
139
150
  private static void emitSyncError(Exception e) {
140
151
  JSONObject syncErrorPayload = new JSONObject();
141
152
  try {
142
- syncErrorPayload.put("meta", e.toString());
153
+ String syncErrorString = e.getMessage() != null ? e.getMessage() : "Unknown error";
154
+ syncErrorPayload.put("meta", syncErrorString);
143
155
  } catch (Exception ignored) { }
144
156
  StallionEventManager.getInstance().sendEvent(
145
157
  NativeProdEventTypes.SYNC_ERROR_PROD.toString(),
@@ -57,7 +57,7 @@ public class StallionMeta {
57
57
 
58
58
  public String getActiveReleaseHash() {
59
59
  if(!this.prodTempHash.isEmpty()) {
60
- return this.prodNewHash;
60
+ return this.prodTempHash;
61
61
  }
62
62
  switch (this.currentProdSlot) {
63
63
  case NEW_SLOT:
@@ -10,17 +10,16 @@ import Foundation
10
10
  class StallionSyncHandler {
11
11
 
12
12
  private static var isSyncInProgress = false
13
+ private static var isDownloadInProgress = false
13
14
  private static let syncQueue = DispatchQueue(label: "com.stallion.syncQueue")
14
15
 
15
- static func sync() {
16
+ static func sync() {
16
17
  syncQueue.async {
17
18
  guard !isSyncInProgress else { return }
18
19
  isSyncInProgress = true
19
20
  }
20
21
 
21
22
  DispatchQueue.global().async {
22
- defer { isSyncInProgress = false }
23
-
24
23
  do {
25
24
  // Fetch StallionStateManager and StallionConfig
26
25
  let stateManager = StallionStateManager.sharedInstance()
@@ -44,6 +43,7 @@ class StallionSyncHandler {
44
43
  makeApiCall(payload: requestPayload, appVersion: appVersion)
45
44
 
46
45
  } catch {
46
+ defer { isSyncInProgress = false }
47
47
  emitSyncError(error)
48
48
  }
49
49
  }
@@ -69,17 +69,20 @@ class StallionSyncHandler {
69
69
  let jsonData = try JSONSerialization.data(withJSONObject: payload, options: [])
70
70
  request.httpBody = jsonData
71
71
  } catch {
72
+ defer { isSyncInProgress = false }
72
73
  emitSyncError(error)
73
74
  return
74
75
  }
75
76
 
76
77
  let task = URLSession.shared.dataTask(with: request) { data, response, error in
77
78
  if let error = error {
79
+ defer { isSyncInProgress = false }
78
80
  emitSyncError(error)
79
81
  return
80
82
  }
81
83
 
82
84
  guard let data = data, let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
85
+ defer { isSyncInProgress = false }
83
86
  let responseError = NSError(domain: "Invalid response from server", code: -2)
84
87
  emitSyncError(responseError)
85
88
  return
@@ -88,12 +91,15 @@ class StallionSyncHandler {
88
91
  // Parse the JSON response
89
92
  do {
90
93
  if let releaseMeta = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
94
+ defer { isSyncInProgress = false }
91
95
  processReleaseMeta(releaseMeta, appVersion: appVersion)
92
96
  } else {
97
+ defer { isSyncInProgress = false }
93
98
  let parsingError = NSError(domain: "Invalid JSON format", code: -3)
94
99
  emitSyncError(parsingError)
95
100
  }
96
101
  } catch {
102
+ defer { isSyncInProgress = false }
97
103
  emitSyncError(error)
98
104
  }
99
105
  }
@@ -145,6 +151,11 @@ class StallionSyncHandler {
145
151
  static func downloadNewRelease(newReleaseHash: String, newReleaseUrl: String) {
146
152
  guard let stateManager = StallionStateManager.sharedInstance(),
147
153
  let config = stateManager.stallionConfig else { return }
154
+
155
+ syncQueue.async {
156
+ guard !isDownloadInProgress else { return }
157
+ isDownloadInProgress = true
158
+ }
148
159
 
149
160
  let downloadPath = config.filesDirectory + "/" + StallionConstants.PROD_DIRECTORY + "/" + StallionConstants.TEMP_FOLDER_SLOT
150
161
  let projectId = config.projectId ?? ""
@@ -156,6 +167,7 @@ class StallionSyncHandler {
156
167
  StallionFileDownloader().downloadBundle(url: fromUrl, downloadDirectory: downloadPath, onProgress: { progress in
157
168
  // Handle progress updates if necessary
158
169
  }, resolve: { _ in
170
+ defer { isDownloadInProgress = false }
159
171
  stateManager.stallionMeta?.currentProdSlot = SlotStates.newSlot
160
172
  stateManager.stallionMeta?.prodTempHash = newReleaseHash
161
173
  if let currentProdNewHash = stateManager.stallionMeta?.prodNewHash,
@@ -165,6 +177,7 @@ class StallionSyncHandler {
165
177
  stateManager.syncStallionMeta()
166
178
  emitDownloadSuccess(releaseHash: newReleaseHash)
167
179
  }, reject: { code, prefix, error in
180
+ defer { isDownloadInProgress = false }
168
181
  emitDownloadError(
169
182
  releaseHash: newReleaseHash,
170
183
  error: "\(String(describing: prefix))\(String(describing: error))"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-stallion",
3
- "version": "2.0.0-alpha.3",
3
+ "version": "2.0.0-alpha.5",
4
4
  "description": "Offical React Native SDK for Stallion",
5
5
  "main": "index",
6
6
  "types": "types/index.d.ts",