defmodule FedecksClient do
@moduledoc """
Establishes a websocket connection to the server.
Eg :
```
defmodule MyApp.MyClient do
use FedecksClient
def device_id do
{:ok, name} = :inet.hostname()
to_string(name)
end
def connection_url do
Application.fetch_env!(:my_app, :fedecks_server_path)
end
end
```
Include in your supervision tree.
eg
```
defmodule MyApp.Application do
children = [
MyApp.MyClient
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
```
Initial authentication to the server with `c:FedecksClient.login/1`. Subsequent
connections will authenticate with a token provided by the server, persisting between reboots,
until the token expires or otherwise becomes invalid.
"""
@type connection_status ::
:unregistered | :connecting | :connection_scheduled | :failed_registration | :connected
defmacro __using__(_) do
quote do
@behaviour unquote(__MODULE__)
@connector FedecksClient.Connector.server_name(__MODULE__)
def start_link(_) do
maybe_val = fn fun, default ->
if function_exported?(__MODULE__, fun, 0) do
apply(__MODULE__, fun, [])
else
default
end
end
FedecksClient.FedecksSupervisor.start_link(
name: __MODULE__,
token_dir: maybe_val.(:token_dir, unquote(__MODULE__).default_token_dir()),
connection_url: connection_url(),
device_id: device_id(),
connect_delay: maybe_val.(:connect_delay, unquote(__MODULE__).default_connect_delay()),
ping_frequency:
maybe_val.(:ping_frequency, unquote(__MODULE__).default_ping_frequency())
)
end
@impl unquote(__MODULE__)
def login(credentials) do
FedecksClient.Connector.login(@connector, credentials)
end
@impl unquote(__MODULE__)
def subscribe do
SimplestPubSub.subscribe(__MODULE__)
end
@impl unquote(__MODULE__)
def send(message) do
FedecksClient.Connector.send_message(@connector, message)
end
@impl unquote(__MODULE__)
def send_raw(message) do
FedecksClient.Connector.send_raw_message(@connector, message)
end
@impl unquote(__MODULE__)
def connection_status do
FedecksClient.Connector.connection_status(@connector)
end
def child_spec(opts) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [opts]},
type: :worker,
restart: :permanent,
shutdown: 500
}
end
end
end
@doc """
Server websocket URL. Must start with "ws://" or "wss://".
Remeber to append "/websocket" if using a Phoenix (ie Fedecks) websocket.
"""
@callback connection_url :: String.t()
@doc """
Device id to identify this device when communicating with the server
"""
@callback device_id :: String.t()
@doc """
Initiate a login to the server.
Implementation provided by the `__using__` macro
"""
@callback login(credentials :: term()) :: :ok
@doc """
Subscribe to Fedecks events. Messages are sent in the form `{ModuleName, message}`.
See module doc for messages
Implementation provided by the `__using__` macro
"""
@callback subscribe :: :ok
@doc """
Send an encoded message to the server. Note that the server will use safe decoding so it is best to avoid
atoms.
Implementation provided by the `__using__` macro
"""
@callback send(message :: term()) :: :ok
@doc """
Send an raw binary message to the server.
Implementation provided by the `__using__` macro
"""
@callback send_raw(message :: term()) :: :ok
@doc """
Send an raw binary message to the server.
Implementation provided by the `__using__` macro
"""
@callback connection_status :: connection_status()
@doc """
Filesystem directory to store the Fedecks Token. Optional and defaults to `FedecksClient.default_token_dir/0`
("root/fedecks" on a Nerves installation)
"""
@callback token_dir :: String.t()
@doc """
How long to wait before and between connection attempts. Bear in mind that it my take some time
for a network connection to be established if using Nerves Networking and WiFi. Frequent restarts
could propagate and take down the application.
Optional and defaults to 10 seconds
"""
@callback connect_delay :: pos_integer()
@doc """
How often to ping the server to maintain the connection. Bear in mind that the server will drop the connection
after 1 minute of inactivity.
Optional and defaults to 19 seconds
"""
@callback ping_frequency :: pos_integer()
@optional_callbacks [token_dir: 0, connect_delay: 0, ping_frequency: 0]
@default_connect_delay :timer.seconds(10)
@default_ping_frequency :timer.seconds(19)
@mix_env Mix.env()
@mix_target Mix.target()
@dev_token_dir "#{System.tmp_dir!()}/#{__MODULE__}"
@doc """
The default directory for Fedecks tokens. Value is depenendend on the mix environment and target and
assumes you are using this for Nerves.
* If the env is `:test` or target is `:host` then it is the module name under the system temp directory.
* Otherwise "/root/fedecks"
"""
@spec default_token_dir() :: String.t()
def default_token_dir(mix_env \\ @mix_env, mix_target \\ @mix_target)
def default_token_dir(:test, _), do: @dev_token_dir
def default_token_dir(_, :host), do: @dev_token_dir
def default_token_dir(_, _), do: "/root/fedecks"
def default_connect_delay, do: @default_connect_delay
def default_ping_frequency, do: @default_ping_frequency
end