ruflet_rails 0.0.7 → 0.0.8
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 +152 -5
- data/lib/generators/ruflet/form/form_generator.rb +55 -0
- data/lib/generators/ruflet/install/install_generator.rb +111 -22
- data/lib/generators/ruflet/scaffold/scaffold_generator.rb +60 -0
- data/lib/ruflet/rails/desktop_launcher.rb +116 -0
- data/lib/ruflet/rails/form_helpers.rb +161 -0
- data/lib/ruflet/rails/install_support.rb +911 -133
- data/lib/ruflet/rails/protocol/endpoint.rb +21 -5
- data/lib/ruflet/rails/protocol/local_server.rb +55 -12
- data/lib/ruflet/rails/protocol/runner.rb +22 -9
- data/lib/ruflet/rails/protocol/web_socket_connection.rb +3 -118
- data/lib/ruflet/rails/protocol/wire_codec.rb +3 -243
- data/lib/ruflet/rails/railtie.rb +63 -4
- data/lib/ruflet/rails/resource_component.rb +191 -0
- data/lib/ruflet/rails/resource_view.rb +124 -0
- data/lib/ruflet/rails/session_registry.rb +94 -0
- data/lib/ruflet/rails/view.rb +57 -0
- data/lib/ruflet/rails.rb +167 -2
- data/lib/ruflet/version.rb +1 -1
- data/lib/ruflet_rails.rb +8 -1
- metadata +13 -5
|
@@ -8,13 +8,13 @@ module Ruflet
|
|
|
8
8
|
class Endpoint
|
|
9
9
|
WEBSOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
|
10
10
|
|
|
11
|
-
def initialize(server:, path:
|
|
11
|
+
def initialize(server:, path: nil)
|
|
12
12
|
@server = server
|
|
13
13
|
@path = path
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def call(env)
|
|
17
|
-
return not_found
|
|
17
|
+
return not_found if @path && env["PATH_INFO"] != @path
|
|
18
18
|
return bad_request("Expected WebSocket upgrade") unless websocket_upgrade_request?(env)
|
|
19
19
|
|
|
20
20
|
hijack = env["rack.hijack"]
|
|
@@ -30,9 +30,7 @@ module Ruflet
|
|
|
30
30
|
captured_env = env.dup
|
|
31
31
|
Thread.new(io, captured_env) do |socket, ws_env|
|
|
32
32
|
Thread.current.report_on_exception = false if Thread.current.respond_to?(:report_on_exception=)
|
|
33
|
-
|
|
34
|
-
@server.handle_upgraded_socket(socket)
|
|
35
|
-
end
|
|
33
|
+
rails_executor_wrap { handle_socket(socket, ws_env) }
|
|
36
34
|
end
|
|
37
35
|
|
|
38
36
|
[-1, {}, []]
|
|
@@ -42,6 +40,24 @@ module Ruflet
|
|
|
42
40
|
|
|
43
41
|
private
|
|
44
42
|
|
|
43
|
+
def handle_socket(socket, env)
|
|
44
|
+
Context.with_env(env) do
|
|
45
|
+
@server.handle_upgraded_socket(socket)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def rails_executor_wrap(&block)
|
|
50
|
+
executor = if defined?(::Rails) && ::Rails.respond_to?(:application) && ::Rails.application
|
|
51
|
+
::Rails.application.executor
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
if executor.respond_to?(:wrap)
|
|
55
|
+
executor.wrap(&block)
|
|
56
|
+
else
|
|
57
|
+
yield
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
45
61
|
def websocket_upgrade_request?(env)
|
|
46
62
|
return false unless env["REQUEST_METHOD"] == "GET"
|
|
47
63
|
|
|
@@ -4,8 +4,9 @@ module Ruflet
|
|
|
4
4
|
module Rails
|
|
5
5
|
module Protocol
|
|
6
6
|
class LocalServer
|
|
7
|
-
def initialize(&app_block)
|
|
7
|
+
def initialize(session_registry: Ruflet::Rails.sessions, &app_block)
|
|
8
8
|
@app_block = app_block
|
|
9
|
+
@session_registry = session_registry
|
|
9
10
|
@sessions = {}
|
|
10
11
|
@sessions_mutex = Mutex.new
|
|
11
12
|
end
|
|
@@ -22,6 +23,7 @@ module Ruflet
|
|
|
22
23
|
handle_message(ws, raw)
|
|
23
24
|
end
|
|
24
25
|
rescue StandardError => e
|
|
26
|
+
Rails.logger.error("RUFLET CRASH: #{e.class}: #{e.message}\n#{e.backtrace.first(10).join("\n")}") if defined?(Rails)
|
|
25
27
|
send_message(ws, Ruflet::Protocol::ACTIONS[:session_crashed], { "message" => e.message.to_s.dup.force_encoding("UTF-8") })
|
|
26
28
|
ensure
|
|
27
29
|
close_connection(ws)
|
|
@@ -35,7 +37,8 @@ module Ruflet
|
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
def remove_session(ws)
|
|
38
|
-
@sessions_mutex.synchronize { @sessions.delete(ws.session_key) }
|
|
40
|
+
page = @sessions_mutex.synchronize { @sessions.delete(ws.session_key) }
|
|
41
|
+
@session_registry.remove(page.session_id, connection_key: ws.session_key) if page
|
|
39
42
|
end
|
|
40
43
|
|
|
41
44
|
def handle_message(ws, raw)
|
|
@@ -98,17 +101,29 @@ module Ruflet
|
|
|
98
101
|
def on_register_client(ws, payload)
|
|
99
102
|
normalized = Ruflet::Protocol.normalize_register_payload(payload)
|
|
100
103
|
session_id = normalized["session_id"].to_s.empty? ? pseudo_uuid : normalized["session_id"]
|
|
104
|
+
existing_session = @session_registry[session_id]
|
|
105
|
+
page = existing_session&.page
|
|
106
|
+
first_registration = page.nil?
|
|
101
107
|
|
|
102
|
-
page
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
if page
|
|
109
|
+
attach_sender(page, ws)
|
|
110
|
+
reset_mount_state(page)
|
|
111
|
+
else
|
|
112
|
+
page = Ruflet::Page.new(
|
|
113
|
+
session_id: session_id,
|
|
114
|
+
client_details: normalized,
|
|
115
|
+
sender: sender_for(ws)
|
|
116
|
+
)
|
|
117
|
+
page.title = "Ruflet App"
|
|
118
|
+
end
|
|
110
119
|
|
|
111
120
|
@sessions_mutex.synchronize { @sessions[ws.session_key] = page }
|
|
121
|
+
@session_registry.add(
|
|
122
|
+
key: page.session_id,
|
|
123
|
+
page: page,
|
|
124
|
+
env: Context.current_env,
|
|
125
|
+
connection_key: ws.session_key
|
|
126
|
+
)
|
|
112
127
|
|
|
113
128
|
initial_response = [
|
|
114
129
|
Ruflet::Protocol::ACTIONS[:register_client],
|
|
@@ -116,19 +131,20 @@ module Ruflet
|
|
|
116
131
|
]
|
|
117
132
|
ws.send_binary(WireCodec.pack(initial_response))
|
|
118
133
|
|
|
119
|
-
@app_block.call(page)
|
|
134
|
+
@app_block.call(page) if first_registration
|
|
120
135
|
page.update
|
|
121
136
|
rescue StandardError => e
|
|
122
137
|
send_message(ws, Ruflet::Protocol::ACTIONS[:session_crashed], { "message" => e.message.to_s })
|
|
123
138
|
raise
|
|
124
139
|
end
|
|
125
140
|
|
|
126
|
-
|
|
127
141
|
def on_control_event(ws, payload)
|
|
128
142
|
event = Ruflet::Protocol.normalize_control_event_payload(payload)
|
|
129
143
|
page = fetch_page(ws)
|
|
130
144
|
return if event["target"].nil? || event["name"].to_s.empty?
|
|
131
145
|
|
|
146
|
+
attach_sender(page, ws)
|
|
147
|
+
debug_event(ws, event)
|
|
132
148
|
page.dispatch_event(
|
|
133
149
|
target: event["target"],
|
|
134
150
|
name: event["name"],
|
|
@@ -141,11 +157,13 @@ module Ruflet
|
|
|
141
157
|
page = fetch_page(ws)
|
|
142
158
|
return if update["id"].nil?
|
|
143
159
|
|
|
160
|
+
attach_sender(page, ws)
|
|
144
161
|
page.apply_client_update(update["id"], update["props"] || {})
|
|
145
162
|
end
|
|
146
163
|
|
|
147
164
|
def on_invoke_control_method(ws, payload)
|
|
148
165
|
page = fetch_page(ws)
|
|
166
|
+
attach_sender(page, ws)
|
|
149
167
|
page.handle_invoke_method_result(Ruflet::Protocol.normalize_invoke_method_result_payload(payload))
|
|
150
168
|
end
|
|
151
169
|
|
|
@@ -173,6 +191,31 @@ module Ruflet
|
|
|
173
191
|
nil
|
|
174
192
|
end
|
|
175
193
|
|
|
194
|
+
def sender_for(ws)
|
|
195
|
+
lambda do |action, msg_payload|
|
|
196
|
+
send_message(ws, action, msg_payload)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def attach_sender(page, ws)
|
|
201
|
+
page.instance_variable_set(:@sender, sender_for(ws))
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def debug_event(ws, event)
|
|
205
|
+
return unless ENV["RUFLET_RAILS_DEBUG_EVENTS"] == "true"
|
|
206
|
+
|
|
207
|
+
warn(
|
|
208
|
+
"[ruflet_rails] event socket=#{ws.session_key} " \
|
|
209
|
+
"target=#{event["target"].inspect} name=#{event["name"].inspect} data=#{event["data"].inspect}"
|
|
210
|
+
)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def reset_mount_state(page)
|
|
214
|
+
page.instance_variable_set(:@overlay_container_mounted, false)
|
|
215
|
+
page.instance_variable_set(:@dialogs_container_mounted, false)
|
|
216
|
+
page.instance_variable_set(:@services_container_mounted, false)
|
|
217
|
+
end
|
|
218
|
+
|
|
176
219
|
def pseudo_uuid
|
|
177
220
|
now = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
|
|
178
221
|
rnd = rand(0..0xffff_ffff)
|
|
@@ -8,27 +8,40 @@ module Ruflet
|
|
|
8
8
|
@entrypoint = entrypoint
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def build_endpoint(path:
|
|
11
|
+
def build_endpoint(path: nil)
|
|
12
12
|
raise ArgumentError, "Ruflet::Rails::Protocol endpoint requires a block" unless @entrypoint.respond_to?(:call)
|
|
13
13
|
|
|
14
14
|
Endpoint.new(server: build_server(@entrypoint), path: path)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
def build_app_endpoint(file_path:, path: nil)
|
|
18
|
+
absolute = File.expand_path(file_path)
|
|
19
|
+
entrypoint = lambda do |page, env|
|
|
20
|
+
loaded = MobileLoader.new(absolute).load!
|
|
21
|
+
run_entrypoint(loaded[:entrypoint], page, env)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
Endpoint.new(
|
|
25
|
+
server: build_server(entrypoint),
|
|
26
|
+
path: path
|
|
27
|
+
)
|
|
20
28
|
end
|
|
29
|
+
alias build_mobile_endpoint build_app_endpoint
|
|
21
30
|
|
|
22
31
|
private
|
|
23
32
|
|
|
24
33
|
def build_server(entrypoint)
|
|
25
34
|
LocalServer.new do |page|
|
|
26
35
|
env = Context.current_env
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
run_entrypoint(entrypoint, page, env)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def run_entrypoint(entrypoint, page, env)
|
|
41
|
+
if entrypoint.arity == 1
|
|
42
|
+
entrypoint.call(page)
|
|
43
|
+
else
|
|
44
|
+
entrypoint.call(page, env)
|
|
32
45
|
end
|
|
33
46
|
end
|
|
34
47
|
end
|
|
@@ -1,126 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "ruflet_server"
|
|
4
|
+
|
|
3
5
|
module Ruflet
|
|
4
6
|
module Rails
|
|
5
7
|
module Protocol
|
|
6
|
-
|
|
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
|
|
8
|
+
WebSocketConnection = ::Ruflet::WebSocketConnection unless const_defined?(:WebSocketConnection, false)
|
|
124
9
|
end
|
|
125
10
|
end
|
|
126
11
|
end
|
|
@@ -1,251 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "ruflet_server"
|
|
4
|
+
|
|
3
5
|
module Ruflet
|
|
4
6
|
module Rails
|
|
5
7
|
module Protocol
|
|
6
|
-
|
|
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
|
-
binary_string?(value) ? pack_binary(value) : 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_binary(value)
|
|
75
|
-
bytes = value.to_s.b
|
|
76
|
-
len = bytes.bytesize
|
|
77
|
-
|
|
78
|
-
if len <= 0xff
|
|
79
|
-
"\xc4".b + [len].pack("C") + bytes
|
|
80
|
-
elsif len <= 0xffff
|
|
81
|
-
"\xc5".b + [len].pack("n") + bytes
|
|
82
|
-
else
|
|
83
|
-
"\xc6".b + [len].pack("N") + bytes
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def binary_string?(value)
|
|
88
|
-
value.encoding == Encoding::BINARY || !value.valid_encoding?
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def pack_array(value)
|
|
92
|
-
len = value.length
|
|
93
|
-
head =
|
|
94
|
-
if len <= 15
|
|
95
|
-
[0x90 | len].pack("C")
|
|
96
|
-
elsif len <= 0xffff
|
|
97
|
-
"\xdc".b + [len].pack("n")
|
|
98
|
-
else
|
|
99
|
-
"\xdd".b + [len].pack("N")
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
body = +"".b
|
|
103
|
-
value.each { |item| body << pack(item) }
|
|
104
|
-
head + body
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def pack_map(value)
|
|
108
|
-
pairs = value.each_with_object({}) { |(k, v), out| out[k.to_s] = v }
|
|
109
|
-
len = pairs.length
|
|
110
|
-
head =
|
|
111
|
-
if len <= 15
|
|
112
|
-
[0x80 | len].pack("C")
|
|
113
|
-
elsif len <= 0xffff
|
|
114
|
-
"\xde".b + [len].pack("n")
|
|
115
|
-
else
|
|
116
|
-
"\xdf".b + [len].pack("N")
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
body = +"".b
|
|
120
|
-
pairs.each do |k, v|
|
|
121
|
-
body << pack(k)
|
|
122
|
-
body << pack(v)
|
|
123
|
-
end
|
|
124
|
-
head + body
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def read_value(reader)
|
|
128
|
-
marker = reader.read_u8
|
|
129
|
-
|
|
130
|
-
return marker if marker <= 0x7f
|
|
131
|
-
return marker - 256 if marker >= 0xe0
|
|
132
|
-
|
|
133
|
-
case marker
|
|
134
|
-
when 0xc0 then nil
|
|
135
|
-
when 0xc2 then false
|
|
136
|
-
when 0xc3 then true
|
|
137
|
-
when 0xcc then reader.read_u8
|
|
138
|
-
when 0xcd then reader.read_u16
|
|
139
|
-
when 0xce then reader.read_u32
|
|
140
|
-
when 0xcf then reader.read_u64
|
|
141
|
-
when 0xd0 then reader.read_i8
|
|
142
|
-
when 0xd1 then reader.read_i16
|
|
143
|
-
when 0xd2 then reader.read_i32
|
|
144
|
-
when 0xd3 then reader.read_i64
|
|
145
|
-
when 0xca then reader.read_f32
|
|
146
|
-
when 0xcb then reader.read_f64
|
|
147
|
-
when 0xd9 then reader.read_string(reader.read_u8)
|
|
148
|
-
when 0xda then reader.read_string(reader.read_u16)
|
|
149
|
-
when 0xdb then reader.read_string(reader.read_u32)
|
|
150
|
-
when 0xc4 then reader.read_binary(reader.read_u8)
|
|
151
|
-
when 0xc5 then reader.read_binary(reader.read_u16)
|
|
152
|
-
when 0xc6 then reader.read_binary(reader.read_u32)
|
|
153
|
-
when 0xdc then read_array(reader, reader.read_u16)
|
|
154
|
-
when 0xdd then read_array(reader, reader.read_u32)
|
|
155
|
-
when 0xde then read_map(reader, reader.read_u16)
|
|
156
|
-
when 0xdf then read_map(reader, reader.read_u32)
|
|
157
|
-
when 0xd4
|
|
158
|
-
read_ext(reader, 1)
|
|
159
|
-
when 0xd5
|
|
160
|
-
read_ext(reader, 2)
|
|
161
|
-
when 0xd6
|
|
162
|
-
read_ext(reader, 4)
|
|
163
|
-
when 0xd7
|
|
164
|
-
read_ext(reader, 8)
|
|
165
|
-
when 0xd8
|
|
166
|
-
read_ext(reader, 16)
|
|
167
|
-
when 0xc7
|
|
168
|
-
read_ext(reader, reader.read_u8)
|
|
169
|
-
when 0xc8
|
|
170
|
-
read_ext(reader, reader.read_u16)
|
|
171
|
-
when 0xc9
|
|
172
|
-
read_ext(reader, reader.read_u32)
|
|
173
|
-
else
|
|
174
|
-
if (marker & 0xf0) == 0x90
|
|
175
|
-
read_array(reader, marker & 0x0f)
|
|
176
|
-
elsif (marker & 0xf0) == 0x80
|
|
177
|
-
read_map(reader, marker & 0x0f)
|
|
178
|
-
elsif (marker & 0xe0) == 0xa0
|
|
179
|
-
reader.read_string(marker & 0x1f)
|
|
180
|
-
else
|
|
181
|
-
raise "Unsupported MessagePack marker: 0x#{marker.to_s(16)}"
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
def read_array(reader, size)
|
|
187
|
-
Array.new(size) { read_value(reader) }
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
def read_map(reader, size)
|
|
191
|
-
out = {}
|
|
192
|
-
size.times do
|
|
193
|
-
key = read_value(reader)
|
|
194
|
-
out[key.to_s] = read_value(reader)
|
|
195
|
-
end
|
|
196
|
-
out
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
def read_ext(reader, size)
|
|
200
|
-
type = reader.read_i8
|
|
201
|
-
data = reader.read_exact(size)
|
|
202
|
-
|
|
203
|
-
case type
|
|
204
|
-
when 1, 2, 4
|
|
205
|
-
data.dup.force_encoding("UTF-8")
|
|
206
|
-
when 3
|
|
207
|
-
data.to_i
|
|
208
|
-
else
|
|
209
|
-
data
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
class ByteReader
|
|
215
|
-
def initialize(bytes)
|
|
216
|
-
@data = bytes.to_s.b
|
|
217
|
-
@offset = 0
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
def read_u8
|
|
221
|
-
value = @data.getbyte(@offset)
|
|
222
|
-
raise "Unexpected EOF" if value.nil?
|
|
223
|
-
|
|
224
|
-
@offset += 1
|
|
225
|
-
value
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
def read_exact(size)
|
|
229
|
-
chunk = @data.byteslice(@offset, size)
|
|
230
|
-
raise "Unexpected EOF" if chunk.nil? || chunk.bytesize != size
|
|
231
|
-
|
|
232
|
-
@offset += size
|
|
233
|
-
chunk
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
def read_u16 = read_exact(2).unpack1("n")
|
|
237
|
-
def read_u32 = read_exact(4).unpack1("N")
|
|
238
|
-
def read_u64 = read_exact(8).unpack1("Q>")
|
|
239
|
-
def read_i8 = read_exact(1).unpack1("c")
|
|
240
|
-
def read_i16 = read_exact(2).unpack1("s>")
|
|
241
|
-
def read_i32 = read_exact(4).unpack1("l>")
|
|
242
|
-
def read_i64 = read_exact(8).unpack1("q>")
|
|
243
|
-
def read_f32 = read_exact(4).unpack1("g")
|
|
244
|
-
def read_f64 = read_exact(8).unpack1("G")
|
|
245
|
-
def read_string(size) = read_exact(size).force_encoding("UTF-8")
|
|
246
|
-
def read_binary(size) = read_exact(size)
|
|
247
|
-
end
|
|
248
|
-
end
|
|
8
|
+
WireCodec = ::Ruflet::WireCodec unless const_defined?(:WireCodec, false)
|
|
249
9
|
end
|
|
250
10
|
end
|
|
251
11
|
end
|
data/lib/ruflet/rails/railtie.rb
CHANGED
|
@@ -7,19 +7,78 @@ module Ruflet
|
|
|
7
7
|
app.middleware.use Ruflet::Rails::Protocol::Middleware
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
initializer "ruflet_rails.desktop_launcher", after: :load_config_initializers do |_app|
|
|
11
|
+
next unless defined?(::Rails.root)
|
|
12
|
+
|
|
13
|
+
Ruflet::Rails::DesktopLauncher.launch_once(root: ::Rails.root)
|
|
14
|
+
end
|
|
15
|
+
|
|
10
16
|
rake_tasks do
|
|
11
17
|
namespace :ruflet do
|
|
12
18
|
desc "Build Ruflet client for this Rails app. Usage: rake ruflet:build[web]"
|
|
13
19
|
task :build, [:platform] do |_task, args|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
requested_platform = args[:platform].to_s.strip.downcase
|
|
21
|
+
build_args = Ruflet::Rails::InstallSupport.build_args_for_platform(requested_platform)
|
|
22
|
+
platform = build_args.first
|
|
23
|
+
if platform.to_s.empty?
|
|
24
|
+
warn "Usage: rake ruflet:build[apk|android|ios|aab|web|desktop|macos|windows|linux]"
|
|
17
25
|
next
|
|
18
26
|
end
|
|
19
27
|
|
|
20
28
|
require "ruflet/cli"
|
|
21
29
|
exit_code = Dir.chdir(::Rails.root) do
|
|
22
|
-
Ruflet::CLI.command_build(
|
|
30
|
+
Ruflet::CLI.command_build(build_args)
|
|
31
|
+
end
|
|
32
|
+
raise SystemExit, exit_code unless exit_code.to_i.zero?
|
|
33
|
+
|
|
34
|
+
if platform == "web"
|
|
35
|
+
published = Ruflet::Rails::InstallSupport.publish_web_build(::Rails.root.to_s)
|
|
36
|
+
if published
|
|
37
|
+
puts "Ruflet web client published at /#{Ruflet::Rails::InstallSupport.default_web_public_path}/"
|
|
38
|
+
else
|
|
39
|
+
warn "Ruflet web build completed, but build/web was not found to publish."
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
desc "Download/update prebuilt Ruflet clients from GitHub releases. Usage: rake ruflet:update[web|desktop|all]"
|
|
45
|
+
task :update, [:target] do |_task, args|
|
|
46
|
+
target = args[:target].to_s.strip
|
|
47
|
+
if target.empty?
|
|
48
|
+
warn "Usage: rake ruflet:update[web|desktop|all]"
|
|
49
|
+
next
|
|
50
|
+
end
|
|
51
|
+
normalized_target = target.downcase
|
|
52
|
+
platform = Ruflet::Rails::InstallSupport.host_desktop_platform
|
|
53
|
+
|
|
54
|
+
require "ruflet/cli"
|
|
55
|
+
exit_code = Dir.chdir(::Rails.root) do
|
|
56
|
+
Ruflet::CLI.command_update([target])
|
|
57
|
+
end
|
|
58
|
+
raise SystemExit, exit_code unless exit_code.to_i.zero?
|
|
59
|
+
|
|
60
|
+
if %w[web all].include?(normalized_target)
|
|
61
|
+
published = Ruflet::Rails::InstallSupport.publish_prebuilt_web_client(
|
|
62
|
+
::Rails.root.to_s,
|
|
63
|
+
platform: platform
|
|
64
|
+
)
|
|
65
|
+
if published
|
|
66
|
+
puts "Ruflet web client published at /#{Ruflet::Rails::InstallSupport.default_web_public_path}/"
|
|
67
|
+
else
|
|
68
|
+
warn "Ruflet web client downloaded, but no prebuilt web index.html was found to publish."
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
desc "Install the last built Ruflet mobile app onto a device. Usage: rake ruflet:install[DEVICE_ID]"
|
|
74
|
+
task :install, [:device] do |_task, args|
|
|
75
|
+
argv = []
|
|
76
|
+
device = args[:device].to_s.strip
|
|
77
|
+
argv += ["--device", device] unless device.empty?
|
|
78
|
+
|
|
79
|
+
require "ruflet/cli"
|
|
80
|
+
exit_code = Dir.chdir(::Rails.root) do
|
|
81
|
+
Ruflet::CLI.command_install(argv)
|
|
23
82
|
end
|
|
24
83
|
raise SystemExit, exit_code unless exit_code.to_i.zero?
|
|
25
84
|
end
|