Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
11ada43
Gradle update
gthea Jul 14, 2025
168243c
Credentials provider & SSL context factory
gthea Jul 14, 2025
3ba8b46
Clean up
gthea Jul 14, 2025
ba8cbdb
Fix visibility
gthea Jul 14, 2025
4f21600
Renaming
gthea Jul 14, 2025
653d1ab
Full rename
gthea Jul 14, 2025
0d206c9
HttpResponse adapter
gthea Jul 15, 2025
844f153
Raw response parser
gthea Jul 15, 2025
b52e780
Tunnel establishment
gthea Jul 16, 2025
dad59ec
Additional test
gthea Jul 16, 2025
ead5823
Additional tests
gthea Jul 16, 2025
f8bf076
HttpOverTunnelExecutor
gthea Jul 16, 2025
b932905
Remove logs
gthea Jul 16, 2025
16bb170
ProxyCacertConnectionHandler integration
gthea Jul 16, 2025
9be445b
Cleanup request helper
gthea Jul 16, 2025
7a87c9d
Remaining classes
gthea Jul 16, 2025
a7f2a8c
Fix HttpProxy constructor
gthea Jul 17, 2025
e16ad94
Close sockets
gthea Jul 17, 2025
8b9e072
Additional tests
gthea Jul 17, 2025
f0d4f6a
OutputStream capabilities in connection adapter
gthea Jul 17, 2025
84e967a
Close all io streams on disconnect
gthea Jul 17, 2025
4674e27
Public proxy config
gthea Jul 18, 2025
42d6869
New proxy config integration
gthea Jul 18, 2025
9a938a8
Fix
gthea Jul 18, 2025
79317ce
Member visibility
gthea Jul 18, 2025
2814b86
External timeout
gthea Jul 18, 2025
0ae1017
Fix
gthea Jul 18, 2025
babe568
Remove parameter in call
gthea Jul 18, 2025
6b109e4
Remove nullability check
gthea Jul 18, 2025
c166499
Match streaming connection timeout
gthea Jul 18, 2025
9ad69f0
Consistent errors
gthea Jul 19, 2025
1ef7e16
Merge pull request #780 from splitio/FME-7171-ssl-factory
gthea Jul 21, 2025
a8bfde6
Merge pull request #781 from splitio/FME-7172-response-parse
gthea Jul 21, 2025
921b8e5
Merge pull request #782 from splitio/FME-7172-response-parse-2
gthea Jul 21, 2025
e65ff44
Merge pull request #783 from splitio/FME-7173-ssl-tunnel
gthea Jul 21, 2025
6f29a95
Merge pull request #784 from splitio/FME-7173-ssl-tunnel-2
gthea Jul 21, 2025
e557f16
Merge pull request #785 from splitio/FME-7173-ssl-tunnel-3
gthea Jul 21, 2025
eef5409
Merge pull request #786 from splitio/FME-7176-proxy-config
gthea Jul 21, 2025
711c605
Remove unnecessary catch
gthea Jul 21, 2025
b694d7d
Streaming socket fix
gthea Jul 22, 2025
2fff9cd
WIP
gthea Jul 22, 2025
3648707
WIP
gthea Jul 22, 2025
02fdc58
Continued clean up and hostname verification
gthea Jul 23, 2025
9a80d20
Fix timeout
gthea Jul 23, 2025
499c671
androidTest compilation fixes
gthea Jul 23, 2025
ccc04c4
Add correct log message
gthea Jul 23, 2025
2a819f4
Testable config
gthea Jul 23, 2025
308af08
Add unimplemented methods
gthea Jul 23, 2025
9f555f4
Merge pull request #787 from splitio/FME-7176-proxy-config-2
gthea Jul 23, 2025
58b4273
Improve logging
gthea Jul 23, 2025
89e5275
Trust all hostname verifier in tests
gthea Jul 23, 2025
71070d2
Refactor
gthea Jul 23, 2025
1dc92da
Additional tests
gthea Jul 23, 2025
641dbb6
Additional tests
gthea Jul 23, 2025
d16fed2
Fix naming in interface
gthea Jul 24, 2025
c500a4a
Merge pull request #788 from splitio/FME-8173-proxy-fixes
gthea Jul 25, 2025
6e5e34e
Cipher & proxy property in GeneralInfoStorage
gthea Jul 25, 2025
909dadc
Tests
gthea Jul 25, 2025
c1867e9
Additional condition
gthea Jul 25, 2025
9c79b45
Fix
gthea Jul 25, 2025
d8a9820
Proxy data serialization
gthea Jul 25, 2025
e00691a
Creation of HttpProxy in SplitWorker
gthea Jul 25, 2025
4f68331
Extract to helper class
gthea Jul 25, 2025
7a98b7b
Fix tests
gthea Jul 25, 2025
3bfef03
HttpClientProvider test
gthea Jul 25, 2025
c4419f9
Tests
gthea Jul 25, 2025
db92f99
Restore commented out code
gthea Jul 25, 2025
7d74941
Fix test
gthea Jul 25, 2025
030dbfe
Merge pull request #789 from splitio/FME-8229-proxy-bgsync
gthea Jul 28, 2025
f80a868
Merge pull request #790 from splitio/FME-8229-proxy-bgsync-2
gthea Jul 28, 2025
cb70383
Fix bad merge
gthea Jul 28, 2025
4d04964
Merge pull request #800 from splitio/development
gthea Aug 21, 2025
a1ed994
Merge branch 'development' into FME-4353-proxy_baseline
gthea Sep 2, 2025
fc69328
Merge branch 'master' into FME-4353-proxy_baseline
gthea Sep 2, 2025
c677a27
New RC version
gthea Sep 2, 2025
6cf705c
Fix test compilation
gthea Sep 2, 2025
66d6c7b
Fix NPE
gthea Sep 2, 2025
5694938
Update WMWrapper test
gthea Sep 3, 2025
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
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ apply from: 'spec.gradle'
apply from: 'jacoco.gradle'

