Skip to content

Commit

Permalink
Merge pull request #11 from stepnivlk/0.2.1
Browse files Browse the repository at this point in the history
Add init_channels/0 function [WIP]
  • Loading branch information
Tomas Koutsky committed Apr 16, 2018
2 parents b7d2e8a + 55676d2 commit 7e0e662
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 30 deletions.
59 changes: 56 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,27 @@ config :simple_client, SimpleClient,
defmodule SimpleClient do
use Pushest, otp_app: :simple_client

# Subscribe to these channels right after application startup.
def init_channels do
[
[name: "public-init-channel", user_data: %{}],
[name: "private-init-channel", user_data: %{}],
[name: "presence-init-channel", user_data: %{user_id: 123}],
]
end

# handle_event/2 is user-defined callback which is triggered whenever an event
# occurs on the channel.
def handle_event({:ok, "public-init-channel", "some-event"}, frame) do
# do something with public-init-channel frame
end

def handle_event({:ok, "public-channel", "some-event"}, frame) do
# do something with public frame
# do something with public-channel frame
end

def handle_event({:ok, "private-channel", "some-other-event"}, frame) do
# do something with private frame
# do something with private-channel frame
end

# We can also catch errors.
Expand Down Expand Up @@ -92,6 +105,14 @@ config = %{

### Now you can use various functions injected in your module
```elixir
SimpleClient.channels()
# => %{
"channels" => %{
"presence-init-channel" => %{},
"private-init-channel" => %{},
"public-init-channel" => %{}
}
# ...
SimpleClient.subscribe("public-channel")
:ok
# ...
Expand All @@ -113,7 +134,8 @@ SimpleClient.trigger("private-channel", "first-event", %{message: "Ahoj"})
:ok
# ...
SimpleClient.subscribed_channels()
["presence-channel", "private-channel", "public-channel"]
["presence-channel", "private-channel", "public-channel",
"presence-init-channel", "private-init-channel", "public-init-channel"]
# ...
SimpleClient.unsubscribe("public-channel")
:ok
Expand Down Expand Up @@ -191,6 +213,37 @@ Unsubscribes from given channel
SimpleClient.unsubscribe("public-channel")
```

### Overridable functions
These functions are meant to be overridden in a module using Pushest
#### handle_event/2
Callback being triggered when there is a WebSocket event on a subscribed channel.
```elixir
defmodule MyApp.MyModule
use Pushest, otp_app: :my_app

def handle_event({:ok, "my-channel", "my-event"}, frame) do
IO.inspect frame
end
end
```

#### init_channels/0
Subscribes to given list of channels right after application startup.
Each element has to be a keyword list in exact format of: `[name: String.t(), user_data: map]`
```elixir
defmodule MyApp.MyModule
use Pushest, otp_app: :my_app

def init_channels do
[
[name: "public-init-channel", user_data: %{}],
[name: "private-init-channel", user_data: %{}],
[name: "presence-init-channel", user_data: %{user_id: 123}],
]
end
end
```

#### `frame` example
`frame` is a `Pushest.Socket.Data.Frame` or `Pushest.Api.Data.Frame` struct with data payload as a map.
```elixir
Expand Down
72 changes: 56 additions & 16 deletions lib/pushest.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ 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
Keeps track of subscribed channels and users presence when subscribed to a presence channel.
Pushest is meant to be `use`d 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:
# Add necessary pusher configuration to your application config (assuming an OTP app):
# simple_client/config/config.exs
config :simple_client, SimpleClient,
pusher_app_id: System.get_env("PUSHER_APP_ID"),
Expand All @@ -19,18 +19,33 @@ defmodule Pushest do
# simple_client/simple_client.ex
defmodule SimpleClient do
# :otp_app option is needed for Pushest to get a config.
use Pushest, otp_app: :simple_client
# Subscribe to these channels right after application startup.
def init_channels do
[
[name: "public-init-channel", user_data: %{}],
[name: "private-init-channel", user_data: %{}],
[name: "presence-init-channel", user_data: %{user_id: 123}],
]
end
# handle incoming events.
def handle_event({:ok, "public-init-channel", "some-event"}, frame) do
# do something with public-init-channel frame
end
def handle_event({:ok, "public-channel", "some-event"}, frame) do
# do something with public frame
# do something with public-channel frame
end
def handle_event({:ok, "private-channel", "some-other-event"}, frame) do
# do something with private frame
# do something with private-channel frame
end
end
# Now you can start your application with Pushest as a part of your supervision tree:
# Now you can start your application as a part of your supervision tree:
# simple_client/lib/simple_client/application.ex
def start(_type, _args) do
children = [
Expand All @@ -55,16 +70,22 @@ defmodule Pushest do
{:ok, pid} = SimpleClient.start_link(config)
```
Now you can interact with Pusher:
Now you can interact with Pusher using methods injected in your module:
```
SimpleClient.trigger("private-channel", "event", %{message: "via api"})
SimpleClient.channels()
# => %{"channels" => %{"public-channel" => %{}}}
# => %{
"channels" => %{
"presence-init-channel" => %{},
"private-init-channel" => %{},
"public-init-channel" => %{}
}
SimpleClient.subscribe("private-channel")
SimpleClient.trigger("private-channel", "event", %{message: "via ws"})
SimpleClient.trigger("private-channel", "event", %{message: "via api"}, force_api: true)
# ...
```
For full list of injected methods please check the README.
"""

alias Pushest.Router
Expand Down Expand Up @@ -112,13 +133,12 @@ defmodule Pushest do
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__)
Pushest.Supervisor.start_link(pusher_opts, __MODULE__, init_channels())
end

def start_link(_) do
Pushest.Supervisor.start_link(@config, __MODULE__)
Pushest.Supervisor.start_link(@config, __MODULE__, init_channels())
end

def child_spec(opts) do
Expand All @@ -136,15 +156,13 @@ defmodule Pushest do
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
Expand All @@ -153,7 +171,6 @@ defmodule Pushest do
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
Expand All @@ -165,7 +182,6 @@ defmodule Pushest do
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
Expand Down Expand Up @@ -199,6 +215,29 @@ defmodule Pushest 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
def init_channels do
[
[name: "public-init-channel", user_data: %{}],
[name: "private-init-channel", user_data: %{}],
[name: "presence-init-channel", user_data: %{user_id: 123}],
]
end
end
```
Subscribes to given list of channels right after application startup.
Each element has to be a keyword list in exact format of:
[name: String.t(), user_data: map]
"""
def init_channels do
[]
end

@doc ~S"""
Function meant to be overwritten in user module, e.g.:
```
Expand All @@ -210,6 +249,7 @@ defmodule Pushest do
end
end
```
Catches events sent to a channels the client is subscribed to.
"""
def handle_event({status, channel, event}, frame) do
require Logger
Expand All @@ -221,7 +261,7 @@ defmodule Pushest do
)
end

