Current section
Files
Jump to
Current section
Files
lib/pushest.ex
defmodule Pushest do
@moduledoc ~S"""
Pushest is a Pusher library leveraging Elixir/OTP to combine server and client-side Pusher features.
Abstracts un/subscription, client-side triggers, private/presence channel authorizations.
Keeps track of subscribed channels and users presence when subscribed to presence channel.
Pushest is meant to be used in your module where you can define callbacks for
events you're interested in.
A simple implementation in an OTP application would be:
```
# Add necessary pusher configuration to your application config:
# simple_client/config/config.exs
config :simple_client, SimpleClient,
pusher_app_id: System.get_env("PUSHER_APP_ID"),
pusher_key: System.get_env("PUSHER_APP_KEY"),
pusher_secret: System.get_env("PUSHER_SECRET"),
pusher_cluster: System.get_env("PUSHER_CLUSTER"),
pusher_encrypted: true
# simple_client/simple_client.ex
defmodule SimpleClient do
use Pushest, otp_app: :simple_client
def handle_event({:ok, "public-channel", "some-event"}, frame) do
# do something with public frame
end
def handle_event({:ok, "private-channel", "some-other-event"}, frame) do
# do something with private frame
end
end
# Now you can start your application with Pushest as a part of your supervision tree:
# simple_client/lib/simple_client/application.ex
def start(_type, _args) do
children = [
{SimpleClient, []}
]
opts = [strategy: :one_for_one, name: Sup.Supervisor]
Supervisor.start_link(children, opts)
end
```
You can also provide Pusher options directly via start_link/1 (without using OTP app configuration):
```
config = %{
app_id: System.get_env("PUSHER_APP_ID"),
key: System.get_env("PUSHER_APP_KEY"),
secret: System.get_env("PUSHER_SECRET"),
cluster: System.get_env("PUSHER_CLUSTER"),
encrypted: true
}
{:ok, pid} = SimpleClient.start_link(config)
```
Now you can interact with Pusher:
```
SimpleClient.trigger("private-channel", "event", %{message: "via api"})
SimpleClient.channels()
# => %{"channels" => %{"public-channel" => %{}}}
SimpleClient.subscribe("private-channel")
SimpleClient.trigger("private-channel", "event", %{message: "via ws"})
SimpleClient.trigger("private-channel", "event", %{message: "via api"}, force_api: true)
# ...
```
"""
alias Pushest.Router
@doc ~S"""
Invoked when the Pusher event occurs (e.g. other client sends a message).
"""
@callback handle_event({atom, String.t(), String.t()}, term) :: term
defmacro __using__(opts) do
quote bind_quoted: [opts: opts] do
@typedoc ~S"""
Options for Pushest to properly communicate with Pusher server.
- `:app_id` - Pusher Application ID.
- `:key` - Pusher Application key.
- `:secret` - Necessary to subscribe to private/presence channels and trigger events.
- `:cluster` - Cluster where your Pusher app is configured.
- `:encrypted` - When set to true communication with Pusher is fully encrypted.
"""
@type pusher_opts :: %{
app_id: String.t(),
secret: String.t(),
key: String.t(),
cluster: String.t(),
encrypted: boolean
}
@typedoc ~S"""
Optional options for trigger function.
- `:force_api` - Always triggers via Pusher REST API endpoint when set to `true`
"""
@type trigger_opts :: [force_api: boolean]
@behaviour Pushest
@config Pushest.Supervisor.config(__MODULE__, opts)
@doc ~S"""
Starts a Pushest Supervisor process linked to current process.
Can be started as a part of host application supervision tree.
Pusher options can be passed as an argument or can be provided in an OTP
application config.
For available pusher_opts values see `t:pusher_opts/0`.
"""
@spec start_link(pusher_opts) :: {:ok, pid} | {:error, term}
def start_link(pusher_opts) when is_map(pusher_opts) do
Pushest.Supervisor.start_link(pusher_opts, __MODULE__)
end
def start_link(_) do
Pushest.Supervisor.start_link(@config, __MODULE__)
end
def child_spec(opts) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [opts]},
type: :supervisor
}
end
@doc ~S"""
Subscribe to a channel with user_data as a map. When subscribing to a
presence- channel user_id key with unique identifier as a value has to be
provided in the user_data map. user_info key can contain a map with optional
informations about user.
E.g.: %{user_id: "1", user_info: %{name: "Tomas Koutsky"}}
"""
@spec subscribe(String.t(), map) :: term
def subscribe(channel, user_data) do
Router.cast({:subscribe, channel, user_data})
end
@doc ~S"""
Subscribe to a channel without any user data, like any public channel.
"""
@spec subscribe(String.t()) :: term
def subscribe(channel) do
Router.cast({:subscribe, channel, %{}})
end
@doc ~S"""
Trigger on given channel/event combination - sends given data to Pusher.
data has to be a map.
"""
@spec trigger(String.t(), String.t(), map) :: term
def trigger(channel, event, data) do
Router.cast({:trigger, channel, event, data})
end
@doc ~S"""
Same as trigger/3 but adds a possiblity to enforce triggering via API endpoint.
For enforced API trigger provide `force_api: true` as an `opts`.
E.g.: `Mod.trigger("channel", "event", %{message: "m"}, force_api: true)`
For trigger_opts values see `t:trigger_opts/0`.
"""
@spec trigger(String.t(), String.t(), map, trigger_opts) :: term
def trigger(channel, event, data, opts) do
Router.cast({:trigger, channel, event, data}, opts)
end
@doc ~S"""
Returns all the channels anyone is using, calls Pusher via REST API.
"""
def channels do
Router.call(:channels)
end
@doc ~S"""
Returns only the channels this client is subscribed to.
"""
def subscribed_channels do
Router.call(:subscribed_channels)
end
@doc ~S"""
Returns information about all the users subscribed to a presence channels
this client is subscribed to.
"""
def presence do
Router.call(:presence)
end
@doc ~S"""
Unsubscribes from a channel
"""
def unsubscribe(channel) do
Router.cast({:unsubscribe, channel})
end
@doc ~S"""
Function meant to be overwritten in user module, e.g.:
```
defmodule MyMod do
use Pushest, otp_app: :my_mod
handle_event({:ok, "my-channel, "my-event"}, frame) do
# Do something with a frame here.
end
end
```
"""
def handle_event({status, channel, event}, frame) do
require Logger
Logger.error(
"No #{inspect(status)} handle_event/2 clause in #{__MODULE__} provided for #{
inspect(event)
}"
)
end
defoverridable handle_event: 2
end
end
end