forward 0.3.3 → 1.0.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 +7 -0
- data/.gitignore +2 -0
- data/Gemfile +0 -2
- data/README.md +24 -0
- data/Rakefile +3 -1
- data/bin/forward +1 -1
- data/forward.gemspec +17 -11
- data/lib/forward/api/resource.rb +51 -83
- data/lib/forward/api/tunnel.rb +41 -68
- data/lib/forward/api/user.rb +14 -11
- data/lib/forward/api.rb +7 -26
- data/lib/forward/cli.rb +55 -253
- data/lib/forward/command/account.rb +69 -0
- data/lib/forward/command/base.rb +62 -0
- data/lib/forward/command/config.rb +64 -0
- data/lib/forward/command/tunnel.rb +178 -0
- data/lib/forward/common.rb +44 -0
- data/lib/forward/config.rb +75 -118
- data/lib/forward/request.rb +72 -0
- data/lib/forward/socket.rb +125 -0
- data/lib/forward/static/app.rb +157 -0
- data/lib/forward/static/directory.erb +142 -0
- data/lib/forward/tunnel.rb +102 -40
- data/lib/forward/version.rb +1 -1
- data/lib/forward.rb +80 -63
- data/test/api/resource_test.rb +70 -54
- data/test/api/tunnel_test.rb +50 -51
- data/test/api/user_test.rb +33 -20
- data/test/cli_test.rb +0 -126
- data/test/command/account_test.rb +26 -0
- data/test/command/tunnel_test.rb +133 -0
- data/test/config_test.rb +103 -54
- data/test/forward_test.rb +47 -0
- data/test/test_helper.rb +35 -26
- data/test/tunnel_test.rb +50 -22
- metadata +210 -169
- data/forwardhq.crt +0 -112
- data/lib/forward/api/client_log.rb +0 -20
- data/lib/forward/api/tunnel_key.rb +0 -18
- data/lib/forward/client.rb +0 -110
- data/lib/forward/error.rb +0 -12
- data/test/api/tunnel_key_test.rb +0 -28
- data/test/api_test.rb +0 -0
- data/test/client_test.rb +0 -8
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b188425121deedc6db3120b631fe576a3e4bbf67
|
4
|
+
data.tar.gz: 1371bf60526e65fd941b9e17398d5f004bd7f7b3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cce5f2e20c6788d420b1faa01e6eb778175cb7486271a58e531a0a1a35c75e49a5c943dbf183deedbf146aed8c7e2f16bd7ed6e8d2a09e9dc30ba2b3740d0429
|
7
|
+
data.tar.gz: 014d3a99305e8e755fd5544239442fb62e342a804e87d2eb918dd9abcfbc08c7f66f94aa7fc8e955bac76b6bf38c59d6a62c262083f2d033f1d5cdab3a549f8c
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -4,9 +4,33 @@ The ruby client for [https://forwardhq.com/](https://forwardhq.com/). Forward gi
|
|
4
4
|
|
5
5
|
## Usage
|
6
6
|
|
7
|
+
|
8
|
+
# login/logout/switch accounts
|
9
|
+
forward login
|
10
|
+
forward accounts
|
11
|
+
forward logout 50east*
|
12
|
+
forward default 50east*
|
13
|
+
|
14
|
+
forward account:login
|
15
|
+
forward account:logout
|
16
|
+
forward account:default
|
17
|
+
|
18
|
+
# use default account
|
19
|
+
forward .
|
20
|
+
forward ~/Dev/static/blah
|
21
|
+
forward 3000
|
22
|
+
forward foo.dev
|
23
|
+
forward blah.local
|
24
|
+
|
25
|
+
# use specific account/subdomain
|
26
|
+
forward . -s tarface
|
27
|
+
forward 3000 -s 50east
|
28
|
+
|
29
|
+
|
7
30
|
> forward <port> [options]
|
8
31
|
> forward <host> [options]
|
9
32
|
> forward <host:port> [options]
|
33
|
+
> forward <path> [options]
|
10
34
|
|
11
35
|
Description:
|
12
36
|
|
data/Rakefile
CHANGED
data/bin/forward
CHANGED
data/forward.gemspec
CHANGED
@@ -2,10 +2,11 @@
|
|
2
2
|
require File.expand_path('../lib/forward/version', __FILE__)
|
3
3
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
|
-
gem.authors = ['
|
6
|
-
gem.email = ['travis@50east.co']
|
7
|
-
gem.summary = 'Forward
|
5
|
+
gem.authors = ['Travis Dunn', 'Noah Burney']
|
6
|
+
gem.email = ['travis@50east.co', 'noah@50east.co']
|
7
|
+
gem.summary = 'Forward lets you get a link to localhost and share it with anyone over the web.'
|
8
8
|
gem.homepage = 'https://forwardhq.com'
|
9
|
+
gem.license = 'MIT'
|
9
10
|
|
10
11
|
gem.files = `git ls-files`.split($\)
|
11
12
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
@@ -14,13 +15,18 @@ Gem::Specification.new do |gem|
|
|
14
15
|
gem.require_paths = ['lib']
|
15
16
|
gem.version = Forward::VERSION
|
16
17
|
|
17
|
-
gem.add_dependency '
|
18
|
-
gem.add_dependency '
|
19
|
-
gem.add_dependency '
|
18
|
+
gem.add_dependency 'slop', '~> 3.6'
|
19
|
+
gem.add_dependency 'json_pure', '~> 1.8'
|
20
|
+
gem.add_dependency 'highline', '~> 1.6'
|
21
|
+
gem.add_dependency 'eventmachine', '~> 1.0.3'
|
22
|
+
gem.add_dependency 'thin', '~> 1.6'
|
23
|
+
gem.add_dependency 'em-http-request', '~> 1.1'
|
24
|
+
gem.add_dependency 'faye-websocket', '~> 0.8'
|
25
|
+
gem.add_dependency 'clipboard', '~> 1.0'
|
20
26
|
|
21
|
-
gem.add_development_dependency 'rake', '~> 10.
|
22
|
-
gem.add_development_dependency 'minitest', '~> 5.3
|
23
|
-
gem.add_development_dependency 'mocha', '~>
|
24
|
-
gem.add_development_dependency '
|
25
|
-
gem.add_development_dependency 'fakefs', '~> 0.
|
27
|
+
gem.add_development_dependency 'rake', '~> 10.3'
|
28
|
+
gem.add_development_dependency 'minitest', '~> 5.3'
|
29
|
+
gem.add_development_dependency 'mocha', '~> 1.0'
|
30
|
+
gem.add_development_dependency 'webmock', '~> 1.20'
|
31
|
+
gem.add_development_dependency 'fakefs', '~> 0.5'
|
26
32
|
end
|
data/lib/forward/api/resource.rb
CHANGED
@@ -5,113 +5,81 @@ require 'net/https'
|
|
5
5
|
require 'uri'
|
6
6
|
|
7
7
|
module Forward
|
8
|
-
module
|
8
|
+
module API
|
9
9
|
class Resource
|
10
|
+
include Common
|
11
|
+
extend Common
|
12
|
+
|
13
|
+
DEFAULT_ERROR_MESSAGE = "Unable to connect to API, please contact support@forwardhq.com".freeze
|
10
14
|
|
11
15
|
attr_accessor :http
|
12
16
|
attr_accessor :uri
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
class JSONify
|
19
|
+
def response(resp)
|
20
|
+
resp.response = JSON.parse(resp.response, symbolize_names: true)
|
21
|
+
rescue JSON::ParserError
|
22
|
+
Forward.logger.debug 'Unable to parse API response'
|
23
|
+
end
|
20
24
|
end
|
21
25
|
|
22
|
-
def
|
23
|
-
|
24
|
-
log(:debug, "Request: params `#{params.reject { |k,v| k == :password }.inspect }'")
|
25
|
-
build_request(method, params)
|
26
|
-
add_headers!
|
26
|
+
def initialize
|
27
|
+
@http = EM::HttpRequest.new(Forward::API.host)
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
parse_response(response)
|
29
|
+
@http.use JSONify
|
31
30
|
end
|
32
31
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
case @method
|
37
|
-
when :get
|
38
|
-
@request = Net::HTTP::Get.new(uri)
|
39
|
-
when :post
|
40
|
-
@request = Net::HTTP::Post.new(uri)
|
41
|
-
@request.body = params.to_json unless params.empty?
|
42
|
-
when :put
|
43
|
-
@request = Net::HTTP::Put.new(uri)
|
44
|
-
@request.body = params.to_json unless params.empty?
|
45
|
-
when :delete
|
46
|
-
@request = Net::HTTP::Delete.new(uri)
|
47
|
-
@request.body = params.to_json unless params.empty?
|
48
|
-
end
|
49
|
-
end
|
32
|
+
def request(method = :get, options = {}, &block)
|
33
|
+
logger.debug "[API] request: #{method.to_s.upcase} #{@http.uri}#{options[:path]}"
|
34
|
+
@options = options || {}
|
50
35
|
|
51
|
-
|
52
|
-
|
53
|
-
@request['Authorization'] = "Token token=#{Forward::Api.token}"
|
54
|
-
end
|
36
|
+
add_head!
|
37
|
+
add_body!
|
55
38
|
|
56
|
-
@
|
57
|
-
@request['Accept'] = 'application/json'
|
58
|
-
end
|
39
|
+
@_request = @http.send(method, @options)
|
59
40
|
|
60
|
-
|
61
|
-
|
62
|
-
end
|
41
|
+
@_request.callback {
|
42
|
+
status = @_request.response_header.status
|
63
43
|
|
64
|
-
|
65
|
-
|
66
|
-
|
44
|
+
logger.debug "[API] response: #{status} #{@_request.response}"
|
45
|
+
if @_request.response.is_a?(String) && !@_request.response.empty?
|
46
|
+
# no JSON response
|
47
|
+
exit_with_error DEFAULT_ERROR_MESSAGE
|
48
|
+
else
|
49
|
+
block.call(@_request.response, status)
|
50
|
+
end
|
51
|
+
}
|
67
52
|
|
68
|
-
|
69
|
-
|
53
|
+
@_request.errback {
|
54
|
+
exit_with_error DEFAULT_ERROR_MESSAGE
|
55
|
+
}
|
70
56
|
end
|
71
57
|
|
72
|
-
def
|
73
|
-
|
58
|
+
def get(options = {}, &block)
|
59
|
+
request(:get, options, &block)
|
74
60
|
end
|
75
61
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
if code == 404
|
81
|
-
raise ResourceNotFound
|
82
|
-
elsif ![ 200, 422, 401 ].include? code
|
83
|
-
raise BadResponse, "response code was: #{response.code}"
|
84
|
-
elsif response['content-type'] !~ /^application\/json/
|
85
|
-
raise BadResponse, "response was not JSON, unable to parse"
|
86
|
-
end
|
62
|
+
def post(options = {}, &block)
|
63
|
+
request(:post, options, &block)
|
64
|
+
end
|
87
65
|
|
88
|
-
|
66
|
+
def delete(options = {}, &block)
|
67
|
+
request(:delete, options, &block)
|
68
|
+
end
|
89
69
|
|
90
|
-
|
91
|
-
|
92
|
-
raise ResourceError.new(code, @action, json) if code != 200
|
93
|
-
end
|
70
|
+
def add_body!
|
71
|
+
return unless @options.has_key?(:params)
|
94
72
|
|
95
|
-
|
73
|
+
logger.debug "[API] request params: #{@options[:params].inspect}"
|
74
|
+
@options[:body] = @options.delete(:params).to_json
|
96
75
|
end
|
97
76
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
# else
|
105
|
-
# Forward::Client.cleanup_and_exit!('An error occured, please contact support@forwardhq.com')
|
106
|
-
# end
|
107
|
-
# end
|
108
|
-
|
109
|
-
private
|
110
|
-
|
111
|
-
def log(level, message)
|
112
|
-
unless self.class.to_s == 'Forward::Api::ClientLog'
|
113
|
-
Forward.log.send(level.to_sym, message)
|
114
|
-
end
|
77
|
+
def add_head!
|
78
|
+
@options[:head] ||= {
|
79
|
+
'content-type' => 'application/json',
|
80
|
+
'accept' => 'application/json'
|
81
|
+
}
|
82
|
+
@options[:head]['authorization'] = "Token token=#{config.api_key}" if @options[:authenticated]
|
115
83
|
end
|
116
84
|
|
117
85
|
end
|
data/lib/forward/api/tunnel.rb
CHANGED
@@ -1,85 +1,58 @@
|
|
1
1
|
module Forward
|
2
|
-
module
|
2
|
+
module API
|
3
3
|
class Tunnel < Resource
|
4
4
|
|
5
|
-
def self.create(options
|
6
|
-
resource
|
7
|
-
|
8
|
-
|
9
|
-
:
|
10
|
-
:
|
11
|
-
|
5
|
+
def self.create(options, &block)
|
6
|
+
resource = new
|
7
|
+
options = {
|
8
|
+
path: "#{API.base_path}/tunnels/",
|
9
|
+
authenticated: true,
|
10
|
+
params: {
|
11
|
+
hostport: options[:port],
|
12
|
+
vhost: options[:host],
|
13
|
+
subdomain: options[:subdomain_prefix],
|
14
|
+
cname: options[:cname],
|
15
|
+
username: options[:username],
|
16
|
+
password: options[:password],
|
17
|
+
no_auth: options[:no_auth],
|
18
|
+
client: Forward.client_string,
|
19
|
+
}
|
12
20
|
}
|
13
|
-
|
14
|
-
params[:subdomain] = options[:subdomain_prefix] if options.has_key?(:subdomain_prefix)
|
15
|
-
[ :cname, :username, :password, :no_auth ].each do |param|
|
16
|
-
params[param] = options[param] if options.has_key?(param)
|
17
|
-
end
|
18
|
-
|
19
|
-
resource.post(params)[:tunnel].symbolize_keys
|
20
|
-
rescue ResourceError => e
|
21
|
-
error_on_create(e, options)
|
22
|
-
end
|
23
21
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
def self.show(id)
|
32
|
-
resource = Tunnel.new(:show)
|
33
|
-
resource.uri = "/api/v2/tunnels/#{id}"
|
34
|
-
|
35
|
-
resource.get[:tunnel].symbolize_keys
|
36
|
-
rescue Forward::Api::ResourceNotFound
|
37
|
-
nil
|
22
|
+
resource.post(options) do |response, status|
|
23
|
+
if status == 200
|
24
|
+
block.call(response)
|
25
|
+
else
|
26
|
+
handle_error(response, status)
|
27
|
+
end
|
28
|
+
end
|
38
29
|
end
|
39
30
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
choose do |menu|
|
47
|
-
menu.prompt = "Choose a tunnel from the list to close or `q' to exit forward "
|
31
|
+
def self.destroy(id, &block)
|
32
|
+
resource = new
|
33
|
+
options = {
|
34
|
+
path: "#{API.base_path}/tunnels/#{id}",
|
35
|
+
authenticated: true,
|
36
|
+
}
|
48
37
|
|
49
|
-
|
50
|
-
|
51
|
-
menu.choice(text) { destroy_and_create(tunnel['_id'], options) }
|
52
|
-
end
|
53
|
-
menu.hidden('quit') { Forward::Client.cleanup_and_exit! }
|
54
|
-
menu.hidden('exit') { Forward::Client.cleanup_and_exit! }
|
38
|
+
resource.delete(options) do |response, status|
|
39
|
+
block.call(response)
|
55
40
|
end
|
56
41
|
end
|
57
42
|
|
58
|
-
|
59
|
-
Forward.log.debug("Destroying tunnel: #{id}")
|
60
|
-
destroy(id)
|
61
|
-
puts "tunnel removed, now we're creating a new one"
|
62
|
-
create(options)
|
63
|
-
end
|
43
|
+
private
|
64
44
|
|
65
|
-
def self.
|
66
|
-
|
45
|
+
def self.handle_error(response, status)
|
46
|
+
exit_with_error "Unable to authenticate your account. Try logging out with `forward account logout' or contact #{SUPPORT_EMAIL}" if status == 401
|
47
|
+
exit_with_error DEFAULT_ERROR_MESSAGE if response.is_a?(String) || !response.has_key?(:type)
|
67
48
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
49
|
+
case response[:type]
|
50
|
+
when 'invalid_request_error'
|
51
|
+
exit_with_error "Invalid tunnel parameters, try `forward --help' or contact us at #{SUPPORT_EMAIL}"
|
52
|
+
when 'account_error'
|
53
|
+
exit_with_error response[:errors][:account].first
|
73
54
|
else
|
74
|
-
|
75
|
-
error.errors.each do |key, value|
|
76
|
-
if key == 'base'
|
77
|
-
message << " #{value.join(', ')}\n"
|
78
|
-
else
|
79
|
-
value.each { |m| message << " #{key} #{m}\n"}
|
80
|
-
end
|
81
|
-
end
|
82
|
-
Forward::Client.cleanup_and_exit!(message)
|
55
|
+
exit_with_error DEFAULT_ERROR_MESSAGE
|
83
56
|
end
|
84
57
|
end
|
85
58
|
|
data/lib/forward/api/user.rb
CHANGED
@@ -1,18 +1,21 @@
|
|
1
1
|
module Forward
|
2
|
-
module
|
2
|
+
module API
|
3
3
|
class User < Resource
|
4
4
|
|
5
|
-
def self.
|
6
|
-
resource
|
7
|
-
|
8
|
-
|
5
|
+
def self.authenticate(email, password, &block)
|
6
|
+
resource = new
|
7
|
+
options = {
|
8
|
+
path: "#{API.base_path}/user/token",
|
9
|
+
params: { email: email, password: password }
|
10
|
+
}
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
resource.post(options) do |response, status|
|
13
|
+
if status != 200
|
14
|
+
exit_with_error "Unable to authenticate `#{email}' on forwardhq.com"
|
15
|
+
else
|
16
|
+
block.call(response[:subdomain], response[:token])
|
17
|
+
end
|
18
|
+
end
|
16
19
|
end
|
17
20
|
|
18
21
|
end
|
data/lib/forward/api.rb
CHANGED
@@ -1,40 +1,21 @@
|
|
1
1
|
require 'forward/api/resource'
|
2
|
-
require 'forward/api/client_log'
|
3
|
-
require 'forward/api/tunnel_key'
|
4
2
|
require 'forward/api/tunnel'
|
5
3
|
require 'forward/api/user'
|
6
4
|
|
7
5
|
module Forward
|
8
|
-
module
|
9
|
-
|
10
|
-
|
11
|
-
class ResourceError < StandardError
|
12
|
-
attr_reader :code, :type, :action, :api_message, :errors
|
13
|
-
|
14
|
-
def initialize(code, action, json = {})
|
15
|
-
@code = code
|
16
|
-
@action = action
|
17
|
-
@json = json
|
18
|
-
@type = json[:type]
|
19
|
-
@api_message = json[:message]
|
20
|
-
@errors = json[:errors] || {}
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
DEFAULT_API_HOST = 'https://forwardhq.com'
|
6
|
+
module API
|
7
|
+
VERSION = 'v3'.freeze
|
8
|
+
DEFAULT_API_HOST = 'https://forwardhq.com'.freeze
|
25
9
|
|
26
10
|
# Returns either an api host set in the environment or a set default.
|
27
11
|
#
|
28
12
|
# Returns a String containing the api host.
|
29
|
-
def self.
|
30
|
-
|
13
|
+
def self.host
|
14
|
+
@host ||= ENV['FORWARD_API_HOST'] || DEFAULT_API_HOST
|
31
15
|
end
|
32
16
|
|
33
|
-
|
34
|
-
|
35
|
-
# Returns a Boolean.
|
36
|
-
def self.ssl?
|
37
|
-
uri.scheme == 'https'
|
17
|
+
def self.base_path
|
18
|
+
@base_path ||= "/api/#{VERSION}"
|
38
19
|
end
|
39
20
|
|
40
21
|
def self.token=(token)
|