defmodule LSGWeb.IrcAuthSseController do use LSGWeb, :controller require Logger @ping_interval 20_000 @expire_delay :timer.minutes(3) def sse(conn, params) do perks = if uri = Map.get(params, "redirect_to") do {:redirect, uri} else nil end token = String.downcase(EntropyString.random_string(65)) conn |> assign(:token, token) |> assign(:perks, perks) |> put_resp_header("X-Accel-Buffering", "no") |> put_resp_header("content-type", "text/event-stream") |> send_chunked(200) |> subscribe() |> send_sse_message("token", token) |> sse_loop end def subscribe(conn) do :timer.send_interval(@ping_interval, {:event, :ping}) :timer.send_after(@expire_delay, {:event, :expire}) {:ok, _} = Registry.register(IRC.PubSub, "messages:private", []) conn end def sse_loop(conn) do {type, event, exit} = receive do {:event, :ping} -> {"ping", "ping", false} {:event, :expire} -> {"expire", "expire", true} {:irc, :text, %{account: account, text: token} = m} -> if String.downcase(String.trim(token)) == conn.assigns.token do path = LSG.AuthToken.new_path(account.id, conn.assigns.perks) m.replyfun.("ok!") {"authenticated", path, true} else {nil, nil, false} end _ -> {nil, nil, false} end conn = if type do send_sse_message(conn, type, event) else conn end if exit do conn else sse_loop(conn) end end defp send_sse_message(conn, type, data) do {:ok, conn} = chunk(conn, "event: #{type}\ndata: #{data}\n\n") conn end end