ext {
splitVersion = '5.3.2'
splitVersion = '5.4.0-rc1'
jacocoVersion = '0.8.8'
}

Expand Down
9 changes: 6 additions & 3 deletions src/androidTest/java/fake/HttpResponseMock.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package fake;

import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.concurrent.BlockingQueue;
import java.security.cert.Certificate;

import io.split.android.client.network.BaseHttpResponseImpl;
import io.split.android.client.network.HttpResponse;
Expand All @@ -25,4 +23,9 @@ public HttpResponseMock(int status, String data) {
public String getData() {
return data;
}

@Override
public Certificate[] getServerCertificates() {
return new Certificate[0];
}
}
7 changes: 7 additions & 0 deletions src/androidTest/java/fake/HttpResponseStub.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package fake;

import java.security.cert.Certificate;

import io.split.android.client.network.BaseHttpResponseImpl;
import io.split.android.client.network.HttpResponse;

Expand Down Expand Up @@ -29,4 +31,9 @@ public boolean isSuccess() {
public String getData() {
return data;
}

@Override
public Certificate[] getServerCertificates() {
return new Certificate[0];
}
}
10 changes: 9 additions & 1 deletion src/androidTest/java/helper/TestableSplitConfigBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.split.android.client.impressions.ImpressionListener;
import io.split.android.client.network.CertificatePinningConfiguration;
import io.split.android.client.network.DevelopmentSslConfig;
import io.split.android.client.network.ProxyConfiguration;
import io.split.android.client.network.SplitAuthenticator;
import io.split.android.client.service.ServiceConstants;
import io.split.android.client.service.impressions.ImpressionsMode;
Expand Down Expand Up @@ -66,6 +67,7 @@ public class TestableSplitConfigBuilder {
private CertificatePinningConfiguration mCertificatePinningConfiguration;
private long mImpressionsDedupeTimeInterval = ServiceConstants.DEFAULT_IMPRESSIONS_DEDUPE_TIME_INTERVAL;
private RolloutCacheConfiguration mRolloutCacheConfiguration = RolloutCacheConfiguration.builder().build();
private ProxyConfiguration mProxyConfiguration = null;

public TestableSplitConfigBuilder() {
mServiceEndpoints = ServiceEndpoints.builder().build();
Expand Down Expand Up @@ -281,6 +283,11 @@ public TestableSplitConfigBuilder rolloutCacheConfiguration(RolloutCacheConfigur
return this;
}

public TestableSplitConfigBuilder logger(ProxyConfiguration proxyConfiguration) {
this.mProxyConfiguration = proxyConfiguration;
return this;
}

public SplitClientConfig build() {
Constructor constructor = SplitClientConfig.class.getDeclaredConstructors()[0];
constructor.setAccessible(true);
Expand Down Expand Up @@ -337,7 +344,8 @@ public SplitClientConfig build() {
mObserverCacheExpirationPeriod,
mCertificatePinningConfiguration,
mImpressionsDedupeTimeInterval,
mRolloutCacheConfiguration);
mRolloutCacheConfiguration,
mProxyConfiguration);

Logger.instance().setLevel(mLogLevel);
return config;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class GeneralInfoStorageTest {
@Before
public void setUp() {
mDb = DatabaseHelper.getTestDatabase(InstrumentationRegistry.getInstrumentation().getContext());
mGeneralInfoStorage = new GeneralInfoStorageImpl(mDb.generalInfoDao());
mGeneralInfoStorage = new GeneralInfoStorageImpl(mDb.generalInfoDao(), null);
}

@After
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ private Data buildInputData(Data customData) {
dataBuilder.putString("databaseName", "test_database_name");
dataBuilder.putString("apiKey", "api_key");
dataBuilder.putBoolean("encryptionEnabled", false);
dataBuilder.putBoolean("usesProxy", false);
if (customData != null) {
dataBuilder.putAll(customData);
}
Expand Down
79 changes: 67 additions & 12 deletions src/main/java/io/split/android/client/SplitClientConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static io.split.android.client.utils.Utils.checkNotNull;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.net.URI;
import java.util.concurrent.TimeUnit;
Expand All @@ -17,6 +18,7 @@
import io.split.android.client.network.CertificatePinningConfiguration;
import io.split.android.client.network.DevelopmentSslConfig;
import io.split.android.client.network.HttpProxy;
import io.split.android.client.network.ProxyConfiguration;
import io.split.android.client.network.SplitAuthenticator;
import io.split.android.client.service.ServiceConstants;
import io.split.android.client.service.impressions.ImpressionsMode;
Expand Down Expand Up @@ -132,6 +134,8 @@ public class SplitClientConfig {
private final long mImpressionsDedupeTimeInterval;
@NonNull
private final RolloutCacheConfiguration mRolloutCacheConfiguration;
@Nullable
private final ProxyConfiguration mProxyConfiguration;

public static Builder builder() {
return new Builder();
Expand Down Expand Up @@ -187,7 +191,8 @@ private SplitClientConfig(String endpoint,
long observerCacheExpirationPeriod,
CertificatePinningConfiguration certificatePinningConfiguration,
long impressionsDedupeTimeInterval,
RolloutCacheConfiguration rolloutCacheConfiguration) {
@NonNull RolloutCacheConfiguration rolloutCacheConfiguration,
@Nullable ProxyConfiguration proxyConfiguration) {
mEndpoint = endpoint;
mEventsEndpoint = eventsEndpoint;
mTelemetryEndpoint = telemetryEndpoint;
Expand Down Expand Up @@ -246,6 +251,7 @@ private SplitClientConfig(String endpoint,
mCertificatePinningConfiguration = certificatePinningConfiguration;
mImpressionsDedupeTimeInterval = impressionsDedupeTimeInterval;
mRolloutCacheConfiguration = rolloutCacheConfiguration;
mProxyConfiguration = proxyConfiguration;
}

public String trafficType() {
Expand Down Expand Up @@ -436,7 +442,9 @@ public boolean persistentAttributesEnabled() {
return mIsPersistentAttributesEnabled;
}

public int offlineRefreshRate() { return mOfflineRefreshRate; }
public int offlineRefreshRate() {
return mOfflineRefreshRate;
}

public boolean shouldRecordTelemetry() {
return mShouldRecordTelemetry;
Expand All @@ -446,7 +454,9 @@ public long telemetryRefreshRate() {
return mTelemetryRefreshRate;
}

public boolean syncEnabled() { return mSyncEnabled; }
public boolean syncEnabled() {
return mSyncEnabled;
}

public int mtkPerPush() {
return mMtkPerPush;
Expand Down Expand Up @@ -476,7 +486,9 @@ public int sseDisconnectionDelay() {
return mSSEDisconnectionDelayInSecs;
}

private void enableTelemetry() { mShouldRecordTelemetry = true; }
private void enableTelemetry() {
mShouldRecordTelemetry = true;
}

public long observerCacheExpirationPeriod() {
return Math.max(mImpressionsDedupeTimeInterval, mObserverCacheExpirationPeriod);
Expand Down Expand Up @@ -572,6 +584,8 @@ public static final class Builder {

private RolloutCacheConfiguration mRolloutCacheConfiguration = RolloutCacheConfiguration.builder().build();

private ProxyConfiguration mProxyConfiguration = null;

public Builder() {
mServiceEndpoints = ServiceEndpoints.builder().build();
}
Expand Down Expand Up @@ -806,7 +820,9 @@ public Builder ready(int milliseconds) {
*
* @param proxyHost proxy URI
* @return this builder
* @deprecated use {@link #proxyConfiguration(ProxyConfiguration)}
*/
@Deprecated
public Builder proxyHost(String proxyHost) {
if (proxyHost != null && proxyHost.endsWith("/")) {
mProxyHost = proxyHost.substring(0, proxyHost.length() - 1);
Expand All @@ -823,6 +839,7 @@ public Builder proxyHost(String proxyHost) {
* @param proxyAuthenticator
* @return this builder
*/
@Deprecated
public Builder proxyAuthenticator(SplitAuthenticator proxyAuthenticator) {
mProxyAuthenticator = proxyAuthenticator;
return this;
Expand Down Expand Up @@ -1030,6 +1047,7 @@ public Builder offlineRefreshRate(int offlineRefreshRate) {
* <p>
* This is an ADVANCED parameter
* </p>
*
* @param telemetryRefreshRate Rate in seconds for telemetry refresh.
* @return This builder
* @default 3600 seconds
Expand Down Expand Up @@ -1101,10 +1119,9 @@ public Builder certificatePinningConfiguration(CertificatePinningConfiguration c
/**
* This configuration is used to control the size of the impressions deduplication window.
*
* @param impressionsDedupeTimeInterval The time interval in milliseconds.
* @Experimental This method is experimental and may change or be removed in future versions.
* To be used upon Split team recommendation.
*
* @param impressionsDedupeTimeInterval The time interval in milliseconds.
*/
@Deprecated
public Builder impressionsDedupeTimeInterval(long impressionsDedupeTimeInterval) {
Expand All @@ -1128,6 +1145,17 @@ public Builder rolloutCacheConfiguration(@NonNull RolloutCacheConfiguration roll
return this;
}

/**
* Sets the proxy configuration
*
* @param proxyConfiguration
* @return this builder
*/
public Builder proxyConfiguration(ProxyConfiguration proxyConfiguration) {
mProxyConfiguration = proxyConfiguration;
return this;
}

public SplitClientConfig build() {
Logger.instance().setLevel(mLogLevel);

Expand Down Expand Up @@ -1207,7 +1235,7 @@ public SplitClientConfig build() {
mImpressionsDedupeTimeInterval = ServiceConstants.DEFAULT_IMPRESSIONS_DEDUPE_TIME_INTERVAL;
}

HttpProxy proxy = parseProxyHost(mProxyHost);
HttpProxy proxy = parseProxyHost(mProxyHost, mProxyConfiguration);

return new SplitClientConfig(
mServiceEndpoints.getSdkEndpoint(),
Expand Down Expand Up @@ -1260,26 +1288,53 @@ public SplitClientConfig build() {
mObserverCacheExpirationPeriod,
mCertificatePinningConfiguration,
mImpressionsDedupeTimeInterval,
mRolloutCacheConfiguration);
mRolloutCacheConfiguration,
mProxyConfiguration);
}

private HttpProxy parseProxyHost(String proxyUri, ProxyConfiguration proxyConfiguration) {
// Use legacy proxy behavior if proxyConfiguration is null
if (proxyConfiguration == null) {
return legacyProxyBehavior(proxyUri);
}

if (mProxyHost != null || mProxyAuthenticator != null) {
Logger.w("Both the deprecated proxy configuration methods (proxyHost, proxyAuthenticator) and the new ProxyConfiguration builder are being used. ProxyConfiguration will take precedence.");
}

// Initialize internal config with null url. This will be verified when building the factory.
HttpProxy.Builder builder = HttpProxy.newBuilder(null, -1);
if (proxyConfiguration.getUrl() != null) {
builder = HttpProxy.newBuilder(proxyConfiguration.getUrl().getHost(), proxyConfiguration.getUrl().getPort())
.mtls(proxyConfiguration.getClientCert(), proxyConfiguration.getClientPk())
.proxyCacert(proxyConfiguration.getCaCert())
.credentialsProvider(proxyConfiguration.getCredentialsProvider());
}
return builder.build();
}

private HttpProxy parseProxyHost(String proxyUri) {
@Nullable
private HttpProxy legacyProxyBehavior(String proxyUri) {
if (!Utils.isNullOrEmpty(proxyUri)) {
try {
String username = null;
String password = null;
URI uri = URI.create(proxyUri);
int port = uri.getPort() != -1 ? uri.getPort() : PROXY_PORT_DEFAULT;
String userInfo = uri.getUserInfo();
if(!Utils.isNullOrEmpty(userInfo)) {
if (!Utils.isNullOrEmpty(userInfo)) {
String[] userInfoComponents = userInfo.split(":");
if(userInfoComponents.length > 1) {
if (userInfoComponents.length > 1) {
username = userInfoComponents[0];
password = userInfoComponents[1];
}
}
String host = String.format("%s%s", uri.getHost(), uri.getPath());
return new HttpProxy(host, port, username, password);
if (username != null && password != null) {
return HttpProxy.newBuilder(host, port).basicAuth(username, password).buildLegacy();
} else {
return HttpProxy.newBuilder(host, port).buildLegacy();
}
} catch (IllegalArgumentException e) {
Logger.e("Proxy URI not valid: " + e.getLocalizedMessage());
throw new IllegalArgumentException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ public static synchronized SplitFactory build(@NonNull String sdkKey, @NonNull K
return new SplitFactoryImpl(sdkKey, key, config, context);
}
} catch (Exception ex) {
if (ex instanceof SplitInstantiationException) {
throw (SplitInstantiationException) ex;
}
throw new SplitInstantiationException("Could not instantiate SplitFactory", ex);
}
}
Expand Down Expand Up @@ -97,5 +100,9 @@ private static void checkPreconditions(@NonNull String sdkKey, @NonNull Key key,
if (context == null) {
throw new SplitInstantiationException("Could not instantiate SplitFactory. Context cannot be null");
}

if (config.proxy() != null && config.proxy().getHost() == null) {
throw new SplitInstantiationException("Could not instantiate SplitFactory. When configured, proxy host cannot be null");
}
}
}
30 changes: 27 additions & 3 deletions src/main/java/io/split/android/client/SplitFactoryHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
import io.split.android.client.service.sseclient.notifications.MySegmentsV2PayloadDecoder;
import io.split.android.client.service.sseclient.notifications.NotificationParser;
import io.split.android.client.service.sseclient.notifications.NotificationProcessor;
import io.split.android.client.service.sseclient.notifications.SplitsChangeNotification;
import io.split.android.client.service.sseclient.notifications.mysegments.MembershipsNotificationProcessorFactory;
import io.split.android.client.service.sseclient.notifications.mysegments.MembershipsNotificationProcessorFactoryImpl;
import io.split.android.client.service.sseclient.reactor.MySegmentsUpdateWorkerRegistry;
Expand Down Expand Up @@ -94,6 +93,7 @@
import io.split.android.client.telemetry.TelemetrySynchronizerStub;
import io.split.android.client.telemetry.storage.TelemetryRuntimeProducer;
import io.split.android.client.telemetry.storage.TelemetryStorage;
import io.split.android.client.utils.HttpProxySerializer;
import io.split.android.client.utils.Utils;
import io.split.android.client.utils.logger.Logger;

Expand Down Expand Up @@ -165,14 +165,15 @@ SplitStorageContainer buildStorageContainer(UserConsent userConsentStatus,
TelemetryStorage telemetryStorage,
long observerCacheExpirationPeriod,
ScheduledThreadPoolExecutor impressionsObserverExecutor,
SplitsStorage splitsStorage) {
SplitsStorage splitsStorage,
SplitCipher alwaysEncryptedSplitCipher) {

boolean isPersistenceEnabled = userConsentStatus == UserConsent.GRANTED;
PersistentEventsStorage persistentEventsStorage =
StorageFactory.getPersistentEventsStorage(splitRoomDatabase, splitCipher);
PersistentImpressionsStorage persistentImpressionsStorage =
StorageFactory.getPersistentImpressionsStorage(splitRoomDatabase, splitCipher);
GeneralInfoStorage generalInfoStorage = StorageFactory.getGeneralInfoStorage(splitRoomDatabase);
GeneralInfoStorage generalInfoStorage = StorageFactory.getGeneralInfoStorage(splitRoomDatabase, alwaysEncryptedSplitCipher);
return new SplitStorageContainer(
splitsStorage,
StorageFactory.getMySegmentsStorage(splitRoomDatabase, splitCipher),
Expand Down Expand Up @@ -228,6 +229,29 @@ WorkManagerWrapper buildWorkManagerWrapper(Context context, SplitClientConfig sp

}

static void setupProxyForBackgroundSync(@NonNull SplitClientConfig config, Runnable proxyConfigSaveTask) {
if (config.proxy() != null && !config.proxy().isLegacy() && config.synchronizeInBackground()) {
// Store proxy config for background sync usage
new Thread(proxyConfigSaveTask).start();
}
}

// Visible to inject for testing
@NonNull
static Runnable getProxyConfigSaveTask(@NonNull SplitClientConfig config, WorkManagerWrapper workManagerWrapper, GeneralInfoStorage generalInfoStorage) {
return new Runnable() {
@Override
public void run() {
try {
generalInfoStorage.setProxyConfig(HttpProxySerializer.serialize(config.proxy()));
} catch (Exception ex) {
Logger.w("Failed to store proxy config for background sync. Disabling background sync", ex);
workManagerWrapper.removeWork();
}
}
};
}

SyncManager buildSyncManager(SplitClientConfig config,
SplitTaskExecutor splitTaskExecutor,
Synchronizer synchronizer,
Expand Down
Loading
Loading