browserstack-local 1.4.3 → 1.5.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d8908a1d1804c2e04da139de1fc8a3bb7631780f0a8ee99753cd78f5c4e8134
4
- data.tar.gz: 2d113c112835b279292b5aac0042efa5d77fce7fbc2d3bf775b817bea0b7b440
3
+ metadata.gz: c6cc02b00944100bc4c6548ee133ad288a6c96271342c358f1c76398bad4087c
4
+ data.tar.gz: 128f9be2ca356f0efff2cf5fa9e9d92d7a2e68da5430b8329c5251ee5f2b30d6
5
5
  SHA512:
6
- metadata.gz: aad48f4723959de92ee40fc564167aea7d78ea98423557feda42e793d75acb57bb26a9f2944595693460aaae273c5b7058ebdcf6bbae8a70923335efa6ad742f
7
- data.tar.gz: bd759d61834aacc1647e0c21caf07fc274a5aef426ec92ee71836153cf9f6d312dfd480c9392527c81d3b018e32b60be544dbacebbdfa6ccb90f015d254a0d59
6
+ metadata.gz: 8c014452cb36ae15b6f3e2b1e3e722fa0f2b4d0fdfbaff6a8c22960072765e30942fa32a754654e7132ddc68fb6c8339adc3d6bc4ab5e41574144ef266b6ffac
7
+ data.tar.gz: e6b001e4caeab902ea85c7d0c5cf0ef2f455aee7ce3b36e864b251dc43ec05dee957e44eda2e4f4592d3be464c328bfd60eff7d001c15190e6aab1920ceb2cf6
@@ -0,0 +1,63 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'json'
4
+ require 'openssl'
5
+ require 'browserstack/localexception'
6
+
7
+ module BrowserStack
8
+ module FetchDownloadSourceUrl
9
+ BS_HOST = 'local.browserstack.com'.freeze
10
+ ENDPOINT_PATH = '/binary/api/v1/endpoint'.freeze
11
+
12
+ def self.call(auth_token:, user_agent:, fallback: false, error_message: nil,
13
+ proxy_host: nil, proxy_port: nil)
14
+ uri = URI::HTTPS.build(host: BS_HOST, path: ENDPOINT_PATH)
15
+
16
+ body = { 'auth_token' => auth_token }
17
+ body['error_message'] = error_message if fallback && error_message
18
+
19
+ http_class = if proxy_host && proxy_port
20
+ Net::HTTP::Proxy(proxy_host, proxy_port.to_i)
21
+ else
22
+ Net::HTTP
23
+ end
24
+
25
+ http = http_class.new(uri.host, uri.port)
26
+ http.use_ssl = true
27
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
28
+ http.open_timeout = 10
29
+ http.read_timeout = 15
30
+
31
+ req = Net::HTTP::Post.new(uri.request_uri)
32
+ req['Content-Type'] = 'application/json'
33
+ req['User-Agent'] = user_agent
34
+ req['X-Local-Fallback-Cloudflare'] = 'true' if fallback
35
+ req.body = JSON.dump(body)
36
+
37
+ res = http.request(req)
38
+
39
+ begin
40
+ parsed = JSON.parse(res.body.to_s)
41
+ rescue JSON::ParserError => e
42
+ raise BrowserStack::LocalException.new(
43
+ "Failed to parse binary endpoint API response (HTTP #{res.code}): #{e.message}"
44
+ )
45
+ end
46
+
47
+ if parsed.is_a?(Hash) && parsed['error']
48
+ raise BrowserStack::LocalException.new(
49
+ "Binary endpoint API returned error: #{parsed['error']}"
50
+ )
51
+ end
52
+
53
+ endpoint = parsed.is_a?(Hash) ? parsed.dig('data', 'endpoint') : nil
54
+ if endpoint.nil? || endpoint.to_s.empty?
55
+ raise BrowserStack::LocalException.new(
56
+ "Binary endpoint API returned no endpoint (HTTP #{res.code})"
57
+ )
58
+ end
59
+
60
+ endpoint
61
+ end
62
+ end
63
+ end
@@ -64,7 +64,11 @@ class Local
64
64
  end
65
65
 
66
66
  @binary_path = if @binary_path.nil?
67
- BrowserStack::LocalBinary.new.binary_path
67
+ BrowserStack::LocalBinary.new(
68
+ auth_token: @key,
69
+ proxy_host: @proxy_host,
70
+ proxy_port: @proxy_port
71
+ ).binary_path
68
72
  else
69
73
  @binary_path
70
74
  end
@@ -3,28 +3,27 @@ require 'net/https'
3
3
  require 'rbconfig'
4
4
  require 'openssl'
5
5
  require 'tmpdir'
6
+ require 'fileutils'
6
7
  require 'browserstack/localexception'
8
+ require 'browserstack/fetch_download_source_url'
9
+ require 'browserstack/version'
7
10
 
8
11
  module BrowserStack
9
-
12
+
10
13
  class LocalBinary
