Skip to content
Merged
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
20 changes: 15 additions & 5 deletions android/src/main/java/com/stallion/StallionModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,21 @@ public String getName() {

@ReactMethod
public void onLaunch(String launchData) {
// try {
// JSONObject launchDataJson = new JSONObject(launchData);
// } catch (Exception e) {
// e.printStackTrace();
// }
try {
String customBaseUrl = null;
if (launchData != null && !launchData.isEmpty()) {
JSONObject launchDataJson = new JSONObject(launchData);
if (launchDataJson.has("baseUrl") && !launchDataJson.isNull("baseUrl")) {
String baseUrl = launchDataJson.optString("baseUrl", "");
if (!baseUrl.isEmpty()) {
customBaseUrl = baseUrl;
}
}
}
com.stallion.utils.StallionApiBaseUrl.set(customBaseUrl);
} catch (Exception e) {
e.printStackTrace();
}
stallionStateManager.setIsMounted(true);
DeviceEventManagerModule.RCTDeviceEventEmitter eventEmitter = getReactApplicationContext().getJSModule(
DeviceEventManagerModule.RCTDeviceEventEmitter.class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,28 @@ public class StallionApiConstants {
public static final String STALLION_SDK_TOKEN_KEY = "x-sdk-pin-access-token";
public static final String STALLION_DEVICE_ID_KEY = "uid";

/** Legacy global API host; used only to detect implicit defaults in prefs. */
public static final String LEGACY_DEFAULT_STALLION_API_BASE = "https://api.stalliontech.io";

public static final String STALLION_API_BASE = "https://api.stalliontech.io";
public static final String REGIONAL_API_BASE_AP = "https://api-ap.stalliontech.io";
public static final String REGIONAL_API_BASE_US = "https://api-us.stalliontech.io";

/** @deprecated Use LEGACY_DEFAULT_STALLION_API_BASE or getStallionApiBase() */
@Deprecated
public static final String DEFAULT_STALLION_API_BASE = LEGACY_DEFAULT_STALLION_API_BASE;

/**
* Gets the API base URL from config or returns default
* @return String - The base URL to use
*/
public static String getStallionApiBase() {
return com.stallion.utils.StallionApiBaseUrl.get();
}

// Keep old constant for backward compatibility, but mark as deprecated
/** @deprecated Use getStallionApiBase() instead */
@Deprecated
public static final String STALLION_API_BASE = DEFAULT_STALLION_API_BASE;

public static final String STALLION_INFO_API_PATH = "/api/v1/promoted/get-update-meta";
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public static void sync() {

// Make API call using StallionApiManager
JSONObject releaseMeta = StallionApiManager.post(
StallionApiConstants.STALLION_API_BASE + StallionApiConstants.STALLION_INFO_API_PATH,
StallionApiConstants.getStallionApiBase() + StallionApiConstants.STALLION_INFO_API_PATH,
requestPayload.toString()
);

Expand Down
25 changes: 24 additions & 1 deletion android/src/main/java/com/stallion/storage/StallionConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

import java.util.UUID;

import com.stallion.storage.StallionConfigConstants;
import com.stallion.utils.StallionApiBaseUrl;

public class StallionConfig {
private String uid;
private final String projectId;
Expand All @@ -22,7 +25,7 @@ public class StallionConfig {
private String lastDownloadingUrl;
private String lastUnverifiedHash;
private final String publicSigningKey;

private String baseUrl;

public StallionConfig(Context context, SharedPreferences sharedPreferences) {
this.sharedPreferences = sharedPreferences;
Expand Down Expand Up @@ -77,6 +80,8 @@ public StallionConfig(Context context, SharedPreferences sharedPreferences) {
this.filesDirectory = context.getFilesDir().getAbsolutePath();
this.lastDownloadingUrl = sharedPreferences.getString(StallionConfigConstants.LAST_DOWNLOADING_URL_IDENTIFIER, "");
this.lastUnverifiedHash = sharedPreferences.getString(StallionConfigConstants.LAST_UNVERIFIED_HASH, "");
String storedBaseUrl = sharedPreferences.getString(StallionConfigConstants.BASE_URL_IDENTIFIER, "");
this.baseUrl = storedBaseUrl != null ? storedBaseUrl : "";
}

public String getLastDownloadingUrl() {
Expand Down Expand Up @@ -143,6 +148,23 @@ public String getPublicSigningKey() {
return this.publicSigningKey;
}

public String getBaseUrl() {
return this.baseUrl;
}

public void setBaseUrl(String baseUrl) {
if (baseUrl == null || baseUrl.isEmpty()) {
this.baseUrl = "";
sharedPreferences.edit().remove(StallionConfigConstants.BASE_URL_IDENTIFIER).apply();
return;
}
this.baseUrl = baseUrl;
sharedPreferences
.edit()
.putString(StallionConfigConstants.BASE_URL_IDENTIFIER, this.baseUrl)
.apply();
}

public JSONObject toJSON() {
JSONObject configJson = new JSONObject();
try {
Expand All @@ -151,6 +173,7 @@ public JSONObject toJSON() {
configJson.put("appToken", this.appToken);
configJson.put("sdkToken", this.sdkToken);
configJson.put("appVersion", this.appVersion);
configJson.put("baseUrl", StallionApiBaseUrl.get());
return configJson;
} catch (JSONException ignored) {
return new JSONObject();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class StallionConfigConstants {
public static final String API_KEY_IDENTIFIER = "x-sdk-access-token";
public static final String LAST_DOWNLOADING_URL_IDENTIFIER = "StallionLastDownloadingUrl";
public static final String LAST_UNVERIFIED_HASH = "LastUnverifiedHash";
public static final String BASE_URL_IDENTIFIER = "StallionBaseUrl";

public static final String PROD_DIRECTORY = "/StallionProd";
public static final String STAGE_DIRECTORY = "/StallionStage";
Expand Down
59 changes: 59 additions & 0 deletions android/src/main/java/com/stallion/utils/StallionApiBaseUrl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.stallion.utils;

import com.stallion.storage.StallionConfig;
import com.stallion.storage.StallionStateManager;
import com.stallion.networkmanager.StallionApiConstants;

public class StallionApiBaseUrl {

/**
* Gets the API base URL: custom baseUrl if set, else regional URL from app token.
* @return String - The base URL to use
*/
public static String get() {
try {
StallionStateManager stateManager = StallionStateManager.getInstance();
if (stateManager != null && stateManager.getStallionConfig() != null) {
return resolve(stateManager.getStallionConfig());
}
} catch (Exception e) {
// Fallback to regional default on any error
}
return StallionApiConstants.REGIONAL_API_BASE_AP;
}

static String resolve(StallionConfig config) {
String stored = config.getBaseUrl();
if (stored != null && !stored.isEmpty()) {
return stored;
}

String region = StallionTokenRegion.parseTokenRegion(config.getAppToken());
if (region == null) {
region = StallionTokenRegion.defaultRegion();
}
return regionalBaseUrl(region);
}

static String regionalBaseUrl(String region) {
if ("us".equals(region)) {
return StallionApiConstants.REGIONAL_API_BASE_US;
}
return StallionApiConstants.REGIONAL_API_BASE_AP;
}

/**
* Sets a custom base URL, or clears it when null/empty.
* @param baseUrl - The custom base URL to set, or null/empty to clear
*/
public static void set(String baseUrl) {
try {
StallionStateManager stateManager = StallionStateManager.getInstance();
if (stateManager != null && stateManager.getStallionConfig() != null) {
stateManager.getStallionConfig().setBaseUrl(baseUrl);
}
} catch (Exception e) {
Comment thread
Thor963 marked this conversation as resolved.
// Silently fail
}
}
}
51 changes: 51 additions & 0 deletions android/src/main/java/com/stallion/utils/StallionTokenRegion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.stallion.utils;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;

public final class StallionTokenRegion {

private static final Set<String> VALID_REGIONS = new HashSet<>(Arrays.asList("ap", "us"));

private StallionTokenRegion() {}

/**
* Parses the region prefix from a Stallion token.
* Returns "ap", "us", or null. Null means legacy unprefixed token; caller should default to "ap".
*/
public static String parseTokenRegion(String token) {
if (token == null) {
return null;
}
token = token.trim();
if (token.isEmpty()) {
return null;
}

// App token: spb_<region>_<44-char nanoid> → 49 chars
if (token.startsWith("spb_") && token.length() == 49 && token.charAt(6) == '_') {
return extractRegion(token);
}

// CI token: stl_<region>_<36-char nanoid> → 43 chars
if (token.startsWith("stl_") && token.length() == 43 && token.charAt(6) == '_') {
return extractRegion(token);
}

return null;
}

public static String defaultRegion() {
return "ap";
}

private static String extractRegion(String token) {
String code = token.substring(4, 6).toLowerCase(Locale.ROOT);
if (!code.matches("[a-z]{2}")) {
return null;
}
return VALID_REGIONS.contains(code) ? code : null;
}
}
7 changes: 0 additions & 7 deletions example/android/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,4 @@
<string name="app_name">StallionExample</string>
<string name="StallionProjectId">67210b6f3e19d894c6a1a4fb</string>
<string name="StallionAppToken">spb_KLNEjOkETM48i3N1SffGDvEl-OsSiHZrhxER2kq3Ok</string>
<string name="StallionPublicSigningKey">MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApm9SgaDU9lXLi5OD6sd9
WpNP8bWeGRzLYOReqH5yuu21kYTDaDt1/+vTpCBNuyX2ZjtKNvKujnAXuWf8Zfj4
JcWb/r4clDQK0sNy8G7x7s+4Mup6W73lW5AtUnREQZX8IA00lp76r9ii6HYxXTpO
CgHpXyRyPLkMNc+HxaTmDHbldZtRCREcLldI7NnQkFMyZlHK2r1QxiQLpTIEK0vI
IJyXeHOeJUROebZn/UI7UDKiSCvlaHMsJ4pnIAyLrtqFQ8w9rf3aljmYX52m6Rbc
df8fwbWtBCH8OPW5SEDTMDzvE1siAvYwRNxbeXHMrAypPg/DaATdXoKMObjJWRm1
owIDAQAB</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
</Testables>
Comment thread
Thor963 marked this conversation as resolved.
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
Expand Down
8 changes: 0 additions & 8 deletions example/ios/StallionExample/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,6 @@
<!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>
<key>StallionProjectId</key>
Expand Down
15 changes: 14 additions & 1 deletion ios/main/Stallion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ class Stallion: RCTEventEmitter {
}

@objc func onLaunch(_ launchData: String) {
var customBaseUrl = ""
if !launchData.isEmpty,
let data = launchData.data(using: .utf8),
let params = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
params.keys.contains("baseUrl"),
let baseUrl = params["baseUrl"] as? String,
!baseUrl.isEmpty {
customBaseUrl = baseUrl
}
StallionApiBaseUrl.set(customBaseUrl)

stallionStateManager.isMounted = true
checkPendingDownloads()
let currentReleaseHash = stallionStateManager.stallionMeta.getHashAtCurrentProdSlot()
Expand Down Expand Up @@ -61,7 +72,9 @@ class Stallion: RCTEventEmitter {
@objc func getStallionConfig(_ promise: RCTPromiseResolveBlock, rejecter: RCTPromiseRejectBlock) {
do {
if let config = stallionStateManager.stallionConfig {
let configJsonData = try JSONSerialization.data(withJSONObject: config.toDictionary(), options: [])
var configDict = config.toDictionary() as? [String: Any] ?? [:]
configDict["baseUrl"] = StallionApiBaseUrl.get()
let configJsonData = try JSONSerialization.data(withJSONObject: configDict, options: [])
if let configJsonString = String(data: configJsonData, encoding: .utf8) {
promise(configJsonString)
} else {
Expand Down
51 changes: 51 additions & 0 deletions ios/main/StallionApiBaseUrl.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// StallionApiBaseUrl.swift
// react-native-stallion
//
// Created for centralized base URL management
//

import Foundation

class StallionApiBaseUrl {
/**
* Gets the API base URL: custom baseUrl if set, else regional URL from app token.
*/
static func get() -> String {
guard let stateManager = StallionStateManager.sharedInstance() else {
return StallionConstants.REGIONAL_API_BASE_AP
}

return resolve(config: stateManager.stallionConfig)
}

static func resolve(config: StallionConfig) -> String {
let stored = config.baseUrl ?? ""
if !stored.isEmpty {
return stored
}

var region = StallionTokenRegion.parseTokenRegion(config.appToken)
if region == nil {
region = StallionTokenRegion.defaultRegion()
}
return regionalBaseUrl(region!)
}

static func regionalBaseUrl(_ region: String) -> String {
if region == "us" {
return StallionConstants.REGIONAL_API_BASE_US
}
return StallionConstants.REGIONAL_API_BASE_AP
}

/**
* Sets a custom base URL, or clears it when empty.
*/
static func set(_ baseUrl: String) {
guard let stateManager = StallionStateManager.sharedInstance() else {
return
}
stateManager.stallionConfig.updateBaseUrl(baseUrl)
}
}
2 changes: 2 additions & 0 deletions ios/main/StallionConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
@property (nonatomic, copy, readonly) NSString *filesDirectory;
@property (nonatomic, copy, readonly) NSString *publicSigningKey;
@property (nonatomic, copy) NSString *lastUnverifiedHash;
@property (nonatomic, copy) NSString *baseUrl;

- (instancetype)initWithDefaults:(NSUserDefaults *)defaults;

- (void)updateSdkToken:(NSString *)newSdkToken;
- (void)updateLastUnverifiedHash:(NSString *)newUnverifiedHash;
- (void)updateBaseUrl:(NSString *)newBaseUrl;
- (NSDictionary *)toDictionary;

@end
Loading