terminalwire 0.1.1 → 0.1.2
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 +4 -4
- data/exe/terminalwire-exec +1 -1
- data/lib/generators/terminalwire/install/install_generator.rb +1 -1
- data/lib/generators/terminalwire/install/templates/application_terminal.rb.tt +19 -1
- data/lib/terminalwire/adapter.rb +32 -0
- data/lib/terminalwire/client/entitlement.rb +107 -0
- data/lib/terminalwire/client/{binary.rb → exec.rb} +2 -2
- data/lib/terminalwire/client/resource.rb +154 -0
- data/lib/terminalwire/client.rb +46 -168
- data/lib/terminalwire/logging.rb +8 -0
- data/lib/terminalwire/rails.rb +69 -0
- data/lib/terminalwire/server.rb +104 -111
- data/lib/terminalwire/thor.rb +21 -13
- data/lib/terminalwire/transport.rb +72 -10
- data/lib/terminalwire/version.rb +1 -1
- data/lib/terminalwire.rb +13 -108
- metadata +23 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8eacca5d9e410385fee49a9115cdc9d92ac1276b0666af88b8c343f11306a05f
|
4
|
+
data.tar.gz: c94050aef56399743f202c13c3813bb66a05cfffd2333ea9431bccded43234f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a25a33121a603fdcb11c5c664d17772f999d4a379379390ad578ed434fa75428f45deda6d2023e490ecf2bedfad61cf58a9fd4d7c880e17fff714978075159c
|
7
|
+
data.tar.gz: 76e5bf2edd88ad0ca092328514ebce7bc22ee64ff13aa4ebf83c77319849fe2564515cc89bf27364f3135ca2456143869066ca6eaef70daae15dabedb006b3e3
|
data/exe/terminalwire-exec
CHANGED
@@ -17,7 +17,7 @@ class Terminalwire::InstallGenerator < Rails::Generators::Base
|
|
17
17
|
def add_route
|
18
18
|
route <<~ROUTE
|
19
19
|
match "/terminal",
|
20
|
-
to: Terminalwire::
|
20
|
+
to: Terminalwire::Server::Thor.new(ApplicationTerminal),
|
21
21
|
via: [:get, :connect]
|
22
22
|
ROUTE
|
23
23
|
end
|
@@ -15,7 +15,7 @@ class ApplicationTerminal < Thor
|
|
15
15
|
email = gets
|
16
16
|
|
17
17
|
print "Password: "
|
18
|
-
password =
|
18
|
+
password = getpass
|
19
19
|
|
20
20
|
if self.current_user = User.authenticate(email, password)
|
21
21
|
puts "Successfully logged in as #{user.email}."
|
@@ -24,9 +24,27 @@ class ApplicationTerminal < Thor
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
+
desc "whoami", "Displays current user information."
|
28
|
+
def whoami
|
29
|
+
if self.current_user
|
30
|
+
puts "Logged in as #{user.email}."
|
31
|
+
else
|
32
|
+
puts "Not logged in. Run `#{self.class.basename} login` to login."
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "logout", "Logout of your account"
|
37
|
+
def logout
|
38
|
+
session.reset
|
39
|
+
puts "Successfully logged out."
|
40
|
+
end
|
41
|
+
|
27
42
|
private
|
28
43
|
|
29
44
|
def current_user=(user)
|
45
|
+
# The Session object is a hash-like object that encrypts and signs a hash that's
|
46
|
+
# stored on the client's file sytem. Conceptually, it's similar to Rails signed
|
47
|
+
# and encrypted client-side cookies.
|
30
48
|
session["user_id"] = user.id
|
31
49
|
end
|
32
50
|
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'msgpack'
|
2
|
+
|
3
|
+
module Terminalwire
|
4
|
+
class Adapter
|
5
|
+
include Logging
|
6
|
+
|
7
|
+
attr_reader :transport
|
8
|
+
|
9
|
+
def initialize(transport)
|
10
|
+
@transport = transport
|
11
|
+
end
|
12
|
+
|
13
|
+
def write(data)
|
14
|
+
logger.debug "Adapter: Sending #{data.inspect}"
|
15
|
+
packed_data = MessagePack.pack(data, symbolize_keys: true)
|
16
|
+
@transport.write(packed_data)
|
17
|
+
end
|
18
|
+
|
19
|
+
def recv
|
20
|
+
logger.debug "Adapter: Reading"
|
21
|
+
packed_data = @transport.read
|
22
|
+
return nil if packed_data.nil?
|
23
|
+
data = MessagePack.unpack(packed_data, symbolize_keys: true)
|
24
|
+
logger.debug "Adapter: Received #{data.inspect}"
|
25
|
+
data
|
26
|
+
end
|
27
|
+
|
28
|
+
def close
|
29
|
+
@transport.close
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Terminalwire::Client
|
2
|
+
class Entitlement
|
3
|
+
class Paths
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@permitted = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def each(&)
|
11
|
+
@permitted.each(&)
|
12
|
+
end
|
13
|
+
|
14
|
+
def permit(path)
|
15
|
+
@permitted.append Pathname.new(path).expand_path
|
16
|
+
end
|
17
|
+
|
18
|
+
def permitted?(path)
|
19
|
+
@permitted.find { |pattern| matches?(permitted: pattern, path:) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def serialize
|
23
|
+
@permitted.to_a.map(&:to_s)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def matches?(permitted:, path:)
|
28
|
+
# This MUST be done via File.fnmatch because Pathname#fnmatch does not work. If you
|
29
|
+
# try changing this 🚨 YOU MAY CIRCUMVENT THE SECURITY MEASURES IN PLACE. 🚨
|
30
|
+
File.fnmatch permitted.to_s, File.expand_path(path), File::FNM_PATHNAME
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Schemes
|
35
|
+
include Enumerable
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
@permitted = Set.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def each(&)
|
42
|
+
@permitted.each(&)
|
43
|
+
end
|
44
|
+
|
45
|
+
def permit(scheme)
|
46
|
+
@permitted << scheme.to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
def permitted?(url)
|
50
|
+
include? URI(url).scheme
|
51
|
+
end
|
52
|
+
|
53
|
+
def serialize
|
54
|
+
@permitted.to_a.map(&:to_s)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
attr_reader :paths, :authority, :schemes
|
59
|
+
|
60
|
+
def initialize(authority:)
|
61
|
+
@authority = authority
|
62
|
+
@paths = Paths.new
|
63
|
+
|
64
|
+
# Permit the domain directory. This is necessary for basic operation of the client.
|
65
|
+
@paths.permit storage_path
|
66
|
+
@paths.permit storage_pattern
|
67
|
+
|
68
|
+
@schemes = Schemes.new
|
69
|
+
# Permit http & https by default.
|
70
|
+
@schemes.permit "http"
|
71
|
+
@schemes.permit "https"
|
72
|
+
end
|
73
|
+
|
74
|
+
def domain_path
|
75
|
+
Pathname.new("~/.terminalwire/authorities/#{@authority}").expand_path
|
76
|
+
end
|
77
|
+
|
78
|
+
def storage_path
|
79
|
+
domain_path.join("storage")
|
80
|
+
end
|
81
|
+
|
82
|
+
def storage_pattern
|
83
|
+
storage_path.join("**/*")
|
84
|
+
end
|
85
|
+
|
86
|
+
def serialize
|
87
|
+
{
|
88
|
+
authority: @authority,
|
89
|
+
schemes: @schemes.serialize,
|
90
|
+
paths: @paths.serialize,
|
91
|
+
storage_path: storage_path.to_s,
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.from_url(url)
|
96
|
+
# I had to lift this from URI::HTTP because `ws://` doesn't
|
97
|
+
# have an authority method.
|
98
|
+
authority = if url.port == url.default_port
|
99
|
+
url.host
|
100
|
+
else
|
101
|
+
"#{url.host}:#{url.port}"
|
102
|
+
end
|
103
|
+
|
104
|
+
new authority:
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -3,7 +3,7 @@ require "yaml"
|
|
3
3
|
require "uri"
|
4
4
|
|
5
5
|
module Terminalwire::Client
|
6
|
-
class
|
6
|
+
class Exec
|
7
7
|
attr_reader :arguments, :path, :configuration, :url
|
8
8
|
|
9
9
|
def initialize(path:, arguments:)
|
@@ -23,7 +23,7 @@ module Terminalwire::Client
|
|
23
23
|
Terminalwire::Client.websocket(url:, arguments:)
|
24
24
|
end
|
25
25
|
|
26
|
-
def self.
|
26
|
+
def self.start
|
27
27
|
case ARGV
|
28
28
|
in path, *arguments
|
29
29
|
new(path:, arguments:).start
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module Terminalwire::Client::Resource
|
2
|
+
# Dispatches messages from the Client::Handler to the appropriate resource.
|
3
|
+
class Handler
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@resources = {}
|
8
|
+
yield self if block_given?
|
9
|
+
end
|
10
|
+
|
11
|
+
def each(&block)
|
12
|
+
@resources.values.each(&block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(resource)
|
16
|
+
# Detect if the resource is already registered and throw an error
|
17
|
+
if @resources.key?(resource.name)
|
18
|
+
raise "Resource #{resource.name} already registered"
|
19
|
+
else
|
20
|
+
@resources[resource.name] = resource
|
21
|
+
end
|
22
|
+
end
|
23
|
+
alias :<< :add
|
24
|
+
|
25
|
+
def dispatch(**message)
|
26
|
+
case message
|
27
|
+
in { event:, action:, name:, command:, parameters: }
|
28
|
+
resource = @resources.fetch(name)
|
29
|
+
resource.command(command, **parameters)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Dispatcher, security, and response macros for resources.
|
35
|
+
class Base < Terminalwire::Resource::Base
|
36
|
+
def initialize(*, entitlement:, **)
|
37
|
+
super(*, **)
|
38
|
+
@entitlement = entitlement
|
39
|
+
connect
|
40
|
+
end
|
41
|
+
|
42
|
+
def command(command, **parameters)
|
43
|
+
begin
|
44
|
+
if permit(command, **parameters)
|
45
|
+
succeed self.public_send(command, **parameters)
|
46
|
+
else
|
47
|
+
fail "Client denied #{command}", command:, parameters:
|
48
|
+
end
|
49
|
+
rescue => e
|
50
|
+
fail e.message, command:, parameters:
|
51
|
+
raise
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def permit(...)
|
56
|
+
false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class STDOUT < Base
|
61
|
+
def connect
|
62
|
+
@io = $stdout
|
63
|
+
end
|
64
|
+
|
65
|
+
def print(data:)
|
66
|
+
@io.print(data)
|
67
|
+
end
|
68
|
+
|
69
|
+
def print_line(data:)
|
70
|
+
@io.puts(data)
|
71
|
+
end
|
72
|
+
|
73
|
+
def permit(...)
|
74
|
+
true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class STDERR < STDOUT
|
79
|
+
def connect
|
80
|
+
@io = $stderr
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class STDIN < Base
|
85
|
+
def connect
|
86
|
+
@io = $stdin
|
87
|
+
end
|
88
|
+
|
89
|
+
def read_line
|
90
|
+
@io.gets
|
91
|
+
end
|
92
|
+
|
93
|
+
def read_password
|
94
|
+
@io.getpass
|
95
|
+
end
|
96
|
+
|
97
|
+
def permit(...)
|
98
|
+
true
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class File < Base
|
103
|
+
File = ::File
|
104
|
+
|
105
|
+
# Ensure the default file mode is read/write for owner only. This ensures
|
106
|
+
# that if the server tries uploading an executable file, it won't be when it
|
107
|
+
# lands on the client.
|
108
|
+
#
|
109
|
+
# Eventually we'll move this into entitlements so the client can set maximum
|
110
|
+
# permissions for files and directories.
|
111
|
+
FILE_PERMISSIONS = 0o600 # rw-------
|
112
|
+
|
113
|
+
def read(path:)
|
114
|
+
File.read File.expand_path(path)
|
115
|
+
end
|
116
|
+
|
117
|
+
def write(path:, content:)
|
118
|
+
File.open(File.expand_path(path), "w", FILE_PERMISSIONS) { |f| f.write(content) }
|
119
|
+
end
|
120
|
+
|
121
|
+
def append(path:, content:)
|
122
|
+
File.open(File.expand_path(path), "a", FILE_PERMISSIONS) { |f| f.write(content) }
|
123
|
+
end
|
124
|
+
|
125
|
+
def mkdir(path:)
|
126
|
+
FileUtils.mkdir_p(File.expand_path(path))
|
127
|
+
end
|
128
|
+
|
129
|
+
def delete(path:)
|
130
|
+
File.delete(File.expand_path(path))
|
131
|
+
end
|
132
|
+
|
133
|
+
def exist(path:)
|
134
|
+
File.exist? File.expand_path(path)
|
135
|
+
end
|
136
|
+
|
137
|
+
def permit(command, path:, **)
|
138
|
+
@entitlement.paths.permitted? path
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class Browser < Base
|
143
|
+
def permit(command, url:, **)
|
144
|
+
@entitlement.schemes.permitted? url
|
145
|
+
end
|
146
|
+
|
147
|
+
def launch(url:)
|
148
|
+
Launchy.open(URI(url))
|
149
|
+
# TODO: This is a hack to get the `respond` method to work.
|
150
|
+
# Maybe explicitly call a `suceed` and `fail` method?
|
151
|
+
nil
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
data/lib/terminalwire/client.rb
CHANGED
@@ -1,199 +1,76 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'launchy'
|
3
|
+
require 'io/console'
|
4
|
+
|
1
5
|
module Terminalwire
|
2
6
|
module Client
|
3
|
-
module Resource
|
4
|
-
class IO < Terminalwire::Resource::Base
|
5
|
-
def dispatch(action, data)
|
6
|
-
if @device.respond_to?(action)
|
7
|
-
respond @device.public_send(action, data)
|
8
|
-
else
|
9
|
-
raise "Unknown action #{action} for device ID #{@id}"
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
class STDOUT < IO
|
15
|
-
def connect
|
16
|
-
@device = $stdout
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
class STDIN < IO
|
21
|
-
def connect
|
22
|
-
@device = $stdin
|
23
|
-
end
|
24
|
-
|
25
|
-
def dispatch(action, data)
|
26
|
-
respond case action
|
27
|
-
when "puts"
|
28
|
-
@device.puts(data)
|
29
|
-
when "gets"
|
30
|
-
@device.gets
|
31
|
-
when "getpass"
|
32
|
-
@device.getpass
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
class STDERR < IO
|
38
|
-
def connect
|
39
|
-
@device = $stderr
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
class File < Terminalwire::Resource::Base
|
44
|
-
def connect
|
45
|
-
@files = {}
|
46
|
-
end
|
47
|
-
|
48
|
-
def dispatch(action, data)
|
49
|
-
respond case action
|
50
|
-
when "read"
|
51
|
-
read_file(data)
|
52
|
-
when "write"
|
53
|
-
write_file(data.fetch(:path), data.fetch(:content))
|
54
|
-
when "append"
|
55
|
-
append_to_file(data.fetch(:path), data.fetch(:content))
|
56
|
-
when "mkdir"
|
57
|
-
mkdir(data.fetch(:path))
|
58
|
-
when "exist"
|
59
|
-
exist?(data.fetch(:path))
|
60
|
-
else
|
61
|
-
raise "Unknown action #{action} for file device"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def mkdir(path)
|
66
|
-
FileUtils.mkdir_p(::File.expand_path(path))
|
67
|
-
end
|
68
|
-
|
69
|
-
def exist?(path)
|
70
|
-
::File.exist? ::File.expand_path(path)
|
71
|
-
end
|
72
|
-
|
73
|
-
def read_file(path)
|
74
|
-
::File.read ::File.expand_path(path)
|
75
|
-
end
|
76
|
-
|
77
|
-
def write_file(path, content)
|
78
|
-
::File.open(::File.expand_path(path), "w") { |f| f.write(content) }
|
79
|
-
end
|
80
|
-
|
81
|
-
def append_to_file(path, content)
|
82
|
-
::File.open(::File.expand_path(path), "a") { |f| f.write(content) }
|
83
|
-
end
|
84
|
-
|
85
|
-
def disconnect
|
86
|
-
@files.clear
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
class Browser < Terminalwire::Resource::Base
|
91
|
-
def dispatch(action, data)
|
92
|
-
respond case action
|
93
|
-
when "launch"
|
94
|
-
Launchy.open(data)
|
95
|
-
"Launched browser with URL: #{data}"
|
96
|
-
else
|
97
|
-
raise "Unknown action #{action} for browser device"
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
class ResourceMapper
|
104
|
-
def initialize(connection, resources)
|
105
|
-
@connection = connection
|
106
|
-
@resources = resources
|
107
|
-
@devices = Hash.new { |h,k| h[Integer(k)] }
|
108
|
-
end
|
109
|
-
|
110
|
-
def connect_device(id, type)
|
111
|
-
klass = @resources.find(type)
|
112
|
-
if klass
|
113
|
-
device = klass.new(id, @connection)
|
114
|
-
device.connect
|
115
|
-
@devices[id] = device
|
116
|
-
@connection.write(event: "device", action: "connect", status: "success", id: id, type: type)
|
117
|
-
else
|
118
|
-
@connection.write(event: "device", action: "connect", status: "failure", id: id, type: type, message: "Unknown device type")
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
def dispatch(id, action, data)
|
123
|
-
device = @devices[id]
|
124
|
-
if device
|
125
|
-
device.dispatch(action, data)
|
126
|
-
else
|
127
|
-
raise "Unknown device ID: #{id}"
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
def disconnect_device(id)
|
132
|
-
device = @devices.delete(id)
|
133
|
-
device&.disconnect
|
134
|
-
@connection.write(event: "device", action: "disconnect", id: id)
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
7
|
class Handler
|
139
8
|
VERSION = "0.1.0".freeze
|
140
9
|
|
141
10
|
include Logging
|
142
11
|
|
143
|
-
attr_reader :
|
12
|
+
attr_reader :adapter, :entitlement, :resources
|
13
|
+
|
14
|
+
def initialize(adapter, arguments: ARGV, program_name: $0, entitlement:)
|
15
|
+
@entitlement = entitlement
|
16
|
+
@adapter = adapter
|
17
|
+
@program_arguments = arguments
|
18
|
+
@program_name = program_name
|
144
19
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
20
|
+
@resources = Resource::Handler.new do |it|
|
21
|
+
it << Resource::STDOUT.new("stdout", @adapter, entitlement:)
|
22
|
+
it << Resource::STDIN.new("stdin", @adapter, entitlement:)
|
23
|
+
it << Resource::STDERR.new("stderr", @adapter, entitlement:)
|
24
|
+
it << Resource::Browser.new("browser", @adapter, entitlement:)
|
25
|
+
it << Resource::File.new("file", @adapter, entitlement:)
|
26
|
+
end
|
149
27
|
end
|
150
28
|
|
151
29
|
def connect
|
152
|
-
@
|
153
|
-
|
154
|
-
|
30
|
+
@adapter.write(event: "initialization",
|
31
|
+
protocol: { version: VERSION },
|
32
|
+
entitlement: @entitlement.serialize,
|
33
|
+
program: {
|
34
|
+
name: @program_name,
|
35
|
+
arguments: @program_arguments
|
36
|
+
})
|
155
37
|
|
156
38
|
loop do
|
157
|
-
handle @
|
39
|
+
handle @adapter.recv
|
158
40
|
end
|
159
41
|
end
|
160
42
|
|
161
43
|
def handle(message)
|
162
44
|
case message
|
163
|
-
in { event: "
|
164
|
-
@
|
165
|
-
in { event: "device", action: "command", id:, command:, data: }
|
166
|
-
@devices.dispatch(id, command, data)
|
167
|
-
in { event: "device", action: "disconnect", id: }
|
168
|
-
@devices.disconnect_device(id)
|
45
|
+
in { event: "resource", action: "command", name:, parameters: }
|
46
|
+
@resources.dispatch(**message)
|
169
47
|
in { event: "exit", status: }
|
170
48
|
exit Integer(status)
|
171
49
|
end
|
172
50
|
end
|
173
|
-
|
174
|
-
def self.resources
|
175
|
-
ResourceRegistry.new.tap do |resources|
|
176
|
-
resources << Client::Resource::STDOUT
|
177
|
-
resources << Client::Resource::STDIN
|
178
|
-
resources << Client::Resource::STDERR
|
179
|
-
resources << Client::Resource::Browser
|
180
|
-
resources << Client::Resource::File
|
181
|
-
end
|
182
|
-
end
|
183
51
|
end
|
184
52
|
|
185
53
|
def self.tcp(...)
|
186
54
|
socket = TCPSocket.new(...)
|
187
55
|
transport = Terminalwire::Transport::Socket.new(socket)
|
188
|
-
|
189
|
-
Terminalwire::Client::Handler.new(
|
56
|
+
adapter = Terminalwire::Adapter.new(transport)
|
57
|
+
Terminalwire::Client::Handler.new(adapter)
|
190
58
|
end
|
191
59
|
|
192
60
|
def self.socket(...)
|
193
61
|
socket = UNIXSocket.new(...)
|
194
62
|
transport = Terminalwire::Transport::Socket.new(socket)
|
195
|
-
|
196
|
-
Terminalwire::Client::Handler.new(
|
63
|
+
adapter = Terminalwire::Adapter.new(transport)
|
64
|
+
Terminalwire::Client::Handler.new(adapter)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Extracted from HTTP. This is so we can
|
68
|
+
def self.authority(url)
|
69
|
+
if url.port == url.default_port
|
70
|
+
url.host
|
71
|
+
else
|
72
|
+
"#{url.host}:#{url.port}"
|
73
|
+
end
|
197
74
|
end
|
198
75
|
|
199
76
|
def self.websocket(url:, arguments: ARGV)
|
@@ -202,10 +79,11 @@ module Terminalwire
|
|
202
79
|
Async do |task|
|
203
80
|
endpoint = Async::HTTP::Endpoint.parse(url)
|
204
81
|
|
205
|
-
Async::WebSocket::Client.connect(endpoint) do |
|
206
|
-
transport = Terminalwire::Transport::WebSocket.new(
|
207
|
-
|
208
|
-
|
82
|
+
Async::WebSocket::Client.connect(endpoint) do |adapter|
|
83
|
+
transport = Terminalwire::Transport::WebSocket.new(adapter)
|
84
|
+
adapter = Terminalwire::Adapter.new(transport)
|
85
|
+
entitlement = Entitlement.from_url(url)
|
86
|
+
Terminalwire::Client::Handler.new(adapter, arguments:, entitlement:).connect
|
209
87
|
end
|
210
88
|
end
|
211
89
|
end
|