ruflet_rails 0.0.14 → 0.0.16

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '097cfb74aac7af1e15b8bcb49135851595cbd7a1856eef1b4df39672c95ebab8'
4
- data.tar.gz: 27d2e764c20535ebc41befd5672741500cb78387ee74f56ea284a60de9d8d721
3
+ metadata.gz: 92fa7112149cede7ab3ba1dfc0f0c235c662513f83c27a3bea423ef3f68a6664
4
+ data.tar.gz: 9903778b68c3b7fd3596509fd91036b3fc4775308b4d3728fafa93269a5d3bcc
5
5
  SHA512:
6
- metadata.gz: ad4c0ab96a6741c9ff4339590e854abf2e72648a0b10c85bdf371ff7aaaee005e394f5063eecf9395756e1d9260d021a4d6c54737a8ad6326e811e63dca9d75a
7
- data.tar.gz: c5aa7b3df32bedad4122d379739e05f770b8caa359e3ac01b114a29ee9c89bc7c5a3ecd175857dc79eaccb737b33bcd007d977086061b9ad25c9187872a2f994
6
+ metadata.gz: 81f65d75640995d5471a8fcfcb0c7fa99792a9de754978f0dee7f43d7dfb14e8db7a24bb3ff1bc826a060981c114c7caab138f50ba16cb6bcef52514ea56c03d
7
+ data.tar.gz: da622f66688a2f49208b13e5be0290e3fc8e19ed7c150bb135ac5f57b8c0f97ebc4e3de5db2f9901af1e87b96d2650cc1a730745a5535ee52738e1065fd055e0
@@ -155,7 +155,7 @@ module Ruflet
155
155
  html.include?("</head>") ? html.sub("</head>", "#{script}</head>") : "#{script}#{html}"
156
156
  end
157
157
 
158
- # Pins Flet-style clients to <mount>/ws (derived from the rewritten
158
+ # Pins Ruflet clients to <mount>/ws (derived from the rewritten
159
159
  # <base href>) instead of the origin's /ws. The Ruflet client itself
160
160
  # uses the mounted page URL, so Rails needs no separate backend URL.
161
161
  def mount_websocket_bootstrap
