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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5c44fadc8aa766bbfe16e6dc94afb0503a03408ee5bb8108fc9064cb10adf84
4
- data.tar.gz: 81a8de81cf0cbbfee1deb8a3fdea5b448a9ba3a8b417ffc942d33fb5dc2d8d2f
3
+ metadata.gz: c8ce7c65f8922fb96c632f08c3fbbfbe5ab4d2f51ef010aca2938dada94bbb06
4
+ data.tar.gz: '09a16364d80b69210248753a9eeb062a8d895be2cfa02844bd7850a96c4392d4'
5
5
  SHA512:
6
- metadata.gz: 04c82e1accf25203c93e3fc987166453cec277b5e29b380c7539d3f32dd0e777843d1f084485513437108bda6cc8047a80c141a890ec926a0c63f8ef7b8ae688
7
- data.tar.gz: 0013bf2ab8f22d327df1f48ced0d956148c495e310bcc7ac0230b0ba2344ebb97d5f744a26cafe23ce4969fa9776da17acdc66f64aacfbb5dcb1772e1826daf6
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
- bundle exec ruflet run
11
- bundle exec ruflet run --web
12
- bundle exec ruflet run --desktop
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
- refreshed_page = Page.new(
128
- session_id: current_page.session_id,
129
- client_details: current_page.client_details,
130
- sender: lambda do |action, payload|
131
- send_message(ws, action, payload)
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
- @sessions_mutex.synchronize do
137
- @sessions[session_key] = refreshed_page
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
- case path
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 path.start_with?("/assets/")
334
- serve_asset(socket, path)
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
- send_message(ws, action, msg_payload)
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
- Protocol.register_response(session_id: session_id)
662
+ response
553
663
  ]
554
664
  ws.send_binary(Ruflet::WireCodec.pack(initial_response))
665
+ registered = true
555
666
 
556
- @app_block.call(page)
557
- page.update
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ruflet
4
- VERSION = "0.0.19" unless const_defined?(:VERSION)
4
+ VERSION = "0.0.20" unless const_defined?(:VERSION)
5
5
  end
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.19
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.19
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.19
25
+ version: 0.0.20
26
26
  description: Ruflet WebSocket server runtime compatible with Flet protocol.
27
27
  email:
28
28
  - adammusa2222@gmail.com