defoverridable handle_event: 2
defoverridable handle_event: 2, init_channels: 0
end
end
end
4 changes: 2 additions & 2 deletions lib/pushest/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ defmodule Pushest.Api do
@client Pushest.Client.for_env()
@version Mix.Project.config()[:version]

def start_link({pusher_opts, _callback_module}) do
def start_link({pusher_opts, _callback_module, _init_channels}) do
GenServer.start_link(
__MODULE__,
%State{url: Utils.url(pusher_opts), options: %Options{} |> Map.merge(pusher_opts)},
Expand Down Expand Up @@ -78,7 +78,7 @@ defmodule Pushest.Api do
end

def handle_info(
{:gun_response, conn_pid, stream_ref, :nofin, status, _headers},
{:gun_response, conn_pid, stream_ref, :fin, status, _headers},
state = %State{conn_pid: conn_pid}
) do
case status do
Expand Down
25 changes: 21 additions & 4 deletions lib/pushest/socket.ex
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,21 @@ defmodule Pushest.Socket do
"""
def handle_info(
{:gun_ws, _conn_pid, {:text, raw_frame}},
state = %State{channels: channels, presence: presence, callback_module: callback_module}
state = %State{
channels: channels,
presence: presence,
callback_module: callback_module,
init_channels: init_channels
}
) do
frame = Frame.decode!(raw_frame)

case frame.event do
"pusher:connection_established" ->
Logger.debug("Socket | pusher:connection_established")

do_init_channels(init_channels)

{:noreply, %{state | socket_info: SocketInfo.decode(frame.data)}}

"pusher_internal:subscription_succeeded" ->
Expand Down Expand Up @@ -166,12 +174,21 @@ defmodule Pushest.Socket do
@client.ws_send(conn_pid, {:text, Frame.encode!(frame)})
end

@spec init_state({map, module}) :: %State{}
defp init_state({pusher_opts, callback_module}) do
@spec init_state({map, module, list}) :: %State{}
defp init_state({pusher_opts, callback_module, init_channels}) do
%State{
options: %Options{} |> Map.merge(pusher_opts),
url: Utils.url(pusher_opts),
callback_module: callback_module
callback_module: callback_module,
init_channels: init_channels
}
end

@spec do_init_channels(list) :: term
defp do_init_channels([[name: channel, user_data: user_data] | other_channels]) do
GenServer.cast(__MODULE__, {:subscribe, channel, user_data})
do_init_channels(other_channels)
end

defp do_init_channels([]), do: nil
end
3 changes: 2 additions & 1 deletion lib/pushest/socket/data/state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ defmodule Pushest.Socket.Data.State do
channels: [],
presence: %Presence{},
conn_pid: nil,
callback_module: nil
callback_module: nil,
init_channels: []
end
10 changes: 7 additions & 3 deletions lib/pushest/supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ defmodule Pushest.Supervisor do
alias Pushest.{Api, Socket}
use Supervisor

@spec start_link(map, module) :: {:ok, pid} | {:error, term}
def start_link(pusher_opts, callback_module) do
Supervisor.start_link(__MODULE__, {pusher_opts, callback_module}, name: __MODULE__)
@spec start_link(map, module, list) :: {:ok, pid} | {:error, term}
def start_link(pusher_opts, callback_module, init_channels) do
Supervisor.start_link(
__MODULE__,
{pusher_opts, callback_module, init_channels},
name: __MODULE__
)
end

def init(opts) do
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule Pushest.MixProject do
def project do
[
app: :pushest,
version: "0.2.0",
version: "0.2.1",
elixir: "~> 1.6",
start_permanent: Mix.env() == :prod,
deps: deps(),
Expand Down
16 changes: 16 additions & 0 deletions test/pushest_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ defmodule PushestTest do
@moduledoc false

use Pushest, otp_app: :pushest

def init_channels do
[
[name: "public-init-channel", user_data: %{}],
[name: "private-init-channel", user_data: %{}],
[name: "presence-init-channel", user_data: %{user_id: 123}],
]
end
end

def child_pid(mod_name) do
Expand Down Expand Up @@ -62,6 +70,14 @@ defmodule PushestTest do
start()
end

describe "subscription to init_channels" do
test "it subscribes to list of init_channels after startup" do
assert Enum.member?(TestPushest.subscribed_channels(), "public-init-channel")
assert Enum.member?(TestPushest.subscribed_channels(), "private-init-channel")
assert Enum.member?(TestPushest.subscribed_channels(), "presence-init-channel")
end
end

describe "subscribe" do
@channel "test-channel"
test "to a public channel", context do
Expand Down

0 comments on commit 7e0e662

Please sign in to comment.