ruflet_server 0.0.19 → 0.0.20
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 +3 -3
- data/lib/ruflet/server.rb +135 -20
- data/lib/ruflet/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c8ce7c65f8922fb96c632f08c3fbbfbe5ab4d2f51ef010aca2938dada94bbb06
|
|
4
|
+
data.tar.gz: '09a16364d80b69210248753a9eeb062a8d895be2cfa02844bd7850a96c4392d4'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0efb74cd09d74eb058e0cfbdaa3027483fb809552e15109070a80e3bad364f9d9d297173249033df16029c2f6c366ee24db3e40848d1adf7fa583927b4fe7a83
|
|
7
|
+
data.tar.gz: 908da326003ca5cfff54b1a608148afaf0f557c8d9871c602e8234abe6710b6e79c27f0f0de0b9bcda78368ec67735d62ba31b41f84af9f6b551791bde15a889
|
data/README.md
CHANGED
|
@@ -7,9 +7,9 @@ It is installed automatically in projects created with `ruflet new`. Start an
|
|
|
7
7
|
application through the Ruflet CLI:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
ruflet run
|
|
11
|
+
ruflet run --web
|
|
12
|
+
ruflet run --desktop
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Application code uses the public `Ruflet.run` API supplied by `ruflet_core`.
|
data/lib/ruflet/server.rb
CHANGED
|
@@ -124,21 +124,50 @@ module Ruflet
|
|
|
124
124
|
ws = @connections_mutex.synchronize { @connections[session_key] }
|
|
125
125
|
next unless ws
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
127
|
+
# Open dialogs, sheets, and snack bars are Navigator routes on the
|
|
128
|
+
# client; replacing the control tree does not pop them. Close them
|
|
129
|
+
# first, otherwise a reload while an overlay is open leaves it on
|
|
130
|
+
# screen wired to control ids the reloaded page cannot resolve.
|
|
131
|
+
begin
|
|
132
|
+
nil while current_page.respond_to?(:close_dialog) && current_page.close_dialog
|
|
133
|
+
rescue StandardError
|
|
134
|
+
nil
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
if current_page.respond_to?(:reset_for_reload!)
|
|
138
|
+
# Re-render on the live page. The client's overlay/service/dialogs
|
|
139
|
+
# containers stay mounted; a recreated Page would re-send them,
|
|
140
|
+
# replacing their client-side instances and detaching them — after
|
|
141
|
+
# which dialog and service patches are silently ignored.
|
|
142
|
+
current_page.reset_for_reload!
|
|
143
|
+
@app_block.call(current_page)
|
|
144
|
+
# Clear page-level chrome the reloaded block dropped and flush the
|
|
145
|
+
# overlay (appbar/drawer/FAB live in the view and rebuild wholesale;
|
|
146
|
+
# page props and the mounted overlay need explicit updates).
|
|
147
|
+
current_page.finalize_reload! if current_page.respond_to?(:finalize_reload!)
|
|
148
|
+
# Keeps route-driven apps on their current route: replays the
|
|
149
|
+
# route_change event when the block did not route itself.
|
|
150
|
+
current_page.replay_route_after_reload! if current_page.respond_to?(:replay_route_after_reload!)
|
|
151
|
+
current_page.update
|
|
152
|
+
else
|
|
153
|
+
# Older ruflet_core without reset_for_reload!: fall back to a fresh
|
|
154
|
+
# page (loses client-side container bindings until reconnect).
|
|
155
|
+
refreshed_page = Page.new(
|
|
156
|
+
session_id: current_page.session_id,
|
|
157
|
+
client_details: current_page.client_details,
|
|
158
|
+
sender: lambda do |action, payload|
|
|
159
|
+
send_message(ws, action, payload)
|
|
160
|
+
end
|
|
161
|
+
)
|
|
162
|
+
refreshed_page.title = "Ruflet App"
|
|
163
|
+
|
|
164
|
+
@sessions_mutex.synchronize do
|
|
165
|
+
@sessions[session_key] = refreshed_page
|
|
132
166
|
end
|
|
133
|
-
)
|
|
134
|
-
refreshed_page.title = "Ruflet App"
|
|
135
167
|
|
|
136
|
-
|
|
137
|
-
|
|
168
|
+
@app_block.call(refreshed_page)
|
|
169
|
+
refreshed_page.update
|
|
138
170
|
end
|
|
139
|
-
|
|
140
|
-
@app_block.call(refreshed_page)
|
|
141
|
-
refreshed_page.update
|
|
142
171
|
rescue StandardError => e
|
|
143
172
|
warn "reload error: #{e.class}: #{e.message}"
|
|
144
173
|
end
|
|
@@ -155,7 +184,10 @@ module Ruflet
|
|
|
155
184
|
|
|
156
185
|
def trap_signal(signal_name)
|
|
157
186
|
Signal.trap(signal_name) do
|
|
158
|
-
stop
|
|
187
|
+
# Trap context restricts Mutex use, so calling stop here raises
|
|
188
|
+
# ThreadError and the signal is silently swallowed. Only unwind the
|
|
189
|
+
# main thread; start's ensure performs the actual stop outside the
|
|
190
|
+
# trap context.
|
|
159
191
|
Thread.main.raise(Interrupt)
|
|
160
192
|
rescue StandardError
|
|
161
193
|
nil
|
|
@@ -324,14 +356,17 @@ module Ruflet
|
|
|
324
356
|
end
|
|
325
357
|
|
|
326
358
|
def handle_http_request(socket, path)
|
|
327
|
-
|
|
359
|
+
request_path = path.to_s.split("?", 2).first.to_s
|
|
360
|
+
return if serve_web_client(socket, request_path)
|
|
361
|
+
|
|
362
|
+
case request_path
|
|
328
363
|
when "/health"
|
|
329
364
|
write_http_response(socket, 200, "text/plain", "ok")
|
|
330
365
|
when "/"
|
|
331
366
|
write_http_response(socket, 200, "text/plain", "ruflet server")
|
|
332
367
|
else
|
|
333
|
-
if
|
|
334
|
-
serve_asset(socket,
|
|
368
|
+
if request_path.start_with?("/assets/")
|
|
369
|
+
serve_asset(socket, request_path)
|
|
335
370
|
else
|
|
336
371
|
write_http_response(socket, 404, "text/plain", "not found")
|
|
337
372
|
end
|
|
@@ -341,6 +376,37 @@ module Ruflet
|
|
|
341
376
|
write_http_response(socket, 500, "text/plain", "server error")
|
|
342
377
|
end
|
|
343
378
|
|
|
379
|
+
# The Flutter web client is served from this same port so that it loads and
|
|
380
|
+
# opens its websocket on one origin. Without that the client cannot reach
|
|
381
|
+
# /ws at all.
|
|
382
|
+
def web_client_root
|
|
383
|
+
return @web_client_root if defined?(@web_client_root)
|
|
384
|
+
|
|
385
|
+
configured = ENV["RUFLET_WEB_CLIENT_DIR"].to_s.strip
|
|
386
|
+
@web_client_root =
|
|
387
|
+
if configured.empty? || !File.directory?(configured)
|
|
388
|
+
nil
|
|
389
|
+
else
|
|
390
|
+
File.expand_path(configured)
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def serve_web_client(socket, request_path)
|
|
395
|
+
root = web_client_root
|
|
396
|
+
return false unless root
|
|
397
|
+
|
|
398
|
+
relative = request_path
|
|
399
|
+
relative = "/index.html" if relative.empty? || relative == "/"
|
|
400
|
+
candidate = File.expand_path(File.join(root, relative))
|
|
401
|
+
# Never serve outside the client bundle.
|
|
402
|
+
return false unless candidate == root || candidate.start_with?("#{root}#{File::SEPARATOR}")
|
|
403
|
+
return false unless File.file?(candidate)
|
|
404
|
+
|
|
405
|
+
content = read_binary_file(candidate)
|
|
406
|
+
write_http_response(socket, 200, content_type_for(candidate), content, binary: true)
|
|
407
|
+
true
|
|
408
|
+
end
|
|
409
|
+
|
|
344
410
|
def serve_asset(socket, path)
|
|
345
411
|
asset_path = resolve_asset_path(path)
|
|
346
412
|
unless asset_path
|
|
@@ -392,6 +458,28 @@ module Ruflet
|
|
|
392
458
|
"image/webp"
|
|
393
459
|
when ".svg"
|
|
394
460
|
"image/svg+xml"
|
|
461
|
+
when ".html", ".htm"
|
|
462
|
+
"text/html; charset=utf-8"
|
|
463
|
+
when ".js", ".mjs"
|
|
464
|
+
"text/javascript; charset=utf-8"
|
|
465
|
+
when ".css"
|
|
466
|
+
"text/css; charset=utf-8"
|
|
467
|
+
when ".json", ".map"
|
|
468
|
+
"application/json; charset=utf-8"
|
|
469
|
+
when ".wasm"
|
|
470
|
+
"application/wasm"
|
|
471
|
+
when ".ttf"
|
|
472
|
+
"font/ttf"
|
|
473
|
+
when ".otf"
|
|
474
|
+
"font/otf"
|
|
475
|
+
when ".woff"
|
|
476
|
+
"font/woff"
|
|
477
|
+
when ".woff2"
|
|
478
|
+
"font/woff2"
|
|
479
|
+
when ".ico"
|
|
480
|
+
"image/x-icon"
|
|
481
|
+
when ".txt", ".symbols"
|
|
482
|
+
"text/plain; charset=utf-8"
|
|
395
483
|
else
|
|
396
484
|
"application/octet-stream"
|
|
397
485
|
end
|
|
@@ -533,11 +621,23 @@ module Ruflet
|
|
|
533
621
|
normalized = Protocol.normalize_register_payload(payload)
|
|
534
622
|
session_id = normalized["session_id"].to_s.empty? ? pseudo_uuid : normalized["session_id"]
|
|
535
623
|
|
|
624
|
+
# Run the app block BEFORE responding and ship the complete state in the
|
|
625
|
+
# register response (page_patch), like Flet. The client merges that map
|
|
626
|
+
# by control id, keeping existing instances alive — required for
|
|
627
|
+
# reconnecting clients (backend restarts) whose control store persists.
|
|
628
|
+
# Incremental op patches sent during the block are superseded by the
|
|
629
|
+
# full state and dropped; everything else is flushed afterwards.
|
|
630
|
+
registered = false
|
|
631
|
+
buffered = []
|
|
536
632
|
page = Page.new(
|
|
537
633
|
session_id: session_id,
|
|
538
634
|
client_details: normalized,
|
|
539
635
|
sender: lambda do |action, msg_payload|
|
|
540
|
-
|
|
636
|
+
if registered
|
|
637
|
+
send_message(ws, action, msg_payload)
|
|
638
|
+
else
|
|
639
|
+
buffered << [action, msg_payload]
|
|
640
|
+
end
|
|
541
641
|
end
|
|
542
642
|
)
|
|
543
643
|
|
|
@@ -547,14 +647,29 @@ module Ruflet
|
|
|
547
647
|
@sessions[ws.session_key] = page
|
|
548
648
|
end
|
|
549
649
|
|
|
650
|
+
@app_block.call(page)
|
|
651
|
+
|
|
652
|
+
page_patch = page.respond_to?(:register_page_patch) ? page.register_page_patch : {}
|
|
653
|
+
response = begin
|
|
654
|
+
Protocol.register_response(session_id: session_id, page_patch: page_patch)
|
|
655
|
+
rescue ArgumentError
|
|
656
|
+
# Older ruflet_core without the page_patch parameter.
|
|
657
|
+
page_patch = {}
|
|
658
|
+
Protocol.register_response(session_id: session_id)
|
|
659
|
+
end
|
|
550
660
|
initial_response = [
|
|
551
661
|
Protocol::ACTIONS[:register_client],
|
|
552
|
-
|
|
662
|
+
response
|
|
553
663
|
]
|
|
554
664
|
ws.send_binary(Ruflet::WireCodec.pack(initial_response))
|
|
665
|
+
registered = true
|
|
555
666
|
|
|
556
|
-
|
|
557
|
-
|
|
667
|
+
buffered.each do |action, msg_payload|
|
|
668
|
+
next if action == Protocol::ACTIONS[:patch_control]
|
|
669
|
+
|
|
670
|
+
send_message(ws, action, msg_payload)
|
|
671
|
+
end
|
|
672
|
+
page.update if page_patch.empty?
|
|
558
673
|
rescue StandardError => e
|
|
559
674
|
send_message(ws, Protocol::ACTIONS[:session_crashed], { "message" => e.message })
|
|
560
675
|
raise e
|
data/lib/ruflet/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruflet_server
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.20
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- AdamMusa
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.0.
|
|
18
|
+
version: 0.0.20
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.0.
|
|
25
|
+
version: 0.0.20
|
|
26
26
|
description: Ruflet WebSocket server runtime compatible with Flet protocol.
|
|
27
27
|
email:
|
|
28
28
|
- adammusa2222@gmail.com
|