Current section

Files

Jump to
urbit_ex lib api api.ex
Raw

lib/api/api.ex

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