Grapple

🍏 Webhook magic in Elixir

CircleCI

Installation

If available in Hex, the package can be installed as:

  1. Add grapple to your list of dependencies in mix.exs:
```elixir
def deps do
[{:grapple, "~> 0.2.0"}]
end
```
  1. Ensure grapple is started before your application:
```elixir
def application do
[applications: [:grapple]]
end
``

Running

iex -S mix

Documentation

https://hexdocs.pm/grapple/0.1.0


Direct API Usage

The default struct, %Grapple.Hook{}, has the following fields: topic, url, owner, life, ref, method, headers, body, and query. Note that topic and url are required. TODO: make this configurable.

To subscribe to a webhook, pass a Hook to the subscribe function, which returns the topic name and the unique refernce to that particular hook:

hook = %Grapple.Hook{topic: "pokemon", url: "http://pokeapi.co/api/v2/pokemon/149"}
{topic, ref} = Grapple.Hook.subscribe(hook)

To broadcast a webhook, pass a topic, and optionally arbitrary data. This will trigger HTTP requests for any stored hooks whose topic values match the given topic, and return the parsed responses.

# this will send hooks with their default `body`
[response] = Grapple.Hook.broadcast("pokemon")
# can also pass arbitrary data that will be sent instead
[response] = Grapple.Hook.broadcast("pokemon", data)

Responses will take one of the following forms:

# on success
{:success, body: body} = response
# on 404
:not_found = response
# on error
{:error, reason: reason} = response

Macro Usage

Broadcasting can also be done via a macro, defhook. Its name will be used in the topic (takes the form "#{MODULE}.#{name}"). Its result will be passed as the body to any hook requests on that topic, unless it returns nil, in which case hooks will be sent with default body.

hook = %Grapple.Hook{topic: "Pokemon.dragonite", url: "http://pokeapi.co/api/v2/pokemon/149"}
defmodule Pokemon do
use Grapple.Hook
# in this case, no body needed
defhook dragonite do
# add other logic, and return body or return nil
nil
end
end

Plug Usage

Finally, broadcasting can be done with Grapple.Plug. Here's an example from a Phoenix Controller:

defmodule Pokedex.PokemonController do
use Pokedex.Web, :controller
plug Grapple.Plug, [topic: "pokemon"] when action in [:get]
def get(conn, _opts) do
conn
end
end