weblink 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +3 -0
- data.tar.gz.sig +0 -0
- data/CHANGELOG +3 -0
- data/VERSION +1 -0
- data/bin/weblink +76 -0
- data/lib/relay.rb +59 -0
- data/lib/weblink.rb +86 -0
- data/public/android-chrome-192x192.png +0 -0
- data/public/android-chrome-512x512.png +0 -0
- data/public/apple-touch-icon.png +0 -0
- data/public/favicon-16x16.png +0 -0
- data/public/favicon-32x32.png +0 -0
- data/public/favicon.ico +0 -0
- data/public/index.html +67 -0
- data/public/weblink.webmanifest +19 -0
- metadata +89 -0
- metadata.gz.sig +3 -0
checksums.yaml
ADDED
@@ -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
|
checksums.yaml.gz.sig
ADDED
data.tar.gz.sig
ADDED
Binary file
|
data/CHANGELOG
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/bin/weblink
ADDED
@@ -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
|
data/lib/relay.rb
ADDED
@@ -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
|
data/lib/weblink.rb
ADDED
@@ -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
|
Binary file
|
Binary file
|
data/public/favicon.ico
ADDED
Binary file
|
data/public/index.html
ADDED
@@ -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: []
|
metadata.gz.sig
ADDED