analytics: filter email-scanner / headless traffic out of Umami stats
Email security gateways (Microsoft Defender Safe Links / ATP, Proofpoint,
Mimecast, Barracuda, etc.) auto-fetch and often render every link in a
campaign email to scan for malware. The advanced ones drive a real headless
browser, execute JS, and fire Umami pageviews/clicks that masquerade as human
visits -- inflating campaign click-through.
New site/public/js/pw-bot-filter.js queries multiple real-browser signals and
gates Umami via its official data-before-send hook (umamiBeforeSend), dropping
all events when the visitor is a bot. Signals (from empirical chromium probing):
decisive: navigator.webdriver, HeadlessChrome UA, known scanner UAs, zero/
collapsed screen|viewport|outer geometry, window LARGER than the
physical screen (impossible on real HW; uses outerW/H so page zoom
does not false-positive), software GPU rasterizer (SwiftShader/
llvmpipe/swrast via WebGL UNMASKED_RENDERER), zero logical CPUs.
soft (>=2 to trip): tiny screen, inner>screen, low color depth, empty
navigator.languages, no input device (no fine/coarse pointer + no
hover + 0 touch), no WebGL on a desktop UA.
Designed to FAIL OPEN: only strong/corroborated evidence suppresses, so real
visitors (incl. zoomed, privacy-tooled, remote-desktop, kiosk) still count.
Wired before the Umami tag in Base.astro (Astro pages) and all 86 static
public/**/*.html pages; both load with defer so order is guaranteed and the
hook is defined before Umami reads it.
Tested end-to-end with chromium (site/tests/bot-filter.test.sh, 4/4):
default headless-new, spoofed-Windows-UA + normal 1366x768 window, and
spoofed-UA + 1x1 window are all caught; hook returns null to drop the event.
This commit is contained in:
parent
40da017b79
commit
f481a1d13c
89 changed files with 341 additions and 87 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
186
site/public/js/pw-bot-filter.js
Normal file
186
site/public/js/pw-bot-filter.js
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
/**
|
||||||
|
* Performance West - email-scanner / headless-browser filter for analytics.
|
||||||
|
*
|
||||||
|
* WHY: Email security gateways (Microsoft Defender Safe Links / ATP detonation,
|
||||||
|
* Proofpoint URL Defense, Mimecast, Barracuda, Cisco, Google) automatically
|
||||||
|
* fetch and often *render* every link in an email to scan it for malware. The
|
||||||
|
* advanced ones drive a real headless browser, so they execute JS and fire
|
||||||
|
* Umami pageviews/clicks that look like human visits -- inflating campaign
|
||||||
|
* stats and making click-through look better than it is.
|
||||||
|
*
|
||||||
|
* WHAT: query a battery of signals that a REAL interactive browser on a REAL
|
||||||
|
* screen has and an automated/headless scanner usually does not, combine them
|
||||||
|
* into a confidence score, and expose window.pwIsBot. The Umami `before-send`
|
||||||
|
* hook (umamiBeforeSend, below) drops every event when this is true, so bot
|
||||||
|
* traffic never reaches analytics. Real visitors are unaffected.
|
||||||
|
*
|
||||||
|
* Designed to FAIL OPEN: any uncertainty counts as human, so we never silently
|
||||||
|
* undercount real people. We only suppress on strong, multi-signal evidence.
|
||||||
|
*/
|
||||||
|
(function (w, d, n) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var reasons = [];
|
||||||
|
function flag(r) { reasons.push(r); }
|
||||||
|
|
||||||
|
// ── 1. navigator.webdriver: set true by Selenium/Puppeteer/Playwright and
|
||||||
|
// by Chrome headless. The single strongest, lowest-false-positive tell.
|
||||||
|
try { if (n.webdriver === true) flag("webdriver"); } catch (e) {}
|
||||||
|
|
||||||
|
// ── 2. Headless user-agent markers ─────────────────────────────────────
|
||||||
|
var ua = (n.userAgent || "");
|
||||||
|
if (/HeadlessChrome/i.test(ua)) flag("ua-headless-chrome");
|
||||||
|
if (/PhantomJS|SlimerJS|Electron|jsdom|Nightmare/i.test(ua)) flag("ua-automation");
|
||||||
|
// Known scanner/preview bots that DO run JS (defense in depth; most are
|
||||||
|
// already excluded server-side, but some spoof regular UAs intermittently).
|
||||||
|
if (/bingpreview|YandexBot|Barracuda|Proofpoint|Mimecast|Cisco|Symantec|MessageLabs|GoogleImageProxy|Google-Safety|Microsoft Office|MSOffice/i.test(ua)) {
|
||||||
|
flag("ua-scanner");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 3. Screen / window geometry ────────────────────────────────────────
|
||||||
|
// Headless browsers default to a fixed off-screen viewport (often 800x600
|
||||||
|
// or 1x1/0x0) with no real device behind them. Real monitors are larger and
|
||||||
|
// the browser window is a sane size. We treat clearly-degenerate geometry as
|
||||||
|
// a bot signal but keep thresholds conservative to spare small/old screens.
|
||||||
|
var s = w.screen || {};
|
||||||
|
var sw = s.width | 0, sh = s.height | 0;
|
||||||
|
var iw = w.innerWidth | 0, ih = w.innerHeight | 0;
|
||||||
|
var ow = w.outerWidth | 0, oh = w.outerHeight | 0;
|
||||||
|
|
||||||
|
// Zero / collapsed dimensions = no real surface (very strong, headless only).
|
||||||
|
if (sw <= 0 || sh <= 0) flag("screen-zero");
|
||||||
|
if (iw <= 0 || ih <= 0) flag("viewport-zero");
|
||||||
|
|
||||||
|
// Absurdly tiny screen that no real desktop/phone reports (phones are >=320).
|
||||||
|
if (sw > 0 && sh > 0 && (sw < 300 || sh < 300) && !/Mobi|Android|iPhone|iPad/i.test(ua)) {
|
||||||
|
flag("screen-tiny");
|
||||||
|
}
|
||||||
|
|
||||||
|
// outerWidth/Height of 0 with a non-zero viewport: classic headless (the
|
||||||
|
// window has no chrome because there is no window manager).
|
||||||
|
if ((ow === 0 || oh === 0) && iw > 0 && ih > 0) flag("outer-zero");
|
||||||
|
|
||||||
|
// Viewport larger than the physical screen is impossible on real hardware.
|
||||||
|
// innerWidth is in CSS pixels and GROWS when the user zooms OUT, so an
|
||||||
|
// inner>screen check alone would false-positive on zoomed-out humans; we
|
||||||
|
// therefore make it a SOFT signal and pair it with an outer>screen check.
|
||||||
|
if (sw > 0 && sh > 0 && (iw > sw + 40 || ih > sh + 40)) flag("viewport-gt-screen");
|
||||||
|
|
||||||
|
// outerWidth/Height is the PHYSICAL window frame -- unaffected by page zoom --
|
||||||
|
// so outer>screen is a much cleaner "window bigger than the monitor" tell.
|
||||||
|
// Headless browsers given a --window-size on top of an unchanged 800x600
|
||||||
|
// virtual screen trip this even with a perfectly spoofed UA and normal-looking
|
||||||
|
// window. (Real false positive: a single window dragged to SPAN two monitors;
|
||||||
|
// rare, and fail-open cost is just one uncounted visit.)
|
||||||
|
if (sw > 0 && sh > 0 && (ow > sw + 40 || oh > sh + 40)) flag("outer-gt-screen");
|
||||||
|
|
||||||
|
// ── 4. Device / interaction surface ────────────────────────────────────
|
||||||
|
// colorDepth of 0/<=8 is a headless default; real displays are 24/30.
|
||||||
|
if (typeof s.colorDepth === "number" && s.colorDepth > 0 && s.colorDepth < 16) {
|
||||||
|
flag("color-depth-low");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 4b. GPU / WebGL renderer ───────────────────────────────────────────
|
||||||
|
// The single hardest signal to spoof. Real machines render through a real
|
||||||
|
// GPU driver: "ANGLE (Intel...)", "Apple GPU", "NVIDIA", "AMD", "Mali",
|
||||||
|
// "Adreno", etc. Headless/VM scanners fall back to a SOFTWARE rasterizer:
|
||||||
|
// SwiftShader (Chrome), llvmpipe / swrast (Mesa). A software renderer on a
|
||||||
|
// device claiming to be a normal desktop browser is a near-certain bot.
|
||||||
|
// (Verified: Chrome --headless=new reports
|
||||||
|
// "ANGLE (Google, ... SwiftShader Device ...)".)
|
||||||
|
try {
|
||||||
|
var glcv = d.createElement("canvas");
|
||||||
|
var gl = glcv.getContext("webgl") || glcv.getContext("experimental-webgl");
|
||||||
|
if (gl) {
|
||||||
|
var dbg = gl.getExtension("WEBGL_debug_renderer_info");
|
||||||
|
var rnd = (dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER)) || "";
|
||||||
|
rnd = ("" + rnd).toLowerCase();
|
||||||
|
w.pwGpu = rnd;
|
||||||
|
if (/swiftshader|llvmpipe|swrast|softpipe|\bsoftware\b/.test(rnd)) {
|
||||||
|
flag("gpu-software");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No WebGL at all on a desktop-class UA is itself suspicious (headless
|
||||||
|
// with GPU fully disabled). Soft, because privacy tools also block it.
|
||||||
|
if (!/Mobi|Android|iPhone|iPad/i.test(ua)) flag("no-webgl");
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
// ── 4c. Input device (pointer / hover media queries) ───────────────────
|
||||||
|
// A real desktop has a fine pointer + hover; a real phone/tablet has a
|
||||||
|
// coarse pointer. A device with NEITHER fine NOR coarse pointer, no hover,
|
||||||
|
// and zero touch points has no human input surface at all -- the hallmark of
|
||||||
|
// a headless renderer. Strong, but kept SOFT (paired with another signal)
|
||||||
|
// because some locked-down/embedded real browsers under-report these.
|
||||||
|
try {
|
||||||
|
var mm = w.matchMedia;
|
||||||
|
if (typeof mm === "function") {
|
||||||
|
var fine = mm("(pointer: fine)").matches;
|
||||||
|
var coarse = mm("(pointer: coarse)").matches;
|
||||||
|
var hover = mm("(any-hover: hover)").matches;
|
||||||
|
var touch = (n.maxTouchPoints | 0) > 0;
|
||||||
|
if (!fine && !coarse && !hover && !touch) flag("no-input-device");
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
// ── 4d. Hardware introspection ─────────────────────────────────────────
|
||||||
|
// hardwareConcurrency (logical CPUs) of 0 is impossible on real hardware;
|
||||||
|
// older Selenium/PhantomJS reported 0/undefined. Modern headless reports a
|
||||||
|
// real value, so this only catches the crude scanners. Soft.
|
||||||
|
try {
|
||||||
|
if (typeof n.hardwareConcurrency === "number" && n.hardwareConcurrency === 0) {
|
||||||
|
flag("zero-cpu");
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
// ── 5. Languages: real browsers expose a populated navigator.languages;
|
||||||
|
// many headless setups leave it empty or report a single bare entry.
|
||||||
|
try {
|
||||||
|
if (n.languages && n.languages.length === 0) flag("no-languages");
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
// ── 6. Permissions API inconsistency (Chrome headless tell): notification
|
||||||
|
// permission reports "denied" while Notification.permission is
|
||||||
|
// "default" -- impossible in a real Chrome.
|
||||||
|
try {
|
||||||
|
if (n.permissions && w.Notification &&
|
||||||
|
w.Notification.permission === "denied" &&
|
||||||
|
n.permissions.query) {
|
||||||
|
// async; record lazily without blocking
|
||||||
|
n.permissions.query({ name: "notifications" }).then(function (p) {
|
||||||
|
if (p && p.state === "prompt") { /* normal */ }
|
||||||
|
}).catch(function () {});
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
// ── Scoring ────────────────────────────────────────────────────────────
|
||||||
|
// DECISIVE signals are individually near-impossible for a real consumer
|
||||||
|
// browser (automation flag, headless UA, zero/over-size geometry, a software
|
||||||
|
// GPU rasterizer, or 0 CPUs). Any one suppresses.
|
||||||
|
// SOFT signals are individually explainable by an unusual-but-real setup
|
||||||
|
// (privacy tools, kiosks, remote desktop, extreme zoom), so we require TWO to
|
||||||
|
// corroborate -- protecting real visitors (fail open).
|
||||||
|
var decisive = reasons.some(function (r) {
|
||||||
|
return r === "webdriver" || r === "ua-headless-chrome" ||
|
||||||
|
r === "ua-automation" || r === "ua-scanner" ||
|
||||||
|
r === "screen-zero" || r === "viewport-zero" || r === "outer-zero" ||
|
||||||
|
r === "outer-gt-screen" || r === "gpu-software" || r === "zero-cpu";
|
||||||
|
});
|
||||||
|
var softCount = reasons.filter(function (r) {
|
||||||
|
return r === "screen-tiny" || r === "viewport-gt-screen" ||
|
||||||
|
r === "color-depth-low" || r === "no-languages" ||
|
||||||
|
r === "no-input-device" || r === "no-webgl";
|
||||||
|
}).length;
|
||||||
|
|
||||||
|
var isBot = decisive || softCount >= 2;
|
||||||
|
|
||||||
|
w.pwIsBot = isBot;
|
||||||
|
w.pwBotReasons = reasons;
|
||||||
|
|
||||||
|
// Umami `data-before-send` hook: return null to DROP the event, or the
|
||||||
|
// payload to allow it. Wired via data-before-send="umamiBeforeSend" on the
|
||||||
|
// Umami <script> tag. Applies to the initial pageview AND every custom event.
|
||||||
|
w.umamiBeforeSend = function (type, payload) {
|
||||||
|
return w.pwIsBot ? null : payload;
|
||||||
|
};
|
||||||
|
})(window, document, navigator);
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -17,7 +17,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script>
|
||||||
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
||||||
<script type="module" src="/_astro/hoisted.yFz1BYXO.js"></script>
|
<script type="module" src="/_astro/hoisted.yFz1BYXO.js"></script>
|
||||||
<style>
|
<style>
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -62,7 +62,7 @@ select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px
|
||||||
.recommend-box strong{color:#166534}
|
.recommend-box strong{color:#166534}
|
||||||
.err{color:#dc2626;font-size:.82rem;margin-top:.5rem;display:none}
|
.err{color:#dc2626;font-size:.82rem;margin-top:.5rem;display:none}
|
||||||
</style>
|
</style>
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script><script defer src="/js/pw-analytics.js"></script></head>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script><script defer src="/js/pw-analytics.js"></script></head>
|
||||||
<body>
|
<body>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1>FCC Carrier / ISP Registration</h1>
|
<h1>FCC Carrier / ISP Registration</h1>
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -49,7 +49,7 @@ select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px
|
||||||
.hidden{display:none}
|
.hidden{display:none}
|
||||||
.err{color:#dc2626;font-size:.82rem;margin-top:.5rem;display:none}
|
.err{color:#dc2626;font-size:.82rem;margin-top:.5rem;display:none}
|
||||||
</style>
|
</style>
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script><script defer src="/js/pw-analytics.js"></script></head>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script><script defer src="/js/pw-analytics.js"></script></head>
|
||||||
<body>
|
<body>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1>NECA OCN Registration</h1>
|
<h1>NECA OCN Registration</h1>
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px
|
||||||
.step-dot.done{background:#22c55e;color:#fff}
|
.step-dot.done{background:#22c55e;color:#fff}
|
||||||
.entity-form input{margin-bottom:.75rem}
|
.entity-form input{margin-bottom:.75rem}
|
||||||
</style>
|
</style>
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script><script defer src="/js/pw-analytics.js"></script></head>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script><script defer src="/js/pw-analytics.js"></script></head>
|
||||||
<body>
|
<body>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1>State PUC/PSC Registration</h1>
|
<h1>State PUC/PSC Registration</h1>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
if (h === "dev.performancewest.net") return "https://api.dev.performancewest.net";
|
if (h === "dev.performancewest.net") return "https://api.dev.performancewest.net";
|
||||||
return "https://api.performancewest.net";
|
return "https://api.performancewest.net";
|
||||||
})();
|
})();
|
||||||
</script><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"><title>Order Confirmed | Performance West Inc.</title><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script><link rel="stylesheet" href="/_astro/about.DhmoKVOS.css"><script type="module" src="/_astro/hoisted.Be9YR9_C.js"></script>
|
</script><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"><title>Order Confirmed | Performance West Inc.</title><script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script><link rel="stylesheet" href="/_astro/about.DhmoKVOS.css"><script type="module" src="/_astro/hoisted.Be9YR9_C.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// Handle free ($0) orders — verify payment_status with server
|
// Handle free ($0) orders — verify payment_status with server
|
||||||
(function(){
|
(function(){
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script>
|
||||||
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
||||||
<script type="module" src="/_astro/hoisted.yFz1BYXO.js"></script>
|
<script type="module" src="/_astro/hoisted.yFz1BYXO.js"></script>
|
||||||
<style>
|
<style>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script>
|
||||||
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
||||||
<script type="module" src="/_astro/hoisted.yFz1BYXO.js"></script>
|
<script type="module" src="/_astro/hoisted.yFz1BYXO.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -55,7 +55,7 @@ body{font-family:'Inter',system-ui,sans-serif;color:#1f2937;background:#f1f5f9;l
|
||||||
/* Signature block locked until signing authorization is given */
|
/* Signature block locked until signing authorization is given */
|
||||||
#sig-block.locked{opacity:.45;pointer-events:none;filter:grayscale(.3)}
|
#sig-block.locked{opacity:.45;pointer-events:none;filter:grayscale(.3)}
|
||||||
</style>
|
</style>
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script><script defer src="/js/pw-analytics.js"></script></head>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script><script defer src="/js/pw-analytics.js"></script></head>
|
||||||
<body>
|
<body>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<div id="loading"><p>Loading your document...</p><p style="font-size:.8rem;color:#94a3b8">Verifying your link</p></div>
|
<div id="loading"><p>Loading your document...</p><p style="font-size:.8rem;color:#94a3b8">Verifying your link</p></div>
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -19,7 +19,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script>
|
||||||
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
||||||
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script>
|
||||||
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
||||||
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script>
|
||||||
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
||||||
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script>
|
||||||
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
||||||
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script>
|
||||||
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
||||||
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script>
|
||||||
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
||||||
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script>
|
||||||
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
||||||
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script>
|
||||||
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
||||||
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script>
|
||||||
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
||||||
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script>
|
||||||
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
||||||
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script>
|
||||||
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
||||||
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script>
|
||||||
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
||||||
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script>
|
||||||
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
||||||
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
<script type="module" src="/_astro/hoisted.aBLqmOPy.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -17,7 +17,7 @@ window.__PW_API = (function() {
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<title>Corporation Status Check — Free Good Standing Lookup | Performance West</title>
|
<title>Corporation Status Check — Free Good Standing Lookup | Performance West</title>
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script>
|
||||||
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
<link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
|
||||||
</head>
|
</head>
|
||||||
<body class="min-h-screen flex flex-col bg-gray-50">
|
<body class="min-h-screen flex flex-col bg-gray-50">
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -45,7 +45,8 @@ try {
|
||||||
<title>{title} | Performance West Inc.</title>
|
<title>{title} | Performance West Inc.</title>
|
||||||
{description && <meta name="description" content={description} />}
|
{description && <meta name="description" content={description} />}
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
|
<script defer src="/js/pw-bot-filter.js"></script>
|
||||||
|
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script>
|
||||||
<script defer src="/js/pw-analytics.js"></script>
|
<script defer src="/js/pw-analytics.js"></script>
|
||||||
{/* TrustedSite trustmark loads ONLY on pages that opt in (order/checkout +
|
{/* TrustedSite trustmark loads ONLY on pages that opt in (order/checkout +
|
||||||
top campaign landing pages) to stay under the free 500 impressions/mo.
|
top campaign landing pages) to stay under the free 500 impressions/mo.
|
||||||
|
|
|
||||||
67
site/tests/bot-filter.test.sh
Normal file
67
site/tests/bot-filter.test.sh
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# End-to-end test for the analytics email-scanner / headless filter.
|
||||||
|
#
|
||||||
|
# Verifies pw-bot-filter.js (a) correctly flags headless/automated browsers as
|
||||||
|
# bots across several scanner disguises, and (b) wires the Umami before-send
|
||||||
|
# hook so flagged traffic is dropped. Run from the repo root:
|
||||||
|
#
|
||||||
|
# bash site/tests/bot-filter.test.sh
|
||||||
|
#
|
||||||
|
# Requires: chromium (or google-chrome) + python3. Skips cleanly if absent.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
FILTER="$(cd "$(dirname "$0")/.." && pwd)/public/js/pw-bot-filter.js"
|
||||||
|
CHROME="$(command -v chromium || command -v chromium-browser || command -v google-chrome || true)"
|
||||||
|
if [ -z "$CHROME" ]; then echo "SKIP: no chromium/chrome found"; exit 0; fi
|
||||||
|
[ -f "$FILTER" ] || { echo "FAIL: $FILTER missing"; exit 1; }
|
||||||
|
|
||||||
|
WORK="$(mktemp -d)"
|
||||||
|
SRV_PID=""
|
||||||
|
cleanup() { [ -n "$SRV_PID" ] && kill "$SRV_PID" 2>/dev/null; rm -rf "$WORK"; }
|
||||||
|
trap cleanup EXIT
|
||||||
|
cp "$FILTER" "$WORK/pw-bot-filter.js"
|
||||||
|
cat > "$WORK/probe.html" <<'HTML'
|
||||||
|
<!doctype html><html><head><meta charset="utf-8"><title>t</title>
|
||||||
|
<script src="/pw-bot-filter.js"></script></head><body><script>
|
||||||
|
document.title = JSON.stringify({
|
||||||
|
isBot: window.pwIsBot,
|
||||||
|
reasons: window.pwBotReasons,
|
||||||
|
hook: typeof window.umamiBeforeSend,
|
||||||
|
drop: window.umamiBeforeSend("event", {x:1}) === null
|
||||||
|
});
|
||||||
|
</script></body></html>
|
||||||
|
HTML
|
||||||
|
( cd "$WORK" && exec python3 -m http.server 8791 >/dev/null 2>&1 ) &
|
||||||
|
SRV_PID=$!
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
run() { # args -> JSON from page title
|
||||||
|
"$CHROME" --headless=new --no-sandbox --disable-dev-shm-usage "$@" \
|
||||||
|
--dump-dom "http://localhost:8791/probe.html" 2>/dev/null \
|
||||||
|
| grep -oE '\{.*\}' | head -1
|
||||||
|
}
|
||||||
|
PASS=0; FAIL=0
|
||||||
|
check() { # name expr_json key wantbool
|
||||||
|
local name="$1" json="$2"
|
||||||
|
local got; got="$(printf '%s' "$json" | python3 -c "import sys,json;print(json.loads(sys.stdin.read()).get('isBot'))" 2>/dev/null)"
|
||||||
|
if [ "$got" = "True" ]; then echo "PASS $name (isBot=True)"; PASS=$((PASS+1));
|
||||||
|
else echo "FAIL $name (isBot=$got; json=$json)"; FAIL=$((FAIL+1)); fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 1. default headless-new
|
||||||
|
check "headless-new default" "$(run)"
|
||||||
|
# 2. spoofed Windows UA + normal window (sophisticated scanner)
|
||||||
|
check "spoofed UA + normal window" "$(run --window-size=1366,768 \
|
||||||
|
--user-agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36')"
|
||||||
|
# 3. spoofed UA + tiny window
|
||||||
|
check "spoofed UA + tiny window" "$(run --window-size=1,1 \
|
||||||
|
--user-agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36')"
|
||||||
|
|
||||||
|
# hook wiring on the default run
|
||||||
|
HJSON="$(run)"
|
||||||
|
HK="$(printf '%s' "$HJSON" | python3 -c "import sys,json;d=json.loads(sys.stdin.read());print(d.get('hook'),d.get('drop'))" 2>/dev/null)"
|
||||||
|
if [ "$HK" = "function True" ]; then echo "PASS umamiBeforeSend drops bot events"; PASS=$((PASS+1));
|
||||||
|
else echo "FAIL hook wiring ($HK)"; FAIL=$((FAIL+1)); fi
|
||||||
|
|
||||||
|
echo ""; echo "$PASS passed, $FAIL failed"
|
||||||
|
[ "$FAIL" -eq 0 ]
|
||||||
Loading…
Add table
Add a link
Reference in a new issue