weblink 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c268c95b2cdcec3669dbf5c90f0753bc28949b6098744bd7c9f9e42aac24bb97
4
+ data.tar.gz: 44f0363bf19eae1f40a5a00fd962f60134a1db2a12e36aeda7bba04fe5090855
5
+ SHA512:
6
+ metadata.gz: 72ba709dd3bbdc06582bb20a83ab50090d8a60859698f4a00576122b0cd9687ed348b24d4ddbadc99219011750b897e4596605369c46e75655a821c7a76c4716
7
+ data.tar.gz: d729d225e330f6ab9714bd078893eeeecccb5d56e85f0f5d39c92f4b192a4fd9f94243789b94e0008b6264906d6a33c50f59682cb5afb215b0f21aa649202515
@@ -0,0 +1,3 @@
1
+ ��M֑�ґ)��+&�}�C�Jy��.�������X+���QvSZ�D4/�B��H�b�p�G#)�p���]�4��U�
2
+ �@R�"D�L��0@N��UQ��P"=
3
+ �HXg9^mzٜϐ:�"�*ZS�h��i,�I}a��s����
Binary file
@@ -0,0 +1,3 @@
1
+ 1.0.0
2
+
3
+ * First public release
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env -S ruby -w
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
5
+
6
+ require 'optparse'
7
+ require 'uri'
8
+ require 'weblink'
9
+
10
+ opts = {}
11
+
12
+ op = OptionParser.new do |op|
13
+ op.summary_width = 24
14
+
15
+ op.banner =
16
+ "Usage: weblink [options]\n" \
17
+ 'Web browser gateway'
18
+
19
+ op.separator ''
20
+ op.separator 'Options'
21
+
22
+ op.on('-c', '--client', 'Starts weblink client (default)') do
23
+ opts[:client] = true
24
+ end
25
+
26
+ op.on('-s', '--server', 'Starts weblink server') do
27
+ opts[:server] = true
28
+ end
29
+
30
+ op.on('-a', '--host HOST', String, 'Address to bind to (default: 0.0.0.0)') do |host|
31
+ opts[:host] = host
32
+ end
33
+
34
+ op.on('-p', '--port PORT', Integer, 'Use PORT (default: 8000)') do |port|
35
+ opts[:port] = port
36
+ end
37
+
38
+ op.on('--proxy-host HOST', String, 'Address to bind proxy to(default: 0.0.0.0)') do |host|
39
+ opts[:proxy_host] = host
40
+ end
41
+
42
+ op.on('--proxy-port PORT', Integer, 'Use proxy PORT (default: 1080)') do |port|
43
+ opts[:proxy_port] = port
44
+ end
45
+
46
+ op.on('-v', '--version', 'Show version and exit') do
47
+ version = File.expand_path('../VERSION', __dir__)
48
+ puts(File.read(version))
49
+ exit
50
+ end
51
+
52
+ op.on_tail('-h', '--help', 'Print this help') do
53
+ puts(op)
54
+ exit
55
+ end
56
+ end
57
+
58
+ begin
59
+ op.parse!
60
+ rescue OptionParser::ParseError => e
61
+ op.abort(e)
62
+ else
63
+ opts[:client] = true unless opts[:client] || opts[:server]
64
+ if opts[:client] && (opts[:host] || opts[:port])
65
+ abort('Changing host or port is not supported yet in client mode')
66
+ end
67
+ opts[:host] ||= '0.0.0.0'
68
+ opts[:port] ||= 8000
69
+ opts[:proxy_host] ||= '0.0.0.0'
70
+ opts[:proxy_port] ||= 1080
71
+ end
72
+
73
+ weblink = Weblink.new(opts)
74
+ weblink.start
75
+
76
+ # vim: ft=ruby
@@ -0,0 +1,59 @@
1
+ require 'csv'
2
+ require 'time'
3
+
4
+ class Relay < EventMachine::Connection
5
+ def initialize(tag, xff = nil)
6
+ pause
7
+ @tag = tag
8
+ @xff = xff
9
+ @remote_port, @remote_ip = unpack_sockaddr_in(get_peername)
10
+ @websocket = nil
11
+ @ws_remote_ip = nil
12
+ @ws_remote_port = nil
13
+ end
14
+
15
+ def start(websocket)
16
+ @websocket = websocket
17
+ @websocket.onbinary do |msg|
18
+ log('recv', msg.bytesize)
19
+ send_data(msg)
20
+ end
21
+ @websocket.onerror { |err| log('ws/error', err) }
22
+ @websocket.onclose { close_connection_after_writing }
23
+ @ws_remote_port, @ws_remote_ip = unpack_sockaddr_in(websocket.get_peername)
24
+ resume
25
+ end
26
+
27
+ def receive_data(data)
28
+ log('sent', data.bytesize)
29
+ @websocket.send_binary(data)
30
+ end
31
+
32
+ def unbind
33
+ log('close')
34
+ @websocket&.close(1000) if @websocket&.state == :connected
35
+ end
36
+
37
+ private
38
+
39
+ def log(event, comment = nil)
40
+ row = [
41
+ Time.now.iso8601(3),
42
+ @tag,
43
+ @xff,
44
+ @remote_ip,
45
+ @remote_port&.to_s,
46
+ @ws_remote_ip,
47
+ @ws_remote_port&.to_s,
48
+ event,
49
+ comment&.to_s,
50
+ ]
51
+
52
+ puts CSV.generate_line(row, col_sep: ' ', write_nil_value: '-')
53
+ end
54
+
55
+ def unpack_sockaddr_in(sock)
56
+ Socket.unpack_sockaddr_in(sock) if sock
57
+ rescue ArgumentError
58
+ end
59
+ end
@@ -0,0 +1,86 @@
1
+ require 'em-websocket'
2
+ require 'eventmachine'
3
+ require 'socket'
4
+ require 'tempfile'
5
+ require 'relay'
6
+
7
+ class Weblink
8
+ def initialize(opts)
9
+ @opts = opts
10
+ @proxxxy_socket = Tempfile.new('weblink')
11
+ @websockets = EventMachine::Queue.new
12
+ end
13
+
14
+ def start
15
+ trap(:EXIT) { Process.waitall }
16
+
17
+ if @opts[:client]
18
+ public = File.expand_path('../public', __dir__)
19
+ spawn('ruby', '-run', '-ehttpd', '--', public, err: '/dev/null')
20
+
21
+ ip = Socket.getifaddrs.find { |ifa| ifa.addr.ipv4_private? }
22
+ puts "Open http://#{ip.addr.ip_address}:8080/ on your other device." if ip
23
+ end
24
+
25
+ if @opts[:server]
26
+ begin
27
+ spawn('proxxxy', "socks5://#{@proxxxy_socket.path}")
28
+ rescue Errno::ENOENT
29
+ abort('Please install proxxxy v2 to run weblink server')
30
+ end
31
+ end
32
+
33
+ EventMachine::WebSocket.start(@opts) do |ws|
34
+ ws.onopen do |handshake|
35
+ case handshake.path
36
+ when '/control'
37
+ start_client(ws)
38
+ puts 'Ready'
39
+ when '/client'
40
+ @websockets.push(ws)
41
+ when '/server'
42
+ proxy(ws, handshake)
43
+ else
44
+ warn("Unexpected request: #{handshake.path.inspect}")
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def start_client(control_ws, min_ws_num: 3)
53
+ host, port = @opts.values_at(:proxy_host, :proxy_port)
54
+ sig = EventMachine.start_server(host, port, Relay, 'client') do |rel|
55
+ # Dogpile effect
56
+ control_ws.send_text('more_ws') if @websockets.size < min_ws_num
57
+ @websockets.pop { |ws| rel.start(ws) }
58
+ end
59
+ control_ws.onclose { EventMachine.stop_server(sig) }
60
+ end
61
+
62
+ def proxy(ws, handshake)
63
+ unless @opts[:server]
64
+ warn 'weblink server is disabled'
65
+ return
66
+ end
67
+ xff = handshake.headers_downcased['x-forwarded-for']
68
+ with_retry(3) do
69
+ EventMachine.connect(@proxxxy_socket.path, Relay, 'server', xff) do |rel|
70
+ rel.start(ws)
71
+ end
72
+ end
73
+ end
74
+
75
+ def with_retry(timeout, wait: 0.1)
76
+ elapsed = 0
77
+ begin
78
+ yield
79
+ rescue RuntimeError
80
+ if elapsed < timeout
81
+ elapsed += sleep(wait)
82
+ retry
83
+ end
84
+ end
85
+ end
86
+ end
Binary file
Binary file
Binary file
@@ -0,0 +1,67 @@
1
+ <html>
2
+ <head>
3
+ <meta charset="utf-8" />
4
+ <title>weblink</title>
5
+ <link rel="manifest" href="/weblink.webmanifest">
6
+ <style>
7
+ body {
8
+ background-color: #3f9cff;
9
+ color: white;
10
+ }
11
+ h1 {
12
+ align-items: center;
13
+ display: flex;
14
+ font-family: sans-serif;
15
+ font-size: 8em;
16
+ justify-content: center;
17
+ height: 100%;
18
+ }
19
+ </style>
20
+ </head>
21
+ <body>
22
+ <h1>weblink</h1>
23
+ <script>
24
+ function connect({ client_addr, server_addr }) {
25
+ const client = new WebSocket(client_addr + "/client");
26
+ const server = new WebSocket(server_addr + "/server");
27
+
28
+ server.onerror = function(event) { console.error("server/error"); };
29
+ client.onerror = function(event) { console.error("client/error"); };
30
+
31
+ client.onopen = function() {
32
+ server.onopen = function() {
33
+ client.onmessage = function(msg) { server.send(msg.data); };
34
+ server.onmessage = function(msg) { client.send(msg.data); };
35
+ };
36
+ };
37
+
38
+ server.onclose = function(event) {
39
+ console.log("server/close", event.code, event.reason);
40
+
41
+ if (client.readyState === WebSocket.OPEN) client.close();
42
+ };
43
+
44
+ client.onclose = function(event) {
45
+ console.log("client/close", event.code, event.reason);
46
+
47
+ if (server.readyState === WebSocket.OPEN) server.close();
48
+ };
49
+ }
50
+
51
+ const url = new URL(location.href);
52
+ const client_addr = `ws://${location.hostname}:8000`;
53
+ const server_addr = url.searchParams.get("server") || "wss://weblinkapp.herokuapp.com";
54
+ const batch_size = Number(url.searchParams.get("batch")) || 4
55
+ const control = new WebSocket(client_addr + "/control");
56
+
57
+ control.onerror = function(event) { console.error("control/error"); };
58
+ control.onclose = function(event) { console.log("control/close"); };
59
+ control.onmessage = function(msg) {
60
+ for (var i = 0; i < batch_size; i++) {
61
+ connect({ client_addr, server_addr });
62
+ }
63
+ };
64
+ control.onmessage();
65
+ </script>
66
+ </body>
67
+ </html>
@@ -0,0 +1,19 @@
1
+ {
2
+ "background_color": "#3f9cff",
3
+ "display": "standalone",
4
+ "icons": [
5
+ {
6
+ "sizes": "192x192",
7
+ "src": "/android-chrome-192x192.png",
8
+ "type": "image/png"
9
+ },
10
+ {
11
+ "sizes": "512x512",
12
+ "src": "/android-chrome-512x512.png",
13
+ "type": "image/png"
14
+ }
15
+ ],
16
+ "name": "weblink",
17
+ "short_name": "weblink",
18
+ "theme_color": "#3f9cff"
19
+ }
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: weblink
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - soylent
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIIDGjCCAgKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDDA9lZmly
14
+ L0RDPXNveWxlbnQwHhcNMjAwMzA3MDEzNzUwWhcNMjEwMzA3MDEzNzUwWjAaMRgw
15
+ FgYDVQQDDA9lZmlyL0RDPXNveWxlbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
16
+ ggEKAoIBAQC2DMbzgA39U+3VTMjXn+0jnOQyLdmXQ5EXgSLKCgBLIcFTc9J47Th0
17
+ 7Yb/f4RzWh49/EkDBiDtLqFeKBYsj3q0e8tRCAs32NtVyl/4FDyJvWsK3R2tcXOV
18
+ qxs48J3CgG+rFLOcMC9YF4FPTkz4p3EYGFVjZTbiqyVVuIZzWtrwdZesBVgpBRyN
19
+ 8sEyNoi8vcDiOmEwf9/TVMTDf/wu6a+i3LNVGYWlvgMJRssaAnj/IbFFtPTz30Hx
20
+ eTUdfJu8YbwRspfFzcJGLf32E7vXfmHHqNzqjh4zD9sVpvTHbqLLsgVa+nYHPHAe
21
+ dzSZ5gZjG1oZ7hZDCJoEPj0oCHT0qkuXAgMBAAGjazBpMAkGA1UdEwQCMAAwCwYD
22
+ VR0PBAQDAgSwMB0GA1UdDgQWBBSsvp1HAfA+QTjOg/ehyhv7adp0FjAXBgNVHREE
23
+ EDAOgQxlZmlyQHNveWxlbnQwFwYDVR0SBBAwDoEMZWZpckBzb3lsZW50MA0GCSqG
24
+ SIb3DQEBCwUAA4IBAQBsJ7htnm3RB5xQwzC2agRAgG2ax5uaD6lCEPWshGJbfT6v
25
+ jaRrwrPSbaxBTD2v8QpXJe/fALJKWHUbNZilZU2t7HyQkfSyVQyLYcjo7lWFoHA1
26
+ z0YB3dCGcMkvLa7r73ynEtYbnYfesbXVlcRJG7SgJqWvZMnj1HnYfBevlcjSBFy2
27
+ 5xZKSwreHM+va8McxpEZmG3ecdefRQ1u+xZabamN2hhel3nKF1BUBqgxYWXYRkZP
28
+ VIAqJBK/gwFlRxVYjmi2w4Ouc4wL8HtX104yQqOuD9gVPN6PJecue66As7i2I/2q
29
+ EARmnubhy24GUdxEooHV6pOs1mLLRuFepMgBnHfs
30
+ -----END CERTIFICATE-----
31
+ date: 2020-12-23 00:00:00.000000000 Z
32
+ dependencies:
33
+ - !ruby/object:Gem::Dependency
34
+ name: em-websocket
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.5'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.5'
47
+ description:
48
+ email:
49
+ executables:
50
+ - weblink
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - CHANGELOG
55
+ - VERSION
56
+ - bin/weblink
57
+ - lib/relay.rb
58
+ - lib/weblink.rb
59
+ - public/android-chrome-192x192.png
60
+ - public/android-chrome-512x512.png
61
+ - public/apple-touch-icon.png
62
+ - public/favicon-16x16.png
63
+ - public/favicon-32x32.png
64
+ - public/favicon.ico
65
+ - public/index.html
66
+ - public/weblink.webmanifest
67
+ homepage: https://github.com/soylent/weblink
68
+ licenses: []
69
+ metadata: {}
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: 2.5.0
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubygems_version: 3.1.4
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: Web browser gateway
89
+ test_files: []
@@ -0,0 +1,3 @@
1
+ f���3ފ�z�K8��z� a���yZ����^Z�S�u��8|>j���?�#]ކ��J�u�R����ޮ�� �_�v�
2
+ �|�Ⴄ�C5m`���%/,��a)����kE7,��&✧/�x9s~�#�����:����Q�c�Mo��R K��<J3m� �s�͍{�7+�;e�f2*͛�-m�,
3
+ S6j��Qp���k�5��Vj��i�{�0�B�l� ��`'����!�q�ڦ�'��i�J)&r���