defmodule UrbitEx.API do alias UrbitEx.{Airlock, Actions} def init(url, code) do %UrbitEx.Session{ url: url, code: code, channel: set_channel() } end @spec login(%{:code => any, :url => binary, optional(any) => any}) :: %{ :code => any, :cookie => binary, :ship => binary | {integer, integer}, :url => binary, optional(any) => any } def login(session) do endpoint = "/~/login" url = session.url <> endpoint body = "password=#{session.code}" {:ok, res} = Airlock.post(url, body) {_, cookiestring} = Enum.find(res.headers, fn x -> match?({"set-cookie", _}, x) end) [cookie | _] = cookiestring |> String.split(";") # ships don't take the '~' [ship] = Regex.run(~r/(?<=~)[a-z]+[^=]*/, cookie) session |> Map.put(:cookie, cookie) |> Map.put(:ship, ship) end defp set_channel do "/~/channel/#{System.os_time(:millisecond)}-#{ :crypto.strong_rand_bytes(3) |> Base.encode16(case: :lower) }" end def open_channel(session) do body = Actions.poke(session.ship, "hood", "helm-hi", "Opening airlock") wrap_put(session, body) session end def start_sse(session) do {:ok, _res} = Airlock.sse(session.url, session.channel, session.cookie, session.last_sse) session end def logout(session) do body = %{id: increment_id(session), action: "delete"} wrap_put(session, body) session end ## subscriptions to be a Map %{app: foo, path: bar} def subscribe(session, subscription) do body = Actions.subscribe(session.ship, subscription.app, subscription.path) wrap_put(session, body) session end defp wrap_put(session, body) do session = session |> increment_id body = Map.put(body, :id, session.last_action) body = if need_ack?(session), do: [Actions.ack(session.last_sse) | [body]], else: [body] Airlock.put(session.url, session.channel, session.cookie, body) end defp increment_id(session) do Map.put(session, :last_action, session.last_action + 1) end defp need_ack?(session) do session.last_ack < session.last_sse end end