Skip to content

Commit db4c3ad

Browse files
committed
Wait to open browser until the app route is ready
1 parent 20726af commit db4c3ad

2 files changed

Lines changed: 81 additions & 1 deletion

File tree

react_on_rails/lib/react_on_rails/dev/server_manager.rb

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require "English"
44
require "fileutils"
5+
require "net/http"
56
require "open3"
67
require "rainbow"
78
require "erb"
@@ -868,14 +869,23 @@ def build_local_url(port, route)
868869
"http://localhost:#{port}/#{normalized_route}"
869870
end
870871

872+
def build_request_path(route)
873+
normalized_route = route.to_s.strip
874+
return "/" if normalized_route.empty? || normalized_route == "/"
875+
876+
normalized_route = normalized_route.sub(%r{\A/+}, "")
877+
"/#{normalized_route}"
878+
end
879+
871880
def schedule_browser_open(port, route:, once:)
872881
return unless browser_auto_open_allowed?
873882
return if once && File.exist?(OPEN_BROWSER_ONCE_MARKER)
874883

875884
url = build_local_url(port, route)
885+
request_path = build_request_path(route)
876886
Thread.new do
877887
Thread.current.report_on_exception = false if Thread.current.respond_to?(:report_on_exception=)
878-
next unless wait_for_server_on_port(port)
888+
next unless wait_for_app_route(port, request_path)
879889
next unless open_browser(url)
880890

881891
mark_browser_opened_once if once
@@ -897,6 +907,32 @@ def wait_for_server_on_port(port)
897907
end
898908
end
899909

910+
def wait_for_app_route(port, request_path)
911+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + OPEN_BROWSER_WAIT_TIMEOUT
912+
913+
loop do
914+
return true if app_route_ready?(port, request_path)
915+
return false if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
916+
917+
sleep OPEN_BROWSER_POLL_INTERVAL
918+
end
919+
end
920+
921+
def app_route_ready?(port, request_path)
922+
return false unless localhost_port_open?(port)
923+
924+
response = http_get_localhost(port, request_path)
925+
response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPRedirection)
926+
end
927+
928+
def http_get_localhost(port, request_path)
929+
Net::HTTP.start("127.0.0.1", port, open_timeout: 1, read_timeout: 1) do |http|
930+
http.get(request_path)
931+
end
932+
rescue StandardError
933+
nil
934+
end
935+
900936
def localhost_port_open?(port)
901937
%w[127.0.0.1 ::1].any? do |host|
902938
Socket.tcp(host, port, connect_timeout: 1) do |socket|

react_on_rails/spec/react_on_rails/dev/server_manager_spec.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,50 @@ def mock_system_calls
256256
end
257257
end
258258

259+
describe "browser auto-open readiness" do
260+
it "normalizes routes to request paths" do
261+
expect(described_class.send(:build_request_path, nil)).to eq("/")
262+
expect(described_class.send(:build_request_path, "/")).to eq("/")
263+
expect(described_class.send(:build_request_path, "hello_world")).to eq("/hello_world")
264+
expect(described_class.send(:build_request_path, "/hello_server")).to eq("/hello_server")
265+
end
266+
267+
it "treats a successful response as ready" do
268+
response = Net::HTTPOK.new("1.1", "200", "OK")
269+
allow(described_class).to receive(:localhost_port_open?).with(3000).and_return(true)
270+
allow(described_class).to receive(:http_get_localhost).with(3000, "/").and_return(response)
271+
272+
expect(described_class.send(:app_route_ready?, 3000, "/")).to be true
273+
end
274+
275+
it "treats a redirect response as ready" do
276+
response = Net::HTTPFound.new("1.1", "302", "Found")
277+
allow(described_class).to receive(:localhost_port_open?).with(3000).and_return(true)
278+
allow(described_class).to receive(:http_get_localhost).with(3000, "/").and_return(response)
279+
280+
expect(described_class.send(:app_route_ready?, 3000, "/")).to be true
281+
end
282+
283+
it "does not treat a server error response as ready" do
284+
response = Net::HTTPInternalServerError.new("1.1", "500", "Internal Server Error")
285+
allow(described_class).to receive(:localhost_port_open?).with(3000).and_return(true)
286+
allow(described_class).to receive(:http_get_localhost).with(3000, "/").and_return(response)
287+
288+
expect(described_class.send(:app_route_ready?, 3000, "/")).to be false
289+
end
290+
291+
it "waits for the route to respond successfully before opening the browser" do
292+
allow(described_class).to receive(:browser_auto_open_allowed?).and_return(true)
293+
allow(File).to receive(:exist?).with(described_class::OPEN_BROWSER_ONCE_MARKER).and_return(false)
294+
allow(Thread).to receive(:new).and_yield
295+
allow(described_class).to receive(:wait_for_app_route).with(3000, "/").and_return(true)
296+
expect(described_class).to receive(:open_browser).with("http://localhost:3000").and_return(true)
297+
expect(described_class).to receive(:mark_browser_opened_once)
298+
299+
described_class.send(:schedule_browser_open, 3000, route: "/", once: true)
300+
end
301+
end
302+
259303
describe ".kill_processes" do
260304
before do
261305
allow_any_instance_of(Kernel).to receive(:`).and_return("")

0 commit comments

Comments
 (0)