Skip to content

Commit

Permalink
Add MongoosePush.Service behaviour
Browse files Browse the repository at this point in the history
  • Loading branch information
rslota committed Feb 15, 2017
1 parent a4a88a5 commit 2dbe39d
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 67 deletions.
68 changes: 7 additions & 61 deletions lib/mongoose_push.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ defmodule MongoosePush do
"""

require Logger
import MongoosePush.Application
alias Pigeon.GCM
alias Pigeon.APNS
alias MongoosePush.Application

@typedoc "Available keys in `request` map"
@type req_key :: :service | :body | :title | :bagde | :mode | :tag |
Expand Down Expand Up @@ -41,66 +39,14 @@ defmodule MongoosePush do
@spec push(String.t, request) :: :ok | {:error, term}
def push(device_id, %{:service => service} = request) do
mode = Map.get(request, :mode, :prod)
[pool | _] = pools_by_mode(service, mode)
worker = worker_name(service, pool,
Enum.random(1..pool_size(service, pool)))
worker = Application.select_worker(service, mode)
module = service_module(service)

push(service, worker, device_id, request)
module.prepare_notification(device_id, request)
|> module.push(device_id, worker)
end

@spec push(service, atom | pid, String.t, request) :: :ok | {:error, term}
defp push(:fcm, worker, device_id, request) do
msg = prepare_notification(:fcm, request)
gcm_notification = Pigeon.GCM.Notification.new(device_id, msg)

gcm_notification
|> GCM.push([name: worker])
|> normalize_response(:fcm, device_id)
end

@spec push(service, atom | pid, String.t, request) :: :ok | {:error, term}
defp push(:apns, worker, device_id, request) do
raw_notification = prepare_notification(:apns, request)

raw_notification
|> APNS.Notification.new(device_id, request[:topic])
|> APNS.push([name: worker])
|> normalize_response(:apns, device_id)
end

defp normalize_response(:ok, _, _), do: :ok
defp normalize_response({:error, reason, _state}, _, _) do
{:error, reason}
end

defp normalize_response({:ok, state}, :fcm, device_id) do
%Pigeon.GCM.NotificationResponse{ok: ok, update: update} = state
case Enum.member?(ok ++ update, device_id) do
true -> :ok
false ->
{:error, :invalid_device_token}
end
end

defp normalize_response({:ok, _state}, :apns, _device_id), do: :ok

defp prepare_notification(:fcm, request) do
Logger.warn inspect request
[:body, :title, :click_action, :tag]
|> Enum.reduce(%{}, fn(field, map) ->
Map.put(map, field, request[field])
end)
end

defp prepare_notification(:apns, request) do
%{
"alert" => %{
"title" => request.title,
"body" => request.body
},
"badge" => request[:badge],
"category" => request[:click_action]
}
end
defp service_module(:fcm), do: MongoosePush.Service.FCM
defp service_module(:apns), do: MongoosePush.Service.APNS

end
5 changes: 5 additions & 0 deletions lib/mongoose_push/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ defmodule MongoosePush.Application do
|> Map.get(mode)
end

def select_worker(service, mode) do
[pool | _] = pools_by_mode(service, mode)
worker_name(service, pool, Enum.random(1..pool_size(service, pool)))
end

@spec env(atom) :: term
def env(var), do: Application.get_env(:mongoose_push, var)

Expand Down
9 changes: 9 additions & 0 deletions lib/mongoose_push/service.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule MongoosePush.Service do
@moduledoc """
Generic interface for push notifications services.
"""
@type notification :: term

@callback push(notification, string, atom) :: :ok | {:error, term}
@callback prepare_notification(string, MongoosePush.request) :: notification
end
35 changes: 35 additions & 0 deletions lib/mongoose_push/service/apns.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
defmodule MongoosePush.Service.APNS do
@moduledoc """
APNS (apple Push Notification Service) service provider implementation.
"""

@behaviour MongoosePush.Service
alias Pigeon.APNS
alias MongoosePush.Service

@spec prepare_notification(string, MongoosePush.request) ::
Service.notification
def prepare_notification(device_id, request) do
%{
"alert" => %{
"title" => request.title,
"body" => request.body
},
"badge" => request[:badge],
"category" => request[:click_action]
}
|>
APNS.Notification.new(device_id, request[:topic])
end

@spec push(Service.notification, string, atom) :: :ok | {:error, term}
def push(notification, _device_id, worker) do
case APNS.push(notification, [name: worker]) do
{:ok, _state} ->
:ok
{:error, reason, _state} ->
{:error, reason}
end
end

end
34 changes: 34 additions & 0 deletions lib/mongoose_push/service/fcm.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
defmodule MongoosePush.Service.FCM do
@moduledoc """
FCM (Firebase Cloud Messaging) service provider implementation.
"""

@behaviour MongoosePush.Service
alias Pigeon.GCM

@spec prepare_notification(string, MongoosePush.request) ::
Service.notification
def prepare_notification(device_id, request) do
msg = [:body, :title, :click_action, :tag]
|> Enum.reduce(%{}, fn(field, map) ->
Map.put(map, field, request[field])
end)
Pigeon.GCM.Notification.new(device_id, msg)
end

@spec push(Service.notification, string, atom) :: :ok | {:error, term}
def push(notification, device_id, worker) do
case GCM.push(notification, [name: worker]) do
{:ok, state} ->
%Pigeon.GCM.NotificationResponse{ok: ok, update: update} = state
case Enum.member?(ok ++ update, device_id) do
true -> :ok
false ->
{:error, :invalid_device_token}
end
{:error, reason, _state} ->
{:error, reason}
end
end

end
9 changes: 3 additions & 6 deletions test/mongoose_push_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -90,20 +90,17 @@ defmodule MongoosePushTest do
# Default should be mode: :prod
with_mock MongoosePush.Application, [:passthrough], [] do
assert :ok = push("androidtestdeviceid65", notification)
assert called(MongoosePush.Application.worker_name(:_, :prod1, :_)) or
called(MongoosePush.Application.worker_name(:_, :prod2, :_))
assert called(MongoosePush.Application.select_worker(:_, :prod))
end

with_mock MongoosePush.Application, [:passthrough], [] do
assert :ok = push("androidtestdeviceid65", dev_notification)
assert called(MongoosePush.Application.worker_name(:_, :dev1, :_)) or
called(MongoosePush.Application.worker_name(:_, :dev2, :_))
assert called(MongoosePush.Application.select_worker(:_, :dev))
end

with_mock MongoosePush.Application, [:passthrough], [] do
assert :ok = push("androidtestdeviceid65", prod_notification)
assert called(MongoosePush.Application.worker_name(:_, :prod1, :_)) or
called(MongoosePush.Application.worker_name(:_, :prod2, :_))
assert called(MongoosePush.Application.select_worker(:_, :prod))
end
end

Expand Down

0 comments on commit 2dbe39d

Please sign in to comment.