diff --git a/README.md b/README.md
index f55730a..fd71907 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ Add this dependency to your project's POM:
com.browserstack
browserstack-local-java
- 1.1.1
+ 1.1.6
```
diff --git a/pom.xml b/pom.xml
index a34fce7..88c1e3d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
com.browserstack
browserstack-local-java
jar
- 1.1.1-SNAPSHOT
+ 1.1.7
browserstack-local-java
Java bindings for BrowserStack Local
@@ -33,13 +33,9 @@
-
- ossrh
- https://oss.sonatype.org/content/repositories/snapshots
-
- ossrh
- https://oss.sonatype.org/service/local/staging/deploy/maven2
+ central
+ https://central.sonatype.com/api/v1/publisher/deployments/upload/
@@ -51,9 +47,9 @@
test
- org.apache.commons
+ commons-io
commons-io
- 1.3.2
+ 2.16.1
org.json
@@ -87,14 +83,14 @@
- org.sonatype.plugins
- nexus-staging-maven-plugin
- 1.6.9
+ org.sonatype.central
+ central-publishing-maven-plugin
+ 0.8.0
true
- ossrh
- https://oss.sonatype.org/
- true
+ central
+ true
+ false
@@ -132,14 +128,14 @@
- org.sonatype.plugins
- nexus-staging-maven-plugin
- 1.6.13
+ org.sonatype.central
+ central-publishing-maven-plugin
+ 0.8.0
true
- ossrh
- https://oss.sonatype.org/
- true
+ central
+ true
+ false
diff --git a/src/main/java/com/browserstack/local/Local.java b/src/main/java/com/browserstack/local/Local.java
index a221b2a..09c08f0 100644
--- a/src/main/java/com/browserstack/local/Local.java
+++ b/src/main/java/com/browserstack/local/Local.java
@@ -23,7 +23,7 @@ public class Local {
private LocalProcess proc = null;
// Current version of binding package, used for --source option of binary
- private final String packageVersion = "1.1.1";
+ private static final String packageVersion = "1.1.7";
private final Map parameters;
private final Map avoidValueParameters;
@@ -55,9 +55,9 @@ public void start(Map options) throws Exception {
startOptions = options;
LocalBinary lb;
if (options.get("binarypath") != null) {
- lb = new LocalBinary(options.get("binarypath"));
+ lb = new LocalBinary(options.get("binarypath"), options.get("key"));
} else {
- lb = new LocalBinary("");
+ lb = new LocalBinary("", options.get("key"));
}
binaryPath = lb.getBinaryPath();
@@ -109,9 +109,9 @@ public void stop() throws Exception {
public void stop(Map options) throws Exception {
LocalBinary lb;
if (options.get("binarypath") != null) {
- lb = new LocalBinary(options.get("binarypath"));
+ lb = new LocalBinary(options.get("binarypath"), options.get("key"));
} else {
- lb = new LocalBinary("");
+ lb = new LocalBinary("", options.get("key"));
}
binaryPath = lb.getBinaryPath();
makeCommand(options, "stop");
@@ -130,6 +130,15 @@ public boolean isRunning() throws Exception {
return isProcessRunning(pid);
}
+ /**
+ * Returns the package version
+ *
+ * @return {String} package version
+ */
+ public static String getPackageVersion() {
+ return packageVersion;
+ }
+
/**
* Creates a list of command-line arguments for the Local instance
*
diff --git a/src/main/java/com/browserstack/local/LocalBinary.java b/src/main/java/com/browserstack/local/LocalBinary.java
index 7eb6e6e..bf4542d 100644
--- a/src/main/java/com/browserstack/local/LocalBinary.java
+++ b/src/main/java/com/browserstack/local/LocalBinary.java
@@ -1,21 +1,39 @@
package com.browserstack.local;
import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+
+import org.json.JSONObject;
+
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.File;
+import java.io.FileOutputStream;
import java.net.URL;
+import java.net.URLConnection;
import java.util.regex.Pattern;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.ZipException;
+
+import java.lang.StringBuilder;
class LocalBinary {
- private static final String BIN_URL = "https://www.browserstack.com/local-testing/downloads/bin/";
+ private String binaryFileName;
- private String httpPath;
+ private String sourceUrl;
private String binaryPath;
+ private Boolean fallbackEnabled = false;
+
+ private Throwable downloadFailureThrowable = null;
+
+ private String key;
+
private boolean isOSWindows;
private final String orderedPaths[] = {
@@ -24,14 +42,30 @@ class LocalBinary {
System.getProperty("java.io.tmpdir")
};
- LocalBinary(String path) throws LocalException {
+ LocalBinary(String path, String key) throws LocalException {
+ this.key = key;
initialize();
- if (path != "") {
- getBinaryOnPath(path);
- } else {
- getBinary();
+ downloadAndVerifyBinary(path);
+ }
+
+ private void downloadAndVerifyBinary(String path) throws LocalException {
+ try {
+ if (path != "") {
+ getBinaryOnPath(path);
+ } else {
+ getBinary();
+ }
+ checkBinary();
+ } catch (Throwable e) {
+ if (fallbackEnabled) throw e;
+ File binary_file = new File(binaryPath);
+ if (binary_file.exists()) {
+ binary_file.delete();
+ }
+ fallbackEnabled = true;
+ downloadFailureThrowable = e;
+ downloadAndVerifyBinary(path);
}
- checkBinary();
}
private void initialize() throws LocalException {
@@ -58,7 +92,7 @@ private void initialize() throws LocalException {
throw new LocalException("Failed to detect OS type");
}
- httpPath = BIN_URL + binFileName;
+ this.binaryFileName = binFileName;
}
private boolean isAlpine() {
@@ -159,8 +193,58 @@ private boolean makePath(String path) {
}
}
+ private void fetchSourceUrl() throws LocalException {
+ if ((!fallbackEnabled && sourceUrl != null) || (fallbackEnabled && downloadFailureThrowable == null)) {
+ /* Retry because binary (from any of the endpoints) validation failed */
+ return;
+ }
+
+ try {
+ URL url = new URL("https://local.browserstack.com/binary/api/v1/endpoint");
+ URLConnection connection = url.openConnection();
+
+ connection.setDoOutput(true);
+ connection.setRequestProperty("Content-Type", "application/json");
+ connection.setRequestProperty("User-Agent", "browserstack-local-java/" + Local.getPackageVersion());
+ connection.setRequestProperty("Accept", "application/json");
+
+ JSONObject inputParams = new JSONObject();
+ inputParams.put("auth_token", this.key);
+ if (fallbackEnabled) {
+ connection.setRequestProperty("X-Local-Fallback-Cloudflare", "true");
+ inputParams.put("error_message", downloadFailureThrowable.getMessage());
+ }
+ String jsonInputParams = inputParams.toString();
+
+ try (OutputStream os = connection.getOutputStream()) {
+ byte[] input = jsonInputParams.getBytes("utf-8");
+ os.write(input, 0, input.length);
+ }
+
+ try (InputStream is = connection.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is, "utf-8"))) {
+ StringBuilder response = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ response.append(line.trim());
+ }
+ String responseBody = response.toString();
+ JSONObject json = new JSONObject(responseBody);
+ if (json.has("error")) {
+ throw new Exception(json.getString("error"));
+ }
+ this.sourceUrl = json.getJSONObject("data").getString("endpoint");
+ if(fallbackEnabled) downloadFailureThrowable = null;
+ }
+ } catch (Throwable e) {
+ throw new LocalException("Error trying to fetch the source URL: " + e.getMessage());
+ }
+ }
+
private void downloadBinary(String destParentDir, Boolean custom) throws LocalException {
try {
+ fetchSourceUrl();
+
String source = destParentDir;
if (!custom) {
if (!new File(destParentDir).exists())
@@ -171,13 +255,13 @@ private void downloadBinary(String destParentDir, Boolean custom) throws LocalEx
source += ".exe";
}
}
- URL url = new URL(httpPath);
+ URL url = new URL(sourceUrl + '/' + binaryFileName);
File f = new File(source);
- FileUtils.copyURLToFile(url, f);
+ newCopyToFile(url, f);
changePermissions(binaryPath);
- } catch (Exception e) {
+ } catch (Throwable e) {
throw new LocalException("Error trying to download BrowserStackLocal binary: " + e.getMessage());
}
}
@@ -192,4 +276,39 @@ private void changePermissions(String path) {
public String getBinaryPath() {
return binaryPath;
}
+
+ private static void newCopyToFile(URL url, File f) throws IOException {
+ URLConnection conn = url.openConnection();
+ conn.setRequestProperty("User-Agent", "browserstack-local-java/" + Local.getPackageVersion());
+ conn.setRequestProperty("Accept-Encoding", "gzip, *");
+ String contentEncoding = conn.getContentEncoding();
+
+ if (contentEncoding == null || !contentEncoding.toLowerCase().contains("gzip")) {
+ customCopyInputStreamToFile(conn.getInputStream(), f, url);
+ return;
+ }
+
+ try (InputStream stream = new GZIPInputStream(conn.getInputStream())) {
+ if (System.getenv().containsKey("BROWSERSTACK_LOCAL_DEBUG_GZIP")) {
+ System.out.println("using gzip in " + conn.getRequestProperty("User-Agent"));
+ }
+
+ customCopyInputStreamToFile(stream, f, url);
+ } catch (ZipException e) {
+ FileUtils.copyURLToFile(url, f);
+ }
+ }
+
+ private static void customCopyInputStreamToFile(InputStream stream, File file, URL url) throws IOException {
+ try {
+ FileUtils.copyInputStreamToFile(stream, file);
+ } catch (Throwable e) {
+ try (FileOutputStream fos = new FileOutputStream(file)) {
+ IOUtils.copy(stream, fos);
+ } catch (Throwable th) {
+ FileUtils.copyURLToFile(url, file);
+ }
+ }
+ }
}
+