Module: Portless::Share::Ngrok

Defined in:
lib/portless/share/ngrok.rb

Overview

Expose the local app publicly via ngrok. We point ngrok at the backend port directly (with the app's Host header) rather than tunnelling through our self-signed TLS proxy — simpler and avoids cert-trust issues. Returns { pid:, url: } or nil. EXPERIMENTAL. Mirrors portless's ngrok.ts.

Constant Summary collapse

API =
"http://127.0.0.1:4040/api/tunnels"

Class Method Summary collapse

Class Method Details

.fetch(url) ⇒ Object



57
58
59
60
61
# File 'lib/portless/share/ngrok.rb', line 57

def fetch(url)
  Net::HTTP.get(URI(url))
rescue StandardError
  nil
end

.poll_public_url(tries: 25) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/portless/share/ngrok.rb', line 42

def poll_public_url(tries: 25)
  tries.times do
    sleep 0.3
    body = fetch(API)
    next unless body

    tunnels = JSON.parse(body)["tunnels"] || []
    url = tunnels.map { |t| t["public_url"] }.compact.find { |u| u.start_with?("https") }
    return url if url
  end
  nil
rescue StandardError
  nil
end

.start(hostname:, backend_port:) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/portless/share/ngrok.rb', line 18

def start(hostname:, backend_port:)
  return nil unless Portless.which("ngrok")

  pid = Process.spawn("ngrok", "http", backend_port.to_s, "--host-header=#{hostname}",
                      out: File::NULL, err: File::NULL)
  Process.detach(pid)

  if (url = poll_public_url)
    { pid: pid, url: url }
  else
    stop(pid: pid)
    nil
  end
rescue StandardError
  nil
end

.stop(handle) ⇒ Object



35
36
37
38
39
40
# File 'lib/portless/share/ngrok.rb', line 35

def stop(handle)
  pid = handle.is_a?(Hash) ? handle[:pid] : handle
  Process.kill("TERM", pid) if pid
rescue StandardError
  nil
end