Script v2: Make detection take less time (#5635)

* Add fast failing dns check to verification

* Convert Detection to a checks pipeline

* Convert detection to checks pipeline

* Unify browserless checks, set retry policy, timeouts

* Fix spelling

* Update change domain v2

* Fix issue with handling errors with detection

* Include timeoutMs in detector function args

* Allow saving npm installation type (#5639)

* small code style/comment improvements

---------

Co-authored-by: RobertJoonas <56999674+RobertJoonas@users.noreply.github.com>
Co-authored-by: Robert Joonas <robertjoonas16@gmail.com>
This commit is contained in:
Artur Pata 2025-08-19 13:16:27 +03:00 committed by GitHub
parent bb1db557a3
commit 276f95cda2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 889 additions and 213 deletions

View File

@ -937,7 +937,7 @@ config :plausible, Plausible.PromEx,
grafana: :disabled,
metrics_server: :disabled
config :plausible, Plausible.InstallationSupport,
config :plausible, Plausible.InstallationSupport.BrowserlessConfig,
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")

View File

@ -26,6 +26,9 @@ config :plausible,
session_timeout: 0,
http_impl: Plausible.HTTPClient.Mock
config :plausible,
dns_lookup_impl: Plausible.DnsLookup.Mock
config :plausible, Plausible.Cache, enabled: false
config :ex_money, api_module: Plausible.ExchangeRateMock
@ -49,7 +52,7 @@ config :plausible, Plausible.HelpScout,
plug: {Req.Test, Plausible.HelpScout}
]
config :plausible, Plausible.InstallationSupport.Detection,
config :plausible, Plausible.InstallationSupport.Checks.Detection,
req_opts: [
plug: {Req.Test, :global}
]

View File

@ -0,0 +1,31 @@
defmodule Plausible.DnsLookupInterface do
@moduledoc """
Behaviour module for DNS lookup operations.
"""
@callback lookup(
name :: charlist(),
class :: atom(),
type :: atom(),
opts :: list(),
timeout :: integer()
) ::
list() | []
end
defmodule Plausible.DnsLookup do
@moduledoc """
Thin wrapper around `:inet_res.lookup/5`.
To use, call `Plausible.DnsLookup.impl().lookup/5`,
this allows for mocking DNS lookups in tests.
"""
@behaviour Plausible.DnsLookupInterface
@impl Plausible.DnsLookupInterface
def lookup(name, class, type, opts, timeout),
do: :inet_res.lookup(name, class, type, opts, timeout)
@spec impl() :: Plausible.DnsLookup
def impl(), do: Application.get_env(:plausible, :dns_lookup_impl, __MODULE__)
end

View File

@ -0,0 +1,30 @@
defmodule Plausible.InstallationSupport.BrowserlessConfig do
@moduledoc """
Req options for browserless.io requests
"""
use Plausible
def retry_browserless_request(_request, %{status: status}) do
case status do
# rate limit
429 -> {:delay, 1000}
# timeout
408 -> {:delay, 500}
# other errors
_ -> false
end
end
on_ee do
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
end
end

View File

@ -27,13 +27,18 @@ defmodule Plausible.InstallationSupport.CheckRunner do
defp do_run(state, checks, slowdown) do
state =
Enum.reduce(
Enum.reduce_while(
checks,
state,
fn check, state ->
state
|> notify_check_start(check, slowdown)
|> check.perform_safe()
if state.skip_further_checks? do
{:halt, state}
else
{:cont,
state
|> notify_check_start(check, slowdown)
|> check.perform_safe()}
end
end
)

View File

@ -0,0 +1,146 @@
defmodule Plausible.InstallationSupport.Checks.Detection do
@moduledoc """
Calls the browserless.io service (local instance can be spawned with `make browserless`)
and runs detector script via the [function API](https://docs.browserless.io/HTTP-APIs/function).
* v1_detected (optional - detection can take up to @plausible_window_check_timeout_ms)
* gtm_likely
* wordpress_likely
* wordpress_plugin
These diagnostics are used to determine what installation type to recommend,
and whether to provide a notice for upgrading an existing v1 integration to v2.
"""
require Logger
use Plausible.InstallationSupport.Check
alias Plausible.InstallationSupport.BrowserlessConfig
@detector_code_path "priv/tracker/installation_support/detector.js"
@external_resource @detector_code_path
# On CI, the file might not be present for static checks so we default to empty string
@detector_code (case File.read(Application.app_dir(:plausible, @detector_code_path)) do
{:ok, content} -> content
{:error, _} -> ""
end)
# Puppeteer wrapper function that executes the vanilla JS detector code
@puppeteer_wrapper_code """
export default async function({ page, context: { url, userAgent, ...functionContext } }) {
try {
await page.setUserAgent(userAgent);
await page.goto(url);
await page.evaluate(() => {
#{@detector_code} // injects window.scanPageBeforePlausibleInstallation
});
return await page.evaluate(
(c) => window.scanPageBeforePlausibleInstallation(c),
{ ...functionContext }
);
} catch (error) {
return {
data: {
completed: false,
error: {
message: error?.message ?? JSON.stringify(error)
}
}
}
}
}
"""
# We define a timeout for the browserless endpoint call to avoid waiting too long for a response
@endpoint_timeout_ms 2_000
# This timeout determines how long we wait for window.plausible to be initialized on the page, used for detecting whether v1 installed
@plausible_window_check_timeout_ms 1_500
# To support browserless API being unavailable or overloaded, we retry the endpoint call if it doesn't return a successful response
@max_retries 1
@impl true
def report_progress_as, do: "We're checking your site to recommend the best installation method"
@impl true
def perform(%State{url: url, assigns: %{detect_v1?: detect_v1?}} = state) do
opts =
[
headers: %{content_type: "application/json"},
body:
Jason.encode!(%{
code: @puppeteer_wrapper_code,
context: %{
url: url,
userAgent: Plausible.InstallationSupport.user_agent(),
detectV1: detect_v1?,
timeoutMs: @plausible_window_check_timeout_ms,
debug: Application.get_env(:plausible, :environment) == "dev"
}
}),
params: %{timeout: @endpoint_timeout_ms},
retry: &BrowserlessConfig.retry_browserless_request/2,
retry_log_level: :warning,
max_retries: @max_retries
]
|> Keyword.merge(Application.get_env(:plausible, __MODULE__)[:req_opts] || [])
extra_opts = Application.get_env(:plausible, __MODULE__)[:req_opts] || []
opts = Keyword.merge(opts, extra_opts)
case Req.post(BrowserlessConfig.browserless_function_api_endpoint(), opts) do
{:ok, %{body: body, status: status}} ->
handle_browserless_response(state, body, status)
{:error, %{reason: reason}} ->
Logger.warning(warning_message("Browserless request error: #{inspect(reason)}", state))
put_diagnostics(state, service_error: reason)
end
end
defp handle_browserless_response(
state,
%{"data" => %{"completed" => completed} = data},
_status
) do
if completed do
put_diagnostics(
state,
parse_to_diagnostics(data)
)
else
Logger.warning(
warning_message(
"Browserless function returned with completed: false, error.message: #{inspect(data["error"]["message"])}",
state
)
)
put_diagnostics(state, service_error: data["error"]["message"])
end
end
defp handle_browserless_response(state, _body, status) do
error = "Unhandled browserless response with status: #{status}"
Logger.warning(warning_message(error, state))
put_diagnostics(state, service_error: error)
end
defp warning_message(message, state) do
"[DETECTION] #{message} (data_domain='#{state.data_domain}')"
end
defp parse_to_diagnostics(data),
do: [
v1_detected: data["v1Detected"],
gtm_likely: data["gtmLikely"],
wordpress_likely: data["wordpressLikely"],
wordpress_plugin: data["wordpressPlugin"],
service_error: nil
]
end

View File

@ -1,5 +1,6 @@
defmodule Plausible.InstallationSupport.Checks.Installation do
require Logger
alias Plausible.InstallationSupport.BrowserlessConfig
@verifier_code_path "priv/tracker/installation_support/verifier-v1.js"
@external_resource @verifier_code_path
@ -134,7 +135,7 @@ defmodule Plausible.InstallationSupport.Checks.Installation do
extra_opts = Application.get_env(:plausible, __MODULE__)[:req_opts] || []
opts = Keyword.merge(opts, extra_opts)
case Req.post(Plausible.InstallationSupport.browserless_function_api_endpoint(), opts) do
case Req.post(BrowserlessConfig.browserless_function_api_endpoint(), opts) do
{:ok, %{status: 200, body: %{"data" => %{"completed" => true} = js_data}}} ->
emit_telemetry_and_log(state.diagnostics, js_data, data_domain)

View File

@ -1,5 +1,12 @@
defmodule Plausible.InstallationSupport.Checks.InstallationV2 do
@moduledoc """
Calls the browserless.io service (local instance can be spawned with `make browserless`)
and runs verifier script via the [function API](https://docs.browserless.io/HTTP-APIs/function).
"""
require Logger
use Plausible.InstallationSupport.Check
alias Plausible.InstallationSupport.BrowserlessConfig
@verifier_code_path "priv/tracker/installation_support/verifier-v2.js"
@external_resource @verifier_code_path
@ -10,19 +17,6 @@ defmodule Plausible.InstallationSupport.Checks.InstallationV2 do
{:error, _} -> ""
end)
# To support browserless API being unavailable or overloaded, we retry the endpoint call if it doesn't return a successful response
@max_retries 2
# We define a timeout for the browserless endpoint call to avoid waiting too long for a response
@endpoint_timeout_ms 10_000
# This timeout determines how long we wait for window.plausible to be initialized on the page, including sending the test event
@plausible_window_check_timeout_ms 4_000
# To handle navigation that happens immediately on the page, we attempt to verify the installation multiple times _within a single browserless endpoint call_
@max_attempts 2
@timeout_between_attempts_ms 500
# Puppeteer wrapper function that executes the vanilla JS verifier code
@puppeteer_wrapper_code """
export default async function({ page, context: { url, userAgent, maxAttempts, timeoutBetweenAttemptsMs, ...functionContext } }) {
@ -75,11 +69,18 @@ defmodule Plausible.InstallationSupport.Checks.InstallationV2 do
}
"""
@moduledoc """
Calls the browserless.io service (local instance can be spawned with `make browserless`)
and runs verifier script via the [function API](https://docs.browserless.io/HTTP-APIs/function).
"""
use Plausible.InstallationSupport.Check
# To support browserless API being unavailable or overloaded, we retry the endpoint call if it doesn't return a successful response
@max_retries 1
# We define a timeout for the browserless endpoint call to avoid waiting too long for a response
@endpoint_timeout_ms 10_000
# This timeout determines how long we wait for window.plausible to be initialized on the page, including sending the test event
@plausible_window_check_timeout_ms 4_000
# To handle navigation that happens immediately on the page, we attempt to verify the installation multiple times _within a single browserless endpoint call_
@max_attempts 2
@timeout_between_attempts_ms 500
@impl true
def report_progress_as, do: "We're verifying that your visitors are being counted correctly"
@ -102,7 +103,7 @@ defmodule Plausible.InstallationSupport.Checks.InstallationV2 do
}
}),
params: %{timeout: @endpoint_timeout_ms},
retry: :transient,
retry: &BrowserlessConfig.retry_browserless_request/2,
retry_log_level: :warning,
max_retries: @max_retries
]
@ -110,7 +111,7 @@ defmodule Plausible.InstallationSupport.Checks.InstallationV2 do
extra_opts = Application.get_env(:plausible, __MODULE__)[:req_opts] || []
opts = Keyword.merge(opts, extra_opts)
case Req.post(Plausible.InstallationSupport.browserless_function_api_endpoint(), opts) do
case Req.post(BrowserlessConfig.browserless_function_api_endpoint(), opts) do
{:ok, %{body: body, status: status}} ->
handle_browserless_response(state, body, status)
@ -129,15 +130,7 @@ defmodule Plausible.InstallationSupport.Checks.InstallationV2 do
if completed do
put_diagnostics(
state,
disallowed_by_csp: data["disallowedByCsp"],
plausible_is_on_window: data["plausibleIsOnWindow"],
plausible_is_initialized: data["plausibleIsInitialized"],
plausible_version: data["plausibleVersion"],
plausible_variant: data["plausibleVariant"],
test_event: data["testEvent"],
cookie_banner_likely: data["cookieBannerLikely"],
attempts: data["attempts"],
service_error: nil
parse_to_diagnostics(data)
)
else
Logger.warning(
@ -161,4 +154,17 @@ defmodule Plausible.InstallationSupport.Checks.InstallationV2 do
defp warning_message(message, state) do
"[VERIFICATION v2] #{message} (data_domain='#{state.data_domain}')"
end
defp parse_to_diagnostics(data),
do: [
disallowed_by_csp: data["disallowedByCsp"],
plausible_is_on_window: data["plausibleIsOnWindow"],
plausible_is_initialized: data["plausibleIsInitialized"],
plausible_version: data["plausibleVersion"],
plausible_variant: data["plausibleVariant"],
test_event: data["testEvent"],
cookie_banner_likely: data["cookieBannerLikely"],
attempts: data["attempts"],
service_error: nil
]
end

View File

@ -0,0 +1,96 @@
defmodule Plausible.InstallationSupport.Checks.Url do
@moduledoc """
Checks if site domain has an A record.
If not, checks if prepending `www.` helps,
because we have specifically requested customers to register the domain with `www.` prefix.
If not, skips all further checks.
"""
use Plausible.InstallationSupport.Check
@impl true
def report_progress_as, do: "We're trying to reach your website"
@impl true
@spec perform(Plausible.InstallationSupport.State.t()) ::
Plausible.InstallationSupport.State.t()
def perform(%State{url: url} = state) when is_binary(url) do
with {:ok, %URI{scheme: scheme} = uri} when scheme in ["https"] <- URI.new(url),
:ok <- check_domain(uri.host) do
stripped_url = URI.to_string(%URI{uri | query: nil, fragment: nil})
%State{state | url: stripped_url}
else
{:error, :no_a_record} ->
put_diagnostics(%State{state | skip_further_checks?: true},
service_error: :domain_not_found
)
_ ->
put_diagnostics(%State{state | skip_further_checks?: true},
service_error: :invalid_url
)
end
end
def perform(%State{data_domain: domain} = state) when is_binary(domain) do
case find_working_url(domain) do
{:ok, working_url} ->
%State{state | url: working_url}
{:error, :domain_not_found} ->
put_diagnostics(%State{state | url: nil, skip_further_checks?: true},
service_error: :domain_not_found
)
end
end
# Check A records of the the domains [domain, "www.#{domain}"]
# at this point, domain can contain path
@spec find_working_url(String.t()) :: {:ok, String.t()} | {:error, :domain_not_found}
defp find_working_url(domain) do
[domain_without_path | rest] = split_domain(domain)
[
domain_without_path,
"www.#{domain_without_path}"
]
|> Enum.reduce_while({:error, :domain_not_found}, fn d, _acc ->
case check_domain(d) do
:ok -> {:halt, {:ok, "https://" <> unsplit_domain(d, rest)}}
{:error, :no_a_record} -> {:cont, {:error, :domain_not_found}}
end
end)
end
@spec check_domain(String.t()) :: :ok | {:error, :no_a_record}
defp check_domain(domain) do
lookup_timeout = 1_000
resolve_timeout = 1_000
case Plausible.DnsLookup.impl().lookup(
to_charlist(domain),
:in,
:a,
[timeout: resolve_timeout],
lookup_timeout
) do
[{a, b, c, d} | _]
when is_integer(a) and is_integer(b) and is_integer(c) and is_integer(d) ->
:ok
# this may mean timeout or no DNS record
[] ->
{:error, :no_a_record}
end
end
@spec split_domain(String.t()) :: [String.t()]
defp split_domain(domain) do
String.split(domain, "/", parts: 2)
end
@spec unsplit_domain(String.t(), [String.t()]) :: String.t()
defp unsplit_domain(domain_without_path, rest) do
Enum.join([domain_without_path] ++ rest, "/")
end
end

View File

@ -1,88 +0,0 @@
defmodule Plausible.InstallationSupport.Detection do
@moduledoc """
Exposes a perform function which visits the given URL via a Browserless
/function API call, and in a returns the following diagnostics:
* v1_detected (optional - detection can take up to 3s)
* gtm_likely
* wordpress_likely
* wordpress_plugin
These diagnostics are used to determine what installation type to recommend,
and whether to provide a notice for upgrading an existing v1 integration to v2.
"""
require Logger
alias Plausible.InstallationSupport
@detector_code_path "priv/tracker/installation_support/detector.js"
@external_resource @detector_code_path
# On CI, the file might not be present for static checks so we default to empty string
@detector_code (case File.read(Application.app_dir(:plausible, @detector_code_path)) do
{:ok, content} -> content
{:error, _} -> ""
end)
# Puppeteer wrapper function that executes the vanilla JS verifier code
@puppeteer_wrapper_code """
export default async function({ page, context }) {
try {
await page.setUserAgent(context.userAgent);
await page.goto(context.url);
await page.evaluate(() => {
#{@detector_code}
});
return await page.evaluate(async (detectV1, debug) => {
return await window.scanPageBeforePlausibleInstallation(detectV1, debug);
}, context.detectV1, context.debug);
} catch (error) {
const msg = error.message ? error.message : JSON.stringify(error)
return {data: {completed: false, error: msg}}
}
}
"""
def perform(url, opts \\ []) do
req_opts =
[
headers: %{content_type: "application/json"},
body:
Jason.encode!(%{
code: @puppeteer_wrapper_code,
context: %{
url: url,
userAgent: InstallationSupport.user_agent(),
detectV1: Keyword.get(opts, :detect_v1?, false),
debug: Application.get_env(:plausible, :environment) == "dev"
}
}),
retry: :transient,
retry_log_level: :warning,
max_retries: 2
]
|> Keyword.merge(Application.get_env(:plausible, __MODULE__)[:req_opts] || [])
case Req.post(InstallationSupport.browserless_function_api_endpoint(), req_opts) do
{:ok, %{status: 200, body: %{"data" => %{"completed" => true} = js_data}}} ->
{:ok,
%{
v1_detected: js_data["v1Detected"],
gtm_likely: js_data["gtmLikely"],
wordpress_likely: js_data["wordpressLikely"],
wordpress_plugin: js_data["wordpressPlugin"]
}}
{:ok, %{body: %{"data" => %{"error" => error}}}} ->
Logger.warning("[DETECTION] Browserless JS error (url='#{url}'): #{inspect(error)}")
{:error, {:browserless, error}}
{:error, %{reason: reason}} ->
Logger.warning("[DETECTION] Browserless request error (url='#{url}'): #{inspect(reason)}")
{:error, {:req, reason}}
end
end
end

View File

@ -0,0 +1,46 @@
defmodule Plausible.InstallationSupport.Detection.Checks do
@moduledoc """
Checks that are performed pre-installation, providing recommended installation
methods and whether v1 is used on the site.
In async execution, each check notifies the caller by sending a message to it.
"""
alias Plausible.InstallationSupport.Detection
alias Plausible.InstallationSupport.{State, CheckRunner, Checks}
require Logger
@checks [
Checks.Url,
Checks.Detection
]
def run(url, data_domain, opts \\ []) do
report_to = Keyword.get(opts, :report_to, self())
async? = Keyword.get(opts, :async?, true)
slowdown = Keyword.get(opts, :slowdown, 500)
detect_v1? = Keyword.get(opts, :detect_v1?, false)
init_state =
%State{
url: url,
data_domain: data_domain,
report_to: report_to,
diagnostics: %Detection.Diagnostics{},
assigns: %{detect_v1?: detect_v1?}
}
CheckRunner.run(init_state, @checks,
async?: async?,
report_to: report_to,
slowdown: slowdown
)
end
def interpret_diagnostics(%State{} = state) do
Detection.Diagnostics.interpret(
state.diagnostics,
state.url
)
end
end

View File

@ -0,0 +1,85 @@
defmodule Plausible.InstallationSupport.Detection.Diagnostics do
@moduledoc """
Module responsible for translating diagnostics to user-friendly errors and recommendations.
"""
require Logger
# in this struct, nil means indeterminate
defstruct v1_detected: nil,
gtm_likely: nil,
wordpress_likely: nil,
wordpress_plugin: nil,
service_error: nil
@type t :: %__MODULE__{}
alias Plausible.InstallationSupport.Result
@spec interpret(t(), String.t()) :: Result.t()
def interpret(
%__MODULE__{
gtm_likely: true,
service_error: nil
} = diagnostics,
_url
) do
get_result("gtm", diagnostics)
end
def interpret(
%__MODULE__{
wordpress_likely: true,
service_error: nil
} = diagnostics,
_url
) do
get_result(
"wordpress",
diagnostics
)
end
def interpret(
%__MODULE__{
service_error: nil
} = diagnostics,
_url
) do
get_result("manual", diagnostics)
end
def interpret(
%__MODULE__{
service_error: service_error
},
_url
)
when service_error in [:domain_not_found, :invalid_url],
do: %Result{ok?: false, data: nil, errors: [Atom.to_string(service_error)]}
def interpret(%__MODULE__{} = diagnostics, url),
do: unhandled_case(diagnostics, url)
defp unhandled_case(diagnostics, url) do
Sentry.capture_message("Unhandled case for detection",
extra: %{
message: inspect(diagnostics),
url: url,
hash: :erlang.phash2(diagnostics)
}
)
%Result{ok?: false, data: nil, errors: ["Unhandled detection case"]}
end
defp get_result(suggested_technology, diagnostics) do
%Result{
ok?: true,
data: %{
v1_detected: diagnostics.v1_detected,
wordpress_plugin: diagnostics.wordpress_plugin,
suggested_technology: suggested_technology
}
}
end
end

View File

@ -4,8 +4,7 @@ defmodule Plausible.InstallationSupport do
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.
Defines the user-agent used with checks.
"""
use Plausible
@ -13,18 +12,7 @@ defmodule Plausible.InstallationSupport 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

View File

@ -1,7 +1,18 @@
defmodule Plausible.InstallationSupport.Result do
@moduledoc """
Diagnostics interpretation result.
## Example
ok?: false,
data: nil,
errors: [error.message],
recommendations: [%{text: error.recommendation, url: error.url}]
ok?: true,
data: %{},
errors: [],
recommendations: []
"""
defstruct ok?: false, errors: [], recommendations: []
defstruct ok?: false, errors: [], recommendations: [], data: nil
@type t :: %__MODULE__{}
end

View File

@ -10,18 +10,21 @@ defmodule Plausible.InstallationSupport.State do
data_domain: nil,
report_to: nil,
assigns: %{},
diagnostics: %{}
diagnostics: %{},
skip_further_checks?: false
@type diagnostics_type ::
Plausible.InstallationSupport.LegacyVerification.Diagnostics.t()
| Plausible.InstallationSupport.Verification.Diagnostics.t()
| Plausible.InstallationSupport.Detection.Diagnostics.t()
@type t :: %__MODULE__{
url: String.t() | nil,
data_domain: String.t() | nil,
report_to: pid() | nil,
assigns: map(),
diagnostics: diagnostics_type()
diagnostics: diagnostics_type(),
skip_further_checks?: boolean()
}
def assign(%__MODULE__{} = state, assigns) do

View File

@ -10,12 +10,12 @@ defmodule Plausible.InstallationSupport.Verification.Checks do
require Logger
@checks [
Checks.Url,
Checks.InstallationV2,
Checks.InstallationV2CacheBust
]
def run(url, data_domain, installation_type, 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)
@ -30,7 +30,7 @@ defmodule Plausible.InstallationSupport.Verification.Checks do
}
}
CheckRunner.run(init_state, checks,
CheckRunner.run(init_state, @checks,
async?: async?,
report_to: report_to,
slowdown: slowdown

View File

@ -198,6 +198,17 @@ defmodule Plausible.InstallationSupport.Verification.Diagnostics do
),
do: error(@error_gtm_selected_maybe_cookie_banner)
@error_domain_not_found Error.new!(%{
message: "We couldn't verify your website",
recommendation:
"Please check that the domain you entered is correct and that the website is reachable publicly. If it's intentionally private, you'll need to verify that Plausible works manually",
url:
"https://plausible.io/docs/troubleshoot-integration#how-to-manually-check-your-integration"
})
def interpret(%__MODULE__{service_error: service_error}, _expected_domain, _url)
when service_error in [:domain_not_found, :invalid_url],
do: error(@error_domain_not_found)
def interpret(%__MODULE__{} = diagnostics, _expected_domain, url),
do: unknown_error(diagnostics, url)

View File

@ -22,7 +22,7 @@ defmodule Plausible.Site.TrackerScriptConfiguration do
@primary_key {:id, Plausible.Ecto.Types.TrackerScriptNanoid, autogenerate: true}
schema "tracker_script_configuration" do
field :installation_type, Ecto.Enum, values: [:manual, :wordpress, :gtm, nil]
field :installation_type, Ecto.Enum, values: [:manual, :wordpress, :gtm, :npm, nil]
field :track_404_pages, :boolean, default: false
field :hash_based_routing, :boolean, default: false

View File

@ -6,7 +6,7 @@ defmodule PlausibleWeb.Live.ChangeDomainV2 do
alias PlausibleWeb.Router.Helpers, as: Routes
alias PlausibleWeb.Live.ChangeDomainV2.Form
alias Plausible.InstallationSupport.Detection
alias Plausible.InstallationSupport.{Detection, Result}
alias Phoenix.LiveView.AsyncResult
def mount(
@ -34,7 +34,7 @@ defmodule PlausibleWeb.Live.ChangeDomainV2 do
site_domain = socket.assigns.site.domain
assign_async(socket, :detection_result, fn ->
run_detection("https://#{site_domain}")
run_detection(site_domain)
end)
else
socket
@ -155,10 +155,34 @@ defmodule PlausibleWeb.Live.ChangeDomainV2 do
|> push_patch(to: Routes.site_path(socket, :success, updated_site.domain))}
end
defp run_detection(url) do
case Detection.perform(url, detect_v1?: true) do
{:ok, result} -> {:ok, %{detection_result: result}}
e -> e
defp run_detection(domain) do
detection_result =
Detection.Checks.run(nil, domain,
detect_v1?: true,
report_to: nil,
async?: false,
slowdown: 0
)
|> Detection.Checks.interpret_diagnostics()
case detection_result do
%Result{
ok?: true,
data: %{
v1_detected: v1_detected,
wordpress_plugin: wordpress_plugin
}
} ->
{:ok,
%{
detection_result: %{
v1_detected: v1_detected,
wordpress_plugin: wordpress_plugin
}
}}
%Result{ok?: false, errors: errors} ->
{:error, List.first(errors, :unknown_reason)}
end
end
end

View File

@ -4,7 +4,7 @@ defmodule PlausibleWeb.Live.InstallationV2 do
"""
alias PlausibleWeb.Flows
alias Phoenix.LiveView.AsyncResult
alias Plausible.InstallationSupport.Detection
alias Plausible.InstallationSupport.{Detection, Result}
alias PlausibleWeb.Live.InstallationV2.Icons
alias PlausibleWeb.Live.InstallationV2.Instructions
use PlausibleWeb, :live_view
@ -144,19 +144,22 @@ defmodule PlausibleWeb.Live.InstallationV2 do
defp verify_cta("gtm"), do: "Verify Tag Manager installation"
defp verify_cta("npm"), do: "Verify NPM installation"
defp get_recommended_installation_type(flow, url) do
case Detection.perform(url, detect_v1?: flow == Flows.review()) do
{:ok, result} ->
Logger.debug("Detection result: #{inspect(result)}")
defp get_recommended_installation_type(flow, site) do
detection_result =
Detection.Checks.run(nil, site.domain,
detect_v1?: flow == Flows.review(),
report_to: nil,
slowdown: 0,
async?: false
)
|> Detection.Checks.interpret_diagnostics()
type =
case result do
%{gtm_likely: true} -> "gtm"
%{wordpress_likely: true} -> "wordpress"
_ -> "manual"
end
{type, result.v1_detected}
case detection_result do
%Result{
ok?: true,
data: %{suggested_technology: suggested_technology, v1_detected: v1_detected}
} ->
{suggested_technology, v1_detected}
_ ->
{"manual", false}
@ -203,7 +206,7 @@ defmodule PlausibleWeb.Live.InstallationV2 do
defp initialize_installation_data(flow, site, params) do
{recommended_installation_type, v1_detected} =
get_recommended_installation_type(flow, "https://#{site.domain}")
get_recommended_installation_type(flow, site)
tracker_script_configuration =
PlausibleWeb.Tracker.get_or_create_tracker_script_configuration!(site, %{

View File

@ -33,6 +33,7 @@ defmodule PlausibleWeb.Live.Verification do
socket =
assign(socket,
url_to_verify: nil,
site: site,
super_admin?: super_admin?,
domain: domain,
@ -110,12 +111,12 @@ defmodule PlausibleWeb.Live.Verification do
def handle_event("launch-verification", _, socket) do
launch_delayed(socket)
{:noreply, reset_component(socket)}
{:noreply, reset_component(socket, nil)}
end
def handle_event("retry", _, socket) do
def handle_event("retry", params, socket) do
launch_delayed(socket)
{:noreply, reset_component(socket)}
{:noreply, reset_component(socket, params["url_to_verify"])}
end
def handle_info({:start, report_to}, socket) do
@ -131,7 +132,6 @@ defmodule PlausibleWeb.Live.Verification do
{:deny, _} -> :timer.sleep(@slowdown_for_frequent_checking)
end
url_to_verify = "https://#{socket.assigns.domain}"
domain = socket.assigns.domain
installation_type = socket.assigns.installation_type
@ -140,13 +140,13 @@ defmodule PlausibleWeb.Live.Verification do
FunWithFlags.enabled?(:scriptv2, for: socket.assigns.site) or
FunWithFlags.enabled?(:scriptv2, for: socket.assigns.current_user),
do:
Verification.Checks.run(url_to_verify, domain, installation_type,
Verification.Checks.run(socket.assigns.url_to_verify, domain, installation_type,
report_to: report_to,
slowdown: socket.assigns.slowdown
),
else:
LegacyVerification.Checks.run(
url_to_verify,
"https://#{socket.assigns.domain}",
domain,
report_to: report_to,
slowdown: socket.assigns.slowdown
@ -157,10 +157,17 @@ defmodule PlausibleWeb.Live.Verification do
end
end
def handle_info({:check_start, {check, _state}}, socket) do
update_component(socket,
message: check.report_progress_as()
)
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
@ -207,7 +214,7 @@ defmodule PlausibleWeb.Live.Verification do
redirect(socket, to: stats_url)
end
defp reset_component(socket) do
defp reset_component(socket, url_to_verify) do
update_component(socket,
message: "We're visiting your site to ensure that everything is working",
finished?: false,
@ -215,7 +222,11 @@ defmodule PlausibleWeb.Live.Verification do
diagnostics: nil
)
socket
if is_binary(url_to_verify) do
assign(socket, url_to_verify: url_to_verify)
else
socket
end
end
defp update_component(_socket, updates) do

View File

@ -0,0 +1,147 @@
defmodule Plausible.InstallationSupport.Checks.UrlTest do
@moduledoc """
Tests for URL check that is used in detection and verification checks pipelines
to fail fast on non-existent domains.
"""
use Plausible.DataCase, async: true
import Mox
alias Plausible.InstallationSupport.{State, Checks, Verification}
@check Checks.Url
describe "when domain is set" do
for {site_domain, expected_lookup_domain} <- [
{"plausible.io", ~c"plausible.io"},
{"www.plausible.io", ~c"www.plausible.io"},
{"plausible.io/sites", ~c"plausible.io"}
] do
test "guesses 'https://#{site_domain}' if A-record is found for '#{site_domain}'" do
Plausible.DnsLookup.Mock
|> expect(:lookup, fn unquote(expected_lookup_domain), _type, _record, _opts, _timeout ->
[{192, 168, 1, 1}]
end)
state =
@check.perform(%State{
data_domain: unquote(site_domain),
url: nil,
diagnostics: %Verification.Diagnostics{}
})
assert state.url == "https://#{unquote(site_domain)}"
refute state.diagnostics.service_error
refute state.skip_further_checks?
end
end
test "guesses 'www.{domain}' if A record is not found for 'domain'" do
site_domain = "example.com/any/deeper/path"
Plausible.DnsLookup.Mock
|> expect(:lookup, fn ~c"example.com", _type, _record, _opts, _timeout ->
[]
end)
|> expect(:lookup, fn ~c"www.example.com", _type, _record, _opts, _timeout ->
[{192, 168, 1, 2}]
end)
state =
@check.perform(%State{
data_domain: site_domain,
url: nil,
diagnostics: %Verification.Diagnostics{}
})
assert state.url == "https://www.example.com/any/deeper/path"
refute state.diagnostics.service_error
refute state.skip_further_checks?
end
test "fails if no A-record is found for 'domain' or 'www.{domain}'" do
expected_lookups = 2
Plausible.DnsLookup.Mock
|> expect(:lookup, expected_lookups, fn _domain, _type, _record, _opts, _timeout ->
[]
end)
domain = "any.example.com"
state =
@check.perform(%State{
data_domain: domain,
url: nil,
diagnostics: %Verification.Diagnostics{}
})
assert state.url == nil
assert state.diagnostics.service_error == :domain_not_found
assert state.skip_further_checks?
end
end
describe "when url is set" do
test "for legitimate urls on domains that have an A-record, strips query and fragment" do
site_domain = "example-com-rollup"
url = "https://blog.example.com/recipes?foo=bar#baz"
Plausible.DnsLookup.Mock
|> expect(:lookup, fn ~c"blog.example.com", _type, _record, _opts, _timeout ->
[{192, 168, 1, 1}]
end)
state =
@check.perform(%State{
data_domain: site_domain,
url: url,
diagnostics: %Verification.Diagnostics{}
})
assert state.url == "https://blog.example.com/recipes"
refute state.diagnostics.service_error
refute state.skip_further_checks?
end
for scheme <- ["http", "file"] do
test "rejects not-https scheme '#{scheme}', does not check domain" do
state =
@check.perform(%State{
data_domain: "example-com-rollup",
url: "#{unquote(scheme)}://example.com/archives/news?p=any#fragment",
diagnostics: %Verification.Diagnostics{}
})
assert state.url == "#{unquote(scheme)}://example.com/archives/news?p=any#fragment"
assert state.diagnostics.service_error == :invalid_url
assert state.skip_further_checks?
end
end
test "rejects invalid urls" do
site_domain = "example-com-rollup"
url = "https://example.com/archives/news?p=any#fragment"
Plausible.DnsLookup.Mock
|> expect(:lookup, fn ~c"example.com", _type, _record, _opts, _timeout ->
[]
end)
state =
@check.perform(%State{
data_domain: site_domain,
url: url,
diagnostics: %Verification.Diagnostics{}
})
assert state.url == url
assert state.diagnostics.service_error == :domain_not_found
assert state.skip_further_checks?
end
end
test "reports progress correctly" do
assert @check.report_progress_as() ==
"We're trying to reach your website"
end
end

View File

@ -3,12 +3,23 @@ defmodule PlausibleWeb.Live.ChangeDomainV2Test do
import Phoenix.LiveViewTest
import Plausible.TestUtils
import ExUnit.CaptureLog
import Mox
alias Plausible.Repo
describe "ChangeDomainV2 LiveView" do
setup [:create_user, :log_in, :create_site]
setup do
# mock all domains resolve
Plausible.DnsLookup.Mock
|> expect(:lookup, fn _domain, _type, _record, _opts, _timeout ->
[{192, 168, 1, 2}]
end)
:ok
end
test "mounts and renders form", %{conn: conn, site: site} do
{:ok, _lv, html} = live(conn, "/#{site.domain}/change-domain-v2")
@ -241,7 +252,10 @@ defmodule PlausibleWeb.Live.ChangeDomainV2Test do
Req.Test.stub(:global, fn conn ->
conn
|> put_resp_content_type("application/json")
|> send_resp(200, Jason.encode!(%{"data" => %{"error" => "Simulated browser error"}}))
|> send_resp(
200,
Jason.encode!(%{"data" => %{"error" => %{"message" => "Simulated browser error"}}})
)
end)
end
end

View File

@ -4,6 +4,7 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
import Phoenix.LiveViewTest
import Plausible.Test.Support.HTML
import Plausible.Teams.Test
import Mox
alias Plausible.Site.TrackerScriptConfiguration
@ -24,6 +25,7 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
describe "LiveView" do
test "detects installation type when mounted", %{conn: conn, site: site} do
stub_dns_lookup_a_records(site.domain)
stub_detection_wordpress()
{lv, _} = get_lv(conn, site)
@ -32,10 +34,24 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
assert text(html) =~ "Verify WordPress installation"
end
test "When ?type URL parameter is supplied, detected type is unused", %{
test "When ?type=wordpress URL parameter is supplied, detected type is unused", %{
conn: conn,
site: site
} do
stub_dns_lookup_a_records(site.domain)
stub_detection_manual()
{lv, _} = get_lv(conn, site, "?type=wordpress")
html = render_async(lv, 500)
assert text(html) =~ "Verify WordPress installation"
end
test "When ?type=gtm URL parameter is supplied, detected type is unused", %{
conn: conn,
site: site
} do
stub_dns_lookup_a_records(site.domain)
stub_detection_wordpress()
{lv, _} = get_lv(conn, site, "?type=gtm")
@ -44,7 +60,34 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
assert text(html) =~ "Verify Tag Manager installation"
end
test "When ?type=npm URL parameter is supplied, detected type is unused", %{
conn: conn,
site: site
} do
stub_dns_lookup_a_records(site.domain)
stub_detection_wordpress()
{lv, _} = get_lv(conn, site, "?type=npm")
html = render_async(lv, 500)
assert text(html) =~ "Verify NPM installation"
end
test "When ?type=manual URL parameter is supplied, detected type is unused", %{
conn: conn,
site: site
} do
stub_dns_lookup_a_records(site.domain)
stub_detection_wordpress()
{lv, _} = get_lv(conn, site, "?type=manual")
html = render_async(lv, 500)
assert text(html) =~ "Verify Script installation"
end
test "allows switching between installation tabs", %{conn: conn, site: site} do
stub_dns_lookup_a_records(site.domain)
stub_detection_manual()
{lv, _html} = get_lv(conn, site, "?type=manual")
@ -74,6 +117,7 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
end
test "manual installations has script snippet with expected ID", %{conn: conn, site: site} do
stub_dns_lookup_a_records(site.domain)
stub_detection_manual()
{lv, _html} = get_lv(conn, site, "?type=manual&flow=review")
@ -90,6 +134,7 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
end
test "manual installation shows optional measurements", %{conn: conn, site: site} do
stub_dns_lookup_a_records(site.domain)
stub_detection_manual()
{lv, _html} = get_lv(conn, site, "?type=manual&flow=review")
@ -102,6 +147,7 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
end
test "manual installation shows advanced options in disclosure", %{conn: conn, site: site} do
stub_dns_lookup_a_records(site.domain)
stub_detection_manual()
{lv, _html} = get_lv(conn, site, "?type=manual&flow=review")
@ -119,6 +165,7 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
conn: conn,
site: site
} do
stub_dns_lookup_a_records(site.domain)
stub_detection_manual()
{lv, _html} = get_lv(conn, site, "?type=manual&flow=review")
@ -147,31 +194,40 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
assert updated_config.form_submissions == true
end
test "submitting form redirects to verification", %{conn: conn, site: site} do
stub_detection_manual()
{lv, _html} = get_lv(conn, site, "?type=manual")
for {type, expected_text} <- [
{"manual", "Verify Script installation"},
{"wordpress", "Verify WordPress installation"},
{"gtm", "Verify Tag Manager installation"},
{"npm", "Verify NPM installation"}
] do
test "submitting form with #{type} redirects to verification", %{conn: conn, site: site} do
stub_dns_lookup_a_records(site.domain)
stub_detection_manual()
{lv, _html} = get_lv(conn, site, "?type=#{unquote(type)}")
html = render_async(lv, 500)
assert html =~ "Verify Script installation"
html = render_async(lv, 500)
assert html =~ unquote(expected_text)
lv
|> element("form[phx-submit='submit']")
|> render_submit(%{
"tracker_script_configuration" => %{
"installation_type" => "manual",
"outbound_links" => "true",
"file_downloads" => "true",
"form_submissions" => "true"
}
})
lv
|> element("form[phx-submit='submit']")
|> render_submit(%{
"tracker_script_configuration" => %{
"installation_type" => unquote(type),
"outbound_links" => "true",
"file_downloads" => "true",
"form_submissions" => "true"
}
})
assert_redirect(
lv,
Routes.site_path(conn, :verification, site.domain, flow: "provisioning")
)
assert_redirect(
lv,
Routes.site_path(conn, :verification, site.domain, flow: "provisioning")
)
end
end
test "404 goal gets created regardless of user options", %{conn: conn, site: site} do
stub_dns_lookup_a_records(site.domain)
stub_detection_manual()
{lv, _html} = get_lv(conn, site, "?type=manual")
@ -199,6 +255,7 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
conn: conn,
site: site
} do
stub_dns_lookup_a_records(site.domain)
stub_detection_manual()
{lv, _html} = get_lv(conn, site, "?type=manual&flow=review")
@ -220,6 +277,7 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
end
test "detected WordPress installation shows special message", %{conn: conn, site: site} do
stub_dns_lookup_a_records(site.domain)
stub_detection_wordpress()
{lv, _} = get_lv(conn, site)
@ -229,6 +287,7 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
end
test "detected GTM installation shows special message", %{conn: conn, site: site} do
stub_dns_lookup_a_records(site.domain)
stub_detection_gtm()
{lv, _} = get_lv(conn, site)
@ -239,6 +298,7 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
end
test "shows v1 detection warning for manual installation", %{conn: conn, site: site} do
stub_dns_lookup_a_records(site.domain)
stub_detection_manual_with_v1()
{lv, _} = get_lv(conn, site, "?type=manual")
@ -251,6 +311,7 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
conn: conn,
site: site
} do
stub_dns_lookup_a_records(site.domain)
stub_detection_wordpress_with_v1()
{lv, _} = get_lv(conn, site, "?type=wordpress")
@ -260,7 +321,28 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
refute text(html) =~ "Your website is running an outdated version of the tracking script"
end
test "falls back to manual installation when detection fails", %{conn: conn, site: site} do
test "falls back to manual installation when detection fails at dns check level", %{
conn: conn,
site: site
} do
stub_dns_lookup_a_records(site.domain, [])
ExUnit.CaptureLog.capture_log(fn ->
{lv, _} = get_lv(conn, site)
assert eventually(fn ->
html = render(lv)
# Should default to manual installation when detection returns {:error, _}
{html =~ "Verify Script installation", html}
end)
end)
end
test "falls back to manual installation when dns succeeds but detection fails", %{
conn: conn,
site: site
} do
stub_dns_lookup_a_records(site.domain)
stub_detection_error()
ExUnit.CaptureLog.capture_log(fn ->
@ -286,6 +368,7 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
test "allows viewer access to installation page", %{conn: conn, user: user} do
site = new_site()
add_guest(site, user: user, role: :viewer)
stub_dns_lookup_a_records(site.domain)
stub_detection_manual()
{lv, _} = get_lv(conn, site)
@ -297,6 +380,7 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
test "allows editor access to installation page", %{conn: conn, user: user} do
site = new_site()
add_guest(site, user: user, role: :editor)
stub_dns_lookup_a_records(site.domain)
stub_detection_manual()
{lv, _} = get_lv(conn, site)
@ -311,6 +395,7 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
conn: conn,
site: site
} do
stub_dns_lookup_a_records(site.domain)
stub_detection_manual()
{lv, _} = get_lv(conn, site, "?type=invalid")
@ -323,6 +408,7 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
conn: conn,
site: site
} do
stub_dns_lookup_a_records(site.domain)
stub_detection_manual()
{lv, _} = get_lv(conn, site, "?flow=invalid")
@ -334,6 +420,8 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
describe "Detection Result Combinations" do
test "When GTM + Wordpress detected, GTM takes precedence", %{conn: conn, site: site} do
stub_dns_lookup_a_records(site.domain)
stub_detection_result(%{
"v1Detected" => false,
"gtmLikely" => true,
@ -361,6 +449,7 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
form_submissions: true
})
stub_dns_lookup_a_records(site.domain)
stub_detection_wordpress()
{lv, _} = get_lv(conn, site, "?flow=review")
@ -427,7 +516,10 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
Req.Test.stub(:global, fn conn ->
conn
|> put_resp_content_type("application/json")
|> send_resp(200, Jason.encode!(%{"data" => %{"error" => "Simulated browser error"}}))
|> send_resp(
200,
Jason.encode!(%{"data" => %{"error" => %{"message" => "Simulated browser error"}}})
)
end)
end
@ -436,4 +528,13 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
{lv, html}
end
defp stub_dns_lookup_a_records(domain, a_records \\ [{192, 168, 1, 1}]) do
lookup_domain = to_charlist(domain)
Plausible.DnsLookup.Mock
|> expect(:lookup, fn ^lookup_domain, _type, _record, _opts, _timeout ->
a_records
end)
end
end

View File

@ -4,6 +4,11 @@ end
{:ok, _} = Application.ensure_all_started(:ex_machina)
Mox.defmock(Plausible.HTTPClient.Mock, for: Plausible.HTTPClient.Interface)
Mox.defmock(Plausible.DnsLookup.Mock,
for: Plausible.DnsLookupInterface
)
Application.ensure_all_started(:double)
FunWithFlags.enable(:channels)

View File

@ -2,7 +2,7 @@ import { waitForPlausibleFunction } from "./plausible-function-check"
import { checkWordPress } from "./check-wordpress"
import { checkGTM } from "./check-gtm"
window.scanPageBeforePlausibleInstallation = async function(detectV1, debug) {
window.scanPageBeforePlausibleInstallation = async function({ detectV1, debug, timeoutMs }) {
function log(message) {
if (debug) console.log('[Plausible Verification]', message)
}
@ -11,7 +11,7 @@ window.scanPageBeforePlausibleInstallation = async function(detectV1, debug) {
if (detectV1) {
log('Waiting for Plausible function...')
const plausibleFound = await waitForPlausibleFunction(3000)
const plausibleFound = await waitForPlausibleFunction(timeoutMs)
log(`plausibleFound: ${plausibleFound}`)
v1Detected = plausibleFound && typeof window.plausible.s === 'undefined'
log(`v1Detected: ${v1Detected}`)

View File

@ -15,7 +15,7 @@ test.describe('detector.js (tech recognition)', () => {
`
})
const result = await detect(page, {url: url, detectV1: false})
const result = await detect(page, {url: url, detectV1: false, timeoutMs: 1000})
expect(result.data.v1Detected).toBe(null)
})
@ -34,7 +34,7 @@ test.describe('detector.js (tech recognition)', () => {
`
})
const result = await detect(page, {url: url, detectV1: false})
const result = await detect(page, {url: url, detectV1: false, timeoutMs: 1000})
expect(result.data.wordpressPlugin).toBe(true)
expect(result.data.wordpressLikely).toBe(true)
@ -47,7 +47,7 @@ test.describe('detector.js (tech recognition)', () => {
response: '<html><head></head></html>'
})
const result = await detect(page, {url: url, detectV1: false})
const result = await detect(page, {url: url, detectV1: false, timeoutMs: 1000})
expect(result.data.wordpressPlugin).toBe(false)
expect(result.data.wordpressLikely).toBe(false)
@ -71,7 +71,7 @@ test.describe('detector.js (v1 detection)', () => {
`
})
const result = await detect(page, {url: url, detectV1: true})
const result = await detect(page, {url: url, detectV1: true, timeoutMs: 1000})
expect(result.data.v1Detected).toBe(true)
expect(result.data.wordpressPlugin).toBe(true)
@ -85,7 +85,7 @@ test.describe('detector.js (v1 detection)', () => {
response: '<html><head></head></html>'
})
const result = await detect(page, {url: url, detectV1: true})
const result = await detect(page, {url: url, detectV1: true, timeoutMs: 1000})
expect(result.data.v1Detected).toBe(false)
expect(result.data.wordpressPlugin).toBe(false)
@ -102,7 +102,7 @@ test.describe('detector.js (v1 detection)', () => {
}
})
const result = await detect(page, {url: url, detectV1: true})
const result = await detect(page, {url: url, detectV1: true, timeoutMs: 1000})
expect(result.data.v1Detected).toBe(false)
})

View File

@ -88,7 +88,7 @@ export async function verifyV1(page, context) {
}
export async function detect(page, context) {
const { url, detectV1 } = context
const { url, detectV1, timeoutMs } = context
const debug = context.debug ? true : false
const detectorCode = await compileFile(DETECTOR_JS_VARIANT, {
@ -99,12 +99,9 @@ export async function detect(page, context) {
await page.evaluate(detectorCode)
return await page.evaluate(
async ({ detectV1, debug }) => {
return await (window as any).scanPageBeforePlausibleInstallation(
detectV1,
debug
)
async (d) => {
return await (window as any).scanPageBeforePlausibleInstallation(d)
},
{ detectV1, debug }
{ detectV1, debug, timeoutMs }
)
}