@@ -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,268 @@
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 ::Ruflet::Protocol::DateTimeValue
11
+ pack_extension(1, value)
12
+ when ::Ruflet::Protocol::TimeOfDayValue
13
+ pack_extension(2, value)
14
+ when ::Ruflet::Protocol::DurationValue
15
+ pack_extension(3, value)
16
+ when NilClass
17
+ "\xc0".b
18
+ when TrueClass
19
+ "\xc3".b
20
+ when FalseClass
21
+ "\xc2".b
22
+ when Integer
23
+ pack_integer(value)
24
+ when Float
25
+ "\xcb".b + [value].pack("G")
26
+ when String
27
+ binary_string?(value) ? pack_binary(value) : pack_string(value)
28
+ when Symbol
29
+ pack_string(value.to_s)
30
+ when Array
31
+ pack_array(value)
32
+ when Hash
33
+ pack_map(value)
34
+ else
35
+ pack_string(value.to_s)
36
+ end
37
+ end
38
+
39
+ def unpack(bytes)
40
+ reader = ByteReader.new(bytes)
41
+ read_value(reader)
42
+ end
43
+
44
+ private
45
+
46
+ def pack_extension(type, value)
47
+ bytes = value.to_s.b
48
+ length = bytes.bytesize
49
+ header =
50
+ if length <= 0xff
51
+ "\xc7".b + [length].pack("C")
52
+ elsif length <= 0xffff
53
+ "\xc8".b + [length].pack("n")
54
+ else
55
+ "\xc9".b + [length].pack("N")
56
+ end
57
+ header + [type].pack("c") + bytes
58
+ end
59
+
60
+ def pack_integer(value)
61
+ if value >= 0
62
+ return [value].pack("C") if value <= 0x7f
63
+ return "\xcc".b + [value].pack("C") if value <= 0xff
64
+ return "\xcd".b + [value].pack("n") if value <= 0xffff
65
+ return "\xce".b + [value].pack("N") if value <= 0xffff_ffff
66
+
67
+ "\xcf".b + [value].pack("Q>")
68
+ else
69
+ return [value & 0xff].pack("C") if value >= -32
70
+ return "\xd0".b + [value].pack("c") if value >= -128
71
+ return "\xd1".b + [value].pack("s>") if value >= -32_768
72
+ return "\xd2".b + [value].pack("l>") if value >= -2_147_483_648
73
+
74
+ "\xd3".b + [value].pack("q>")
75
+ end
76
+ end
77
+
78
+ def pack_string(value)
79
+ str = value.to_s.dup.force_encoding("UTF-8")
80
+ bytes = str.b
81
+ len = bytes.bytesize
82
+
83
+ if len <= 31
84
+ [0xA0 | len].pack("C") + bytes
85
+ elsif len <= 0xff
86
+ "\xd9".b + [len].pack("C") + bytes
87
+ elsif len <= 0xffff
88
+ "\xda".b + [len].pack("n") + bytes
89
+ else
90
+ "\xdb".b + [len].pack("N") + bytes
91
+ end
92
+ end
93
+
94
+ def pack_binary(value)
95
+ bytes = value.to_s.b
96
+ len = bytes.bytesize
97
+
98
+ if len <= 0xff
99
+ "\xc4".b + [len].pack("C") + bytes
100
+ elsif len <= 0xffff
101
+ "\xc5".b + [len].pack("n") + bytes
102
+ else
103
+ "\xc6".b + [len].pack("N") + bytes
104
+ end
105
+ end
106
+
107
+ def binary_string?(value)
108
+ value.encoding == Encoding::BINARY || !value.valid_encoding?
109
+ end
110
+
111
+ def pack_array(value)
112
+ len = value.length
113
+ head =
114
+ if len <= 15
115
+ [0x90 | len].pack("C")
116
+ elsif len <= 0xffff
117
+ "\xdc".b + [len].pack("n")
118
+ else
119
+ "\xdd".b + [len].pack("N")
120
+ end
121
+
122
+ body = +"".b
123
+ value.each { |item| body << pack(item) }
124
+ head + body
125
+ end
126
+
127
+ def pack_map(value)
128
+ pairs = value.each_with_object({}) { |(k, v), out| out[k.to_s] = v }
129
+ len = pairs.length
130
+ head =
131
+ if len <= 15
132
+ [0x80 | len].pack("C")
133
+ elsif len <= 0xffff
134
+ "\xde".b + [len].pack("n")
135
+ else
136
+ "\xdf".b + [len].pack("N")
137
+ end
138
+
139
+ body = +"".b
140
+ pairs.each do |k, v|
141
+ body << pack(k)
142
+ body << pack(v)
143
+ end
144
+ head + body
145
+ end
146
+
147
+ def read_value(reader)
148
+ marker = reader.read_u8
149
+
150
+ return marker if marker <= 0x7f
151
+ return marker - 256 if marker >= 0xe0
152
+
153
+ case marker
154
+ when 0xc0 then nil
155
+ when 0xc2 then false
156
+ when 0xc3 then true
157
+ when 0xcc then reader.read_u8
158
+ when 0xcd then reader.read_u16
159
+ when 0xce then reader.read_u32
160
+ when 0xcf then reader.read_u64
161
+ when 0xd0 then reader.read_i8
162
+ when 0xd1 then reader.read_i16
163
+ when 0xd2 then reader.read_i32
164
+ when 0xd3 then reader.read_i64
165
+ when 0xca then reader.read_f32
166
+ when 0xcb then reader.read_f64
167
+ when 0xd9 then reader.read_string(reader.read_u8)
168
+ when 0xda then reader.read_string(reader.read_u16)
169
+ when 0xdb then reader.read_string(reader.read_u32)
170
+ when 0xc4 then reader.read_binary(reader.read_u8)
171
+ when 0xc5 then reader.read_binary(reader.read_u16)
172
+ when 0xc6 then reader.read_binary(reader.read_u32)
173
+ when 0xdc then read_array(reader, reader.read_u16)
174
+ when 0xdd then read_array(reader, reader.read_u32)
175
+ when 0xde then read_map(reader, reader.read_u16)
176
+ when 0xdf then read_map(reader, reader.read_u32)
177
+ when 0xd4
178
+ read_ext(reader, 1)
179
+ when 0xd5
180
+ read_ext(reader, 2)
181
+ when 0xd6
182
+ read_ext(reader, 4)
183
+ when 0xd7
184
+ read_ext(reader, 8)
185
+ when 0xd8
186
+ read_ext(reader, 16)
187
+ when 0xc7
188
+ read_ext(reader, reader.read_u8)
189
+ when 0xc8
190
+ read_ext(reader, reader.read_u16)
191
+ when 0xc9
192
+ read_ext(reader, reader.read_u32)
193
+ else
194
+ if (marker & 0xf0) == 0x90
195
+ read_array(reader, marker & 0x0f)
196
+ elsif (marker & 0xf0) == 0x80
197
+ read_map(reader, marker & 0x0f)
198
+ elsif (marker & 0xe0) == 0xa0
199
+ reader.read_string(marker & 0x1f)
200
+ else
201
+ raise "Unsupported MessagePack marker: 0x#{marker.to_s(16)}"
202
+ end
203
+ end
204
+ end
205
+
206
+ def read_array(reader, size)
207
+ Array.new(size) { read_value(reader) }
208
+ end
209
+
210
+ def read_map(reader, size)
211
+ out = {}
212
+ size.times do
213
+ key = read_value(reader)
214
+ out[key.to_s] = read_value(reader)
215
+ end
216
+ out
217
+ end
218
+
219
+ def read_ext(reader, size)
220
+ type = reader.read_i8
221
+ value = reader.read_exact(size).force_encoding("UTF-8")
222
+ case type
223
+ when 1 then ::Ruflet::Protocol.date_time(value)
224
+ when 2 then ::Ruflet::Protocol.time_of_day(value)
225
+ when 3 then ::Ruflet::Protocol.duration(value)
226
+ else value
227
+ end
228
+ end
229
+ end
230
+
231
+ class ByteReader
232
+ def initialize(bytes)
233
+ @data = bytes.to_s.b
234
+ @offset = 0
235
+ end
236
+
237
+ def read_u8
238
+ value = @data.getbyte(@offset)
239
+ raise "Unexpected EOF" if value.nil?
240
+
241
+ @offset += 1
242
+ value
243
+ end
244
+
245
+ def read_exact(size)
246
+ chunk = @data.byteslice(@offset, size)
247
+ raise "Unexpected EOF" if chunk.nil? || chunk.bytesize != size
248
+
249
+ @offset += size
250
+ chunk
251
+ end
252
+
253
+ def read_u16 = read_exact(2).unpack1("n")
254
+ def read_u32 = read_exact(4).unpack1("N")
255
+ def read_u64 = read_exact(8).unpack1("Q>")
256
+ def read_i8 = read_exact(1).unpack1("c")
257
+ def read_i16 = read_exact(2).unpack1("s>")
258
+ def read_i32 = read_exact(4).unpack1("l>")
259
+ def read_i64 = read_exact(8).unpack1("q>")
260
+ def read_f32 = read_exact(4).unpack1("g")
261
+ def read_f64 = read_exact(8).unpack1("G")
262
+ def read_string(size) = read_exact(size).force_encoding("UTF-8")
263
+ def read_binary(size) = read_exact(size)
264
+ end
265
+ end
266
+ end
267
+ end
268
+ end
@@ -100,6 +100,25 @@ module Ruflet
100
100
  end
