Packages
livebook
0.5.1
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook_web/channels/js_output_channel.ex
defmodule LivebookWeb.JSOutputChannel do
use Phoenix.Channel
@impl true
def join("js_output", %{"session_id" => session_id}, socket) do
{:ok, assign(socket, session_id: session_id, ref_with_pid: %{}, ref_with_count: %{})}
end
@impl true
def handle_in("connect", %{"session_token" => session_token, "ref" => ref}, socket) do
{:ok, data} = Phoenix.Token.verify(LivebookWeb.Endpoint, "js output", session_token)
%{pid: pid} = data
send(pid, {:connect, self(), %{origin: self(), ref: ref}})
ref_with_pid = Map.put(socket.assigns.ref_with_pid, ref, pid)
ref_with_count = Map.update(socket.assigns.ref_with_count, ref, 1, &(&1 + 1))
socket = assign(socket, ref_with_pid: ref_with_pid, ref_with_count: ref_with_count)
if socket.assigns.ref_with_count[ref] == 1 do
Livebook.Session.subscribe_to_runtime_events(socket.assigns.session_id, "js_live", ref)
end
{:noreply, socket}
end
def handle_in("event", %{"event" => event, "payload" => payload, "ref" => ref}, socket) do
pid = socket.assigns.ref_with_pid[ref]
send(pid, {:event, event, payload, %{origin: self(), ref: ref}})
{:noreply, socket}
end
def handle_in("disconnect", %{"ref" => ref}, socket) do
socket =
if socket.assigns.ref_with_count[ref] == 1 do
Livebook.Session.unsubscribe_from_runtime_events(
socket.assigns.session_id,
"js_live",
ref
)
{_, ref_with_count} = Map.pop!(socket.assigns.ref_with_count, ref)
{_, ref_with_pid} = Map.pop!(socket.assigns.ref_with_pid, ref)
assign(socket, ref_with_count: ref_with_count, ref_with_pid: ref_with_pid)
else
ref_with_count = Map.update!(socket.assigns.ref_with_count, ref, &(&1 - 1))
assign(socket, ref_with_count: ref_with_count)
end
{:noreply, socket}
end
@impl true
def handle_info({:connect_reply, data, %{ref: ref}}, socket) do
with {:error, error} <- try_push(socket, "init:#{ref}", %{"data" => data}) do
message = "Failed to serialize initial widget data, " <> error
push(socket, "error:#{ref}", %{"message" => message})
end
{:noreply, socket}
end
def handle_info({:event, event, payload, %{ref: ref}}, socket) do
with {:error, error} <-
try_push(socket, "event:#{ref}", %{"event" => event, "payload" => payload}) do
message = "Failed to serialize event payload, " <> error
push(socket, "error:#{ref}", %{"message" => message})
end
{:noreply, socket}
end
# In case the payload fails to encode we catch the error
defp try_push(socket, event, payload) do
try do
push(socket, event, payload)
catch
:error, %Protocol.UndefinedError{protocol: Jason.Encoder, value: value} ->
{:error, "value #{inspect(value)} is not JSON-serializable, use another data type"}
:error, error ->
{:error, Exception.message(error)}
end
end
end