11
- def initialize
12
- host_os = RbConfig::CONFIG['host_os']
13
- @http_path = case host_os
14
- when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
15
- @windows = true
16
- "https://www.browserstack.com/local-testing/downloads/binaries/BrowserStackLocal.exe"
17
- when /darwin|mac os/
18
- "https://www.browserstack.com/local-testing/downloads/binaries/BrowserStackLocal-darwin-x64"
19
- when /linux-musl/
20
- "https://www.browserstack.com/local-testing/downloads/binaries/BrowserStackLocal-alpine"
21
- when /linux/
22
- if 1.size == 8
23
- "https://www.browserstack.com/local-testing/downloads/binaries/BrowserStackLocal-linux-x64"
24
- else
25
- "https://www.browserstack.com/local-testing/downloads/binaries/BrowserStackLocal-linux-ia32"
26
- end
27
- end
14
+ BASE_RETRIES = 9
15
+ FALLBACK_TRIGGER_RETRY = 4
16
+
17
+ def initialize(conf = {})
18
+ @auth_token = conf[:auth_token] || ENV['BROWSERSTACK_ACCESS_KEY']
19
+ @proxy_host = conf[:proxy_host]
20
+ @proxy_port = conf[:proxy_port]
21
+ @user_agent = conf[:user_agent] || "browserstack-local-ruby/#{BrowserStack::VERSION}"
22
+
23
+ @windows = !!(RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/)
24
+ @binary_filename = compute_binary_filename
25
+ @source_url = nil
26
+ @download_error_message = nil
28
27
 
29
28
  @ordered_paths = [
30
29
  File.join(File.expand_path('~'), '.browserstack'),
@@ -33,79 +32,125 @@ class LocalBinary
33
32
  ]
34
33
  end
35
34
 
36
- def download(dest_parent_dir)
37
- unless File.exist? dest_parent_dir
38
- Dir.mkdir dest_parent_dir
39
- end
40
- uri = URI.parse(@http_path)
41
- binary_path = File.join(dest_parent_dir, "BrowserStackLocal#{".exe" if @windows}")
42
- http = Net::HTTP.new(uri.host, uri.port)
43
- http.use_ssl = true
44
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
45
-
46
- res = http.get(uri.path)
47
- file = open(binary_path, 'wb')
48
- file.write(res.body)
49
- file.close
50
- FileUtils.chmod 0755, binary_path
51
-
52
- binary_path
53
- end
35
+ def binary_path
36
+ dest_parent_dir = get_available_dirs
37
+ bin_path = File.join(dest_parent_dir, dest_binary_name)
54
38
 
55
- def verify_binary(binary_path)
56
- binary_response = IO.popen(binary_path + " --version").readline
57
- binary_response =~ /BrowserStack Local version \d+\.\d+/
58
- rescue Exception => e
59
- false
39
+ return bin_path if File.exist?(bin_path) && verify_binary(bin_path)
40
+
41
+ File.delete(bin_path) if File.exist?(bin_path)
42
+ download_with_retries(bin_path)
60
43
  end
61
44
 
62
- def binary_path
63
- dest_parent_dir = get_available_dirs
64
- binary_path = File.join(dest_parent_dir, "BrowserStackLocal#{".exe" if @windows}")
45
+ private
65
46
 
66
- if File.exist? binary_path
67
- binary_path
47
+ def dest_binary_name
48
+ @windows ? 'BrowserStackLocal.exe' : 'BrowserStackLocal'
49
+ end
50
+
51
+ def compute_binary_filename
52
+ host_os = RbConfig::CONFIG['host_os']
53
+ host_cpu = RbConfig::CONFIG['host_cpu']
54
+ case host_os
55
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
56
+ 'BrowserStackLocal.exe'
57
+ when /darwin|mac os/
58
+ 'BrowserStackLocal-darwin-x64'
59
+ when /linux/
60
+ if host_cpu =~ /arm64|aarch64/
61
+ 'BrowserStackLocal-linux-arm64'
62
+ elsif host_os =~ /linux-musl/
63
+ 'BrowserStackLocal-alpine'
64
+ elsif 1.size == 8
65
+ 'BrowserStackLocal-linux-x64'
66
+ else
67
+ 'BrowserStackLocal-linux-ia32'
68
+ end
68
69
  else
69
- binary_path = download(dest_parent_dir)
70
+ raise BrowserStack::LocalException.new("Unsupported host OS: #{host_os}")
70
71
  end
72
+ end
71
73
 
