class Ngrok::Wrapper

Constants

VERSION

Attributes

ngrok_url[R]
ngrok_url_https[R]
params[R]
pid[R]
status[R]

Public Class Methods

addr() click to toggle source
# File lib/ngrok/wrapper.rb, line 61
def addr
  @params[:addr]
end
inherited(subclass) click to toggle source
Calls superclass method
# File lib/ngrok/wrapper.rb, line 71
def inherited(subclass)
  super
  init
end
init(params = {}) click to toggle source
# File lib/ngrok/wrapper.rb, line 17
def init(params = {})
  # map old key 'port' to 'addr' to maintain backwards compatibility with versions 2.0.21 and earlier
  params[:addr] = params.delete(:port) if params.key?(:port)

  @params = { addr: 3001, timeout: 10, config: '/dev/null' }.merge!(params)
  @status ||= :stopped # rubocop:disable Naming/MemoizedInstanceVariableName
end
port() click to toggle source
# File lib/ngrok/wrapper.rb, line 65
def port
  return addr if addr.is_a?(Numeric)

  addr.split(':').last.to_i
end
running?() click to toggle source
# File lib/ngrok/wrapper.rb, line 53
def running?
  @status == :running
end
start(params = {}) click to toggle source
# File lib/ngrok/wrapper.rb, line 25
def start(params = {})
  ensure_binary
  init(params)

  persistent_ngrok = @params[:persistence] == true
  # Attempt to read the attributes of an existing process instead of starting a new process.
  try_params_from_running_ngrok if persistent_ngrok

  spawn_new_ngrok(persistent_ngrok: persistent_ngrok) if stopped?

  @status = :running
  if persistent_ngrok
    # Record the attributes of the new process so that it can be reused on a subsequent call.
    File.write(@persistence_file, { pid: @pid, ngrok_url: @ngrok_url, ngrok_url_https: @ngrok_url_https }.to_json)
  end

  @ngrok_url
end
stop() click to toggle source
# File lib/ngrok/wrapper.rb, line 44
def stop
  if running?
    Process.kill(9, @pid)
    @ngrok_url = @ngrok_url_https = @pid = nil
    @status = :stopped
  end
  @status
end
stopped?() click to toggle source
# File lib/ngrok/wrapper.rb, line 57
def stopped?
  @status == :stopped
end

Private Class Methods

ensure_binary() click to toggle source
# File lib/ngrok/wrapper.rb, line 187
def ensure_binary
  `ngrok version`
rescue Errno::ENOENT
  raise Ngrok::NotFound, 'Ngrok binary not found'
end
fetch_urls() click to toggle source
# File lib/ngrok/wrapper.rb, line 156
def fetch_urls
  @params[:timeout].times do
    break if scan_log_for_urls || !@error.empty?

    sleep 1
    @params[:log].rewind
  end

  @params[:log].close
  return if @ngrok_url || @ngrok_url_https

  stop
  raise FetchUrlError, 'Unable to fetch external url' if @error.empty?

  raise Ngrok::Error, @error.first
end
ngrok_exec_params() click to toggle source
# File lib/ngrok/wrapper.rb, line 148
def ngrok_exec_params
  exec_params = +'-log=stdout -log-level=debug '
  OPTIONAL_PARAMS.each do |opt|
    exec_params << "-#{opt.to_s.tr('_', '-')}=#{@params[opt]} " if @params.key?(opt)
  end
  exec_params << "-config #{@params[:config]} #{@params[:addr]} > #{@params[:log].path}"
end
ngrok_process_status_lines(refetch: false) click to toggle source
# File lib/ngrok/wrapper.rb, line 104
def ngrok_process_status_lines(refetch: false)
  return @ngrok_process_status_lines if defined?(@ngrok_process_status_lines) && !refetch

  @ngrok_process_status_lines = (`ps ax | grep "ngrok http"`).split("\n")
end
ngrok_running?(pid) click to toggle source
# File lib/ngrok/wrapper.rb, line 125
def ngrok_running?(pid)
  ngrok_process_status_lines.find do |line|
    # If found the Ngrok process with correct pid, tunneling on the port, specified in Ngrok::Wrapper.start params
    line.include?('ngrok http -log') && line.start_with?(pid) && line.end_with?(addr.to_s)
  end
end
parse_persistence_file() click to toggle source
# File lib/ngrok/wrapper.rb, line 78
def parse_persistence_file
  JSON.parse(File.read(@persistence_file))
rescue StandardError => _e # Catch all possible errors on reading and parsing the file
  nil
end
raise_if_similar_ngroks(pid) click to toggle source
# File lib/ngrok/wrapper.rb, line 84
def raise_if_similar_ngroks(pid)
  other_ngrok_on_port = ngrok_process_status_lines.find do |line|
    # If found an Ngrok process with other pid, tunneling on the port, specified in Ngrok::Wrapper.start params
    line.include?('ngrok http -log') && !line.start_with?(pid || '') && line.end_with?(addr.to_s)
  end

  raise Ngrok::Error, "ERROR: Other ngrok instances tunneling to port #{addr} found" if other_ngrok_on_port

  return unless pid

  tunnel_on_other_port = ngrok_process_status_lines.find do |line|
    # If the line starts with this pid, but the port is other than specified in Ngrok::Wrapper.start params
    line.include?('ngrok http -log') && line.start_with?(pid) && !line.end_with?(addr.to_s)
  end

  return unless tunnel_on_other_port

  raise Ngrok::Error, "ERROR: Ngrok pid ##{pid} tunneling on other port #{tunnel_on_other_port.split(':').last}"
end
scan_log_for_urls() click to toggle source
# File lib/ngrok/wrapper.rb, line 173
def scan_log_for_urls
  log_content = @params[:log].read
  result = log_content.scan(/URL:(.+)\sProto:(http|https)\s/)
  unless result.empty?
    result           = Hash[*result.flatten].invert
    @ngrok_url       = result['http']
    @ngrok_url_https = result['https']
    return true if @ngrok_url || @ngrok_url_https
  end

  @error = log_content.scan(/msg="command failed" err="([^"]+)"/).flatten
  false
end
spawn_new_ngrok(persistent_ngrok:) click to toggle source
# File lib/ngrok/wrapper.rb, line 132
def spawn_new_ngrok(persistent_ngrok:)
  raise_if_similar_ngroks(nil)
  # Prepare the log file into which ngrok output will be redirected in `ngrok_exec_params`
  @params[:log] = @params[:log] ? File.open(@params[:log], 'w+') : Tempfile.new('ngrok')
  if persistent_ngrok
    Process.spawn("exec nohup ngrok http #{ngrok_exec_params} &")
    @pid = ngrok_process_status_lines(refetch: true)
           .find { |line| line.include?('ngrok http -log') && line.end_with?(addr.to_s) }.split[0]
  else
    @pid = Process.spawn("exec ngrok http #{ngrok_exec_params}")
    at_exit { Ngrok::Wrapper.stop }
  end

  fetch_urls
end
try_params_from_running_ngrok() click to toggle source
# File lib/ngrok/wrapper.rb, line 110
def try_params_from_running_ngrok
  @persistence_file = @params[:persistence_file] || "#{File.dirname(@params[:config])}/ngrok-process.json"
  state = parse_persistence_file
  return unless (pid = state&.[]('pid'))

  raise_if_similar_ngroks(pid)

  return unless ngrok_running?(pid)

  @status = :running
  @pid = pid
  @ngrok_url = state['ngrok_url']
  @ngrok_url_https = state['ngrok_url_https']
end