Module: Ruflet::Rails

Defined in:
lib/ruflet/rails.rb,
lib/ruflet/rails/assets.rb,
lib/ruflet/rails/railtie.rb,
lib/ruflet/rails/native_app.rb,
lib/ruflet/rails/native_app.rb,
lib/ruflet/rails/view_helpers.rb,
lib/ruflet/rails/configuration.rb,
lib/ruflet/rails/web_installer.rb,
lib/ruflet/rails/install_support.rb,
lib/ruflet/rails/protocol/runner.rb,
lib/ruflet/rails/desktop_launcher.rb,
lib/ruflet/rails/native_app/shell.rb,
lib/ruflet/rails/protocol/context.rb,
lib/ruflet/rails/protocol/web_app.rb,
lib/ruflet/rails/session_registry.rb,
lib/ruflet/rails/protocol/endpoint.rb,
lib/ruflet/rails/native_app/actions.rb,
lib/ruflet/rails/native_app/overlays.rb,
lib/ruflet/rails/native_app/navigation.rb,
lib/ruflet/rails/protocol/local_server.rb,
lib/ruflet/rails/protocol/mobile_loader.rb,
lib/ruflet/rails/native_app/html_adapter.rb,
lib/ruflet/rails/native_app/customization.rb,
lib/ruflet/rails/protocol/websocket_detection.rb

Defined Under Namespace

Modules: DesktopLauncher, InstallSupport, Protocol, ViewHelpers, WebInstaller Classes: Configuration, NativeApp, Railtie, Session, SessionRegistry

Class Method Summary collapse

Class Method Details

.app(file_path) ⇒ Object

Shorthand for a standalone app-file endpoint.

match "/ws", to: Ruflet::Rails.app(Rails.root.join("app/ruflet/main.rb")), via: :all


54
55
56
# File 'lib/ruflet/rails.rb', line 54

def app(file_path)
  endpoint(app_file: file_path)
end

.asset_url(source, host: nil) ⇒ Object



43
44
45
46
47
48
49
50
51
52
# File 'lib/ruflet/rails/assets.rb', line 43

def asset_url(source, host: nil)
  raw = source.to_s
  return raw if absolute_url?(raw)

  path = asset_pipeline_path(raw)
  return path if absolute_url?(path)

  base = backend_url(host: host)
  base.empty? ? path : "#{base}#{path}"
end

.backend_url(host: nil) ⇒ Object

The base URL the Flutter client uses to reach this Rails app — the single source of truth for asset URLs, the build-time RUFLET_URL define and the desktop launcher. A Rails Ruflet app always needs one, so this always resolves to a usable value:

1. an explicit host: argument
2. Ruflet::Rails.config.backend_url (set it in config/initializers/ruflet.rb)
3. the host the client connected on (the live WebSocket request)

Returns "" only when none of those are available (e.g. a build with no configured backend_url) — set config.backend_url to cover that case.



37
38
39
40
41
# File 'lib/ruflet/rails/assets.rb', line 37

def backend_url(host: nil)
  candidate = host || config.backend_url
  candidate = request_base_url if candidate.to_s.strip.empty?
  candidate.to_s.strip.sub(%r{/+\z}, "")
end

.broadcast(&block) ⇒ Object



27
28
29
# File 'lib/ruflet/rails.rb', line 27

def broadcast(&block)
  sessions.broadcast(&block)
end

.configObject

Returns the global configuration object.



10
11
12
# File 'lib/ruflet/rails.rb', line 10

def config
  @config ||= Configuration.new
end

.configure {|config| ... } ⇒ Object

Yields the configuration object for block-style setup.

Ruflet::Rails.configure do |c|
c.backend_url = "https://example.com"
end

Yields:



19
20
21
# File 'lib/ruflet/rails.rb', line 19

def configure
  yield config
end

.endpoint(app_file: nil, &block) ⇒ Object

WebSocket endpoint for native mobile/desktop clients. The developer owns the Ruflet entrypoint explicitly; there is no Rails component discovery:

# a standalone Ruflet app file (Ruflet.run/MyApp.new.run), per session:
match "/ws", to: Ruflet::Rails.endpoint(app_file: Rails.root.join("app/ruflet/main.rb")), via: :all

# a custom block:
match "/ws", to: Ruflet::Rails.endpoint { |page| MyHome.render(page) }, via: :all

One of app_file: or a block is required.

Raises:

  • (ArgumentError)


41
42
43
44
45
46
47
48
49
# File 'lib/ruflet/rails.rb', line 41

def endpoint(app_file: nil, &block)
  sources = [app_file, block].compact
  raise ArgumentError, "endpoint accepts only one of app_file: or a block" if sources.length > 1
  raise ArgumentError, "endpoint requires one of app_file: or a block" if sources.empty?

  return Protocol::Runner.new.build_app_endpoint(file_path: app_file) if app_file

  Protocol::Runner.new(&block).build_endpoint
end

.image_url(source, host: nil) ⇒ Object

Readability alias for image sources — identical resolution.



55
56
57
# File 'lib/ruflet/rails/assets.rb', line 55

def image_url(source, host: nil)
  asset_url(source, host: host)
end

.native_app(page, **opts) ⇒ Object

Start a managed webview app. See NativeApp.



53
54
55
# File 'lib/ruflet/rails/native_app.rb', line 53

def native_app(page, **opts)
  NativeApp.new(page, **opts).start
end

.sessionsObject



23
24
25
# File 'lib/ruflet/rails.rb', line 23

def sessions
  @sessions ||= SessionRegistry.new
end

.web_app(app_file: nil, build_dir: nil, &app_block) ⇒ Object

Self-contained web frontend, mountable under any route. Serves the Flutter web build (with rewritten to the mount point) and answers the Ruflet WebSocket on the same path. Routes stay routing-only; UI code lives in dev files:

# a standalone Ruflet app file (MyApp.new.run), loaded per session:
mount Ruflet::Rails.web_app(app_file: "app/ruflet/showcase/main.rb"), at: "/showcase"

# a custom block:
mount Ruflet::Rails.web_app { |page| MyHome.render(page) }, at: "/app"

One of app_file: or a block is required.

Raises:

  • (ArgumentError)


70
71
72
73
74
75
76
77
78
79
80
# File 'lib/ruflet/rails.rb', line 70

def web_app(app_file: nil, build_dir: nil, &app_block)
  sources = [app_file, app_block].compact
  raise ArgumentError, "web_app accepts only one of app_file: or a block" if sources.length > 1
  raise ArgumentError, "web_app requires one of app_file: or a block" if sources.empty?

  Protocol::WebApp.new(
    build_dir: build_dir,
    entrypoint: web_app_entrypoint(app_file: app_file),
    &app_block
  )
end

.web_app_entrypoint(app_file: nil) ⇒ Object



82
83
84
85
86
87
88
89
90
91
# File 'lib/ruflet/rails.rb', line 82

def web_app_entrypoint(app_file: nil)
  return unless app_file

  absolute = app_file.to_s
  lambda do |page, env|
    loaded = Protocol::MobileLoader.new(File.expand_path(absolute)).load!
    entry = loaded[:entrypoint]
    entry.arity == 1 ? entry.call(page) : entry.call(page, env)
  end
end