Class: Phlex::Reactive::ActionsController

Inherits:
Object
  • Object
show all
Defined in:
app/controllers/phlex/reactive/actions_controller.rb

Overview

The single endpoint behind every reactive component. The generic reactive Stimulus controller POSTs here with a signed identity token, an action name, and params. We verify the token, rebuild the component (re-finding the record from the DB for record-backed components), run the whitelisted action, and return an auto-targeted Turbo Stream the client morphs in.

Customizing in your app:

* Authentication — by default this inherits from
Phlex::Reactive.base_controller (ActionController::Base). Set it to
your ApplicationController to get current_user/Current/CSRF, but make
sure the action path isn't force-redirected for logged-out users if
you have public reactive components.
* Authorization — DO IT IN THE COMPONENT ACTION. The token proves the
identity is ours, not that this user may act. Raise from the action
(e.g. authorize!), and configure Phlex::Reactive.authorization_errors
so it's rendered as 403 here.

Instance Method Summary collapse

Instance Method Details

#createObject



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'app/controllers/phlex/reactive/actions_controller.rb', line 29

def create
  # ONE action.phlex_reactive event per request (issue #107). The event
  # payload carries the component/action NAMES + outcome ONLY (never the
  # token, params, or state); we fill it as those become known and set
  # :outcome on every exit path — the success tail and each rescue. The
  # rescue bodies are unchanged (verbose diagnostics + reactive_error per
  # #82/#87); we only ADD the outcome finalizer. `action` is safe to read
  # up front (it comes from the request, not the verified token).
  event = { component: nil, action: reactive_action_name.to_s, outcome: nil }
  # Mint any reply.defer directive tokens UNDER the actor's binding (issue
  # #165 security), so the defer endpoint accepts them ONLY back from this
  # same actor. The defer token is built in response_streams (inside this
  # block via the Defer builder), so the binding must be established here.
  # Thread the ACTOR's url_options (protocol/host/port) into the reply
  # render (issue #232): the reply renders through the memoized
  # off-request view context, whose process-default url_options emit the
  # wrong host for absolute URL helpers on a multi-host app. Broadcasts
  # fired inside the action are exempted at their render (see
  # Streamable.broadcast_component) — subscribers can be on other hosts.
  Phlex::Reactive.with_url_options(Phlex::Reactive.url_options_for(request)) do
    Phlex::Reactive.with_defer_binding(Phlex::Reactive.defer_binding_for(request)) do
      Phlex::Reactive.instrument("action", event) do
        create_action(event)
      end
    end
  end
end

#deferredObject

The defer endpoint (issue #165) — the pull lane's render leg. Verifies the purpose-scoped, short-TTL defer token (an ACTION token is rejected here by signature — purpose confusion fails closed), rebuilds the component from its signed identity, and returns its replace (or morph, per the SIGNED mode) stream. No action runs and no transaction opens — this is a read. Authorization: the base controller's auth applies as on every reactive request; a component that guards visibility can raise a registered authorization error from from_identity/render (→ 403) or return false from render? (→ 204: keep content, clear pending).



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'app/controllers/phlex/reactive/actions_controller.rb', line 66

def deferred
  event = { component: nil, outcome: nil }
  # Verify UNDER the actor's binding (issue #165 security): a defer token
  # minted for another actor's session fails the binding-scoped purpose,
  # so a leaked token can't be exchanged here for this actor's render (and
  # its embedded fresh identity token). Unbound requests (no session) are
  # unchanged.
  # The defer PULL is an actor request too (issue #232) — its render gets
  # the same request-derived url_options as the action reply. The PUSH
  # lane (job → SSE) has no request and stays on process defaults.
  Phlex::Reactive.with_url_options(Phlex::Reactive.url_options_for(request)) do
    Phlex::Reactive.with_defer_binding(Phlex::Reactive.defer_binding_for(request)) do
      Phlex::Reactive.instrument("defer", event) do
        deferred_action(event)
      end
    end
  end
end