72
- valid_binary = verify_binary(binary_path)
73
-
74
- if valid_binary
75
- binary_path
76
- else
77
- binary_path = download(dest_parent_dir)
78
- valid_binary = verify_binary(binary_path)
79
- if valid_binary
80
- binary_path
81
- else
82
- raise BrowserStack::LocalException.new('BrowserStack Local binary is corrupt')
74
+ def download_with_retries(bin_path)
75
+ retries = BASE_RETRIES
76
+ while retries > 0
77
+ refresh_source_url(retries) if retries == BASE_RETRIES || retries == FALLBACK_TRIGGER_RETRY
78
+ begin
79
+ download_to(@source_url + '/' + @binary_filename, bin_path)
80
+ return bin_path if verify_binary(bin_path)
81
+ @download_error_message = 'Downloaded binary failed verification'
82
+ rescue StandardError => e
83
+ @download_error_message = "Download failed: #{e.message}"
83
84
  end
85
+ File.delete(bin_path) if File.exist?(bin_path)
86
+ retries -= 1
84
87
  end
88
+
89
+ raise BrowserStack::LocalException.new(
90
+ "Failed to download BrowserStack Local binary after #{BASE_RETRIES} attempts. " \
91
+ "Last error: #{@download_error_message}"
92
+ )
85
93
  end
86
94
 
87
- private
95
+ def refresh_source_url(retries)
96
+ is_fallback = (retries == FALLBACK_TRIGGER_RETRY) && !@download_error_message.nil?
97
+ begin
98
+ @source_url = BrowserStack::FetchDownloadSourceUrl.call(
99
+ auth_token: @auth_token,
100
+ user_agent: @user_agent,
101
+ fallback: is_fallback,
102
+ error_message: @download_error_message,
103
+ proxy_host: @proxy_host,
104
+ proxy_port: @proxy_port
105
+ )
106
+ rescue StandardError => e
107
+ raise if @source_url.nil?
108
+ @download_error_message = "Source URL refresh failed: #{e.message}"
109
+ end
110
+ end
111
+
112
+ def download_to(url, bin_path)
113
+ uri = URI.parse(url)
114
+ http_class = if @proxy_host && @proxy_port
115
+ Net::HTTP::Proxy(@proxy_host, @proxy_port.to_i)
116
+ else
117
+ Net::HTTP
118
+ end
119
+ http = http_class.new(uri.host, uri.port)
120
+ http.use_ssl = (uri.scheme == 'https')
121
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
122
+ http.open_timeout = 10
123
+ http.read_timeout = 30
124
+
125
+ req = Net::HTTP::Get.new(uri.request_uri)
126
+ req['User-Agent'] = @user_agent
127
+
128
+ res = http.request(req)
129
+ raise "HTTP #{res.code}" unless res.is_a?(Net::HTTPSuccess)
130
+
131
+ File.open(bin_path, 'wb') { |f| f.write(res.body) }
132
+ FileUtils.chmod 0755, bin_path
133
+ end
134
+
135
+ def verify_binary(bin_path)
136
+ binary_response = IO.popen(bin_path + " --version").readline
137
+ !!(binary_response =~ /BrowserStack Local version \d+\.\d+/)
138
+ rescue StandardError
139
+ false
140
+ end
88
141
 
89
142
  def get_available_dirs
90
- i = 0
91
- while i < @ordered_paths.size
92
- path = @ordered_paths[i]
93
- if make_path(path)
94
- return path
95
- else
96
- i += 1
97
- end
143
+ @ordered_paths.each do |path|
144
+ return path if make_path(path)
98
145
  end
99
146
  raise BrowserStack::LocalException.new('Error trying to download BrowserStack Local binary')
100
147
  end
101
148
 
102
149
  def make_path(path)
103
- begin
104
- FileUtils.mkdir_p path if !File.directory?(path)
105
- return true
106
- rescue Exception
107
- return false
108
- end
150
+ FileUtils.mkdir_p(path) unless File.directory?(path)
151
+ true
152
+ rescue StandardError
153
+ false
109
154
  end
110
155
  end
111
156
 
@@ -0,0 +1,3 @@
1
+ module BrowserStack
2
+ VERSION = '1.5.0'.freeze
3
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: browserstack-local
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.3
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - BrowserStack
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-24 00:00:00.000000000 Z
11
+ date: 2026-06-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Ruby bindings for BrowserStack Local
14
14
  email: support@browserstack.com
@@ -16,14 +16,16 @@ executables: []
16
16
  extensions: []
17
17
  extra_rdoc_files: []
18
18
  files:
19
+ - lib/browserstack/fetch_download_source_url.rb
19
20
  - lib/browserstack/local.rb
20
21
  - lib/browserstack/localbinary.rb
21
22
  - lib/browserstack/localexception.rb
23
+ - lib/browserstack/version.rb
22
24
  homepage: http://rubygems.org/gems/browserstack-local
23
25
  licenses:
24
26
  - MIT
25
27
  metadata: {}
26
- post_install_message:
28
+ post_install_message:
27
29
  rdoc_options: []
28
30
  require_paths:
29
31
  - lib
@@ -39,7 +41,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
39
41
  version: '0'
40
42
  requirements: []
41
43
  rubygems_version: 3.0.3.1
42
- signing_key:
44
+ signing_key:
43
45
  specification_version: 4
44
46
  summary: BrowserStack Local
45
47
  test_files: []