consign 0.1.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8c62636b6885f9f8bff11cae0039659e05ce5808744d52fca7afee29d9616183
4
+ data.tar.gz: 73a23bf8e80a50b3d0f1e12d6323887a761aa5eb8ddb0d2d5ed73fbe93e9c562
5
+ SHA512:
6
+ metadata.gz: b690e81fe0be9e0dc41a213db28dc094f78dd5a209301b0aabae1c3ce65b37cc0c4b0c5a82b0a68e269fa7be4ca06a61821003a547e608f0e649ff01ac877392
7
+ data.tar.gz: 5b9f940ceb11132378cd6ded5840a32ce80f015d74019ebd0c6878117f54e15415c8d867b2197030bbfd37b81692ebee14963bd238dd247c841797025c55ea42
@@ -0,0 +1,27 @@
1
+ module Consign
2
+ class Railtie < ::Rails::Railtie
3
+ config.before_configuration do
4
+ Consign.api_key = ENV["CONSIGN_API_KEY"]
5
+
6
+ host = Consign.local_mode ? ".lvh.me" : ".consign.dev"
7
+ Rails.application.config.hosts << host
8
+ end
9
+
10
+ server do
11
+ if Rails.env.development? && Consign.api_key.present?
12
+ Thread.new { Consign::Tunnel.start(port: detect_port) }
13
+ else
14
+ puts "[Consign] Tunnel disabled. Set CONSIGN_API_KEY to enable." if Rails.env.development?
15
+ end
16
+ end
17
+
18
+ private
19
+ def self.detect_port
20
+ if (idx = ARGV.index("-p") || ARGV.index("--port"))
21
+ ARGV[idx + 1].to_i
22
+ else
23
+ ENV.fetch("PORT", 3000).to_i
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,178 @@
1
+ require "socket"
2
+ require "net/http"
3
+ require "uri"
4
+ require "json"
5
+ require "openssl"
6
+
7
+ module Consign
8
+ class Tunnel
9
+ MAX_RETRIES = 10
10
+ RETRY_DELAY = 1
11
+
12
+ def self.start(port: nil)
13
+ new(port: port).start
14
+ end
15
+
16
+ def initialize(port: nil)
17
+ @local_port = port || ENV.fetch("PORT", "3000").to_i
18
+ end
19
+
20
+ def start
21
+ log "🔐 Authenticating with #{control_url}..."
22
+
23
+ if config = fetch_configuration
24
+ log "🔌 Connecting to tunnel server..."
25
+ run_tunnel_loop(config)
26
+ else
27
+ log "❌ Failed to authenticate after multiple attempts.", :error
28
+ end
29
+ end
30
+
31
+ private
32
+ def fetch_configuration
33
+ attempt_with_retries(max: MAX_RETRIES) do
34
+ response = post_authentication
35
+
36
+ if response.is_a?(Net::HTTPSuccess)
37
+ JSON.parse(response.body)
38
+ else
39
+ raise "Authentication failed: #{response.code}"
40
+ end
41
+ end
42
+ end
43
+
44
+ def run_tunnel_loop(config)
45
+ loop do
46
+ begin
47
+ connect_and_serve(config)
48
+ rescue Interrupt
49
+ break
50
+ rescue StandardError => e
51
+ log "⚠️ Tunnel disconnected: #{e.message}. Reconnecting...", :warn
52
+ sleep RETRY_DELAY
53
+ end
54
+ end
55
+ end
56
+
57
+ def connect_and_serve(config)
58
+ socket = open_socket(config["server_host"], config["server_port"])
59
+ return unless perform_handshake(socket, config)
60
+
61
+ log_connected(config)
62
+
63
+ while line = socket.gets
64
+ handle_request(socket, JSON.parse(line))
65
+ end
66
+ ensure
67
+ socket&.close
68
+ end
69
+
70
+ def open_socket(host, port)
71
+ socket = TCPSocket.new(host, port)
72
+ if port == 443
73
+ ctx = OpenSSL::SSL::SSLContext.new
74
+ socket = OpenSSL::SSL::SSLSocket.new(socket, ctx)
75
+ socket.hostname = host
76
+ socket.connect
77
+ end
78
+ socket
79
+ end
80
+
81
+ def perform_handshake(socket, config)
82
+ req = "GET /_connect HTTP/1.1\r\n" \
83
+ "Host: #{config['server_host']}\r\n" \
84
+ "Connection: Upgrade\r\n" \
85
+ "Upgrade: consign\r\n" \
86
+ "X-Consign-Subdomain: #{config['subdomain']}\r\n" \
87
+ "X-Consign-Token: #{config['otp']}\r\n" \
88
+ "\r\n"
89
+ socket.write(req)
90
+
91
+ read_handshake_response(socket)
92
+ end
93
+
94
+ def read_handshake_response(socket)
95
+ buffer = ""
96
+ while (line = socket.gets)
97
+ buffer += line
98
+ break if line == "\r\n"
99
+ end
100
+ buffer.start_with?("HTTP/1.1 101")
101
+ end
102
+
103
+ def handle_request(socket, req)
104
+ Thread.new do
105
+ begin
106
+ response = forward_to_local(req)
107
+ socket.puts({ id: req['id'], status: response.code.to_i, headers: response.to_hash, body: response.body }.to_json)
108
+ rescue StandardError => e
109
+ socket.puts({ id: req['id'], status: 502, headers: {}, body: "Bad Gateway: #{e.message}" }.to_json)
110
+ end
111
+ end
112
+ end
113
+
114
+ def forward_to_local(req)
115
+ uri = URI("http://127.0.0.1:#{@local_port}#{req['url']}")
116
+
117
+ Net::HTTP.start(uri.hostname, uri.port) do |http|
118
+ local_req = Net::HTTPGenericRequest.new(req['method'], true, true, req['url'])
119
+ req['headers']&.each { |k, v| local_req[k] = v.first }
120
+
121
+ if req['host']
122
+ local_req['Host'] = req['host']
123
+ local_req['X-Forwarded-Host'] = req['host']
124
+ local_req['X-Forwarded-Proto'] = "http" # or https if we were terminating TLS locally
125
+ local_req['X-Forwarded-Port'] = "80"
126
+ end
127
+
128
+ local_req.body = req['body'] if req['body']
129
+ http.request(local_req)
130
+ end
131
+ end
132
+
133
+ def post_authentication
134
+ uri = URI("#{control_url}/api/v1/tunnels")
135
+ req = Net::HTTP::Post.new(uri)
136
+ req["Authorization"] = "Token token=\"#{Consign.api_key}\""
137
+
138
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
139
+ http.request(req)
140
+ end
141
+ end
142
+
143
+ def attempt_with_retries(max:)
144
+ attempts = 0
145
+ begin
146
+ yield
147
+ rescue StandardError => e
148
+ attempts += 1
149
+ if attempts < max
150
+ sleep RETRY_DELAY
151
+ retry
152
+ end
153
+ nil
154
+ end
155
+ end
156
+
157
+ def control_url
158
+ ENV.fetch("CONSIGN_URL", default_host)
159
+ end
160
+
161
+ def default_host
162
+ Consign.local_mode ? "http://localhost:3000" : "https://www.consign.dev"
163
+ end
164
+
165
+ def log_connected(config)
166
+ display_host = config["server_host"]
167
+ display_host = "lvh.me" if display_host == "127.0.0.1"
168
+ display_host = display_host.sub(/^tunnel\./, "") if display_host.start_with?("tunnel.")
169
+
170
+ log "✅ Tunnel active: https://#{config['subdomain']}.#{display_host}"
171
+ end
172
+
173
+ def log(msg, level = :info)
174
+ output = "[Consign] #{msg}"
175
+ defined?(Rails) ? Rails.logger.send(level, output) : puts(output)
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,3 @@
1
+ module Consign
2
+ VERSION = "0.1.1"
3
+ end
data/lib/consign.rb ADDED
@@ -0,0 +1,15 @@
1
+ require "consign/version"
2
+ require "consign/railtie"
3
+ require "consign/tunnel"
4
+
5
+ module Consign
6
+ mattr_accessor :api_key
7
+ mattr_accessor :local_mode
8
+
9
+ @@environment = :production
10
+ @@local_mode = false
11
+
12
+ def self.configure
13
+ yield self
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: consign
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Consign Team
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: railties
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.0'
26
+ description: Expose your local Rails server to the world with a public subdomain.
27
+ email:
28
+ - hello@consign.dev
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/consign.rb
34
+ - lib/consign/railtie.rb
35
+ - lib/consign/tunnel.rb
36
+ - lib/consign/version.rb
37
+ homepage: https://www.consign.dev
38
+ licenses:
39
+ - MIT
40
+ metadata:
41
+ homepage_uri: https://www.consign.dev
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 3.0.0
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubygems_version: 4.0.3
57
+ specification_version: 4
58
+ summary: Zero-config tunneling for Rails
59
+ test_files: []