101
101
  end
102
102
  end
103
+
104
+ rake_tasks do
105
+ namespace :ruflet do
106
+ desc "Build Ruflet client for this Rails app. Usage: rake ruflet:build[web]"
107
+ task :build, [:platform] do |_task, args|
108
+ platform = args[:platform].to_s.strip
109
+ if platform.empty?
110
+ warn "Usage: rake ruflet:build[apk|android|ios|aab|web|macos|windows|linux]"
111
+ next
112
+ end
113
+
114
+ require "ruflet/cli"
115
+ exit_code = Dir.chdir(::Rails.root) do
116
+ Ruflet::CLI.command_build([platform])
117
+ end
118
+ raise SystemExit, exit_code unless exit_code.to_i.zero?
119
+ end
120
+ end
121
+ end
103
122
  end
104
123
  end
105
124
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ruflet
4
- VERSION = "0.0.14" unless const_defined?(:VERSION)
4
+ VERSION = "0.0.16" unless const_defined?(:VERSION)
5
5
  end
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.14
4
+ version: 0.0.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Moussa Ali
@@ -29,42 +29,42 @@ dependencies:
29
29
  requirements:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 0.0.17
32
+ version: 0.0.19
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.17
39
+ version: 0.0.19
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: ruflet_core
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: 0.0.17
46
+ version: 0.0.19
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: 0.0.17
53
+ version: 0.0.19
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: ruflet_server
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: 0.0.17
60
+ version: 0.0.19
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - ">="
66
66
  - !ruby/object:Gem::Version
67
- version: 0.0.17
67
+ version: 0.0.19
68
68
  description: Build cross-platform mobile and desktop apps with Ruby on Rails using
69
69
  Ruflet.
70
70
  email:
@@ -94,7 +94,9 @@ files:
94
94
  - lib/ruflet/rails/protocol/mobile_loader.rb
95
95
  - lib/ruflet/rails/protocol/runner.rb
96
96
  - lib/ruflet/rails/protocol/web_app.rb
97
+ - lib/ruflet/rails/protocol/web_socket_connection.rb
97
98
  - lib/ruflet/rails/protocol/websocket_detection.rb
99
+ - lib/ruflet/rails/protocol/wire_codec.rb
98
100
  - lib/ruflet/rails/railtie.rb
99
101
  - lib/ruflet/rails/session_registry.rb
100
102
  - lib/ruflet/rails/view_helpers.rb