weblink 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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���