ruflet_rails 0.0.2 → 0.0.4
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/README.md +1 -3
- data/lib/ruflet/rails/protocol/local_server.rb +185 -0
- data/lib/ruflet/rails/protocol/runner.rb +1 -1
- data/lib/ruflet/rails/protocol/web_socket_connection.rb +126 -0
- data/lib/ruflet/rails/protocol/wire_codec.rb +211 -0
- data/lib/ruflet/rails/protocol.rb +3 -1
- data/lib/ruflet/version.rb +1 -1
- data/lib/ruflet_rails.rb +5 -0
- metadata +7 -5
- data/lib/ruflet/rails/protocol/mount.rb +0 -23
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 192d25ccff11cbfc7a31a638a77df68daf83d24103b9580b2586ef91a2a0f7d3
|
|
4
|
+
data.tar.gz: 2ea8a4bf2c663a5a50d6d5f64426aa0420c70cf6dbc556d1c836cd41e7274a90
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d3e91b9375cf0df47090c8f736e50c241df1dc60a0418bc0519dadb3994a1337aa3fcfa49fb40d4bb9e3fb6f5c39337da3c9042450995203b6011301ec54dc99
|
|
7
|
+
data.tar.gz: d7f7021df9efd74b0c235c2ca59682eaf5ba11894b88d4c6967338c76615c691808a0630cd7d5d674db065a16ae6b2806b50f1b7534c4ea6cd25fe3db0342b28
|
data/README.md
CHANGED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruflet
|
|
4
|
+
module Rails
|
|
5
|
+
module Protocol
|
|
6
|
+
class LocalServer
|
|
7
|
+
def initialize(&app_block)
|
|
8
|
+
@app_block = app_block
|
|
9
|
+
@sessions = {}
|
|
10
|
+
@sessions_mutex = Mutex.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def handle_upgraded_socket(io)
|
|
14
|
+
ws = WebSocketConnection.new(io)
|
|
15
|
+
run_connection(ws)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def run_connection(ws)
|
|
21
|
+
while (raw = ws.read_message)
|
|
22
|
+
handle_message(ws, raw)
|
|
23
|
+
end
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
send_message(ws, Ruflet::Protocol::ACTIONS[:session_crashed], { "message" => e.message.to_s.dup.force_encoding("UTF-8") })
|
|
26
|
+
ensure
|
|
27
|
+
close_connection(ws)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def close_connection(ws)
|
|
31
|
+
return unless ws
|
|
32
|
+
|
|
33
|
+
remove_session(ws)
|
|
34
|
+
ws.close
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def remove_session(ws)
|
|
38
|
+
@sessions_mutex.synchronize { @sessions.delete(ws.session_key) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def handle_message(ws, raw)
|
|
42
|
+
action, payload = decode_incoming(raw)
|
|
43
|
+
payload ||= {}
|
|
44
|
+
|
|
45
|
+
case action
|
|
46
|
+
when Ruflet::Protocol::ACTIONS[:register_client], Ruflet::Protocol::ACTIONS[:register_web_client]
|
|
47
|
+
on_register_client(ws, payload)
|
|
48
|
+
when Ruflet::Protocol::ACTIONS[:control_event], Ruflet::Protocol::ACTIONS[:page_event_from_web]
|
|
49
|
+
on_control_event(ws, payload)
|
|
50
|
+
when Ruflet::Protocol::ACTIONS[:update_control], Ruflet::Protocol::ACTIONS[:update_control_props]
|
|
51
|
+
on_update_control(ws, payload)
|
|
52
|
+
when Ruflet::Protocol::ACTIONS[:invoke_control_method]
|
|
53
|
+
nil
|
|
54
|
+
else
|
|
55
|
+
raise "Unknown action: #{action.inspect}"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def decode_incoming(raw)
|
|
60
|
+
parsed = normalize_incoming(WireCodec.unpack(raw.to_s.b))
|
|
61
|
+
|
|
62
|
+
if parsed.is_a?(Array) && parsed.length >= 2
|
|
63
|
+
return [parsed[0], parsed[1]]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
if parsed.is_a?(Hash)
|
|
67
|
+
action = parsed["action"] || parsed[:action]
|
|
68
|
+
payload = parsed["payload"] || parsed[:payload]
|
|
69
|
+
return [action, payload] unless action.nil?
|
|
70
|
+
|
|
71
|
+
if (parsed.key?("target") || parsed.key?(:target)) && (parsed.key?("name") || parsed.key?(:name))
|
|
72
|
+
return [Ruflet::Protocol::ACTIONS[:control_event], parsed]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
raise "Unsupported payload format"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def normalize_incoming(value)
|
|
80
|
+
case value
|
|
81
|
+
when String
|
|
82
|
+
value.dup.force_encoding("UTF-8")
|
|
83
|
+
when Integer, Float, TrueClass, FalseClass, NilClass
|
|
84
|
+
value
|
|
85
|
+
when Symbol
|
|
86
|
+
value.to_s
|
|
87
|
+
when Array
|
|
88
|
+
value.map { |v| normalize_incoming(v) }
|
|
89
|
+
when Hash
|
|
90
|
+
value.each_with_object({}) do |(k, v), out|
|
|
91
|
+
out[k.to_s] = normalize_incoming(v)
|
|
92
|
+
end
|
|
93
|
+
else
|
|
94
|
+
value.to_s
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def on_register_client(ws, payload)
|
|
99
|
+
normalized = Ruflet::Protocol.normalize_register_payload(payload)
|
|
100
|
+
session_id = normalized["session_id"].to_s.empty? ? pseudo_uuid : normalized["session_id"]
|
|
101
|
+
|
|
102
|
+
page = Ruflet::Page.new(
|
|
103
|
+
session_id: session_id,
|
|
104
|
+
client_details: normalized,
|
|
105
|
+
sender: lambda do |action, msg_payload|
|
|
106
|
+
send_message(ws, action, msg_payload)
|
|
107
|
+
end
|
|
108
|
+
)
|
|
109
|
+
page.title = "Ruflet App"
|
|
110
|
+
|
|
111
|
+
@sessions_mutex.synchronize { @sessions[ws.session_key] = page }
|
|
112
|
+
|
|
113
|
+
initial_response = [
|
|
114
|
+
Ruflet::Protocol::ACTIONS[:register_client],
|
|
115
|
+
Ruflet::Protocol.register_response(session_id: session_id)
|
|
116
|
+
]
|
|
117
|
+
ws.send_binary(WireCodec.pack(initial_response))
|
|
118
|
+
|
|
119
|
+
@app_block.call(page)
|
|
120
|
+
page.update
|
|
121
|
+
rescue StandardError => e
|
|
122
|
+
send_message(ws, Ruflet::Protocol::ACTIONS[:session_crashed], { "message" => e.message.to_s })
|
|
123
|
+
raise
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def on_control_event(ws, payload)
|
|
128
|
+
event = Ruflet::Protocol.normalize_control_event_payload(payload)
|
|
129
|
+
page = fetch_page(ws)
|
|
130
|
+
return if event["target"].nil? || event["name"].to_s.empty?
|
|
131
|
+
|
|
132
|
+
page.dispatch_event(
|
|
133
|
+
target: event["target"],
|
|
134
|
+
name: event["name"],
|
|
135
|
+
data: normalize_event_data(event["data"])
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def on_update_control(ws, payload)
|
|
140
|
+
update = Ruflet::Protocol.normalize_update_control_payload(payload)
|
|
141
|
+
page = fetch_page(ws)
|
|
142
|
+
return if update["id"].nil?
|
|
143
|
+
|
|
144
|
+
page.apply_client_update(update["id"], update["props"] || {})
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def fetch_page(ws)
|
|
148
|
+
page = @sessions_mutex.synchronize { @sessions[ws.session_key] }
|
|
149
|
+
raise "Session not found" unless page
|
|
150
|
+
|
|
151
|
+
page
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def normalize_event_data(value)
|
|
155
|
+
case value
|
|
156
|
+
when Hash
|
|
157
|
+
value.each_with_object({}) { |(k, v), out| out[k.to_sym] = normalize_event_data(v) }
|
|
158
|
+
when Array
|
|
159
|
+
value.map { |entry| normalize_event_data(entry) }
|
|
160
|
+
else
|
|
161
|
+
value
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def send_message(ws, action, payload)
|
|
166
|
+
ws.send_binary(WireCodec.pack([action, payload]))
|
|
167
|
+
rescue StandardError
|
|
168
|
+
nil
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def pseudo_uuid
|
|
172
|
+
now = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
|
|
173
|
+
rnd = rand(0..0xffff_ffff)
|
|
174
|
+
"%08x-%04x-%04x-%04x-%012x" % [
|
|
175
|
+
rnd,
|
|
176
|
+
now & 0xffff,
|
|
177
|
+
(now >> 16) & 0xffff,
|
|
178
|
+
(now >> 32) & 0xffff,
|
|
179
|
+
(now >> 48) & 0xffff_ffff_ffff
|
|
180
|
+
]
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruflet
|
|
4
|
+
module Rails
|
|
5
|
+
module Protocol
|
|
6
|
+
class WebSocketConnection
|
|
7
|
+
def initialize(socket)
|
|
8
|
+
@socket = socket
|
|
9
|
+
@write_mutex = Mutex.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def session_key
|
|
13
|
+
@socket.object_id
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def closed?
|
|
17
|
+
@socket.closed?
|
|
18
|
+
rescue IOError
|
|
19
|
+
true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def send_binary(payload)
|
|
23
|
+
send_frame(0x2, payload.to_s.b)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def read_message
|
|
27
|
+
frame = read_frame
|
|
28
|
+
return nil if frame.nil?
|
|
29
|
+
|
|
30
|
+
opcode = frame[:opcode]
|
|
31
|
+
payload = frame[:payload]
|
|
32
|
+
|
|
33
|
+
case opcode
|
|
34
|
+
when 0x8
|
|
35
|
+
close
|
|
36
|
+
nil
|
|
37
|
+
when 0x9
|
|
38
|
+
send_frame(0xA, payload)
|
|
39
|
+
read_message
|
|
40
|
+
when 0xA
|
|
41
|
+
read_message
|
|
42
|
+
when 0x1, 0x2
|
|
43
|
+
payload
|
|
44
|
+
else
|
|
45
|
+
read_message
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def close
|
|
50
|
+
return if closed?
|
|
51
|
+
|
|
52
|
+
@socket.close
|
|
53
|
+
rescue IOError
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def read_frame
|
|
60
|
+
header = read_exact(2)
|
|
61
|
+
return nil if header.nil?
|
|
62
|
+
|
|
63
|
+
b1 = header.getbyte(0)
|
|
64
|
+
b2 = header.getbyte(1)
|
|
65
|
+
|
|
66
|
+
masked = (b2 & 0x80) != 0
|
|
67
|
+
payload_len = b2 & 0x7f
|
|
68
|
+
|
|
69
|
+
payload_len = read_exact(2).unpack1("n") if payload_len == 126
|
|
70
|
+
payload_len = read_exact(8).unpack1("Q>") if payload_len == 127
|
|
71
|
+
|
|
72
|
+
masking_key = masked ? read_exact(4) : nil
|
|
73
|
+
payload = payload_len.zero? ? "".b : read_exact(payload_len)
|
|
74
|
+
return nil if payload.nil?
|
|
75
|
+
|
|
76
|
+
payload = unmask(payload, masking_key) if masked
|
|
77
|
+
|
|
78
|
+
{ opcode: b1 & 0x0f, payload: payload }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def send_frame(opcode, payload)
|
|
82
|
+
bytes = payload.to_s.b
|
|
83
|
+
len = bytes.bytesize
|
|
84
|
+
header = [0x80 | (opcode & 0x0f)].pack("C")
|
|
85
|
+
|
|
86
|
+
header <<
|
|
87
|
+
if len <= 125
|
|
88
|
+
[len].pack("C")
|
|
89
|
+
elsif len <= 0xffff
|
|
90
|
+
[126].pack("C") + [len].pack("n")
|
|
91
|
+
else
|
|
92
|
+
[127].pack("C") + [len].pack("Q>")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
@write_mutex.synchronize do
|
|
96
|
+
@socket.write(header)
|
|
97
|
+
@socket.write(bytes) unless bytes.empty?
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def unmask(payload, mask)
|
|
102
|
+
out = +""
|
|
103
|
+
out.force_encoding(Encoding::BINARY)
|
|
104
|
+
payload.bytes.each_with_index do |byte, idx|
|
|
105
|
+
out << (byte ^ mask.getbyte(idx % 4))
|
|
106
|
+
end
|
|
107
|
+
out
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def read_exact(length)
|
|
111
|
+
chunk = +""
|
|
112
|
+
chunk.force_encoding(Encoding::BINARY)
|
|
113
|
+
|
|
114
|
+
while chunk.bytesize < length
|
|
115
|
+
part = @socket.read(length - chunk.bytesize)
|
|
116
|
+
return nil if part.nil? || part.empty?
|
|
117
|
+
|
|
118
|
+
chunk << part
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
chunk
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruflet
|
|
4
|
+
module Rails
|
|
5
|
+
module Protocol
|
|
6
|
+
class WireCodec
|
|
7
|
+
class << self
|
|
8
|
+
def pack(value)
|
|
9
|
+
case value
|
|
10
|
+
when NilClass
|
|
11
|
+
"\xc0".b
|
|
12
|
+
when TrueClass
|
|
13
|
+
"\xc3".b
|
|
14
|
+
when FalseClass
|
|
15
|
+
"\xc2".b
|
|
16
|
+
when Integer
|
|
17
|
+
pack_integer(value)
|
|
18
|
+
when Float
|
|
19
|
+
"\xcb".b + [value].pack("G")
|
|
20
|
+
when String
|
|
21
|
+
pack_string(value)
|
|
22
|
+
when Symbol
|
|
23
|
+
pack_string(value.to_s)
|
|
24
|
+
when Array
|
|
25
|
+
pack_array(value)
|
|
26
|
+
when Hash
|
|
27
|
+
pack_map(value)
|
|
28
|
+
else
|
|
29
|
+
pack_string(value.to_s)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def unpack(bytes)
|
|
34
|
+
reader = ByteReader.new(bytes)
|
|
35
|
+
read_value(reader)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def pack_integer(value)
|
|
41
|
+
if value >= 0
|
|
42
|
+
return [value].pack("C") if value <= 0x7f
|
|
43
|
+
return "\xcc".b + [value].pack("C") if value <= 0xff
|
|
44
|
+
return "\xcd".b + [value].pack("n") if value <= 0xffff
|
|
45
|
+
return "\xce".b + [value].pack("N") if value <= 0xffff_ffff
|
|
46
|
+
|
|
47
|
+
"\xcf".b + [value].pack("Q>")
|
|
48
|
+
else
|
|
49
|
+
return [value & 0xff].pack("C") if value >= -32
|
|
50
|
+
return "\xd0".b + [value].pack("c") if value >= -128
|
|
51
|
+
return "\xd1".b + [value].pack("s>") if value >= -32_768
|
|
52
|
+
return "\xd2".b + [value].pack("l>") if value >= -2_147_483_648
|
|
53
|
+
|
|
54
|
+
"\xd3".b + [value].pack("q>")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def pack_string(value)
|
|
59
|
+
str = value.to_s.dup.force_encoding("UTF-8")
|
|
60
|
+
bytes = str.b
|
|
61
|
+
len = bytes.bytesize
|
|
62
|
+
|
|
63
|
+
if len <= 31
|
|
64
|
+
[0xA0 | len].pack("C") + bytes
|
|
65
|
+
elsif len <= 0xff
|
|
66
|
+
"\xd9".b + [len].pack("C") + bytes
|
|
67
|
+
elsif len <= 0xffff
|
|
68
|
+
"\xda".b + [len].pack("n") + bytes
|
|
69
|
+
else
|
|
70
|
+
"\xdb".b + [len].pack("N") + bytes
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def pack_array(value)
|
|
75
|
+
len = value.length
|
|
76
|
+
head =
|
|
77
|
+
if len <= 15
|
|
78
|
+
[0x90 | len].pack("C")
|
|
79
|
+
elsif len <= 0xffff
|
|
80
|
+
"\xdc".b + [len].pack("n")
|
|
81
|
+
else
|
|
82
|
+
"\xdd".b + [len].pack("N")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
body = +"".b
|
|
86
|
+
value.each { |item| body << pack(item) }
|
|
87
|
+
head + body
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def pack_map(value)
|
|
91
|
+
pairs = value.each_with_object({}) { |(k, v), out| out[k.to_s] = v }
|
|
92
|
+
len = pairs.length
|
|
93
|
+
head =
|
|
94
|
+
if len <= 15
|
|
95
|
+
[0x80 | len].pack("C")
|
|
96
|
+
elsif len <= 0xffff
|
|
97
|
+
"\xde".b + [len].pack("n")
|
|
98
|
+
else
|
|
99
|
+
"\xdf".b + [len].pack("N")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
body = +"".b
|
|
103
|
+
pairs.each do |k, v|
|
|
104
|
+
body << pack(k)
|
|
105
|
+
body << pack(v)
|
|
106
|
+
end
|
|
107
|
+
head + body
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def read_value(reader)
|
|
111
|
+
marker = reader.read_u8
|
|
112
|
+
|
|
113
|
+
return marker if marker <= 0x7f
|
|
114
|
+
return marker - 256 if marker >= 0xe0
|
|
115
|
+
|
|
116
|
+
case marker
|
|
117
|
+
when 0xc0 then nil
|
|
118
|
+
when 0xc2 then false
|
|
119
|
+
when 0xc3 then true
|
|
120
|
+
when 0xcc then reader.read_u8
|
|
121
|
+
when 0xcd then reader.read_u16
|
|
122
|
+
when 0xce then reader.read_u32
|
|
123
|
+
when 0xcf then reader.read_u64
|
|
124
|
+
when 0xd0 then reader.read_i8
|
|
125
|
+
when 0xd1 then reader.read_i16
|
|
126
|
+
when 0xd2 then reader.read_i32
|
|
127
|
+
when 0xd3 then reader.read_i64
|
|
128
|
+
when 0xca then reader.read_f32
|
|
129
|
+
when 0xcb then reader.read_f64
|
|
130
|
+
when 0xd9 then reader.read_string(reader.read_u8)
|
|
131
|
+
when 0xda then reader.read_string(reader.read_u16)
|
|
132
|
+
when 0xdb then reader.read_string(reader.read_u32)
|
|
133
|
+
when 0xdc then read_array(reader, reader.read_u16)
|
|
134
|
+
when 0xdd then read_array(reader, reader.read_u32)
|
|
135
|
+
when 0xde then read_map(reader, reader.read_u16)
|
|
136
|
+
when 0xdf then read_map(reader, reader.read_u32)
|
|
137
|
+
when 0xc7
|
|
138
|
+
read_ext(reader, reader.read_u8)
|
|
139
|
+
when 0xc8
|
|
140
|
+
read_ext(reader, reader.read_u16)
|
|
141
|
+
when 0xc9
|
|
142
|
+
read_ext(reader, reader.read_u32)
|
|
143
|
+
else
|
|
144
|
+
if (marker & 0xf0) == 0x90
|
|
145
|
+
read_array(reader, marker & 0x0f)
|
|
146
|
+
elsif (marker & 0xf0) == 0x80
|
|
147
|
+
read_map(reader, marker & 0x0f)
|
|
148
|
+
elsif (marker & 0xe0) == 0xa0
|
|
149
|
+
reader.read_string(marker & 0x1f)
|
|
150
|
+
else
|
|
151
|
+
raise "Unsupported MessagePack marker: 0x#{marker.to_s(16)}"
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def read_array(reader, size)
|
|
157
|
+
Array.new(size) { read_value(reader) }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def read_map(reader, size)
|
|
161
|
+
out = {}
|
|
162
|
+
size.times do
|
|
163
|
+
key = read_value(reader)
|
|
164
|
+
out[key.to_s] = read_value(reader)
|
|
165
|
+
end
|
|
166
|
+
out
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def read_ext(reader, size)
|
|
170
|
+
reader.read_i8
|
|
171
|
+
reader.read_exact(size)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
class ByteReader
|
|
176
|
+
def initialize(bytes)
|
|
177
|
+
@data = bytes.to_s.b
|
|
178
|
+
@offset = 0
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def read_u8
|
|
182
|
+
value = @data.getbyte(@offset)
|
|
183
|
+
raise "Unexpected EOF" if value.nil?
|
|
184
|
+
|
|
185
|
+
@offset += 1
|
|
186
|
+
value
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def read_exact(size)
|
|
190
|
+
chunk = @data.byteslice(@offset, size)
|
|
191
|
+
raise "Unexpected EOF" if chunk.nil? || chunk.bytesize != size
|
|
192
|
+
|
|
193
|
+
@offset += size
|
|
194
|
+
chunk
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def read_u16 = read_exact(2).unpack1("n")
|
|
198
|
+
def read_u32 = read_exact(4).unpack1("N")
|
|
199
|
+
def read_u64 = read_exact(8).unpack1("Q>")
|
|
200
|
+
def read_i8 = read_exact(1).unpack1("c")
|
|
201
|
+
def read_i16 = read_exact(2).unpack1("s>")
|
|
202
|
+
def read_i32 = read_exact(4).unpack1("l>")
|
|
203
|
+
def read_i64 = read_exact(8).unpack1("q>")
|
|
204
|
+
def read_f32 = read_exact(4).unpack1("g")
|
|
205
|
+
def read_f64 = read_exact(8).unpack1("G")
|
|
206
|
+
def read_string(size) = read_exact(size).force_encoding("UTF-8")
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "protocol/context"
|
|
4
4
|
require_relative "protocol/middleware"
|
|
5
|
+
require_relative "protocol/wire_codec"
|
|
6
|
+
require_relative "protocol/web_socket_connection"
|
|
7
|
+
require_relative "protocol/local_server"
|
|
5
8
|
require_relative "protocol/endpoint"
|
|
6
9
|
require_relative "protocol/mobile_loader"
|
|
7
10
|
require_relative "protocol/runner"
|
|
8
|
-
require_relative "protocol/mount"
|
data/lib/ruflet/version.rb
CHANGED
data/lib/ruflet_rails.rb
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
local_ruflet_lib = File.expand_path("../../ruflet/lib", __dir__)
|
|
4
|
+
if File.directory?(local_ruflet_lib) && !$LOAD_PATH.include?(local_ruflet_lib)
|
|
5
|
+
$LOAD_PATH.unshift(local_ruflet_lib)
|
|
6
|
+
end
|
|
7
|
+
|
|
3
8
|
require "ruflet"
|
|
4
9
|
require_relative "ruflet/rails/protocol"
|
|
5
10
|
require_relative "ruflet/rails"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruflet_rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- AdamMusa
|
|
@@ -29,14 +29,14 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - '='
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.0.
|
|
32
|
+
version: 0.0.4
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - '='
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.0.
|
|
39
|
+
version: 0.0.4
|
|
40
40
|
description: Rails-first integration package for mounting Ruflet mobile apps in Rails
|
|
41
41
|
routes.
|
|
42
42
|
email:
|
|
@@ -50,14 +50,16 @@ files:
|
|
|
50
50
|
- lib/ruflet/rails/protocol.rb
|
|
51
51
|
- lib/ruflet/rails/protocol/context.rb
|
|
52
52
|
- lib/ruflet/rails/protocol/endpoint.rb
|
|
53
|
+
- lib/ruflet/rails/protocol/local_server.rb
|
|
53
54
|
- lib/ruflet/rails/protocol/middleware.rb
|
|
54
55
|
- lib/ruflet/rails/protocol/mobile_loader.rb
|
|
55
|
-
- lib/ruflet/rails/protocol/mount.rb
|
|
56
56
|
- lib/ruflet/rails/protocol/runner.rb
|
|
57
|
+
- lib/ruflet/rails/protocol/web_socket_connection.rb
|
|
58
|
+
- lib/ruflet/rails/protocol/wire_codec.rb
|
|
57
59
|
- lib/ruflet/rails/railtie.rb
|
|
58
60
|
- lib/ruflet/version.rb
|
|
59
61
|
- lib/ruflet_rails.rb
|
|
60
|
-
homepage: https://github.com/AdamMusa/
|
|
62
|
+
homepage: https://github.com/AdamMusa/ruflet/tree/main/packages/ruflet_rails
|
|
61
63
|
licenses: []
|
|
62
64
|
metadata: {}
|
|
63
65
|
rdoc_options: []
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ruflet
|
|
4
|
-
module Rails
|
|
5
|
-
module Protocol
|
|
6
|
-
class Mount
|
|
7
|
-
def initialize(app, file_path:, path: "/ws")
|
|
8
|
-
@app = app
|
|
9
|
-
@path = path
|
|
10
|
-
@endpoint = Runner.new.build_mobile_endpoint(file_path: file_path, path: "/")
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def call(env)
|
|
14
|
-
return @app.call(env) unless env["PATH_INFO"] == @path
|
|
15
|
-
|
|
16
|
-
mounted_env = env.dup
|
|
17
|
-
mounted_env["PATH_INFO"] = "/"
|
|
18
|
-
@endpoint.call(mounted_env)
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|