Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Teams Server

talks/alchemyconf/teams_server.livemd

Teams Server

Mix.install([
  {:phoenix_playground, "~> 0.1.7"},
  {:kino, "~> 0.15.3"},
  {:livebook_proto,
   github: "hugobarauna/livebook-notebooks", sparse: "talks/alchemyconf/livebook_proto"},
])

require Logger

Server

defmodule Teams.ClientsTracker do
  use Phoenix.Tracker

  @default_options [
    name: __MODULE__,
    pubsub_server: PhoenixPlayground.PubSub
  ]

  @topic "teams-clients"

  def start_link(opts \\ []) do
    opts = Keyword.merge(@default_options, opts)
    Phoenix.Tracker.start_link(__MODULE__, opts, opts)
  end

  def track(org_name) do
    key = random_string()
    Phoenix.Tracker.track(__MODULE__, self(), @topic, key, %{org_name: org_name})
  end

  def list do
    Phoenix.Tracker.list(__MODULE__, @topic)
  end

  @impl true
  def init(opts) do
    server = Keyword.fetch!(opts, :pubsub_server)
    {:ok, %{pubsub_server: server, node_name: Phoenix.PubSub.node_name(server)}}
  end

  @impl true
  def handle_diff(diff, state) do
    for {_topic, {joins, leaves}} <- diff do
      for {key, meta} <- joins do
        Logger.debug("[SERVER] presence join: key '#{key}' with meta #{inspect(meta)}")
      end

      for {key, meta} <- leaves do
        Logger.debug("[SERVER] presence leave: key '#{key}' with meta #{inspect(meta)}")
      end
    end

    {:ok, state}
  end

  defp random_string() do
    :crypto.strong_rand_bytes(8)
    |> Base.url_encode64(padding: false)
  end
end
defmodule UserSocket do
  @behaviour Phoenix.Socket.Transport

  @impl true
  def child_spec(_opts) do
    # We won't spawn any process, so let's ignore the child spec
    :ignore
  end

  @impl true
  def connect(%{connect_info: %{x_headers: x_headers}}) do
    org_name = get_header(x_headers, "x-org-name")
    Logger.debug("[SERVER] Received WebSocket connection with org name: #{org_name}")
    Teams.ClientsTracker.track(org_name)

    {:ok, %{org_name: org_name}}
  end

  @impl true
  def init(state) do
    Phoenix.PubSub.subscribe(PhoenixPlayground.PubSub, "org_events:#{state.org_name}")
    
    message = build_client_connected_message(state.org_name)
    send(self(), {:message, message})

    {:ok, state}
  end

  defp build_client_connected_message(_org_name) do
    env_var = %LivebookProto.EnvironmentVariable{name: "foo", value: "some value"}
    client_connected = %LivebookProto.ClientConnected{env_vars: [env_var]}
    event = %LivebookProto.Event{type: {:client_connected, client_connected}}

    LivebookProto.Event.encode(event)
  end

  @impl true
  def handle_info({:message, message}, state) do
    Logger.debug("[SERVER] Sending WebSocket message to client: #{inspect(message)}")
    {:push, {:binary, message}, state}
  end

  @impl true
  def handle_in({_message, _opts}, state) do
    {:ok, state}
  end

  @impl true
  def terminate(_reason, _state) do
    :ok
  end

  defp get_header(headers, key) do
    for {^key, value} <- headers, do: value
  end
end
defmodule Teams.Endpoint do
  use Phoenix.Endpoint, otp_app: :phoenix_playground

  socket("/user", UserSocket, websocket: [connect_info: [:x_headers]])
end
{:ok, phx_playground_pid} =
  PhoenixPlayground.start(endpoint: Teams.Endpoint, port: 4701, open_browser: false)

Kino.start_child!(Teams.ClientsTracker)
import Kino.Shorts

build_event = fn :env_var_created, env_var ->
  env_var = %LivebookProto.EnvironmentVariable{name: env_var.name, value: env_var.value}
  env_var_created = %LivebookProto.EnvironmentVariableCreated{env_var: env_var}
  event = %LivebookProto.Event{type: {:env_var_created, env_var_created}}

  LivebookProto.Event.encode(event)
end

feedback_frame = Kino.Frame.new(placeholder: false)

form =
  Kino.Control.form(
    [
      org_name: Kino.Input.text("Org name", default: "dashbit"),
      name: Kino.Input.text("Name"),
      value: Kino.Input.text("Value")
    ],
    submit: "Create"
  )

Kino.listen(form, fn form_event ->
  %{data: %{org_name: org_name, name: name, value: value}} = form_event
  Kino.Frame.render(feedback_frame, Kino.Text.new("env var created", style: [color: :green]))

  message = build_event.(:env_var_created, %{name: name, value: value})

  Phoenix.PubSub.broadcast!(
    PhoenixPlayground.PubSub,
    "org_events:#{org_name}",
    {:message, message}
  )
end)

grid([markdown("### New env var"), feedback_frame, form], boxed: true)