analytics/extra/lib/plausible_web/live/verification.ex

308 lines
8.9 KiB
Elixir

defmodule PlausibleWeb.Live.Verification do
@moduledoc """
LiveView coordinating the site verification process.
Onboarding new sites, renders a standalone component.
Embedded modal variant is available for general site settings.
"""
use PlausibleWeb, :live_view
import PlausibleWeb.Components.Generic
alias Plausible.InstallationSupport.{State, LegacyVerification, Verification}
@component PlausibleWeb.Live.Components.Verification
@slowdown_for_frequent_checking :timer.seconds(5)
def mount(
%{"domain" => domain} = params,
_session,
socket
) do
current_user = socket.assigns.current_user
site =
Plausible.Sites.get_for_user!(current_user, domain,
roles: [
:owner,
:admin,
:editor,
:super_admin,
:viewer
]
)
true = Plausible.Sites.regular?(site)
private = Map.get(socket.private.connect_info, :private, %{})
super_admin? = Plausible.Auth.is_super_admin?(current_user)
has_pageviews? = has_pageviews?(site)
custom_url_input? =
PlausibleWeb.Tracker.scriptv2?(site, current_user) and params["custom_url"] == "true"
socket =
assign(socket,
url_to_verify: nil,
site: site,
super_admin?: super_admin?,
domain: domain,
has_pageviews?: has_pageviews?,
component: @component,
installation_type: get_installation_type(params, site, current_user),
report_to: self(),
delay: private[:delay] || 500,
slowdown: private[:slowdown] || 500,
flow: params["flow"] || "",
checks_pid: nil,
attempts: 0,
polling_pageviews?: false,
custom_url_input?: custom_url_input?
)
if connected?(socket) and not custom_url_input? do
launch_delayed(socket)
end
{:ok, socket}
end
def render(assigns) do
~H"""
<PlausibleWeb.Components.FlowProgress.render flow={@flow} current_step="Verify installation" />
<.custom_url_form :if={@custom_url_input?} domain={@domain} />
<.live_component
:if={not @custom_url_input?}
module={@component}
installation_type={@installation_type}
domain={@domain}
id="verification-standalone"
attempts={@attempts}
flow={@flow}
awaiting_first_pageview?={not @has_pageviews?}
super_admin?={@super_admin?}
/>
"""
end
def handle_event("launch-verification", _, socket) do
launch_delayed(socket)
{:noreply, reset_component(socket)}
end
def handle_event("retry", _, socket) do
launch_delayed(socket)
{:noreply, reset_component(socket)}
end
def handle_event("verify-custom-url", %{"custom_url" => custom_url}, socket) do
socket =
socket
|> assign(url_to_verify: custom_url)
|> assign(custom_url_input?: false)
launch_delayed(socket)
{:noreply, reset_component(socket)}
end
def handle_info({:start, report_to}, socket) do
domain = socket.assigns.domain
checks_pid = socket.assigns.checks_pid
if is_pid(checks_pid) and Process.alive?(checks_pid) do
{:noreply, socket}
else
case Plausible.RateLimit.check_rate(
"site_verification:#{domain}",
:timer.minutes(60),
3
) do
{:allow, _} -> :ok
{:deny, _} -> :timer.sleep(@slowdown_for_frequent_checking)
end
{:ok, pid} =
if PlausibleWeb.Tracker.scriptv2?(socket.assigns.site, socket.assigns.current_user) do
Verification.Checks.run(
socket.assigns.url_to_verify,
domain,
socket.assigns.installation_type,
report_to: report_to,
slowdown: socket.assigns.slowdown
)
else
LegacyVerification.Checks.run(
"https://#{domain}",
domain,
report_to: report_to,
slowdown: socket.assigns.slowdown
)
end
{:noreply, assign(socket, checks_pid: pid, attempts: socket.assigns.attempts + 1)}
end
end
def handle_info({:check_start, {check, state}}, socket) do
to_update = [message: check.report_progress_as()]
to_update =
if is_binary(state.url) do
Keyword.put(to_update, :url_to_verify, state.url)
else
to_update
end
update_component(socket, to_update)
{:noreply, socket}
end
def handle_info({:all_checks_done, %State{} = state}, socket) do
interpretation =
if PlausibleWeb.Tracker.scriptv2?(socket.assigns.site, socket.assigns.current_user) do
Verification.Checks.interpret_diagnostics(state)
else
LegacyVerification.Checks.interpret_diagnostics(state)
end
if not socket.assigns.has_pageviews? do
schedule_pageviews_check(socket)
end
update_component(socket,
finished?: true,
success?: interpretation.ok?,
interpretation: interpretation,
verification_state: state
)
{:noreply, assign(socket, checks_pid: nil)}
end
def handle_info(:check_pageviews, socket) do
socket =
if has_pageviews?(socket.assigns.site) do
redirect_to_stats(socket)
else
socket
|> assign(polling_pageviews?: false)
|> schedule_pageviews_check()
end
{:noreply, socket}
end
@supported_installation_types_atoms PlausibleWeb.Tracker.supported_installation_types()
|> Enum.map(&String.to_atom/1)
defp get_installation_type(params, site, current_user) do
if PlausibleWeb.Tracker.scriptv2?(site, current_user) do
cond do
params["installation_type"] in PlausibleWeb.Tracker.supported_installation_types() ->
params["installation_type"]
(saved_installation_type = get_saved_installation_type(site)) in @supported_installation_types_atoms ->
Atom.to_string(saved_installation_type)
true ->
PlausibleWeb.Tracker.fallback_installation_type()
end
else
params["installation_type"]
end
end
defp get_saved_installation_type(site) do
case PlausibleWeb.Tracker.get_tracker_script_configuration(site) do
%{installation_type: installation_type} ->
installation_type
_ ->
nil
end
end
defp schedule_pageviews_check(socket) do
if socket.assigns.polling_pageviews? do
socket
else
Process.send_after(self(), :check_pageviews, socket.assigns.delay * 2)
assign(socket, polling_pageviews?: true)
end
end
defp redirect_to_stats(socket) do
stats_url = Routes.stats_path(PlausibleWeb.Endpoint, :stats, socket.assigns.domain, [])
redirect(socket, to: stats_url)
end
defp reset_component(socket) do
update_component(socket,
message: "We're visiting your site to ensure that everything is working",
finished?: false,
success?: false,
diagnostics: nil
)
socket
end
defp update_component(_socket, updates) do
send_update(
@component,
Keyword.merge(updates, id: "verification-standalone")
)
end
defp launch_delayed(socket) do
Process.send_after(self(), {:start, socket.assigns.report_to}, socket.assigns.delay)
end
defp has_pageviews?(site) do
Plausible.Stats.Clickhouse.has_pageviews?(site)
end
defp custom_url_form(assigns) do
~H"""
<.focus_box>
<div class="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900">
<Heroicons.globe_alt class="h-6 w-6 text-blue-600 dark:text-blue-200" />
</div>
<div class="mt-8">
<h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white">
Enter Your Custom URL
</h3>
<p class="text-sm mt-4 text-gray-600 dark:text-gray-400">
Please enter the URL where your website with the Plausible script is located.
</p>
<form phx-submit="verify-custom-url" class="mt-6">
<div class="mb-4">
<label
for="custom_url"
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"
>
Website URL
</label>
<input
type="url"
name="custom_url"
id="custom_url"
required
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-xs focus:outline-hidden focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 dark:text-white"
placeholder={"https://#{@domain}"}
value={"https://#{@domain}"}
/>
</div>
<button
type="submit"
class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-xs text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-hidden focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-600"
>
Verify Installation
</button>
</form>
</div>
</.focus_box>
"""
end
end