308 lines
8.9 KiB
Elixir
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
|