Skip to content

Commit

Permalink
Add support for widget callbacks/events
Browse files Browse the repository at this point in the history
  • Loading branch information
jsonmaur committed Apr 25, 2023
1 parent 1f1cec9 commit c6c45df
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 5 deletions.
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<a href="https://github.com/jsonmaur/phoenix-turnstile/actions/workflows/test.yml"><img alt="Test Status" src="https://img.shields.io/github/actions/workflow/status/jsonmaur/phoenix-turnstile/test.yml?label=&style=for-the-badge&logo=github"></a> <a href="https://hexdocs.pm/phoenix_turnstile/"><img alt="Hex Version" src="https://img.shields.io/hexpm/v/phoenix_turnstile?style=for-the-badge&label=&logo=elixir" /></a>

Components and helpers for using [Cloudflare Turnstile CAPTCHAs](https://www.cloudflare.com/products/turnstile/) in Phoenix apps. To get started, log into the Cloudflare dashboard and visit the Turnstile tab. Add a new site with your domain name (no need to add `localhost` if using the default test keys), and take note of your site key and secret key. You'll need these values later.
Phoenix components and helpers for using CAPTCHAs with [Cloudflare Turnstile](https://www.cloudflare.com/products/turnstile/). To get started, log into the Cloudflare dashboard and visit the Turnstile tab. Add a new site with your domain name (no need to add `localhost` if using the default test keys), and take note of your site key and secret key. You'll need these values later.

## Getting Started

Expand Down Expand Up @@ -107,6 +107,32 @@ def handle_event("submit", values, socket) do
end
```

### Events

The Turnstile widget supports the following events:

* `:success` - When the challenge was successfully completed
* `:error` - When there was an error (like a network error or the challenge failed)
* `:expired` - When the challenge token expires and was not automatically reset
* `:beforeInteractive` - Before the challenge enters interactive mode
* `:afterInteractive` - After the challenge has left interactive mode
* `:unsupported` - When a given client/browser is not supported by Turnstile
* `:timeout` - When the challenge expires (after 5 minutes)

These can be useful for doing things like disabling the submit button until the challenge successfully completes, or refreshing the widget if it fails. To handle an event, add it to the `events` attribute and create a Turnstile event handler in the Live View:

```heex
<Turnstile.widget events={[:success]} />
```

```elixir
handle_event("turnstile:success", _params, socket) do
# ...

{:noreply, socket}
end
```

### Multiple Widgets

If you want to have multiple widgets on the same page, pass a unique ID to `Turnstile.widget/1`, `Turnstile.refresh/1`, and `Turnstile.remove/1`.
Expand Down
16 changes: 14 additions & 2 deletions lib/turnstile.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Turnstile do
@behaviour Turnstile.Behaviour
@moduledoc """
Use Cloudflare Turnstile in Phoenix apps
Use Cloudflare Turnstile in Phoenix
"""

import Phoenix.Component
Expand Down Expand Up @@ -49,6 +49,7 @@ defmodule Turnstile do
* `:class` - The class name passed to the element. Defaults to `nil`.
* `:hook` - The phx-hook used. Defaults to `"Turnstile"`.
* `:sitekey` - The Turnstile site key. Defaults to the `:site_key` config value.
* `:events` - An atom list of the callback events to listen for for in the Live View. See [events](readme.html#events).
All other attributes will be passed through to the element as `data-*` attributes so the widget
can be customized. See the [Turnstile docs](https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/#configurations)
Expand All @@ -57,15 +58,25 @@ defmodule Turnstile do
def widget(assigns) do
rest =
assigns
|> assigns_to_attributes([:id, :class, :hook, :sitekey])
|> assigns_to_attributes([:id, :class, :hook, :sitekey, :events])
|> Enum.map(fn {k, v} -> {"data-#{k}", v} end)
|> Keyword.put(:class, assigns[:class])

events =
assigns
|> Map.get(:events, [])
|> Enum.join(",")
|> case do
"" -> nil
value -> value
end

assigns =
assigns
|> assign_new(:id, fn -> "cf-turnstile" end)
|> assign_new(:hook, fn -> "Turnstile" end)
|> assign_new(:sitekey, &site_key/0)
|> assign(:events, events)
|> assign(:rest, rest)

~H"""
Expand All @@ -74,6 +85,7 @@ defmodule Turnstile do
phx-hook={@hook}
phx-update="ignore"
data-sitekey={@sitekey}
data-events={@events}
{@rest}
/>
"""
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ defmodule PhoenixTurnstile.MixProject do
aliases: aliases(),
source_url: @url,
homepage_url: "#{@url}#readme",
description: "Use Cloudflare Turnstile in Phoenix apps",
description: "Use Cloudflare Turnstile in Phoenix",
authors: ["Jason Maurer"],
package: [
licenses: ["MIT"],
Expand Down
20 changes: 19 additions & 1 deletion priv/static/phoenix_turnstile.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
function callbackEvent(self, name, eventName) {
return (payload) => {
const events = self.el.dataset.events || ""

if (events.split(",").indexOf(name) > -1) {
self.pushEventTo(self.el, `turnstile:${eventName || name}`, payload)
}
}
}

export const TurnstileHook = {
mounted() {
turnstile.render(this.el)
turnstile.render(this.el, {
callback: callbackEvent(this, "success"),
"error-callback": callbackEvent(this, "error"),
"expired-callback": callbackEvent(this, "expired"),
"before-interactive-callback": callbackEvent(this, "beforeInteractive", "before-interactive"),
"after-interactive-callback": callbackEvent(this, "afterInteractive", "after-interactive"),
"unsupported-callback": callbackEvent(this, "unsupported"),
"timeout-callback": callbackEvent(this, "timeout")
})

this.handleEvent("turnstile:refresh", (event) => {
if (!event.id || event.id === this.el.id) {
Expand Down
5 changes: 5 additions & 0 deletions test/turnstile_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ defmodule TurnstileTest do
assert render_component(&Turnstile.widget/1, theme: "dark") ==
"<div id=\"cf-turnstile\" phx-hook=\"Turnstile\" phx-update=\"ignore\" data-sitekey=\"1x00000000000000000000AA\" data-theme=\"dark\"></div>"
end

test "should render component with a list of events" do
assert render_component(&Turnstile.widget/1, events: [:success, :error]) ==
"<div id=\"cf-turnstile\" phx-hook=\"Turnstile\" phx-update=\"ignore\" data-sitekey=\"1x00000000000000000000AA\" data-events=\"success,error\"></div>"
end
end

test "refresh/2" do
Expand Down

0 comments on commit c6c45df

Please sign in to comment.