Hi everyone!
I trying to create a Phoenix LiveView dependency to authorize the lifecycle stages with attach_hook/4
via on_mount/4
callback.
I need to attach hook to :mount
stage and it possible with the on_mount/4
callback as described here. I don’t know why I getting the following error:
# ArgumentError at GET /some/route
Exception:
** (ArgumentError) invalid lifecycle event provided to attach_hook.
Expected one of: :handle_event | :handle_info | :handle_params
Got: :mount
(phoenix_live_view 0.20.1) lib/phoenix_live_view/lifecycle.ex:61: Phoenix.LiveView.Lifecycle.attach_hook/4
(elixir 1.15.7) lib/enum.ex:2510: Enum."-reduce/3-lists^foldl/2-0-"/3
(dronteh 0.1.0) lib/my_dependency_web/on_mount.ex:6: MyDependency.OnMount.on_mount/4
(phoenix_live_view 0.20.1) lib/phoenix_live_view/lifecycle.ex:149: anonymous fn/4 in Phoenix.LiveView.Lifecycle.mount/3
(phoenix_live_view 0.20.1) lib/phoenix_live_view/lifecycle.ex:212: Phoenix.LiveView.Lifecycle.reduce_socket/3
(phoenix_live_view 0.20.1) lib/phoenix_live_view/utils.ex:392: anonymous fn/6 in Phoenix.LiveView.Utils.maybe_call_live_view_mount!/5
(telemetry 1.2.1) /app/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
(phoenix_live_view 0.20.1) lib/phoenix_live_view/static.ex:277: Phoenix.LiveView.Static.call_mount_and_handle_params!/5
(phoenix_live_view 0.20.1) lib/phoenix_live_view/static.ex:118: Phoenix.LiveView.Static.render/3
(phoenix_live_view 0.20.1) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
(phoenix 1.7.9) lib/phoenix/router.ex:432: Phoenix.Router.__call__/5
(dronteh 0.1.0) lib/dronteh_web/endpoint.ex:1: DrontehWeb.Endpoint.plug_builder_call/2
(dronteh 0.1.0) deps/plug/lib/plug/debugger.ex:136: DrontehWeb.Endpoint."call (overridable 3)"/2
(dronteh 0.1.0) lib/dronteh_web/endpoint.ex:1: DrontehWeb.Endpoint.call/2
(phoenix 1.7.9) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
(plug_cowboy 2.6.1) lib/plug/cowboy/handler.ex:11: Plug.Cowboy.Handler.init/2
(cowboy 2.10.0) /app/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
(cowboy 2.10.0) /app/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
(cowboy 2.10.0) /app/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
(stdlib 5.1.1) proc_lib.erl:241: :proc_lib.init_p_do_apply/3
Code:
`lib/phoenix_live_view/lifecycle.ex`
56 hooks ++ [hook]
57 end)
58 end
59
60 def attach_hook(%Socket{}, _id, stage, _fun) do
61> raise ArgumentError, """
62 invalid lifecycle event provided to attach_hook.
63
64 Expected one of: :handle_event | :handle_info | :handle_params
65
66 Got: #{inspect(stage)}
`lib/enum.ex`
2505 operation cannot be expressed by any of the functions in the `Enum`
2506 module, developers will most likely resort to `reduce/3`.
2507 """
2508 @spec reduce(t, acc, (element, acc -> acc)) :: acc
2509 def reduce(enumerable, acc, fun) when is_list(enumerable) do
2510> :lists.foldl(fun, acc, enumerable)
2511 end
2512
2513 def reduce(first..last//step, acc, fun) do
2514 reduce_range(first, last, step, acc, fun)
2515 end
`lib/my_dependency_web/on_mount.ex`
1 defmodule MyDependency do
2 import Phoenix.LiveView
3
4 def on_mount(:default, _params, _session, socket) do
5 # some code...
6 socket = attach_hook(socket, "protect_mount", :mount, fn _params, _session, socket) ->
7 # some code...
8
9 {:cont, socket} or {:halt socket} # with redirect
10 end)
11
`lib/phoenix_live_view/lifecycle.ex`
144 end
145
146 @doc false
147 def mount(params, session, %Socket{private: %{@lifecycle => lifecycle}} = socket) do
148 reduce_socket(lifecycle.mount, socket, fn %{id: {_mod, arg}} = hook, acc ->
149> case hook.function.(arg, params, session, acc) do
150 {:halt, %Socket{redirected: nil}} ->
151 raise_halt_without_redirect!(hook)
152
153 {:cont, %Socket{redirected: to}} when not is_nil(to) ->
154 raise_continue_with_redirect!(hook)
`lib/phoenix_live_view/lifecycle.ex`
207
208 new_socket
209 end
210
211 defp reduce_socket([hook | hooks], acc, function) do
212> case function.(hook, acc) do
213 {:cont, %Socket{} = socket} -> reduce_socket(hooks, socket, function)
214 {:halt, %Socket{} = socket} -> {:halt, socket}
215 other -> bad_lifecycle_response!(other, hook)
216 end
217 end
`lib/phoenix_live_view/utils.ex`
387 :telemetry.span(
388 [:phoenix, :live_view, :mount],
389 %{socket: socket, params: params, session: session, uri: uri},
390 fn ->
391 socket =
392> case Lifecycle.mount(params, session, socket) do
393 {:cont, %Socket{} = socket} when exported? ->
394 view.mount(params, session, socket)
395
396 {_, %Socket{} = socket} ->
397 {:ok, socket}
`/app/deps/telemetry/src/telemetry.erl`
316 EventPrefix ++ [start],
317 #{monotonic_time => StartTime, system_time => erlang:system_time()},
318 merge_ctx(StartMetadata, DefaultCtx)
319 ),
320
321> try {_, #{}} = SpanFunction() of
322 {Result, StopMetadata} ->
323 StopTime = erlang:monotonic_time(),
324 execute(
325 EventPrefix ++ [stop],
326 #{duration => StopTime - StartTime, monotonic_time => StopTime},
`lib/phoenix_live_view/static.ex`
272
273 defp call_mount_and_handle_params!(socket, view, session, params, uri) do
274 mount_params = if socket.router, do: params, else: :not_mounted_at_router
275
276 socket
277> |> Utils.maybe_call_live_view_mount!(view, mount_params, session, uri)
278 |> mount_handle_params(view, params, uri)
279 |> case do
280 {:noreply, %Socket{redirected: {:live, _, _}} = socket} ->
281 {:stop, socket}
282
`lib/phoenix_live_view/static.ex`
113 action,
114 flash,
115 host_uri
116 )
117
118> case call_mount_and_handle_params!(socket, view, mount_session, conn.params, request_url) do
119 {:ok, socket} ->
120 data_attrs = [
121 phx_session: sign_root_session(socket, router, view, to_sign_session, live_session),
122 phx_static: sign_static_token(socket)
123 ]
`lib/phoenix_live_view/controller.ex`
34 end
35 end
36
37 """
38 def live_render(%Plug.Conn{} = conn, view, opts \\ []) do
39> case LiveView.Static.render(conn, view, opts) do
40 {:ok, content, socket_assigns} ->
41 conn
42 |> Phoenix.Controller.put_view(LiveView.Static)
43 |> Phoenix.Controller.render(
44 "template.html",
`lib/phoenix/router.ex`
427 :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
428 halted_conn
429
430 %Plug.Conn{} = piped_conn ->
431 try do
432> plug.call(piped_conn, plug.init(opts))
433 else
434 conn ->
435 measurements = %{duration: System.monotonic_time() - start}
436 metadata = %{metadata | conn: conn}
437 :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
`lib/dronteh_web/endpoint.ex`
1> defmodule DrontehWeb.Endpoint do
2 use Phoenix.Endpoint, otp_app: :dronteh
3
4 # The session will be stored in the cookie and signed,
5 # this means its contents can be read but not tampered with.
6 # Set :encryption_salt if you would also like to encrypt it.
`deps/plug/lib/plug/debugger.ex`
131 case conn do
132 %Plug.Conn{path_info: ["__plug__", "debugger", "action"], method: "POST"} ->
133 Plug.Debugger.run_action(conn)
134
135 %Plug.Conn{} ->
136> super(conn, opts)
137 end
138 rescue
139 e in Plug.Conn.WrapperError ->
140 %{conn: conn, kind: kind, reason: reason, stack: stack} = e
141 Plug.Debugger.__catch__(conn, kind, reason, stack, @plug_debugger)
`lib/dronteh_web/endpoint.ex`
1> defmodule DrontehWeb.Endpoint do
2 use Phoenix.Endpoint, otp_app: :dronteh
3
4 # The session will be stored in the cookie and signed,
5 # this means its contents can be read but not tampered with.
6 # Set :encryption_salt if you would also like to encrypt it.
`lib/phoenix/endpoint/sync_code_reload_plug.ex`
17
18 def call(conn, {endpoint, opts}), do: do_call(conn, endpoint, opts, true)
19
20 defp do_call(conn, endpoint, opts, retry?) do
21 try do
22> endpoint.call(conn, opts)
23 rescue
24 exception in [UndefinedFunctionError] ->
25 case exception do
26 %UndefinedFunctionError{module: ^endpoint} when retry? ->
27 # Sync with the code reloader and retry once
`lib/plug/cowboy/handler.ex`
6 def init(req, {plug, opts}) do
7 conn = @connection.conn(req)
8
9 try do
10 conn
11> |> plug.call(opts)
12 |> maybe_send(plug)
13 |> case do
14 %Plug.Conn{adapter: {@connection, %{upgrade: {:websocket, websocket_args}} = req}} = conn ->
15 {handler, state, cowboy_opts} = websocket_args
16 {__MODULE__, copy_resp_headers(conn, req), {handler, state}, cowboy_opts}
`/app/deps/cowboy/src/cowboy_handler.erl`
32 -optional_callbacks([terminate/3]).
33
34 -spec execute(Req, Env) -> {ok, Req, Env}
35 when Req::cowboy_req:req(), Env::cowboy_middleware:env().
36 execute(Req, Env=#{handler := Handler, handler_opts := HandlerOpts}) ->
37> try Handler:init(Req, HandlerOpts) of
38 {ok, Req2, State} ->
39 Result = terminate(normal, Req2, State, Handler),
40 {ok, Req2, Env#{result => Result}};
41 {Mod, Req2, State} ->
42 Mod:upgrade(Req2, Env, Handler, State);
`/app/deps/cowboy/src/cowboy_stream_h.erl`
301 end.
302
303 execute(_, _, []) ->
304 ok;
305 execute(Req, Env, [Middleware|Tail]) ->
306> case Middleware:execute(Req, Env) of
307 {ok, Req2, Env2} ->
308 execute(Req2, Env2, Tail);
309 {suspend, Module, Function, Args} ->
310 proc_lib:hibernate(?MODULE, resume, [Env, Tail, Module, Function, Args]);
311 {stop, _Req2} ->
`/app/deps/cowboy/src/cowboy_stream_h.erl`
290 %% to simplify the debugging of errors. The proc_lib library
291 %% already adds the stacktrace to other types of exceptions.
292 -spec request_process(cowboy_req:req(), cowboy_middleware:env(), [module()]) -> ok.
293 request_process(Req, Env, Middlewares) ->
294 try
295> execute(Req, Env, Middlewares)
296 catch
297 exit:Reason={shutdown, _}:Stacktrace ->
298 erlang:raise(exit, Reason, Stacktrace);
299 exit:Reason:Stacktrace when Reason =/= normal, Reason =/= shutdown ->
300 erlang:raise(exit, {Reason, Stacktrace}, Stacktrace)
`proc_lib.erl`
No code available.
## Connection details
### Params
%{}
### Request info
* URI: http://localhost:4000/some/route
* Query string:
### Headers
* accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8
* accept-encoding: gzip, deflate, br
* accept-language: en-US,en;q=0.9,hu;q=0.8,sr;q=0.7
* cache-control: max-age=0
* connection: keep-alive
* cookie: _dronteh_key=SFMyNTY.g3QAAAADbQAAAAtfY3NyZl90b2tlbm0AAAAYNEZzX2RLRWpkOXBqbFE1TGJvR0lqcVI2bQAAAA5saXZlX3NvY2tldF9pZG0AAAA7dXNlcnNfc2Vzc2lvbnM6M2E4VW9vN1hBRm51S3hZTWF2S2cyN0RmNHFVZlltQk1obDBZczBxTXBSRT1tAAAACnVzZXJfdG9rZW5tAAAAIN2vFKKO1wBZ7isWDGryoNuw3-KlH2JgTIZdGLNKjKUR.lBUv7RmSNBZqPQ9UZHJ2Lb8_KokcFoukItDpXDw-aGw
* host: localhost:4000
* referer: http://localhost:4000/some/route
* sec-ch-ua: "Brave";v="119", "Chromium";v="119", "Not?A_Brand";v="24"
* sec-ch-ua-mobile: ?0
* sec-ch-ua-platform: "Windows"
* sec-fetch-dest: document
* sec-fetch-mode: navigate
* sec-fetch-site: same-origin
* sec-gpc: 1
* upgrade-insecure-requests: 1
* user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36
### Session
%{"_csrf_token" => "4Fs_dKEjd9pjlQ5LboGIjqR6", "live_socket_id" => "users_sessions:3a8Uoo7XAFnuKxYMavKg27Df4qUfYmBMhl0Ys0qMpRE=", "user_token" => <<221, 175, 20, 162, 142, 215, 0, 89, 238, 43, 22, 12, 106, 242, 160, 219, 176, 223, 226, 165, 31, 98, 96, 76, 134, 93, 24, 179, 74, 140, 165, 17>>}
My code is the following:
defmodule MyAppWeb.SomeLive do
use MyAppWeb, :live_view
on_mount MyDependency
def mount(_params, _session, socket) do
# some code...
{:ok, socket}
end
# other code...
end
defmodule MyDependency do
import Phoenix.LiveView
def on_mount(:default, _params, _session, socket) do
# some code...
socket = attach_hook(socket, "protect_mount", :mount, fn _params, _session, socket) ->
# some code...
{:cont, socket} or {:halt socket} # with redirect
end)
{:cont, socket}
end
end
Of course my code has more logic but this is the main idea. I got that error in other projects too.
Can somebody tell me what I doing wrong?
Versions I use:
- elixir: ~> 1.15.7
- phoenix: ~> 1.7.9
- phoenix_live_view: ~> 0.20.1