{"id":17949391,"url":"https://github.com/camirmas/grapple","last_synced_at":"2025-03-24T22:35:53.152Z","repository":{"id":57503335,"uuid":"66789557","full_name":"camirmas/grapple","owner":"camirmas","description":":green_apple: Webhook magic in Elixir","archived":false,"fork":false,"pushed_at":"2018-06-06T02:20:31.000Z","size":95,"stargazers_count":50,"open_issues_count":3,"forks_count":4,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-10-29T09:19:08.885Z","etag":null,"topics":["elixir","polling","webhook"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/camirmas.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-08-28T20:36:42.000Z","updated_at":"2024-04-30T13:03:50.000Z","dependencies_parsed_at":"2022-09-13T08:21:37.606Z","dependency_job_id":null,"html_url":"https://github.com/camirmas/grapple","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/camirmas%2Fgrapple","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/camirmas%2Fgrapple/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/camirmas%2Fgrapple/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/camirmas%2Fgrapple/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/camirmas","download_url":"https://codeload.github.com/camirmas/grapple/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245366205,"owners_count":20603438,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","polling","webhook"],"created_at":"2024-10-29T09:16:35.393Z","updated_at":"2025-03-24T22:35:48.134Z","avatar_url":"https://github.com/camirmas.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Grapple\n\u003e :green_apple: Webhook magic in Elixir\n\n[![Build Status](https://travis-ci.org/camirmas/grapple.svg?branch=master)](https://travis-ci.org/camirmas/grapple)\n\nGrapple defines a simple API for hookable actions that broadcast updates to subscribers over HTTP.\n\nThis API lends itself nicely to Webhooks, REST Hooks, Server Push, and more!\n\n## Installation\n\n1. Add `grapple` to your list of dependencies in `mix.exs`:\n\n  ```elixir\n  def deps do\n    [{:grapple, \"~\u003e 1.2.3\"}]\n  end\n  ```\n\n2. Ensure `grapple` is started before your application:\n\n  ```elixir\n  def application do\n    [applications: [:grapple]]\n  end\n  ```\n\n## Running\n\n```bash\niex -S mix\n```\n\n## Documentation\nhttps://hexdocs.pm/grapple/1.2.3\n\n---\n\n## Usage\n\n### Direct\n\nThe default struct, `%Grapple.Hook{}`, has the following fields:\n\n- `url`\n- `owner`\n- `life`\n- `ref`\n- `method`\n- `body`\n- `headers`\n- `options`\n- `query`\n\nNote that `url` is **required**. Additionally, the fields `body`, `headers`, and\n`options` all correspond to those used in `HTTPoison` requests. See [HTTPoison](https://github.com/edgurgel/httpoiso://github.com/edgurgel/httpoison)\nfor more info.\n\n**Topics**\n\nTo create a new topic, pass an atom to the `add_topic` function, which returns\na `Topic` struct.\n\n```elixir\n{:ok, topic = %Grapple.Server.Topic{}} = Grapple.add_topic(:pokemon)\n```\n\n**Subscribing**\n\nTo subscribe to a webhook, pass the topic name and a `Hook` to the `subscribe` function, which returns the topic name and the unique refernce to that particular hook:\n```elixir\n{:ok, pid} = Grapple.subscribe(:pokemon, %Grapple.Hook{url: \"my-api\"})\n```\n\nIt's important that topics are unique across your application's modules (and `topicof` ensures this) because it makes implementing higher-level features, such as [REST Hooks](http://resthooks.org), much easier.\n\n**Broadcasting**\n\nTo broadcast all webhooks for a given topic, pass a `topic` name, and optionally arbitrary `data`.\nThis will trigger HTTP requests for any stored hooks (and their subscribers) whose `topic` values match the given `topic`, and return the parsed responses.\n\n```elixir\n# this will send hooks with their default `body`\nGrapple.broadcast(:pokemon)\n\n# you can also pass arbitrary data that will be sent instead\nGrapple.Hook.broadcast(\"pokemon\", data)\n```\n\nNote that the call to `broadcast` does not actually return the responses. This is because\nhooks run asynchronously. In order to retrieve responses, you can either ask for them explicitly:\n```elixir\n[{_pid, responses}] = Grapple.get_responses(:pokemon)\n```\n\nOr you can, when subscribing a `Hook`, set `:owner` to the pid of an existing\nprocess that can receive a message when that `Hook` completes its `broadcast`.\nThe format of the message is `{:hook_response, hook_pid, response}`, with\n`response` being a response from an `HTTPoison` request. See [HTTPoison](https://github.com/edgurgel/httpoiso://github.com/edgurgel/httpoison)\nfor more info. As an example, if your `:owner` process is a `GenServer`,\nyou would define a `handle_info` function like so:\n```elixir\ndef handle_info({:hook_response, pid, response}, state) do\n  # some logic\n  {:noreply, state}\nend\n```\n\n**Polling**\n\nYou can have individual hooks `broadcast` on an interval in two different ways.\nThe first is to include an `interval` field with an integer (in milliseconds)\nwhen defining a `Hook`:\n```elixir\nGrapple.subscribe(:pokemon, %Grapple.Hook{url: \"my-api\", interval: 3000}\n```\n\nYou can also take an existing hook that does not yet have an interval, and tell\nit to start polling:\n```elixir\n{:ok, pid} = Grapple.subscribe(:pokemon, %Grapple.Hook{url: \"my-api\", interval: 3000}\nGrapple.start_polling(pid, 3000)\n```\n\nTo turn off polling for a particular hook:\n```elixir\nGrapple.stop_polling(pid)\n```\n\nTo start polling for a particular hook, if it already has an `interval`:\n```elixir\nGrapple.start_polling(pid)\n```\n\n### Macro\n\nBroadcasting can also be done via a macro, `defhook`. The macro defines a named\nmethod in the lexical module. When invoked, the method's name will be used as the\ntopic, and if the method name matches an existing topic, all `Hook`s on that topic\nwill be `broadcast`. The result will be broadcast as the `body` to any hook requests\non that topic, unless it returns `nil`, in which case hooks will be sent with the\ndefault `body`.\n\nThe following example implements a hook that determines the game profile for Dragonite,\nautomatically sending requests to the `http://pokeapi.co`:\n\n```elixir\nGrapple.subscribe(:pokemon, %Grapple.Hook{url: \"http://pokeapi.co/api/v2/pokemon/149\"})\n\ndefmodule Pokemon do\n  use Grapple.Hook\n\n  defhook dragonite do\n    # add some logic and return a body or return nil\n    # In this case, sends \"GET\" request to the `Hook` URL.\n  end\nend\n```\n\nYou should try to ensure that your hook method doesn't get called excessively\nsince it's highly unlikely that subscribers will want to be repeatedly hit.\nThis certainly depends on your own unique needs, but it's good to keep this fact in mind.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcamirmas%2Fgrapple","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcamirmas%2Fgrapple","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcamirmas%2Fgrapple/lists"}