Refactor Verification module structure (#5570)

* detector.js

* refactor: organize modules better

* Renaming (Elixir + JS)

* lib/plausible/verification -> lib/plausible/installation_support
* test/plausible/verification -> test/plausible/installation_support
* priv/tracker/verifier -> priv/tracker/installation_support
* tracker/verifier -> tracker/installation_support
* tracker/test/verifier -> tracker/test/installation-support

* rename remaining test modules

* add documentation

* dialyzer: remove module refs that do not exist yet

* Fix CI

* fix tracker CI

* fix tracker CI for good
This commit is contained in:
RobertJoonas 2025-07-15 13:50:34 +03:00 committed by GitHub
parent 39c006bfb9
commit 97dcc3fe7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
62 changed files with 531 additions and 336 deletions

2
.gitignore vendored
View File

@ -92,7 +92,7 @@ plausible-report.xml
# Auto-generated tracker files # Auto-generated tracker files
/priv/tracker/js/plausible*.js* /priv/tracker/js/plausible*.js*
/priv/tracker/verifier/*.js /priv/tracker/installation_support/*.js
# Docker volumes # Docker volumes
.clickhouse_db_vol* .clickhouse_db_vol*

View File

@ -937,7 +937,7 @@ config :plausible, Plausible.PromEx,
grafana: :disabled, grafana: :disabled,
metrics_server: :disabled metrics_server: :disabled
config :plausible, Plausible.Verification.Checks.Installation, config :plausible, Plausible.InstallationSupport,
token: get_var_from_path_or_env(config_dir, "BROWSERLESS_TOKEN", "dummy_token"), token: get_var_from_path_or_env(config_dir, "BROWSERLESS_TOKEN", "dummy_token"),
endpoint: get_var_from_path_or_env(config_dir, "BROWSERLESS_ENDPOINT", "http://0.0.0.0:3000") endpoint: get_var_from_path_or_env(config_dir, "BROWSERLESS_ENDPOINT", "http://0.0.0.0:3000")

View File

@ -34,14 +34,14 @@ config :plausible, Plausible.Ingestion.Counters, enabled: false
config :plausible, Oban, testing: :manual config :plausible, Oban, testing: :manual
config :plausible, Plausible.Verification.Checks.FetchBody, config :plausible, Plausible.InstallationSupport.Checks.FetchBody,
req_opts: [ req_opts: [
plug: {Req.Test, Plausible.Verification.Checks.FetchBody} plug: {Req.Test, Plausible.InstallationSupport.Checks.FetchBody}
] ]
config :plausible, Plausible.Verification.Checks.Installation, config :plausible, Plausible.InstallationSupport.Checks.Installation,
req_opts: [ req_opts: [
plug: {Req.Test, Plausible.Verification.Checks.Installation} plug: {Req.Test, Plausible.InstallationSupport.Checks.Installation}
] ]
config :plausible, Plausible.HelpScout, config :plausible, Plausible.HelpScout,

View File

@ -21,7 +21,7 @@ defmodule Plausible.Ingestion.Event do
salts: nil, salts: nil,
changeset: nil changeset: nil
@verification_user_agent Plausible.Verification.user_agent() @verification_user_agent Plausible.InstallationSupport.user_agent()
@type drop_reason() :: @type drop_reason() ::
:bot :bot

View File

@ -0,0 +1,39 @@
defmodule Plausible.InstallationSupport.Check do
@moduledoc """
Behaviour to be implemented by a specific installation support check.
`report_progress_as()` doesn't necessarily reflect the actual check
description, it serves as a user-facing message grouping mechanism,
to prevent frequent message flashing when checks rotate often.
Each check operates on `%Plausible.InstallationSupport.State{}` and is
expected to return it, optionally modified, by all means. `perform_safe/1`
is used to guarantee no exceptions are thrown by faulty implementations,
not to interrupt LiveView.
"""
@type state() :: Plausible.InstallationSupport.State.t()
@callback report_progress_as() :: String.t()
@callback perform(state()) :: state()
defmacro __using__(_) do
quote do
import Plausible.InstallationSupport.State
alias Plausible.InstallationSupport.State
require Logger
@behaviour Plausible.InstallationSupport.Check
def perform_safe(state) do
perform(state)
catch
_, e ->
Logger.error(
"Error running check #{inspect(__MODULE__)} on #{state.url}: #{inspect(e)}"
)
put_diagnostics(state, service_error: e)
end
end
end
end

View File

@ -0,0 +1,60 @@
defmodule Plausible.InstallationSupport.CheckRunner do
@moduledoc """
Takes two arguments:
1. A `%Plausible.InstallationSupport.State{}` struct - the `diagnostics`
field is a struct representing the set of diagnostics shared between
all the checks in this flow.
2. A list of modules implementing `Plausible.InstallationSupport.Check`
behaviour.
Checks are normally run asynchronously, except when synchronous
execution is optionally required for tests. Slowdowns can be optionally
added, the user doesn't benefit from running the checks too quickly.
"""
def run(state, checks, opts) do
async? = Keyword.get(opts, :async?, true)
slowdown = Keyword.get(opts, :slowdown, 500)
if async? do
Task.start_link(fn -> do_run(state, checks, slowdown) end)
else
do_run(state, checks, slowdown)
end
end
defp do_run(state, checks, slowdown) do
state =
Enum.reduce(
checks,
state,
fn check, state ->
state
|> notify_check_start(check, slowdown)
|> check.perform_safe()
end
)
notify_all_checks_done(state, slowdown)
end
defp notify_check_start(state, check, slowdown) do
if is_pid(state.report_to) do
if is_integer(slowdown) and slowdown > 0, do: :timer.sleep(slowdown)
send(state.report_to, {:check_start, {check, state}})
end
state
end
defp notify_all_checks_done(state, slowdown) do
if is_pid(state.report_to) do
if is_integer(slowdown) and slowdown > 0, do: :timer.sleep(slowdown)
send(state.report_to, {:all_checks_done, state})
end
state
end
end

View File

@ -1,9 +1,9 @@
defmodule Plausible.Verification.Checks.CSP do defmodule Plausible.InstallationSupport.Checks.CSP do
@moduledoc """ @moduledoc """
Scans the Content Security Policy header to ensure that the Plausible domain is allowed. Scans the Content Security Policy header to ensure that the Plausible domain is allowed.
See `Plausible.Verification.Checks` for the execution sequence. See `Plausible.InstallationSupport.LegacyVerification.Checks` for the execution sequence.
""" """
use Plausible.Verification.Check use Plausible.InstallationSupport.Check
@impl true @impl true
def report_progress_as, do: "We're visiting your site to ensure that everything is working" def report_progress_as, do: "We're visiting your site to ensure that everything is working"

View File

@ -1,15 +1,16 @@
defmodule Plausible.Verification.Checks.FetchBody do defmodule Plausible.InstallationSupport.Checks.FetchBody do
@moduledoc """ @moduledoc """
Fetches the body of the site and extracts the HTML document, if available, for Fetches the body of the site and extracts the HTML document, if available, for
further processing. further processing. See `Plausible.InstallationSupport.LegacyVerification.Checks`
See `Plausible.Verification.Checks` for the execution sequence. for the execution sequence.
""" """
use Plausible.Verification.Check use Plausible.InstallationSupport.Check
@impl true @impl true
def report_progress_as, do: "We're visiting your site to ensure that everything is working" def report_progress_as, do: "We're visiting your site to ensure that everything is working"
@impl true @impl true
def perform(%State{url: "https://" <> _ = url} = state) do def perform(%State{url: "https://" <> _ = url} = state) do
fetch_body_opts = Application.get_env(:plausible, __MODULE__)[:req_opts] || [] fetch_body_opts = Application.get_env(:plausible, __MODULE__)[:req_opts] || []

View File

@ -1,12 +1,12 @@
defmodule Plausible.Verification.Checks.Installation do defmodule Plausible.InstallationSupport.Checks.Installation do
require Logger require Logger
path = Application.app_dir(:plausible, "priv/tracker/verifier/verifier-v1.js") path = Application.app_dir(:plausible, "priv/tracker/installation_support/verifier-v1.js")
# On CI, the file might not be present for static checks so we create an empty one # On CI, the file might not be present for static checks so we create an empty one
File.touch!(path) File.touch!(path)
@verifier_code File.read!(path) @verifier_code File.read!(path)
@external_resource "priv/tracker/verifier/verifier-v1.js" @external_resource "priv/tracker/installation_support/verifier-v1.js"
# Puppeteer wrapper function that executes the vanilla JS verifier code # Puppeteer wrapper function that executes the vanilla JS verifier code
@puppeteer_wrapper_code """ @puppeteer_wrapper_code """
@ -45,7 +45,7 @@ defmodule Plausible.Verification.Checks.Installation do
- `data.callbackStatus` - integer. 202 indicates that the server acknowledged the test event. - `data.callbackStatus` - integer. 202 indicates that the server acknowledged the test event.
The test event ingestion is discarded based on user-agent, see: The test event ingestion is discarded based on user-agent, see:
`Plausible.Verification.user_agent/0` `Plausible.InstallationSupport.user_agent/0`
- `data.dataDomainMismatch` - whether or not script[data-domain] mismatched with site.domain - `data.dataDomainMismatch` - whether or not script[data-domain] mismatched with site.domain
@ -63,7 +63,7 @@ defmodule Plausible.Verification.Checks.Installation do
- `data.cookieBannerLikely` - whether or not there's a cookie banner blocking Plausible - `data.cookieBannerLikely` - whether or not there's a cookie banner blocking Plausible
""" """
use Plausible.Verification.Check use Plausible.InstallationSupport.Check
@impl true @impl true
def report_progress_as, do: "We're verifying that your visitors are being counted correctly" def report_progress_as, do: "We're verifying that your visitors are being counted correctly"
@ -77,8 +77,8 @@ defmodule Plausible.Verification.Checks.Installation do
code: @puppeteer_wrapper_code, code: @puppeteer_wrapper_code,
context: %{ context: %{
expectedDataDomain: data_domain, expectedDataDomain: data_domain,
url: Plausible.Verification.URL.bust_url(url), url: Plausible.InstallationSupport.URL.bust_url(url),
userAgent: Plausible.Verification.user_agent(), userAgent: Plausible.InstallationSupport.user_agent(),
debug: Application.get_env(:plausible, :environment) == "dev" debug: Application.get_env(:plausible, :environment) == "dev"
} }
}), }),
@ -90,7 +90,7 @@ defmodule Plausible.Verification.Checks.Installation do
extra_opts = Application.get_env(:plausible, __MODULE__)[:req_opts] || [] extra_opts = Application.get_env(:plausible, __MODULE__)[:req_opts] || []
opts = Keyword.merge(opts, extra_opts) opts = Keyword.merge(opts, extra_opts)
case Req.post(verification_endpoint(), opts) do case Req.post(Plausible.InstallationSupport.browserless_function_api_endpoint(), opts) do
{:ok, %{status: 200, body: %{"data" => %{"completed" => true} = js_data}}} -> {:ok, %{status: 200, body: %{"data" => %{"completed" => true} = js_data}}} ->
emit_telemetry_and_log(state.diagnostics, js_data, data_domain) emit_telemetry_and_log(state.diagnostics, js_data, data_domain)
@ -159,11 +159,4 @@ defmodule Plausible.Verification.Checks.Installation do
:telemetry.execute(telemetry_event(any_diff?), %{}) :telemetry.execute(telemetry_event(any_diff?), %{})
end end
defp verification_endpoint() do
config = Application.fetch_env!(:plausible, __MODULE__)
token = Keyword.fetch!(config, :token)
endpoint = Keyword.fetch!(config, :endpoint)
Path.join(endpoint, "function?token=#{token}&stealth")
end
end end

View File

@ -1,8 +1,8 @@
defmodule Plausible.Verification.Checks.ScanBody do defmodule Plausible.InstallationSupport.Checks.ScanBody do
@moduledoc """ @moduledoc """
Naive way of detecting GTM and WordPress powered sites. Naive way of detecting GTM and WordPress powered sites.
""" """
use Plausible.Verification.Check use Plausible.InstallationSupport.Check
@impl true @impl true
def report_progress_as, do: "We're visiting your site to ensure that everything is working" def report_progress_as, do: "We're visiting your site to ensure that everything is working"
@ -67,7 +67,7 @@ defmodule Plausible.Verification.Checks.ScanBody do
defp scan_cookie_banners(%{assigns: %{raw_body: body}} = state) do defp scan_cookie_banners(%{assigns: %{raw_body: body}} = state) do
# We'll start with CookieBot. Not using the selectors yet, as seen at # We'll start with CookieBot. Not using the selectors yet, as seen at
# https://github.com/cavi-au/Consent-O-Matic/blob/master/rules/cookiebot.json # https://github.com/cavi-au/Consent-O-Matic/blob/master/rules/cookiebot.json
# because those don't seem to be appearing without JS evaluation. # because those don't seem to be appearing without JS evaluation.
# If this ever becomes an issue, we'll have to move that check to headless. # If this ever becomes an issue, we'll have to move that check to headless.
if String.contains?(body, "cookiebot") do if String.contains?(body, "cookiebot") do
put_diagnostics(state, cookie_banner_likely?: true) put_diagnostics(state, cookie_banner_likely?: true)

View File

@ -1,10 +1,10 @@
defmodule Plausible.Verification.Checks.Snippet do defmodule Plausible.InstallationSupport.Checks.Snippet do
@moduledoc """ @moduledoc """
The check looks for Plausible snippets and tries to address the common The check looks for Plausible snippets and tries to address the common
integration issues, such as bad placement, data-domain typos, unknown integration issues, such as bad placement, data-domain typos, unknown
attributes frequently added by performance optimization plugins, etc. attributes frequently added by performance optimization plugins, etc.
""" """
use Plausible.Verification.Check use Plausible.InstallationSupport.Check
@impl true @impl true
def report_progress_as, do: "We're looking for the Plausible snippet on your site" def report_progress_as, do: "We're looking for the Plausible snippet on your site"

View File

@ -1,12 +1,15 @@
defmodule Plausible.Verification.Checks.SnippetCacheBust do defmodule Plausible.InstallationSupport.Checks.SnippetCacheBust do
@moduledoc """ @moduledoc """
A naive way of trying to figure out whether the latest site contents A naive way of trying to figure out whether the latest site contents
is wrapped with some CDN/caching layer. is wrapped with some CDN/caching layer.
In case no snippets were found, we'll try to bust the cache by appending a random query parameter
and re-run `Plausible.Verification.Checks.FetchBody` and `Plausible.Verification.Checks.Snippet` checks. In case no snippets were found, we'll try to bust the cache by appending
a random query parameter and re-run `FetchBody` and `Snippet` checks.
If the result is different this time, we'll assume cache likely. If the result is different this time, we'll assume cache likely.
""" """
use Plausible.Verification.Check use Plausible.InstallationSupport.Check
alias Plausible.InstallationSupport.{LegacyVerification, Checks, URL}
@impl true @impl true
def report_progress_as, do: "We're looking for the Plausible snippet on your site" def report_progress_as, do: "We're looking for the Plausible snippet on your site"
@ -15,7 +18,7 @@ defmodule Plausible.Verification.Checks.SnippetCacheBust do
def perform( def perform(
%State{ %State{
url: url, url: url,
diagnostics: %Diagnostics{ diagnostics: %LegacyVerification.Diagnostics{
snippets_found_in_head: 0, snippets_found_in_head: 0,
snippets_found_in_body: 0, snippets_found_in_body: 0,
body_fetched?: true body_fetched?: true
@ -23,10 +26,10 @@ defmodule Plausible.Verification.Checks.SnippetCacheBust do
} = state } = state
) do ) do
state2 = state2 =
%{state | url: Plausible.Verification.URL.bust_url(url)} %{state | url: URL.bust_url(url)}
|> Plausible.Verification.Checks.FetchBody.perform() |> Checks.FetchBody.perform()
|> Plausible.Verification.Checks.ScanBody.perform() |> Checks.ScanBody.perform()
|> Plausible.Verification.Checks.Snippet.perform() |> Checks.Snippet.perform()
if state2.diagnostics.snippets_found_in_head > 0 or if state2.diagnostics.snippets_found_in_head > 0 or
state2.diagnostics.snippets_found_in_body > 0 do state2.diagnostics.snippets_found_in_body > 0 do

View File

@ -0,0 +1,32 @@
defmodule Plausible.InstallationSupport do
@moduledoc """
This top level module is the middle ground between pre-installation
site scans and verification of whether Plausible has been installed
correctly.
Defines the user-agent used by Elixir-native HTTP requests as well
as headless browser checks on the client side via Browserless.
"""
use Plausible
on_ee do
def user_agent() do
"Plausible Verification Agent - if abused, contact support@plausible.io"
end
def browserless_function_api_endpoint() do
config = Application.fetch_env!(:plausible, __MODULE__)
token = Keyword.fetch!(config, :token)
endpoint = Keyword.fetch!(config, :endpoint)
Path.join(endpoint, "function?token=#{token}&stealth")
end
else
def browserless_function_api_endpoint() do
"Browserless API should not be called on Community Edition"
end
def user_agent() do
"Plausible Community Edition"
end
end
end

View File

@ -0,0 +1,48 @@
defmodule Plausible.InstallationSupport.LegacyVerification.Checks do
@moduledoc """
Checks that are performed during v1 site verification.
In async execution, each check notifies the caller by sending a message to it.
"""
alias Plausible.InstallationSupport.LegacyVerification
alias Plausible.InstallationSupport.{State, CheckRunner, Checks}
require Logger
@checks [
Checks.FetchBody,
Checks.CSP,
Checks.ScanBody,
Checks.Snippet,
Checks.SnippetCacheBust,
Checks.Installation
]
def run(url, data_domain, opts \\ []) do
checks = Keyword.get(opts, :checks, @checks)
report_to = Keyword.get(opts, :report_to, self())
async? = Keyword.get(opts, :async?, true)
slowdown = Keyword.get(opts, :slowdown, 500)
init_state =
%State{
url: url,
data_domain: data_domain,
report_to: report_to,
diagnostics: %LegacyVerification.Diagnostics{}
}
CheckRunner.run(init_state, checks,
async?: async?,
report_to: report_to,
slowdown: slowdown
)
end
def interpret_diagnostics(%State{} = state) do
LegacyVerification.Diagnostics.interpret(
state.diagnostics,
state.url
)
end
end

View File

@ -1,10 +1,10 @@
defmodule Plausible.Verification.Diagnostics do defmodule Plausible.InstallationSupport.LegacyVerification.Diagnostics do
@moduledoc """ @moduledoc """
Module responsible for translating diagnostics to user-friendly errors and recommendations. Module responsible for translating diagnostics to user-friendly errors and recommendations.
""" """
require Logger require Logger
@errors Plausible.Verification.Errors.all() @errors Plausible.InstallationSupport.LegacyVerification.Errors.all()
defstruct plausible_installed?: false, defstruct plausible_installed?: false,
snippets_found_in_head: 0, snippets_found_in_head: 0,

View File

@ -1,6 +1,6 @@
defmodule Plausible.Verification.Errors do defmodule Plausible.InstallationSupport.LegacyVerification.Errors do
@moduledoc """ @moduledoc """
A go-to definition of all verification errors A go-to definition of all legacy verification errors
""" """
@errors %{ @errors %{

View File

@ -1,17 +1,26 @@
defmodule Plausible.Verification.State do defmodule Plausible.InstallationSupport.State do
@moduledoc """ @moduledoc """
The struct and interface describing the state of the site verification process. The state to be shared across check during site installation support.
Assigns are meant to be used to communicate between checks, while diagnostics
are later on interpreted (translated into user-friendly messages and recommendations) Assigns are meant to be used to communicate between checks, while
via `Plausible.Verification.Diagnostics` module. `diagnostics` are specific to the check group being executed.
""" """
defstruct url: nil, defstruct url: nil,
data_domain: nil, data_domain: nil,
report_to: nil, report_to: nil,
assigns: %{}, assigns: %{},
diagnostics: %Plausible.Verification.Diagnostics{} diagnostics: %{}
@type t() :: %__MODULE__{} @type diagnostics_type :: Plausible.InstallationSupport.LegacyVerification.Diagnostics.t()
@type t :: %__MODULE__{
url: String.t() | nil,
data_domain: String.t() | nil,
report_to: pid() | nil,
assigns: map(),
diagnostics: diagnostics_type()
}
def assign(%__MODULE__{} = state, assigns) do def assign(%__MODULE__{} = state, assigns) do
%{state | assigns: Map.merge(state.assigns, Enum.into(assigns, %{}))} %{state | assigns: Map.merge(state.assigns, Enum.into(assigns, %{}))}

View File

@ -1,6 +1,6 @@
defmodule Plausible.Verification.URL do defmodule Plausible.InstallationSupport.URL do
@moduledoc """ @moduledoc """
Busting some caches by appending ?plausible_verification=12345 to it. URL utilities for installation support, including cache busting functionality.
""" """
def bust_url(url) do def bust_url(url) do

View File

@ -124,11 +124,13 @@ defmodule Plausible.PromEx.Plugins.PlausibleMetrics do
), ),
counter( counter(
metric_prefix ++ [:verification, :js_elixir_diff], metric_prefix ++ [:verification, :js_elixir_diff],
event_name: Plausible.Verification.Checks.Installation.telemetry_event(_diff = true) event_name:
Plausible.InstallationSupport.Checks.Installation.telemetry_event(_diff = true)
), ),
counter( counter(
metric_prefix ++ [:verification, :js_elixir_match], metric_prefix ++ [:verification, :js_elixir_match],
event_name: Plausible.Verification.Checks.Installation.telemetry_event(_diff = false) event_name:
Plausible.InstallationSupport.Checks.Installation.telemetry_event(_diff = false)
) )
] ]
) )

View File

@ -1,16 +0,0 @@
defmodule Plausible.Verification do
@moduledoc """
Module defining the user-agent used for site verification.
"""
use Plausible
on_ee do
def user_agent() do
"Plausible Verification Agent - if abused, contact support@plausible.io"
end
else
def user_agent() do
"Plausible Community Edition"
end
end
end

View File

@ -1,37 +0,0 @@
defmodule Plausible.Verification.Check do
@moduledoc """
Behaviour to be implemented by specific site verification checks.
`report_progress_as()` doesn't necessarily reflect the actual check description,
it serves as a user-facing message grouping mechanism, to prevent frequent message flashing when checks rotate often.
Each check operates on `state()` and is expected to return it, optionally modified, by all means.
`perform_safe/1` is used to guarantee no exceptions are thrown by faulty implementations, not to interrupt LiveView.
"""
@type state() :: Plausible.Verification.State.t()
@callback report_progress_as() :: String.t()
@callback perform(state()) :: state()
defmacro __using__(_) do
quote do
import Plausible.Verification.State
alias Plausible.Verification.Checks
alias Plausible.Verification.State
alias Plausible.Verification.Diagnostics
require Logger
@behaviour Plausible.Verification.Check
def perform_safe(state) do
perform(state)
catch
_, e ->
Logger.error(
"Error running check #{inspect(__MODULE__)} on #{state.url}: #{inspect(e)}"
)
put_diagnostics(state, service_error: e)
end
end
end
end

View File

@ -1,75 +0,0 @@
defmodule Plausible.Verification.Checks do
@moduledoc """
Checks that are performed during site verification.
Each module defined in `@checks` implements the `Plausible.Verification.Check` behaviour.
Checks are normally run asynchronously, except when synchronous execution is optionally required
for tests. Slowdowns can be optionally added, the user doesn't benefit from running the checks too quickly.
In async execution, each check notifies the caller by sending a message to it.
"""
alias Plausible.Verification.Checks
alias Plausible.Verification.State
require Logger
@checks [
Checks.FetchBody,
Checks.CSP,
Checks.ScanBody,
Checks.Snippet,
Checks.SnippetCacheBust,
Checks.Installation
]
def run(url, data_domain, opts \\ []) do
checks = Keyword.get(opts, :checks, @checks)
report_to = Keyword.get(opts, :report_to, self())
async? = Keyword.get(opts, :async?, true)
slowdown = Keyword.get(opts, :slowdown, 500)
if async? do
Task.start_link(fn -> do_run(url, data_domain, checks, report_to, slowdown) end)
else
do_run(url, data_domain, checks, report_to, slowdown)
end
end
def interpret_diagnostics(%State{} = state) do
Plausible.Verification.Diagnostics.interpret(state.diagnostics, state.url)
end
defp do_run(url, data_domain, checks, report_to, slowdown) do
init_state = %State{url: url, data_domain: data_domain, report_to: report_to}
state =
Enum.reduce(
checks,
init_state,
fn check, state ->
state
|> notify_start(check, slowdown)
|> check.perform_safe()
end
)
notify_verification_end(state, slowdown)
end
defp notify_start(state, check, slowdown) do
if is_pid(state.report_to) do
if is_integer(slowdown) and slowdown > 0, do: :timer.sleep(slowdown)
send(state.report_to, {:verification_check_start, {check, state}})
end
state
end
defp notify_verification_end(state, slowdown) do
if is_pid(state.report_to) do
if is_integer(slowdown) and slowdown > 0, do: :timer.sleep(slowdown)
send(state.report_to, {:verification_end, state})
end
state
end
end

View File

@ -7,6 +7,7 @@ defmodule PlausibleWeb.Live.Components.Verification do
use Plausible use Plausible
alias PlausibleWeb.Router.Helpers, as: Routes alias PlausibleWeb.Router.Helpers, as: Routes
alias Plausible.InstallationSupport.{State, LegacyVerification}
import PlausibleWeb.Components.Generic import PlausibleWeb.Components.Generic
@ -19,8 +20,8 @@ defmodule PlausibleWeb.Live.Components.Verification do
attr(:super_admin?, :boolean, default: false) attr(:super_admin?, :boolean, default: false)
attr(:finished?, :boolean, default: false) attr(:finished?, :boolean, default: false)
attr(:success?, :boolean, default: false) attr(:success?, :boolean, default: false)
attr(:verification_state, Plausible.Verification.State, default: nil) attr(:verification_state, State, default: nil)
attr(:interpretation, Plausible.Verification.Diagnostics.Result, default: nil) attr(:interpretation, LegacyVerification.Diagnostics.Result, default: nil)
attr(:attempts, :integer, default: 0) attr(:attempts, :integer, default: 0)
attr(:flow, :string, default: "") attr(:flow, :string, default: "")
attr(:installation_type, :string, default: nil) attr(:installation_type, :string, default: nil)

View File

@ -3,7 +3,7 @@ defmodule PlausibleWeb.Live.Installation do
User assistance module around Plausible installation instructions/onboarding User assistance module around Plausible installation instructions/onboarding
""" """
use PlausibleWeb, :live_view use PlausibleWeb, :live_view
alias Plausible.Verification.{Checks, State} alias Plausible.InstallationSupport.{State, Checks, LegacyVerification}
@script_extension_params %{ @script_extension_params %{
"outbound_links" => "outbound-links", "outbound_links" => "outbound-links",
@ -60,7 +60,7 @@ defmodule PlausibleWeb.Live.Installation do
end) end)
if connected?(socket) and is_nil(installation_type) do if connected?(socket) and is_nil(installation_type) do
Checks.run("https://#{domain}", domain, LegacyVerification.Checks.run("https://#{domain}", domain,
checks: [ checks: [
Checks.FetchBody, Checks.FetchBody,
Checks.ScanBody Checks.ScanBody
@ -86,7 +86,7 @@ defmodule PlausibleWeb.Live.Installation do
end end
end end
def handle_info({:verification_end, %State{} = state}, socket) do def handle_info({:all_checks_done, %State{} = state}, socket) do
installation_type = installation_type =
case state.diagnostics do case state.diagnostics do
%{wordpress_likely?: true} -> "wordpress" %{wordpress_likely?: true} -> "wordpress"

View File

@ -3,8 +3,8 @@ defmodule PlausibleWeb.Live.InstallationV2 do
User assistance module around Plausible installation instructions/onboarding User assistance module around Plausible installation instructions/onboarding
""" """
alias PlausibleWeb.Flows alias PlausibleWeb.Flows
alias Plausible.InstallationSupport.{State, Checks, LegacyVerification}
use PlausibleWeb, :live_view use PlausibleWeb, :live_view
alias Plausible.Verification.{Checks, State}
def mount( def mount(
%{"domain" => domain} = params, %{"domain" => domain} = params,
@ -34,7 +34,7 @@ defmodule PlausibleWeb.Live.InstallationV2 do
connected?(socket) and flow == Flows.provisioning() and !params["type"] connected?(socket) and flow == Flows.provisioning() and !params["type"]
if detect_installation_type? do if detect_installation_type? do
Checks.run("https://#{site.domain}", site.domain, LegacyVerification.Checks.run("https://#{site.domain}", site.domain,
checks: [ checks: [
Checks.FetchBody, Checks.FetchBody,
Checks.ScanBody Checks.ScanBody
@ -62,7 +62,7 @@ defmodule PlausibleWeb.Live.InstallationV2 do
)} )}
end end
def handle_info({:verification_end, %State{} = state}, socket) do def handle_info({:all_checks_done, %State{} = state}, socket) do
installation_type = installation_type =
case state.diagnostics do case state.diagnostics do
%{wordpress_likely?: true} -> "wordpress" %{wordpress_likely?: true} -> "wordpress"
@ -77,7 +77,7 @@ defmodule PlausibleWeb.Live.InstallationV2 do
)} )}
end end
def handle_info({:verification_check_start, _}, socket) do def handle_info({:check_start, _}, socket) do
{:noreply, socket} {:noreply, socket}
end end

View File

@ -7,7 +7,7 @@ defmodule PlausibleWeb.Live.Verification do
use Plausible use Plausible
use PlausibleWeb, :live_view use PlausibleWeb, :live_view
alias Plausible.Verification.{Checks, State} alias Plausible.InstallationSupport.{State, LegacyVerification}
@component PlausibleWeb.Live.Components.Verification @component PlausibleWeb.Live.Components.Verification
@slowdown_for_frequent_checking :timer.seconds(5) @slowdown_for_frequent_checking :timer.seconds(5)
@ -132,7 +132,7 @@ defmodule PlausibleWeb.Live.Verification do
end end
{:ok, pid} = {:ok, pid} =
Checks.run( LegacyVerification.Checks.run(
"https://#{socket.assigns.domain}", "https://#{socket.assigns.domain}",
socket.assigns.domain, socket.assigns.domain,
report_to: report_to, report_to: report_to,
@ -143,7 +143,7 @@ defmodule PlausibleWeb.Live.Verification do
end end
end end
def handle_info({:verification_check_start, {check, _state}}, socket) do def handle_info({:check_start, {check, _state}}, socket) do
update_component(socket, update_component(socket,
message: check.report_progress_as() message: check.report_progress_as()
) )
@ -151,8 +151,8 @@ defmodule PlausibleWeb.Live.Verification do
{:noreply, socket} {:noreply, socket}
end end
def handle_info({:verification_end, %State{} = state}, socket) do def handle_info({:all_checks_done, %State{} = state}, socket) do
interpretation = Checks.interpret_diagnostics(state) interpretation = LegacyVerification.Checks.interpret_diagnostics(state)
if not socket.assigns.has_pageviews? do if not socket.assigns.has_pageviews? do
schedule_pageviews_check(socket) schedule_pageviews_check(socket)

View File

@ -80,7 +80,7 @@ defmodule Plausible.Ingestion.EventTest do
assert_receive :telemetry_handled assert_receive :telemetry_handled
end end
test "drops verification agent" do test "drops installation support user agent" do
site = new_site() site = new_site()
payload = %{ payload = %{
@ -90,7 +90,7 @@ defmodule Plausible.Ingestion.EventTest do
conn = conn =
build_conn(:post, "/api/events", payload) build_conn(:post, "/api/events", payload)
|> Plug.Conn.put_req_header("user-agent", Plausible.Verification.user_agent()) |> Plug.Conn.put_req_header("user-agent", Plausible.InstallationSupport.user_agent())
assert {:ok, request} = Request.build(conn) assert {:ok, request} = Request.build(conn)

View File

@ -1,17 +1,18 @@
defmodule Plausible.Verification.Checks.CSPTest do defmodule Plausible.InstallationSupport.Checks.CSPTest do
use Plausible.DataCase, async: true use Plausible.DataCase, async: true
alias Plausible.Verification.State alias Plausible.InstallationSupport.{State, LegacyVerification}
@check Plausible.Verification.Checks.CSP @check Plausible.InstallationSupport.Checks.CSP
@default_state %State{diagnostics: %LegacyVerification.Diagnostics{}}
test "skips no headers" do test "skips no headers" do
state = %State{} state = @default_state
assert ^state = @check.perform(state) assert ^state = @check.perform(state)
end end
test "skips no headers 2" do test "skips no headers 2" do
state = %State{} |> State.assign(headers: %{}) state = @default_state |> State.assign(headers: %{})
assert ^state = @check.perform(state) assert ^state = @check.perform(state)
end end
@ -19,7 +20,7 @@ defmodule Plausible.Verification.Checks.CSPTest do
headers = %{"content-security-policy" => ["default-src 'self' foo.local; example.com"]} headers = %{"content-security-policy" => ["default-src 'self' foo.local; example.com"]}
state = state =
%State{} @default_state
|> State.assign(headers: headers) |> State.assign(headers: headers)
|> @check.perform() |> @check.perform()
@ -30,7 +31,7 @@ defmodule Plausible.Verification.Checks.CSPTest do
headers = %{"content-security-policy" => ["default-src 'self' example.com; localhost"]} headers = %{"content-security-policy" => ["default-src 'self' example.com; localhost"]}
state = state =
%State{} @default_state
|> State.assign(headers: headers) |> State.assign(headers: headers)
|> @check.perform() |> @check.perform()

View File

@ -1,9 +1,11 @@
defmodule Plausible.Verification.Checks.FetchBodyTest do defmodule Plausible.InstallationSupport.Checks.FetchBodyTest do
use Plausible.DataCase, async: true use Plausible.DataCase, async: true
import Plug.Conn import Plug.Conn
@check Plausible.Verification.Checks.FetchBody alias Plausible.InstallationSupport.{State, Checks, LegacyVerification}
@check Checks.FetchBody
@normal_body """ @normal_body """
<html> <html>
@ -16,8 +18,9 @@ defmodule Plausible.Verification.Checks.FetchBodyTest do
setup do setup do
{:ok, {:ok,
state: %Plausible.Verification.State{ state: %State{
url: "https://example.com" url: "https://example.com",
diagnostics: %LegacyVerification.Diagnostics{}
}} }}
end end

View File

@ -1,18 +1,18 @@
defmodule Plausible.Verification.Checks.ScanBodyTest do defmodule Plausible.InstallationSupport.Checks.ScanBodyTest do
use Plausible.DataCase, async: true use Plausible.DataCase, async: true
alias Plausible.Verification.State alias Plausible.InstallationSupport.{State, Checks, LegacyVerification}
@check Plausible.Verification.Checks.ScanBody @check Checks.ScanBody
@default_state %State{diagnostics: %LegacyVerification.Diagnostics{}}
test "skips on no raw body" do test "skips on no raw body" do
state = %State{} assert @default_state = @check.perform(@default_state)
assert ^state = @check.perform(state)
end end
test "detects nothing" do test "detects nothing" do
state = state =
%State{} @default_state
|> State.assign(raw_body: "...") |> State.assign(raw_body: "...")
|> @check.perform() |> @check.perform()
@ -22,7 +22,7 @@ defmodule Plausible.Verification.Checks.ScanBodyTest do
test "detects GTM" do test "detects GTM" do
state = state =
%State{} @default_state
|> State.assign(raw_body: "...googletagmanager.com/gtm.js...") |> State.assign(raw_body: "...googletagmanager.com/gtm.js...")
|> @check.perform() |> @check.perform()
@ -33,7 +33,7 @@ defmodule Plausible.Verification.Checks.ScanBodyTest do
test "detects GTM and cookie banner" do test "detects GTM and cookie banner" do
state = state =
%State{} @default_state
|> State.assign(raw_body: "...googletagmanager.com/gtm.js...cookiebot...") |> State.assign(raw_body: "...googletagmanager.com/gtm.js...cookiebot...")
|> @check.perform() |> @check.perform()
@ -45,7 +45,7 @@ defmodule Plausible.Verification.Checks.ScanBodyTest do
for signature <- ["wp-content", "wp-includes", "wp-json"] do for signature <- ["wp-content", "wp-includes", "wp-json"] do
test "detects WordPress: #{signature}" do test "detects WordPress: #{signature}" do
state = state =
%State{} @default_state
|> State.assign(raw_body: "...#{unquote(signature)}...") |> State.assign(raw_body: "...#{unquote(signature)}...")
|> @check.perform() |> @check.perform()
@ -57,7 +57,7 @@ defmodule Plausible.Verification.Checks.ScanBodyTest do
test "detects GTM and WordPress" do test "detects GTM and WordPress" do
state = state =
%State{} @default_state
|> State.assign(raw_body: "...googletagmanager.com/gtm.js....wp-content...") |> State.assign(raw_body: "...googletagmanager.com/gtm.js....wp-content...")
|> @check.perform() |> @check.perform()
@ -72,7 +72,7 @@ defmodule Plausible.Verification.Checks.ScanBodyTest do
test "detects official plugin" do test "detects official plugin" do
state = state =
%State{} @default_state
|> State.assign(raw_body: @d, document: Floki.parse_document!(@d)) |> State.assign(raw_body: @d, document: Floki.parse_document!(@d))
|> @check.perform() |> @check.perform()

View File

@ -1,12 +1,13 @@
defmodule Plausible.Verification.Checks.SnippetTest do defmodule Plausible.InstallationSupport.Checks.SnippetTest do
use Plausible.DataCase, async: true use Plausible.DataCase, async: true
alias Plausible.Verification.State alias Plausible.InstallationSupport.{State, Checks, LegacyVerification}
@check Plausible.Verification.Checks.Snippet @check Checks.Snippet
@default_state %State{diagnostics: %LegacyVerification.Diagnostics{}}
test "skips when there's no document" do test "skips when there's no document" do
state = %State{} state = @default_state
assert ^state = @check.perform(state) assert ^state = @check.perform(state)
end end
@ -156,7 +157,7 @@ defmodule Plausible.Verification.Checks.SnippetTest do
[data_domain: "example.com"] [data_domain: "example.com"]
|> Keyword.merge(opts) |> Keyword.merge(opts)
State @default_state
|> struct!(opts) |> struct!(opts)
|> State.assign(document: doc) |> State.assign(document: doc)
end end

View File

@ -1,14 +1,12 @@
defmodule Plausible.Verification.ChecksTest do defmodule Plausible.InstallationSupport.LegacyVerification.ChecksTest do
use Plausible.DataCase, async: true use Plausible.DataCase, async: true
alias Plausible.Verification.Checks alias Plausible.InstallationSupport.{State, Checks, LegacyVerification}
alias Plausible.Verification.Diagnostics
alias Plausible.Verification.State
import ExUnit.CaptureLog import ExUnit.CaptureLog
import Plug.Conn import Plug.Conn
@errors Plausible.Verification.Errors.all() @errors LegacyVerification.Errors.all()
describe "successful verification" do describe "successful verification" do
@normal_body """ @normal_body """
@ -25,7 +23,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation() stub_installation()
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_ok() |> assert_ok()
end end
@ -33,7 +31,7 @@ defmodule Plausible.Verification.ChecksTest do
ref = :counters.new(1, [:atomics]) ref = :counters.new(1, [:atomics])
test = self() test = self()
Req.Test.stub(Plausible.Verification.Checks.FetchBody, fn conn -> Req.Test.stub(Checks.FetchBody, fn conn ->
if :counters.get(ref, 1) < 2 do if :counters.get(ref, 1) < 2 do
:counters.add(ref, 1, 1) :counters.add(ref, 1, 1)
send(test, :redirect_sent) send(test, :redirect_sent)
@ -51,7 +49,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation() stub_installation()
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_ok() |> assert_ok()
assert_receive :redirect_sent assert_receive :redirect_sent
@ -76,7 +74,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation() stub_installation()
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_ok() |> assert_ok()
end end
@ -94,7 +92,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation() stub_installation()
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_ok() |> assert_ok()
end end
@ -107,7 +105,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(true, 202)) stub_installation(200, plausible_installed(true, 202))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_ok() |> assert_ok()
end end
@ -124,7 +122,7 @@ defmodule Plausible.Verification.ChecksTest do
ref = :counters.new(1, [:atomics]) ref = :counters.new(1, [:atomics])
test = self() test = self()
Req.Test.stub(Plausible.Verification.Checks.FetchBody, fn conn -> Req.Test.stub(Checks.FetchBody, fn conn ->
if :counters.get(ref, 1) == 0 do if :counters.get(ref, 1) == 0 do
:counters.add(ref, 1, 1) :counters.add(ref, 1, 1)
send(test, :redirect_sent) send(test, :redirect_sent)
@ -142,7 +140,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation() stub_installation()
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_ok() |> assert_ok()
assert_receive :redirect_sent assert_receive :redirect_sent
@ -155,7 +153,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(400, %{}) stub_installation(400, %{})
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.temporary) |> assert_error(@errors.temporary)
end end
@ -167,7 +165,7 @@ defmodule Plausible.Verification.ChecksTest do
{_, log} = {_, log} =
with_log(fn -> with_log(fn ->
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_ok() |> assert_ok()
end) end)
@ -190,7 +188,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation() stub_installation()
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.unreachable, url: "https://example.com") |> assert_error(@errors.unreachable, url: "https://example.com")
assert_receive :redirect_sent assert_receive :redirect_sent
@ -217,7 +215,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation() stub_installation()
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.snippet_in_body) |> assert_error(@errors.snippet_in_body)
end end
@ -242,7 +240,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation() stub_installation()
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.multiple_snippets) |> assert_error(@errors.multiple_snippets)
end end
@ -262,7 +260,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false)) stub_installation(200, plausible_installed(false))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.no_snippet) |> assert_error(@errors.no_snippet)
end end
@ -283,7 +281,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation() stub_installation()
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_ok() |> assert_ok()
end end
@ -315,7 +313,7 @@ defmodule Plausible.Verification.ChecksTest do
end) end)
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.cache_general) |> assert_error(@errors.cache_general)
end end
@ -357,7 +355,7 @@ defmodule Plausible.Verification.ChecksTest do
end) end)
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.cache_wp_no_plugin) |> assert_error(@errors.cache_wp_no_plugin)
end end
@ -400,7 +398,7 @@ defmodule Plausible.Verification.ChecksTest do
end) end)
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.cache_wp_plugin) |> assert_error(@errors.cache_wp_plugin)
end end
@ -409,7 +407,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false)) stub_installation(200, plausible_installed(false))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.no_snippet) |> assert_error(@errors.no_snippet)
end end
@ -429,13 +427,13 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false)) stub_installation(200, plausible_installed(false))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.no_snippet_wp) |> assert_error(@errors.no_snippet_wp)
end end
test "a check that raises" do test "a check that raises" do
defmodule FaultyCheckRaise do defmodule FaultyCheckRaise do
use Plausible.Verification.Check use Plausible.InstallationSupport.Check
@impl true @impl true
def report_progress_as, do: "Faulty check" def report_progress_as, do: "Faulty check"
@ -450,16 +448,16 @@ defmodule Plausible.Verification.ChecksTest do
end) end)
assert log =~ assert log =~
~s|Error running check Plausible.Verification.ChecksTest.FaultyCheckRaise on https://example.com: %RuntimeError{message: "boom"}| ~s|Error running check Plausible.InstallationSupport.LegacyVerification.ChecksTest.FaultyCheckRaise on https://example.com: %RuntimeError{message: "boom"}|
result result
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.unreachable, url: "https://example.com") |> assert_error(@errors.unreachable, url: "https://example.com")
end end
test "a check that throws" do test "a check that throws" do
defmodule FaultyCheckThrow do defmodule FaultyCheckThrow do
use Plausible.Verification.Check use Plausible.InstallationSupport.Check
@impl true @impl true
def report_progress_as, do: "Faulty check" def report_progress_as, do: "Faulty check"
@ -474,10 +472,10 @@ defmodule Plausible.Verification.ChecksTest do
end) end)
assert log =~ assert log =~
~s|Error running check Plausible.Verification.ChecksTest.FaultyCheckThrow on https://example.com: :boom| ~s|Error running check Plausible.InstallationSupport.LegacyVerification.ChecksTest.FaultyCheckThrow on https://example.com: :boom|
result result
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.unreachable, url: "https://example.com") |> assert_error(@errors.unreachable, url: "https://example.com")
end end
@ -492,7 +490,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false)) stub_installation(200, plausible_installed(false))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.csp) |> assert_error(@errors.csp)
end end
@ -507,7 +505,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false)) stub_installation(200, plausible_installed(false))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.no_snippet) |> assert_error(@errors.no_snippet)
end end
@ -517,13 +515,15 @@ defmodule Plausible.Verification.ChecksTest do
final_state = run_checks(report_to: self()) final_state = run_checks(report_to: self())
assert_receive {:verification_check_start, {Checks.FetchBody, %State{}}} assert_receive {:check_start, {Checks.FetchBody, %State{}}}
assert_receive {:verification_check_start, {Checks.CSP, %State{}}} assert_receive {:check_start, {Checks.CSP, %State{}}}
assert_receive {:verification_check_start, {Checks.ScanBody, %State{}}} assert_receive {:check_start, {Checks.ScanBody, %State{}}}
assert_receive {:verification_check_start, {Checks.Snippet, %State{}}} assert_receive {:check_start, {Checks.Snippet, %State{}}}
assert_receive {:verification_check_start, {Checks.SnippetCacheBust, %State{}}}
assert_receive {:verification_check_start, {Checks.Installation, %State{}}} assert_receive {:check_start, {Checks.SnippetCacheBust, %State{}}}
assert_receive {:verification_end, %State{} = ^final_state}
assert_receive {:check_start, {Checks.Installation, %State{}}}
assert_receive {:all_checks_done, %State{} = ^final_state}
refute_receive _ refute_receive _
end end
@ -555,7 +555,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false)) stub_installation(200, plausible_installed(false))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.csp) |> assert_error(@errors.csp)
end end
@ -564,7 +564,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false)) stub_installation(200, plausible_installed(false))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.gtm) |> assert_error(@errors.gtm)
end end
@ -591,7 +591,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false)) stub_installation(200, plausible_installed(false))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.gtm_cookie_banner) |> assert_error(@errors.gtm_cookie_banner)
end end
@ -605,7 +605,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false)) stub_installation(200, plausible_installed(false))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.unreachable, url: "https://example.com") |> assert_error(@errors.unreachable, url: "https://example.com")
end end
@ -614,7 +614,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(true, 0)) stub_installation(200, plausible_installed(true, 0))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.proxy_misconfigured) |> assert_error(@errors.proxy_misconfigured)
end end
@ -633,7 +633,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false, 0)) stub_installation(200, plausible_installed(false, 0))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.proxy_wp_no_plugin) |> assert_error(@errors.proxy_wp_no_plugin)
end end
@ -642,13 +642,13 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false, 0)) stub_installation(200, plausible_installed(false, 0))
result = run_checks() result = run_checks()
interpretation = Checks.interpret_diagnostics(result) interpretation = LegacyVerification.Checks.interpret_diagnostics(result)
refute interpretation.ok? refute interpretation.ok?
assert interpretation.errors == ["We encountered an error with your Plausible proxy"] assert interpretation.errors == ["We encountered an error with your Plausible proxy"]
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.proxy_general) |> assert_error(@errors.proxy_general)
end end
@ -657,7 +657,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(true, 0)) stub_installation(200, plausible_installed(true, 0))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.unknown) |> assert_error(@errors.unknown)
end end
@ -675,7 +675,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false, 0)) stub_installation(200, plausible_installed(false, 0))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.illegal_attrs_general) |> assert_error(@errors.illegal_attrs_general)
end end
@ -694,7 +694,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false, 0)) stub_installation(200, plausible_installed(false, 0))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.illegal_attrs_wp_no_plugin) |> assert_error(@errors.illegal_attrs_wp_no_plugin)
end end
@ -714,7 +714,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false, 0)) stub_installation(200, plausible_installed(false, 0))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.illegal_attrs_wp_plugin) |> assert_error(@errors.illegal_attrs_wp_plugin)
end end
@ -723,7 +723,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(true, -1)) stub_installation(200, plausible_installed(true, -1))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.generic) |> assert_error(@errors.generic)
end end
@ -732,7 +732,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(true, -1)) stub_installation(200, plausible_installed(true, -1))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.old_script_wp_no_plugin) |> assert_error(@errors.old_script_wp_no_plugin)
end end
@ -741,7 +741,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(true, -1)) stub_installation(200, plausible_installed(true, -1))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.old_script_wp_plugin) |> assert_error(@errors.old_script_wp_plugin)
end end
@ -750,7 +750,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(true, 500)) stub_installation(200, plausible_installed(true, 500))
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.unknown) |> assert_error(@errors.unknown)
end end
@ -759,7 +759,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation() stub_installation()
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.different_data_domain, domain: "example.com") |> assert_error(@errors.different_data_domain, domain: "example.com")
end end
@ -780,14 +780,14 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation() stub_installation()
run_checks() run_checks()
|> Checks.interpret_diagnostics() |> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.different_data_domain, domain: "example.com") |> assert_error(@errors.different_data_domain, domain: "example.com")
end end
end end
describe "unhhandled cases from sentry" do describe "unhhandled cases from sentry" do
test "APP-58: 4b1435e3f8a048eb949cc78fa578d1e4" do test "APP-58: 4b1435e3f8a048eb949cc78fa578d1e4" do
%Plausible.Verification.Diagnostics{ %LegacyVerification.Diagnostics{
plausible_installed?: true, plausible_installed?: true,
snippets_found_in_head: 0, snippets_found_in_head: 0,
snippets_found_in_body: 0, snippets_found_in_body: 0,
@ -810,7 +810,7 @@ defmodule Plausible.Verification.ChecksTest do
end end
test "service timeout" do test "service timeout" do
%Plausible.Verification.Diagnostics{ %LegacyVerification.Diagnostics{
plausible_installed?: false, plausible_installed?: false,
snippets_found_in_head: 1, snippets_found_in_head: 1,
snippets_found_in_body: 0, snippets_found_in_body: 0,
@ -833,7 +833,7 @@ defmodule Plausible.Verification.ChecksTest do
end end
test "malformed snippet code, that headless somewhat accepts" do test "malformed snippet code, that headless somewhat accepts" do
%Plausible.Verification.Diagnostics{ %LegacyVerification.Diagnostics{
plausible_installed?: true, plausible_installed?: true,
snippets_found_in_head: 0, snippets_found_in_head: 0,
snippets_found_in_body: 0, snippets_found_in_body: 0,
@ -856,7 +856,7 @@ defmodule Plausible.Verification.ChecksTest do
end end
test "gtm+wp detected, but likely script id attribute interfering" do test "gtm+wp detected, but likely script id attribute interfering" do
%Plausible.Verification.Diagnostics{ %LegacyVerification.Diagnostics{
plausible_installed?: false, plausible_installed?: false,
snippets_found_in_head: 1, snippets_found_in_head: 1,
snippets_found_in_body: 0, snippets_found_in_body: 0,
@ -881,12 +881,12 @@ defmodule Plausible.Verification.ChecksTest do
defp interpret_sentry_case(diagnostics) do defp interpret_sentry_case(diagnostics) do
diagnostics diagnostics
|> Diagnostics.interpret("example.com") |> LegacyVerification.Diagnostics.interpret("example.com")
|> refute_unhandled() |> refute_unhandled()
end end
defp run_checks(extra_opts \\ []) do defp run_checks(extra_opts \\ []) do
Checks.run( LegacyVerification.Checks.run(
"https://example.com", "https://example.com",
"example.com", "example.com",
Keyword.merge([async?: false, report_to: nil, slowdown: 0], extra_opts) Keyword.merge([async?: false, report_to: nil, slowdown: 0], extra_opts)
@ -894,11 +894,11 @@ defmodule Plausible.Verification.ChecksTest do
end end
defp stub_fetch_body(f) when is_function(f, 1) do defp stub_fetch_body(f) when is_function(f, 1) do
Req.Test.stub(Plausible.Verification.Checks.FetchBody, f) Req.Test.stub(Checks.FetchBody, f)
end end
defp stub_installation(f) when is_function(f, 1) do defp stub_installation(f) when is_function(f, 1) do
Req.Test.stub(Plausible.Verification.Checks.Installation, f) Req.Test.stub(Checks.Installation, f)
end end
defp stub_fetch_body(status, body) do defp stub_fetch_body(status, body) do

View File

@ -3,6 +3,8 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do
import Phoenix.LiveViewTest, only: [render_component: 2] import Phoenix.LiveViewTest, only: [render_component: 2]
import Plausible.Test.Support.HTML import Plausible.Test.Support.HTML
alias Plausible.InstallationSupport.{State, LegacyVerification}
@component PlausibleWeb.Live.Components.Verification @component PlausibleWeb.Live.Components.Verification
@progress ~s|#progress-indicator p#progress| @progress ~s|#progress-indicator p#progress|
@ -36,8 +38,9 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do
test "renders diagnostic interpretation" do test "renders diagnostic interpretation" do
interpretation = interpretation =
Plausible.Verification.Checks.interpret_diagnostics(%Plausible.Verification.State{ LegacyVerification.Checks.interpret_diagnostics(%State{
url: "example.com" url: "example.com",
diagnostics: %LegacyVerification.Diagnostics{}
}) })
html = html =
@ -58,12 +61,13 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do
end end
test "renders super-admin report" do test "renders super-admin report" do
state = %Plausible.Verification.State{ state = %State{
url: "example.com" url: "example.com",
diagnostics: %LegacyVerification.Diagnostics{}
} }
interpretation = interpretation =
Plausible.Verification.Checks.interpret_diagnostics(state) LegacyVerification.Checks.interpret_diagnostics(state)
html = html =
render_component(@component, render_component(@component,

View File

@ -355,7 +355,7 @@ defmodule PlausibleWeb.Live.InstallationTest do
end end
defp stub_fetch_body(f) when is_function(f, 1) do defp stub_fetch_body(f) when is_function(f, 1) do
Req.Test.stub(Plausible.Verification.Checks.FetchBody, f) Req.Test.stub(Plausible.InstallationSupport.Checks.FetchBody, f)
end end
defp stub_fetch_body(status, body) do defp stub_fetch_body(status, body) do

View File

@ -279,7 +279,7 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
end end
defp stub_fetch_body(f) when is_function(f, 1) do defp stub_fetch_body(f) when is_function(f, 1) do
Req.Test.stub(Plausible.Verification.Checks.FetchBody, f) Req.Test.stub(Plausible.InstallationSupport.Checks.FetchBody, f)
end end
defp stub_fetch_body(status, body) do defp stub_fetch_body(status, body) do

View File

@ -184,11 +184,11 @@ defmodule PlausibleWeb.Live.VerificationTest do
end end
defp stub_fetch_body(f) when is_function(f, 1) do defp stub_fetch_body(f) when is_function(f, 1) do
Req.Test.stub(Plausible.Verification.Checks.FetchBody, f) Req.Test.stub(Plausible.InstallationSupport.Checks.FetchBody, f)
end end
defp stub_installation(f) when is_function(f, 1) do defp stub_installation(f) when is_function(f, 1) do
Req.Test.stub(Plausible.Verification.Checks.Installation, f) Req.Test.stub(Plausible.InstallationSupport.Checks.Installation, f)
end end
defp stub_fetch_body(status, body) do defp stub_fetch_body(status, body) do

View File

@ -3,6 +3,16 @@
This document describes the design goals informing the architecture of the Plausible tracker script codebase as well as a map This document describes the design goals informing the architecture of the Plausible tracker script codebase as well as a map
to how the code is laid out. to how the code is laid out.
## Installation Support
The tracker subdirectory also includes site verification and pre-installation checks that are run in headless browser, via
browserless.io. These files live under the `/tracker/installation-support/` director and are meant to provide Plausible
installation support - checking the site for what technologies to recommend and verifying whether Plausible has been
installed correctly. Please see `lib/plausible/installation_support/checks/installation.ex` for the Elixir context and how
this JS code ends up being used.
While this logic could be separated from the tracker script, it's convenient for installation support to reuse the Playwright test structure and JS compilation logic without introducing yet another subdirectory with its own dependencies.
## Design goals ## Design goals
We want to provide users a javascript suite to capture page views and activities done on the web page. We want to provide users a javascript suite to capture page views and activities done on the web page.

View File

@ -31,10 +31,16 @@
}, },
"npm_package": "esm" "npm_package": "esm"
}, },
{
"name": "detector.js",
"entry_point": "installation_support/detector.js",
"output_path": "priv/tracker/installation_support/detector.js",
"globals": {}
},
{ {
"name": "verifier-v1.js", "name": "verifier-v1.js",
"entry_point": "verifier/verifier-v1.js", "entry_point": "installation_support/verifier-v1.js",
"output_path": "priv/tracker/verifier/verifier-v1.js", "output_path": "priv/tracker/installation_support/verifier-v1.js",
"globals": {} "globals": {}
} }
], ],

View File

@ -0,0 +1,35 @@
import { waitForSnippetsV1 } from "./snippet-checks"
import { checkWordPress } from "./check-wordpress"
import { checkGTM } from "./check-gtm"
window.scanPageBeforePlausibleInstallation = async function(detectV1, debug) {
function log(message) {
if (debug) console.log('[Plausible Verification]', message)
}
const {wordpressPlugin, wordpressLikely} = checkWordPress(document)
log(`wordpressPlugin: ${wordpressPlugin}`)
log(`wordpressLikely: ${wordpressLikely}`)
const gtmLikely = checkGTM(document)
log(`gtmLikely: ${gtmLikely}`)
// Cannot implement yet: we should detect the WP plugin version here and
// decide `v1Detected` based on that. For now we assume WP plugin is v1.
let v1Detected = wordpressPlugin
if (!v1Detected && detectV1) {
const snippetData = await waitForSnippetsV1(log)
v1Detected = snippetData.counts.all > 0
}
return {
data: {
completed: true,
v1Detected: v1Detected,
wordpressPlugin: wordpressPlugin,
wordpressLikely: wordpressLikely,
gtmLikely: gtmLikely,
}
}
}

View File

@ -15,7 +15,11 @@ export default defineConfig({
retries: process.env.CI ? 1 : 0, retries: process.env.CI ? 1 : 0,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'list', reporter: 'list',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ /*
Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions.
NOTE: We run the installation support tests on Chrome only because the Browserless /function API
runs a Chromium-based browser environment.
*/
projects: [ projects: [
{ {
name: 'chromium', name: 'chromium',
@ -25,13 +29,13 @@ export default defineConfig({
{ {
name: 'firefox', name: 'firefox',
use: { ...devices['Desktop Firefox'] }, use: { ...devices['Desktop Firefox'] },
testIgnore: 'test/verifier/**', testIgnore: 'test/installation_support/**',
}, },
{ {
name: 'webkit', name: 'webkit',
use: { ...devices['Desktop Safari'] }, use: { ...devices['Desktop Safari'] },
testIgnore: 'test/verifier/**', testIgnore: 'test/installation_support/**',
}, },
], ],
webServer: { webServer: {

View File

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test' import { test, expect } from '@playwright/test'
import { checkDataDomainMismatch } from '../../verifier/check-data-domain-mismatch' import { checkDataDomainMismatch } from '../../installation_support/check-data-domain-mismatch'
function mockSnippet(dataDomain) { function mockSnippet(dataDomain) {
return { getAttribute: _ => dataDomain } return { getAttribute: _ => dataDomain }

View File

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test' import { test, expect } from '@playwright/test'
import { checkGTM } from '../../verifier/check-gtm' import { checkGTM } from '../../installation_support/check-gtm'
function mockDocument(html) { function mockDocument(html) {
return { return {

View File

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test' import { test, expect } from '@playwright/test'
import { checkManualExtension } from '../../verifier/check-manual-extension' import { checkManualExtension } from '../../installation_support/check-manual-extension'
function mockSnippet(dataDomain) { function mockSnippet(dataDomain) {
return { getAttribute: _ => dataDomain } return { getAttribute: _ => dataDomain }

View File

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test' import { test, expect } from '@playwright/test'
import { checkProxyLikely } from '../../verifier/check-proxy-likely' import { checkProxyLikely } from '../../installation_support/check-proxy-likely'
function mockSnippet(src) { function mockSnippet(src) {
return { getAttribute: _ => src } return { getAttribute: _ => src }

View File

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test' import { test, expect } from '@playwright/test'
import { checkUnknownAttributes } from '../../verifier/check-unknown-attributes.js' import { checkUnknownAttributes } from '../../installation_support/check-unknown-attributes.js'
test.describe('checkUnknownAttributes', () => { test.describe('checkUnknownAttributes', () => {
test('returns false when no snippets', () => { test('returns false when no snippets', () => {

View File

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test' import { test, expect } from '@playwright/test'
import { checkWordPress, WORDPRESS_PLUGIN_VERSION_SELECTOR } from '../../verifier/check-wordpress' import { checkWordPress, WORDPRESS_PLUGIN_VERSION_SELECTOR } from '../../installation_support/check-wordpress'
function mockDocument(html, hasMetaTag) { function mockDocument(html, hasMetaTag) {
return { return {

View File

@ -0,0 +1,56 @@
import { test, expect } from '@playwright/test'
import { detect } from '../support/installation-support-playwright-wrappers'
import { initializePageDynamically } from '../support/initialize-page-dynamically'
test.describe('detector.js (basic diagnostics)', () => {
test('skips v1 snippet detection by default', async ({ page }, { testId }) => {
const { url } = await initializePageDynamically(page, {
testId,
response: `
<html>
<head>
<script src="" data-domain=""></script>
</head>
</html>
`
})
const result = await detect(page, {url: url, detectV1: false})
expect(result.data.v1Detected).toBe(false)
})
test('detects WP plugin, WP and GTM', async ({ page }, { testId }) => {
const { url } = await initializePageDynamically(page, {
testId,
response: `
<html>
<head>
<link rel="icon" href="https://example.com/wp-content/uploads/favicon.ico" sizes="32x32">
<meta name="plausible-analytics-version" content="2.3.1">
<script async src="https://www.googletagmanager.com/gtm.js?id=GTM-123"></script>
</head>
</html>
`
})
const result = await detect(page, {url: url, detectV1: false})
expect(result.data.wordpressPlugin).toBe(true)
expect(result.data.wordpressLikely).toBe(true)
expect(result.data.gtmLikely).toBe(true)
})
test('No WP plugin, WP or GTM', async ({ page }, { testId }) => {
const { url } = await initializePageDynamically(page, {
testId,
response: '<html><head></head></html>'
})
const result = await detect(page, {url: url, detectV1: false})
expect(result.data.wordpressPlugin).toBe(false)
expect(result.data.wordpressLikely).toBe(false)
expect(result.data.gtmLikely).toBe(false)
})
})

View File

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test' import { test, expect } from '@playwright/test'
import verify from '../support/verify-playwright-wrapper' import { verify } from '../support/installation-support-playwright-wrappers'
import { delay } from '../support/test-utils' import { delay } from '../support/test-utils'
import { initializePageDynamically } from '../support/initialize-page-dynamically' import { initializePageDynamically } from '../support/initialize-page-dynamically'
import { compileFile } from '../../compiler' import { compileFile } from '../../compiler'

View File

@ -0,0 +1,33 @@
import { compileFile } from '../../compiler/index.js'
import variantsFile from '../../compiler/variants.json' with { type: 'json' }
const VERIFIER_V1_JS_VARIANT = variantsFile.manualVariants.find(variant => variant.name === 'verifier-v1.js')
const DETECTOR_JS_VARIANT = variantsFile.manualVariants.find(variant => variant.name === 'detector.js')
export async function verify(page, context) {
const {url, expectedDataDomain} = context
const debug = context.debug ? true : false
const verifierCode = await compileFile(VERIFIER_V1_JS_VARIANT, { returnCode: true })
await page.goto(url)
await page.evaluate(verifierCode)
return await page.evaluate(async ({expectedDataDomain, debug}) => {
return await window.verifyPlausibleInstallation(expectedDataDomain, debug)
}, {expectedDataDomain, debug})
}
export async function detect(page, context) {
const {url, detectV1} = context
const debug = context.debug ? true : false
const detectorCode = await compileFile(DETECTOR_JS_VARIANT, { returnCode: true })
await page.goto(url)
await page.evaluate(detectorCode)
return await page.evaluate(async ({detectV1, debug}) => {
return await window.scanPageBeforePlausibleInstallation(detectV1, debug)
}, {detectV1, debug})
}

View File

@ -1,18 +0,0 @@
import { compileFile } from '../../compiler/index.js'
import variantsFile from '../../compiler/variants.json' with { type: 'json' }
const VARIANT = variantsFile.manualVariants.find(variant => variant.name === 'verifier-v1.js')
export default async function verify(page, context) {
const {url, expectedDataDomain} = context
const debug = context.debug ? true : false
const verifierCode = await compileFile(VARIANT, { returnCode: true })
await page.goto(url)
await page.evaluate(verifierCode)
return await page.evaluate(async ({expectedDataDomain, debug}) => {
return await window.verifyPlausibleInstallation(expectedDataDomain, debug)
}, {expectedDataDomain, debug})
}