@@ -74,14 +74,27 @@ defmodule Phoenix.LiveView.Channel do
7474 end
7575
7676 @ impl true
77+ def init ( { :adoptable_mount , init , timeout } ) do
78+ # adoptable render happens in handle_call
79+ { :ok , { :adoptable , init , timeout } }
80+ end
81+
7782 def init ( { pid , _ref } ) do
7883 { :ok , Process . monitor ( pid ) }
7984 end
8085
8186 @ impl true
87+ def handle_info ( { Phoenix.Channel , auth_payload , from , phx_socket } , { :adoptable , state } ) do
88+ IO . puts ( "adopting existing channel!" )
89+ mount ( auth_payload , from , phx_socket , state )
90+ rescue
91+ # Normalize exceptions for better client debugging
92+ e -> reraise ( e , __STACKTRACE__ )
93+ end
94+
8295 def handle_info ( { Phoenix.Channel , auth_payload , from , phx_socket } , ref ) do
8396 Process . demonitor ( ref )
84- mount ( auth_payload , from , phx_socket )
97+ mount ( auth_payload , from , phx_socket , nil )
8598 rescue
8699 # Normalize exceptions for better client debugging
87100 e -> reraise ( e , __STACKTRACE__ )
@@ -359,6 +372,21 @@ defmodule Phoenix.LiveView.Channel do
359372 handle_changed ( state , new_socket , nil )
360373 end
361374
375+ def handle_info ( { @ prefix , :adoption_timeout } , { :adoptable , state } ) do
376+ # State is still {:adoptable, _}, so we were not adopted.
377+ # Bye!
378+ IO . puts ( "shutting down adoptable socket due to timeout" )
379+ { :stop , :shutdown , { :adoptable , state } }
380+ end
381+
382+ def handle_info ( { @ prefix , :adoption_timeout } , state ) do
383+ { :noreply , state }
384+ end
385+
386+ def handle_info ( { @ prefix , :connected_diff , diff } , state ) do
387+ { :noreply , push_diff ( state , diff , nil ) }
388+ end
389+
362390 def handle_info ( msg , % { socket: socket } = state ) do
363391 msg
364392 |> view_handle_info ( socket )
@@ -431,6 +459,30 @@ defmodule Phoenix.LiveView.Channel do
431459 { :reply , { :ok , component_info } , state }
432460 end
433461
462+ # Phoenix.LiveView.Static.render/3
463+ def handle_call ( { @ prefix , :disconnected_adoptable_render } , _from , { :adoptable , init , timeout } )
464+ when is_function ( init , 0 ) do
465+ case init . ( ) do
466+ { :ok , socket } ->
467+ state = % {
468+ socket: socket ,
469+ components: Diff . new_components ( ) ,
470+ fingerprints: Diff . new_fingerprints ( ) ,
471+ initial_diff: nil
472+ }
473+
474+ { :diff , diff , state } = render_diff ( state , socket , true )
475+ state = % { state | initial_diff: diff }
476+
477+ Process . send_after ( self ( ) , { @ prefix , :adoption_timeout } , timeout )
478+
479+ { :reply , { :ok , Diff . to_iodata ( diff ) } , { :adoptable , state } }
480+
481+ { :stop , socket } ->
482+ { :stop , :shutdown , { :stop , socket } , nil }
483+ end
484+ end
485+
434486 def handle_call ( msg , from , % { socket: socket } = state ) do
435487 case socket . view . handle_call ( msg , from , socket ) do
436488 { :reply , reply , % Socket { } = new_socket } ->
@@ -1035,11 +1087,20 @@ defmodule Phoenix.LiveView.Channel do
10351087 { socket , % { } , state . fingerprints , state . components }
10361088 end
10371089
1038- diff = Diff . render_private ( socket , diff )
1090+ diff =
1091+ case state do
1092+ % { initial_diff: initial_diff } when is_map ( initial_diff ) ->
1093+ send ( self ( ) , { @ prefix , :connected_diff , Diff . render_private ( socket , diff ) } )
1094+ initial_diff
1095+
1096+ _ ->
1097+ Diff . render_private ( socket , diff )
1098+ end
1099+
10391100 new_socket = Utils . clear_temp ( socket )
10401101
10411102 { :diff , diff ,
1042- % { state | socket: new_socket , fingerprints: fingerprints , components: components } }
1103+ % { state | socket: new_socket , fingerprints: fingerprints , components: components , initial_diff: nil } }
10431104 end
10441105
10451106 defp reply ( state , { ref , extra } , status , payload ) do
@@ -1066,7 +1127,7 @@ defmodule Phoenix.LiveView.Channel do
10661127
10671128 ## Mount
10681129
1069- defp mount ( % { "session" => session_token } = params , from , phx_socket ) do
1130+ defp mount ( % { "session" => session_token } = params , from , phx_socket , adopted_state ) do
10701131 % Phoenix.Socket { endpoint: endpoint , topic: topic } = phx_socket
10711132
10721133 case Session . verify_session ( endpoint , topic , session_token , params [ "static" ] ) do
@@ -1126,7 +1187,8 @@ defmodule Phoenix.LiveView.Channel do
11261187 params ,
11271188 from ,
11281189 phx_socket ,
1129- connect_info
1190+ connect_info ,
1191+ adopted_state
11301192 )
11311193 else
11321194 { :error , :unauthorized } ->
@@ -1145,7 +1207,7 @@ defmodule Phoenix.LiveView.Channel do
11451207 end
11461208 end
11471209
1148- defp mount ( % { } , from , phx_socket ) do
1210+ defp mount ( % { } , from , phx_socket , _adopted_state ) do
11491211 Logger . error ( "Mounting #{ phx_socket . topic } failed because no session was provided" )
11501212 GenServer . reply ( from , { :error , % { reason: "stale" } } )
11511213 { :stop , :shutdown , :no_session }
@@ -1171,7 +1233,8 @@ defmodule Phoenix.LiveView.Channel do
11711233 params ,
11721234 from ,
11731235 phx_socket ,
1174- connect_info
1236+ connect_info ,
1237+ adopted_state
11751238 ) do
11761239 % Session {
11771240 id: id ,
@@ -1206,16 +1269,24 @@ defmodule Phoenix.LiveView.Channel do
12061269 Process . monitor ( transport_pid )
12071270 load_csrf_token ( endpoint , socket_session )
12081271
1209- socket = % Socket {
1210- endpoint: endpoint ,
1211- view: view ,
1212- transport_pid: transport_pid ,
1213- parent_pid: parent ,
1214- root_pid: root_pid || self ( ) ,
1215- id: id ,
1216- router: router ,
1217- sticky?: params [ "sticky" ]
1218- }
1272+ socket =
1273+ % Socket {
1274+ endpoint: endpoint ,
1275+ view: view ,
1276+ transport_pid: transport_pid ,
1277+ parent_pid: parent ,
1278+ root_pid: root_pid || self ( ) ,
1279+ id: id ,
1280+ router: router ,
1281+ sticky?: params [ "sticky" ]
1282+ }
1283+ # For adoption, we only need to copy assigns and private.
1284+ # The state already contains existing fingerprints and
1285+ # components, but we need to do a full render anyway, since
1286+ # the client needs the full diff and the connected mount may
1287+ # have changed assigns. Unchanged expressions won't be executed.
1288+ |> maybe_copy_adopted ( adopted_state , :private )
1289+ |> maybe_copy_adopted ( adopted_state , :assigns )
12191290
12201291 { params , host_uri , action } =
12211292 case route do
@@ -1237,7 +1308,7 @@ defmodule Phoenix.LiveView.Channel do
12371308 socket
12381309 |> load_layout ( route )
12391310 |> Utils . maybe_call_live_view_mount! ( view , params , merged_session , url )
1240- |> build_state ( phx_socket )
1311+ |> build_state ( phx_socket , adopted_state )
12411312 |> maybe_call_mount_handle_params ( router , url , params )
12421313 |> reply_mount ( from , verified , route )
12431314 |> maybe_subscribe_to_live_reload ( )
@@ -1276,6 +1347,12 @@ defmodule Phoenix.LiveView.Channel do
12761347 end
12771348 end
12781349
1350+ defp maybe_copy_adopted ( socket , nil , _ ) , do: socket
1351+
1352+ defp maybe_copy_adopted ( socket , adopted_state , key ) do
1353+ Map . put ( socket , key , Map . fetch! ( adopted_state . socket , key ) )
1354+ end
1355+
12791356 defp verify_flash ( endpoint , % Session { } = verified , flash_token , connect_params ) do
12801357 cond do
12811358 # flash_token is given by the client on live_redirects and has higher priority.
@@ -1446,18 +1523,24 @@ defmodule Phoenix.LiveView.Channel do
14461523 end
14471524 end
14481525
1449- defp build_state ( % Socket { } = lv_socket , % Phoenix.Socket { } = phx_socket ) do
1450- % {
1526+ defp build_state ( % Socket { } = lv_socket , % Phoenix.Socket { } = phx_socket , adopted_state ) do
1527+ base =
1528+ adopted_state ||
1529+ % {
1530+ components: Diff . new_components ( ) ,
1531+ fingerprints: Diff . new_fingerprints ( ) ,
1532+ initial_diff: nil
1533+ }
1534+
1535+ Map . merge ( base , % {
14511536 join_ref: phx_socket . join_ref ,
14521537 serializer: phx_socket . serializer ,
14531538 socket: lv_socket ,
14541539 topic: phx_socket . topic ,
1455- components: Diff . new_components ( ) ,
1456- fingerprints: Diff . new_fingerprints ( ) ,
14571540 redirect_count: 0 ,
14581541 upload_names: % { } ,
14591542 upload_pids: % { }
1460- }
1543+ } )
14611544 end
14621545
14631546 defp build_uri ( % { socket: socket } , "/" <> _ = to ) do
0 commit comments