Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ runs:
using: composite
steps:
- name: Setup Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v5
with:
node-version: '20.x'
node-version: '24.x'
- name: Cache dependencies
id: yarn-cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: |
**/node_modules
Expand Down
15 changes: 12 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@ on:
jobs:
build-and-publish:
runs-on: ubuntu-latest
# Must match the Environment on npm's trusted publisher for this package
environment: prod
# Prevent the app's own release commit from re-triggering this workflow
if: github.actor != 'stallion-release-bot[bot]'
permissions: write-all
# id-token: write mints the OIDC token npm's trusted publisher verifies
permissions:
contents: write
issues: write
pull-requests: write
id-token: write
steps:
- name: Generate app token
id: app-token
uses: actions/create-github-app-token@v1
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
Expand Down Expand Up @@ -42,11 +49,13 @@ jobs:
- name: Prep release
run: yarn prepare-release

# Publishes to npm via trusted publishing, so no NPM_TOKEN is needed.
# npm binds the trusted publisher to this workflow's file name: renaming
# ci.yml means updating the publisher on npmjs.com in the same change.
- name: Release package
run: yarn semantic-release
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GIT_AUTHOR_EMAIL: ${{ secrets.GH_DEPLOY_EMAIL }}
GIT_AUTHOR_NAME: ${{ secrets.GH_DEPLOY_NAME }}
GIT_COMMITTER_EMAIL: ${{ secrets.GH_DEPLOY_EMAIL }}
Expand Down
118 changes: 76 additions & 42 deletions CHANGELOG.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ public class StallionApiConstants {
// Buffer Size
public static final int DOWNLOAD_BUFFER_SIZE = 1024 * 256;

// Network timeouts (aligned with iOS URLSession defaults)
public static final int CONNECT_TIMEOUT_MS = 30_000;
public static final int READ_TIMEOUT_MS = 60_000;

// File and Folder Names
public static final String ZIP_FILE_NAME = "build.zip";
public static final String UNZIP_FOLDER_NAME = "/build";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public static JSONObject post(String urlString, String requestBodyString) {
URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setConnectTimeout(StallionApiConstants.CONNECT_TIMEOUT_MS);
urlConnection.setReadTimeout(StallionApiConstants.READ_TIMEOUT_MS);
urlConnection.setRequestProperty("Content-Type", "application/json");

String appToken = stallionConfig.getAppToken();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ public static void downloadBundle(

} catch (Exception e) {
Log.e(TAG, "Error in downloadBundle: " + e.getMessage(), e);
stallionDownloadCallback.onReject(
StallionApiConstants.DOWNLOAD_ERROR_PREFIX,
e.getMessage() != null ? e.getMessage() : "Unknown download error"
);
}
});
}
Expand Down Expand Up @@ -159,8 +163,8 @@ private static void downloadFile(
return;
}
} catch (IOException e) {
callback.onReject(StallionApiConstants.DOWNLOAD_ERROR_PREFIX, "IOException occurred: ");
throw e;
String message = e.getMessage() != null ? e.getMessage() : "Unknown IO error";
throw new IOException("IOException occurred: " + message, e);
} finally {
connection.disconnect();
}
Expand All @@ -176,6 +180,8 @@ private static HttpURLConnection setupConnection(
URL url = new URL(downloadUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(StallionApiConstants.CONNECT_TIMEOUT_MS);
connection.setReadTimeout(StallionApiConstants.READ_TIMEOUT_MS);

if (offset > 0) {
connection.setRequestProperty("Range", "bytes=" + offset + "-");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ public static void applyPatch(String baseBundlePath, String diffPath, boolean is
// Create a temporary directory for the patched bundle
File tempPatchedDir = new File(diffDir.getParent(), diffDir.getName() + "_patched_temp");
try {
// Copy base bundle to temporary location
// Clear any leftover temp dir, then copy base bundle
if (tempPatchedDir.exists()) {
StallionFileManager.deleteFileOrFolderSilently(tempPatchedDir);
}
StallionFileManager.copyDirectory(baseBundleDir, tempPatchedDir);

// Read and parse manifest.json
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public static void downloadStageBundle(ReadableMap bundleInfo, Promise promise)
long alreadyDownloaded = StallionDownloadCacheManager.getDownloadCache(config, receivedDownloadUrl, downloadPath);

emitDownloadStartedStage(receivedHash, alreadyDownloaded > 0);
final long downloadStartMs = System.currentTimeMillis();

StallionFileDownloader.downloadBundle(
receivedDownloadUrl,
Expand All @@ -43,10 +44,11 @@ public void onReject(String prefix, String error) {

@Override
public void onSuccess(String successPayload) {
long downloadTimeMs = System.currentTimeMillis() - downloadStartMs;
stallionStateManager.stallionMeta.setCurrentStageSlot(StallionMetaConstants.SlotStates.NEW_SLOT);
stallionStateManager.stallionMeta.setStageTempHash(receivedHash);
stallionStateManager.syncStallionMeta();
emitDownloadSuccessStage(receivedHash);
emitDownloadSuccessStage(receivedHash, downloadTimeMs);
StallionDownloadCacheManager.deleteDownloadCache(downloadPath);
promise.resolve(successPayload);
}
Expand All @@ -60,10 +62,13 @@ public void onProgress(double downloadFraction) {
}
}

private static void emitDownloadSuccessStage(String releaseHash) {
private static void emitDownloadSuccessStage(String releaseHash, long downloadTimeMs) {
JSONObject successPayload = new JSONObject();
try {
successPayload.put("releaseHash", releaseHash);
JSONObject meta = new JSONObject();
meta.put("downloadTime", downloadTimeMs);
successPayload.put("meta", meta.toString());
} catch (Exception ignored) { }
StallionEventManager.getInstance().sendEvent(
StallionEventConstants.NativeStageEventTypes.DOWNLOAD_COMPLETE_STAGE.toString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ public static void downloadNewRelease(String newReleaseHash, String newReleaseUr
long alreadyDownloaded = StallionDownloadCacheManager.getDownloadCache(config, downloadUrl, downloadPath);

emitDownloadStarted(newReleaseHash, alreadyDownloaded > 0, bundleDiffIdForEvents);
final long downloadStartMs = System.currentTimeMillis();

StallionFileDownloader.downloadBundle(
downloadUrl,
Expand All @@ -185,6 +186,7 @@ public void onReject(String prefix, String error) {

@Override
public void onSuccess(String successPayload) {
long downloadTimeMs = System.currentTimeMillis() - downloadStartMs;
isDownloadInProgress.set(false);
StallionDownloadCacheManager.deleteDownloadCache(downloadPath);

Expand Down Expand Up @@ -224,14 +226,14 @@ public void onSuccess(String successPayload) {
}
}

stateManager.stallionMeta.setCurrentProdSlot(StallionMetaConstants.SlotStates.NEW_SLOT);
stateManager.stallionMeta.setProdTempHash(newReleaseHash);
String currentProdNewHash = stateManager.stallionMeta.getProdNewHash();
if(currentProdNewHash != null && !currentProdNewHash.isEmpty()) {
StallionSlotManager.stabilizeProd();
}
stateManager.syncStallionMeta();
emitDownloadSuccess(newReleaseHash, bundleDiffIdForEvents);
stateManager.stallionMeta.setCurrentProdSlot(StallionMetaConstants.SlotStates.NEW_SLOT);
stateManager.stallionMeta.setProdTempHash(newReleaseHash);
String currentProdNewHash = stateManager.stallionMeta.getProdNewHash();
if(currentProdNewHash != null && !currentProdNewHash.isEmpty()) {
StallionSlotManager.stabilizeProd();
}
stateManager.syncStallionMeta();
emitDownloadSuccess(newReleaseHash, bundleDiffIdForEvents, downloadTimeMs);
}

@Override
Expand Down Expand Up @@ -282,13 +284,16 @@ private static void emitDownloadError(String releaseHash, String error) {
);
}

private static void emitDownloadSuccess(String releaseHash, String bundleDiffId) {
private static void emitDownloadSuccess(String releaseHash, String bundleDiffId, long downloadTimeMs) {
JSONObject successPayload = new JSONObject();
try {
successPayload.put("releaseHash", releaseHash);
if (bundleDiffId != null && !bundleDiffId.isEmpty()) {
successPayload.put("diffId", bundleDiffId);
}
JSONObject meta = new JSONObject();
meta.put("downloadTime", downloadTimeMs);
successPayload.put("meta", meta.toString());
} catch (Exception ignored) { }
StallionEventManager.getInstance().sendEvent(
NativeProdEventTypes.DOWNLOAD_COMPLETE_PROD.toString(),
Expand Down
12 changes: 2 additions & 10 deletions android/src/main/java/com/stallion/storage/StallionConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.res.Resources;
import android.provider.Settings;

import org.json.JSONException;
import org.json.JSONObject;
Expand Down Expand Up @@ -53,22 +52,15 @@ public StallionConfig(Context context, SharedPreferences sharedPreferences) {
);
this.publicSigningKey = stallionPublicKeyRes != 0 ? context.getString(stallionPublicKeyRes) : "";

// get or generate UID
// get or generate UID (random install-scoped UUID)
String cachedUniqueId = sharedPreferences.getString(
StallionConfigConstants.UNIQUE_ID_IDENTIFIER,
""
);
if(!cachedUniqueId.isEmpty()) {
this.uid = cachedUniqueId;
} else {
try {
this.uid = Settings.Secure.getString(
context.getContentResolver(),
Settings.Secure.ANDROID_ID
);
} catch (Exception ignored) {
this.uid = UUID.randomUUID().toString();
}
this.uid = UUID.randomUUID().toString();
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(StallionConfigConstants.UNIQUE_ID_IDENTIFIER, this.uid);
editor.apply();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public static void stabilizeProd() {
File newSlot = new File(baseFolderPath, StallionConfigConstants.PROD_DIRECTORY + StallionConfigConstants.NEW_FOLDER_SLOT);
File stableSlot = new File(baseFolderPath, StallionConfigConstants.PROD_DIRECTORY + StallionConfigConstants.STABLE_FOLDER_SLOT);

StallionFileManager.deleteFileOrFolderSilently(stableSlot);
StallionFileManager.copyDirectory(newSlot, stableSlot);

String newReleaseHash = stateManager.stallionMeta.getProdNewHash();
Expand Down
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ PODS:
- React-jsinspector (0.71.10)
- React-logger (0.71.10):
- glog
- react-native-stallion (2.4.0-alpha.4):
- react-native-stallion (2.4.2):
- RCT-Folly (= 2021.07.22.00)
- React-Core
- ZIPFoundation
Expand Down Expand Up @@ -497,7 +497,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: 4bb480a183a354e4dbfb1012936b1a2bb9357de7
React-jsinspector: cdc854f8b13abd202afa54bc12578e5afb9cfae1
React-logger: ef2269b3afa6ba868da90496c3e17a4ec4f4cee0
react-native-stallion: 24b0975e63e09e3437a86cbfcf665c7643d0a647
react-native-stallion: 5723bb5ff2923719fad7c0cc6f1d3578c6dc601b
React-perflogger: 217095464d5c4bb70df0742fa86bf2a363693468
React-RCTActionSheet: 8deae9b85a4cbc6a2243618ea62a374880a2c614
React-RCTAnimation: 59c62353a8b59ce206044786c5d30e4754bffa64
Expand Down
10 changes: 1 addition & 9 deletions example/ios/StallionExample/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>StallionPublicSigningKey</key>
<string>MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApm9SgaDU9lXLi5OD6sd9
WpNP8bWeGRzLYOReqH5yuu21kYTDaDt1/+vTpCBNuyX2ZjtKNvKujnAXuWf8Zfj4
JcWb/r4clDQK0sNy8G7x7s+4Mup6W73lW5AtUnREQZX8IA00lp76r9ii6HYxXTpO
CgHpXyRyPLkMNc+HxaTmDHbldZtRCREcLldI7NnQkFMyZlHK2r1QxiQLpTIEK0vI
IJyXeHOeJUROebZn/UI7UDKiSCvlaHMsJ4pnIAyLrtqFQ8w9rf3aljmYX52m6Rbc
df8fwbWtBCH8OPW5SEDTMDzvE1siAvYwRNxbeXHMrAypPg/DaATdXoKMObjJWRm1
owIDAQAB</string>
<key>StallionAppToken</key>
<string>spb_qLFBKtdR9TBZtsKPDqMXvFD_ebcs-Tdjyc7F4-dX7q</string>
<string>spb_ap_Ss_cRvqX-B6iMGxIxKaUqTn9K-RgSmf4ZNTPhx4zjd</string>
<key>StallionProjectId</key>
<string>67210b6f3e19d894c6a1a4fb</string>
<key>CFBundleDevelopmentRegion</key>
Expand Down
Loading
Loading