defmodule UrbitEx.API do @moduledoc """ Module with the base functions to build requests to send to Airlock. The Genserver calls functions in this module. Each function returns the session it received. """ 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 '~', annoyingly [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, 1), action: "delete"} wrap_put(session, body) session end # must be a list def subscribe(session, subscriptions) do body = Enum.map(subscriptions, fn sub -> Actions.subscribe(session.ship, sub.app, sub.path) end) wrap_put(session, body) session end defp wrap_put(session, items) do body = items |> Enum.with_index() |> Enum.map(fn {action, index} -> Map.put(action, :id, session.last_action + index + 1) end) session = increment_id(session, length(body)) 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, times) do Map.put(session, :last_action, session.last_action + times) end defp need_ack?(session) do session.last_ack < session.last_sse end end