├── .rspec ├── Rakefile ├── lib └── ngrok │ ├── tunnel │ └── version.rb │ └── tunnel.rb ├── Gemfile ├── .gitignore ├── spec ├── spec_helper.rb └── tunnel │ └── tunnel_spec.rb ├── CHANGELOG.md ├── examples └── rack-server.rb ├── LICENSE.txt ├── ngrok-tunnel.gemspec └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | -------------------------------------------------------------------------------- /lib/ngrok/tunnel/version.rb: -------------------------------------------------------------------------------- 1 | module Ngrok 2 | class Tunnel 3 | VERSION = "2.1.1" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in ngrok-tunnel.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | test.log 16 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'ngrok/tunnel' 2 | require 'pry' 3 | 4 | RSpec.configure do |config| 5 | config.expect_with :rspec do |expectations| 6 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 7 | end 8 | 9 | config.mock_with :rspec do |mocks| 10 | mocks.verify_partial_doubles = true 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.1.1 2 | 3 | - Added support for `region` parameter 4 | - Added support for `host-header` parameter 5 | 6 | ## 2.1.0 7 | 8 | - Added support for non-local server using `addr` parameter 9 | 10 | ## 2.0.21 11 | 12 | - Added support for -inspect option 13 | 14 | ## 2.0.20 15 | 16 | - Added support for custom hostname 17 | 18 | ## 2.0.16 19 | 20 | - Supports ngrok v2.0.16 21 | -------------------------------------------------------------------------------- /examples/rack-server.rb: -------------------------------------------------------------------------------- 1 | # Add these to the end of a configuration file of your prefered web-server, 2 | # e.g. config/puma.rb, config/unicorn.rb, or config/thin.rb 3 | # Use ./.ngrok or ~/.ngrok as a config file. Don't forget to add it to `.gitignore' in the former case. 4 | # Set NGROK_INSPECT=false to disable the inspector web-server. 5 | unless ENV['RAILS_ENV'] == 'production' 6 | require 'ngrok/tunnel' 7 | options = {addr: ENV['PORT']} 8 | if File.file? '.ngrok' 9 | options[:config] = '.ngrok' 10 | elsif File.file? ENV['HOME'] + '/.ngrok' 11 | options[:config] = ENV['HOME'] + '/.ngrok' 12 | end 13 | if ENV['NGROK_INSPECT'] 14 | options[:inspect] = ENV['NGROK_INSPECT'] 15 | end 16 | puts "[NGROK] tunneling at " + Ngrok::Tunnel.start(options) 17 | unless ENV['NGROK_INSPECT'] == 'false' 18 | puts "[NGROK] inspector web interface listening at http://127.0.0.1:4040" 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Anton Bogdanovich 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /ngrok-tunnel.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'ngrok/tunnel/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "ngrok-tunnel" 8 | spec.version = Ngrok::Tunnel::VERSION 9 | spec.authors = ["Anton Bogdanovich"] 10 | spec.email = ["27bogdanovich@gmail.com"] 11 | spec.summary = %q{Ngrok-tunnel gem is a ruby wrapper for ngrok} 12 | spec.description = %q{Ngrok-tunnel gem is a ruby wrapper for ngrok} 13 | spec.homepage = "https://github.com/bogdanovich/ngrok-tunnel" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency("pry", "~> 0.10") 22 | spec.add_development_dependency("pry-byebug", "~> 2.0") 23 | spec.add_development_dependency("rspec", "~> 3.1") 24 | spec.add_development_dependency "bundler", "~> 1.7" 25 | spec.add_development_dependency "rake", "~> 10.0" 26 | end 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ngrok::Tunnel 2 | 3 | ## This package is not actively maintainted. Please use [ngrok-wrapper](https://github.com/texpert/ngrok-wrapper) instead. 4 | 5 | 6 | 7 | 8 | 9 | 10 | Ngrok-tunnel gem is a ruby wrapper for ngrok v2. 11 | 12 | [![Gem Version](https://badge.fury.io/rb/ngrok-tunnel.svg)](http://badge.fury.io/rb/ngrok-tunnel) [![Code Climate](https://codeclimate.com/github/bogdanovich/ngrok-tunnel/badges/gpa.svg)](https://codeclimate.com/github/bogdanovich/ngrok-tunnel) 13 | 14 | ## Installation 15 | 16 | *Note:* You must have ngrok v2+ installed available in your `PATH`. 17 | 18 | Add this line to your application's Gemfile: 19 | 20 | ```ruby 21 | gem 'ngrok-tunnel' 22 | ``` 23 | 24 | And then execute: 25 | 26 | $ bundle 27 | 28 | Or install it yourself as: 29 | 30 | $ gem install ngrok-tunnel 31 | 32 | ## Usage 33 | 34 | ```ruby 35 | require 'ngrok/tunnel' 36 | 37 | # spawn ngrok (default port 3001) 38 | Ngrok::Tunnel.start 39 | 40 | # ngrok local_port 41 | Ngrok::Tunnel.port 42 | => 3001 43 | 44 | # ngrok external url 45 | Ngrok::Tunnel.ngrok_url 46 | => "http://aaa0e65.ngrok.io" 47 | 48 | Ngrok::Tunnel.ngrok_url_https 49 | => "https://aaa0e65.ngrok.io" 50 | 51 | Ngrok::Tunnel.running? 52 | => true 53 | 54 | Ngrok::Tunnel.stopped? 55 | => false 56 | 57 | # ngrok process id 58 | Ngrok::Tunnel.pid 59 | => 27384 60 | 61 | # ngrok log file descriptor 62 | Ngrok::Tunnel.log 63 | => # 64 | 65 | # kill ngrok 66 | Ngrok::Tunnel.stop 67 | => :stopped 68 | 69 | ``` 70 | 71 | ```ruby 72 | # ngrok custom parameters 73 | Ngrok::Tunnel.start(addr: 'foo.dev:80', 74 | subdomain: 'MY_SUBDOMAIN', 75 | hostname: 'MY_HOSTNAME', 76 | authtoken: 'MY_TOKEN', 77 | inspect: false, 78 | log: 'ngrok.log', 79 | config: '~/.ngrok') 80 | 81 | ``` 82 | 83 | ### With Rails (Rack server) 84 | 85 | See [examples/rack-server.rb](examples/rack-server.rb) to get an idea how to use it along with a Rack server so that it automatically starts and stops when a Rack server does. 86 | 87 | ### With RSpec and Capybara 88 | 89 | Use this gem: [ngrok-rspec](https://github.com/bogdanovich/ngrok-rspec) 90 | 91 | ## Contributing 92 | 93 | 1. Fork it ( https://github.com/bogdanovich/ngrok-tunnel/fork ) 94 | 2. Create your feature branch (`git checkout -b my-new-feature`) 95 | 3. Commit your changes (`git commit -am 'Add some feature'`) 96 | 4. Push to the branch (`git push origin my-new-feature`) 97 | 5. Create a new Pull Request 98 | -------------------------------------------------------------------------------- /lib/ngrok/tunnel.rb: -------------------------------------------------------------------------------- 1 | require "ngrok/tunnel/version" 2 | require "tempfile" 3 | 4 | module Ngrok 5 | 6 | class NotFound < StandardError; end 7 | class FetchUrlError < StandardError; end 8 | class Error < StandardError; end 9 | 10 | class Tunnel 11 | 12 | class << self 13 | attr_reader :pid, :ngrok_url, :ngrok_url_https, :status 14 | 15 | def init(params = {}) 16 | # map old key 'port' to 'addr' to maintain backwards compatibility with versions 2.0.21 and earlier 17 | params[:addr] = params.delete(:port) if params.key?(:port) 18 | 19 | @params = {addr: 3001, timeout: 10, config: '/dev/null'}.merge(params) 20 | @status = :stopped unless @status 21 | end 22 | 23 | def start(params = {}) 24 | ensure_binary 25 | init(params) 26 | 27 | if stopped? 28 | @params[:log] = (@params[:log]) ? File.open(@params[:log], 'w+') : Tempfile.new('ngrok') 29 | @pid = spawn("exec ngrok http " + ngrok_exec_params) 30 | at_exit { Ngrok::Tunnel.stop } 31 | fetch_urls 32 | end 33 | 34 | @status = :running 35 | @ngrok_url 36 | end 37 | 38 | def stop 39 | if running? 40 | Process.kill(9, @pid) 41 | @ngrok_url = @ngrok_url_https = @pid = nil 42 | @status = :stopped 43 | end 44 | @status 45 | end 46 | 47 | def running? 48 | @status == :running 49 | end 50 | 51 | def stopped? 52 | @status == :stopped 53 | end 54 | 55 | def addr 56 | @params[:addr] 57 | end 58 | 59 | def port 60 | return addr if addr.is_a?(Numeric) 61 | addr.split(":").last.to_i 62 | end 63 | 64 | def log 65 | @params[:log] 66 | end 67 | 68 | def subdomain 69 | @params[:subdomain] 70 | end 71 | 72 | def authtoken 73 | @params[:authtoken] 74 | end 75 | 76 | def inherited(subclass) 77 | init 78 | end 79 | 80 | private 81 | 82 | def ngrok_exec_params 83 | exec_params = "-log=stdout -log-level=debug " 84 | exec_params << "-bind-tls=#{@params[:bind_tls]} " if @params.has_key? :bind_tls 85 | exec_params << "-region=#{@params[:region]} " if @params[:region] 86 | exec_params << "-host-header=#{@params[:host_header]} " if @params[:host_header] 87 | exec_params << "-authtoken=#{@params[:authtoken]} " if @params[:authtoken] 88 | exec_params << "-subdomain=#{@params[:subdomain]} " if @params[:subdomain] 89 | exec_params << "-hostname=#{@params[:hostname]} " if @params[:hostname] 90 | exec_params << "-inspect=#{@params[:inspect]} " if @params.has_key? :inspect 91 | exec_params << "-config=#{@params[:config]} #{@params[:addr]} > #{@params[:log].path}" 92 | end 93 | 94 | def fetch_urls 95 | @params[:timeout].times do 96 | log_content = @params[:log].read 97 | result = log_content.scan(/URL:(.+)\sProto:(http|https)\s/) 98 | if !result.empty? 99 | result = Hash[*result.flatten].invert 100 | @ngrok_url = result['http'] 101 | @ngrok_url_https = result['https'] 102 | return @ngrok_url if @ngrok_url 103 | return @ngrok_url_https if @ngrok_url_https 104 | end 105 | 106 | error = log_content.scan(/msg="command failed" err="([^"]+)"/).flatten 107 | unless error.empty? 108 | self.stop 109 | raise Ngrok::Error, error.first 110 | end 111 | 112 | sleep 1 113 | @params[:log].rewind 114 | end 115 | raise FetchUrlError, "Unable to fetch external url" 116 | @ngrok_url 117 | end 118 | 119 | def ensure_binary 120 | `ngrok version` 121 | rescue Errno::ENOENT 122 | raise Ngrok::NotFound, "Ngrok binary not found" 123 | end 124 | end 125 | 126 | init 127 | 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /spec/tunnel/tunnel_spec.rb: -------------------------------------------------------------------------------- 1 | describe Ngrok::Tunnel do 2 | 3 | describe "Before start" do 4 | 5 | it "is not running" do 6 | expect(Ngrok::Tunnel.running?).to be false 7 | end 8 | 9 | it "is stopped" do 10 | expect(Ngrok::Tunnel.stopped?).to be true 11 | end 12 | 13 | it "has :stopped status" do 14 | expect(Ngrok::Tunnel.status).to eq :stopped 15 | end 16 | 17 | end 18 | 19 | describe "After start" do 20 | 21 | before(:all) { Ngrok::Tunnel.start } 22 | after(:all) { Ngrok::Tunnel.stop } 23 | 24 | it "is running" do 25 | expect(Ngrok::Tunnel.running?).to be true 26 | end 27 | 28 | it "is not stopped" do 29 | expect(Ngrok::Tunnel.stopped?).to be false 30 | end 31 | 32 | it "has :running status" do 33 | expect(Ngrok::Tunnel.status).to eq :running 34 | end 35 | 36 | it "has correct port property" do 37 | expect(Ngrok::Tunnel.port).to eq(3001) 38 | end 39 | 40 | it "has correct addr property" do 41 | expect(Ngrok::Tunnel.addr).to eq(3001) 42 | end 43 | 44 | it "has valid ngrok_url" do 45 | expect(Ngrok::Tunnel.ngrok_url).to be =~ /http:\/\/.*ngrok\.io$/ 46 | end 47 | 48 | it "has valid ngrok_url_https" do 49 | expect(Ngrok::Tunnel.ngrok_url_https).to be =~ /https:\/\/.*ngrok\.io$/ 50 | end 51 | 52 | it "has correct pid property" do 53 | expect(Ngrok::Tunnel.pid).to be > 0 54 | end 55 | 56 | end 57 | 58 | describe "Custom log file" do 59 | it "uses custom log file" do 60 | Ngrok::Tunnel.start(log: 'test.log') 61 | expect(Ngrok::Tunnel.running?).to eq true 62 | expect(Ngrok::Tunnel.log.path).to eq 'test.log' 63 | Ngrok::Tunnel.stop 64 | expect(Ngrok::Tunnel.stopped?).to eq true 65 | end 66 | end 67 | 68 | describe "Custom subdomain" do 69 | it "fails without authtoken" do 70 | expect {Ngrok::Tunnel.start(subdomain: 'test-subdomain')}.to raise_error Ngrok::Error 71 | end 72 | 73 | it "fails with incorrect authtoken" do 74 | expect {Ngrok::Tunnel.start(subdomain: 'test-subdomain', authtoken: 'incorrect_token')}.to raise_error Ngrok::Error 75 | end 76 | end 77 | 78 | describe "Custom hostname" do 79 | it "fails without authtoken" do 80 | expect {Ngrok::Tunnel.start(hostname: 'example.com')}.to raise_error Ngrok::Error 81 | end 82 | 83 | it "fails with incorrect authtoken" do 84 | expect {Ngrok::Tunnel.start(hostname: 'example.com', authtoken: 'incorrect_token')}.to raise_error Ngrok::Error 85 | end 86 | end 87 | 88 | describe "Custom addr" do 89 | it "maps port param to addr" do 90 | port = 10010 91 | Ngrok::Tunnel.start(port: port) 92 | expect(Ngrok::Tunnel.addr).to eq port 93 | Ngrok::Tunnel.stop 94 | end 95 | 96 | it "returns just the port when the address contains a host" do 97 | addr = '192.168.0.5:10010' 98 | Ngrok::Tunnel.start(addr: addr) 99 | expect(Ngrok::Tunnel.port).to eq 10010 100 | Ngrok::Tunnel.stop 101 | end 102 | 103 | it "supports remote addresses" do 104 | addr = '192.168.0.5:10010' 105 | Ngrok::Tunnel.start(addr: addr) 106 | expect(Ngrok::Tunnel.addr).to eq addr 107 | Ngrok::Tunnel.stop 108 | end 109 | end 110 | 111 | describe "Custom region" do 112 | it "doesn't include the -region parameter when it is not provided" do 113 | Ngrok::Tunnel.start() 114 | expect(Ngrok::Tunnel.send(:ngrok_exec_params)).not_to include("-region=") 115 | Ngrok::Tunnel.stop 116 | end 117 | 118 | it "includes the -region parameter with the correct value when it is provided" do 119 | region = 'eu' 120 | Ngrok::Tunnel.start(region: region) 121 | expect(Ngrok::Tunnel.send(:ngrok_exec_params)).to include("-region=#{region}") 122 | Ngrok::Tunnel.stop 123 | end 124 | end 125 | 126 | describe "Custom bind-tls" do 127 | it "doesn't include the -bind-tls parameter when it is not provided" do 128 | Ngrok::Tunnel.start() 129 | expect(Ngrok::Tunnel.send(:ngrok_exec_params)).not_to include("-bind-tls=") 130 | Ngrok::Tunnel.stop 131 | end 132 | 133 | it "includes the -bind-tls parameter with the correct value when it is true" do 134 | bind_tls = true 135 | Ngrok::Tunnel.start(bind_tls: bind_tls) 136 | expect(Ngrok::Tunnel.send(:ngrok_exec_params)).to include("-bind-tls=#{bind_tls}") 137 | Ngrok::Tunnel.stop 138 | end 139 | 140 | it "includes the -bind-tls parameter with the correct value when it is false" do 141 | bind_tls = false 142 | Ngrok::Tunnel.start(bind_tls: bind_tls) 143 | expect(Ngrok::Tunnel.send(:ngrok_exec_params)).to include("-bind-tls=#{bind_tls}") 144 | Ngrok::Tunnel.stop 145 | end 146 | end 147 | 148 | describe "Custom host header" do 149 | it "doesn't include the -host-header parameter when it is not provided" do 150 | Ngrok::Tunnel.start() 151 | expect(Ngrok::Tunnel.send(:ngrok_exec_params)).not_to include("-host-header=") 152 | Ngrok::Tunnel.stop 153 | end 154 | 155 | it "includes the -host-header parameter with the correct value when it is provided" do 156 | host_header = 'foo.bar' 157 | Ngrok::Tunnel.start(host_header: host_header) 158 | expect(Ngrok::Tunnel.send(:ngrok_exec_params)).to include("-host-header=#{host_header}") 159 | Ngrok::Tunnel.stop 160 | end 161 | end 162 | 163 | describe "Custom parameters provided" do 164 | it "doesn't include the -inspect parameter when it is not provided" do 165 | Ngrok::Tunnel.start() 166 | expect(Ngrok::Tunnel.send(:ngrok_exec_params)).not_to include("-inspect=") 167 | Ngrok::Tunnel.stop 168 | end 169 | 170 | it "includes the -inspect parameter with the correct value when it is provided" do 171 | Ngrok::Tunnel.start(inspect: true) 172 | expect(Ngrok::Tunnel.send(:ngrok_exec_params)).to include("-inspect=true") 173 | Ngrok::Tunnel.stop 174 | 175 | Ngrok::Tunnel.start(inspect: false) 176 | expect(Ngrok::Tunnel.send(:ngrok_exec_params)).to include("-inspect=false") 177 | Ngrok::Tunnel.stop 178 | end 179 | end 180 | end 181 | --------------------------------------------------------------------------------