@affiliateo/web 2.4.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Client-side bot detection. Mirrors Datafast / PostHog / Plausible's
3
+ * approach: catch the obvious automation tools at SDK init so they
4
+ * never fire a single network request.
5
+ *
6
+ * Defense in depth:
7
+ * 1. This check — catches Selenium / Puppeteer / curl / etc
8
+ * BEFORE anything hits the wire. Saves bandwidth.
9
+ * Can be bypassed by a determined attacker who
10
+ * edits the bundled SDK.
11
+ * 2. Server UA regex — catches the same patterns plus legit crawlers
12
+ * (Googlebot, FB crawler, etc). Server is
13
+ * authoritative. Can't be bypassed.
14
+ * 3. Cloudflare — invisible browser-attestation challenge. Real
15
+ * Turnstile browsers pass in 50-200ms; scripts that don't
16
+ * run real JS can't pass it. Catches sophisticated
17
+ * attackers who spoofed their UA past layer 2.
18
+ *
19
+ * This file implements layer 1 only. Order: layer 1 first (cheap), then
20
+ * layer 3 (kicks in on the request), then layer 2 (server checks before
21
+ * writing). If any one layer rejects, the event drops.
22
+ *
23
+ * Patterns sourced from the Datafast script tag + npm SDK (publicly
24
+ * inspectable). We include their full list because it represents years
25
+ * of accumulated bot-shape knowledge that's expensive to reproduce.
26
+ */
27
+ /**
28
+ * Returns true if the current browser context looks like a bot or
29
+ * automation tool. Safe to call in SSR (returns true since there's no
30
+ * real visitor in that context anyway — a server-rendered page
31
+ * shouldn't be firing analytics).
32
+ *
33
+ * Wraps every check in try/catch so a hostile environment (frozen
34
+ * navigator, locked-down iframe) can't crash the SDK init. Any throw
35
+ * == "not a bot, let them through" so we err on the side of measuring.
36
+ */
37
+ export declare function isLikelyBot(): boolean;
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Client-side bot detection. Mirrors Datafast / PostHog / Plausible's
3
+ * approach: catch the obvious automation tools at SDK init so they
4
+ * never fire a single network request.
5
+ *
6
+ * Defense in depth:
7
+ * 1. This check — catches Selenium / Puppeteer / curl / etc
8
+ * BEFORE anything hits the wire. Saves bandwidth.
9
+ * Can be bypassed by a determined attacker who
10
+ * edits the bundled SDK.
11
+ * 2. Server UA regex — catches the same patterns plus legit crawlers
12
+ * (Googlebot, FB crawler, etc). Server is
13
+ * authoritative. Can't be bypassed.
14
+ * 3. Cloudflare — invisible browser-attestation challenge. Real
15
+ * Turnstile browsers pass in 50-200ms; scripts that don't
16
+ * run real JS can't pass it. Catches sophisticated
17
+ * attackers who spoofed their UA past layer 2.
18
+ *
19
+ * This file implements layer 1 only. Order: layer 1 first (cheap), then
20
+ * layer 3 (kicks in on the request), then layer 2 (server checks before
21
+ * writing). If any one layer rejects, the event drops.
22
+ *
23
+ * Patterns sourced from the Datafast script tag + npm SDK (publicly
24
+ * inspectable). We include their full list because it represents years
25
+ * of accumulated bot-shape knowledge that's expensive to reproduce.
26
+ */
27
+ // Selenium/WebDriver/headless flags set on window or navigator. Modern
28
+ // automation frameworks try to hide these but the defaults still leak.
29
+ const AUTOMATION_GLOBALS = [
30
+ '__webdriver_evaluate',
31
+ '__selenium_evaluate',
32
+ '__webdriver_script_function',
33
+ '__webdriver_unwrapped',
34
+ '__fxdriver_evaluate',
35
+ '__driver_evaluate',
36
+ '_Selenium_IDE_Recorder',
37
+ '_selenium',
38
+ 'calledSelenium',
39
+ // ChromeDriver's signature global. The dollar-sign-cdc prefix is
40
+ // a known fingerprint that vanilla undetected-chromedriver tries to
41
+ // strip but base ChromeDriver still includes.
42
+ '$cdc_asdjflasutopfhvcZLmcfl_',
43
+ ];
44
+ // User-agent substrings (lowercased before match). These cover both the
45
+ // headless browser families AND the tool families (curl/python/etc).
46
+ // Real human UAs do contain things like "Mobile/15E148" so we match on
47
+ // distinctive automation tokens only.
48
+ const BOT_UA_PATTERNS = [
49
+ 'headlesschrome',
50
+ 'phantomjs',
51
+ 'selenium',
52
+ 'webdriver',
53
+ 'puppeteer',
54
+ 'playwright',
55
+ 'python',
56
+ 'curl',
57
+ 'wget',
58
+ 'java/',
59
+ 'go-http',
60
+ 'node.js',
61
+ 'axios',
62
+ 'postman',
63
+ ];
64
+ // documentElement attributes some Selenium variants set. Cheap to check.
65
+ const BOT_DOC_ATTRS = ['webdriver', 'selenium', 'driver'];
66
+ /**
67
+ * Returns true if the current browser context looks like a bot or
68
+ * automation tool. Safe to call in SSR (returns true since there's no
69
+ * real visitor in that context anyway — a server-rendered page
70
+ * shouldn't be firing analytics).
71
+ *
72
+ * Wraps every check in try/catch so a hostile environment (frozen
73
+ * navigator, locked-down iframe) can't crash the SDK init. Any throw
74
+ * == "not a bot, let them through" so we err on the side of measuring.
75
+ */
76
+ export function isLikelyBot() {
77
+ // SSR or non-browser context. We can't run analytics here anyway
78
+ // because there's no localStorage / sendBeacon / DOM events.
79
+ if (typeof window === 'undefined' || typeof navigator === 'undefined')
80
+ return true;
81
+ try {
82
+ const nav = window.navigator;
83
+ const win = window;
84
+ // Selenium / Puppeteer / Playwright signature on navigator.
85
+ // Modern stealth plugins try to override this but the override
86
+ // is detectable too (the property descriptor looks wrong) — we
87
+ // catch the easy case here and let Turnstile catch the rest.
88
+ if (nav.webdriver === true)
89
+ return true;
90
+ // Legacy headless flags (PhantomJS / Nightmare). Still present in
91
+ // some old scraper toolchains; cheap to check.
92
+ if (win.callPhantom !== undefined)
93
+ return true;
94
+ if (win._phantom !== undefined)
95
+ return true;
96
+ if (win.__nightmare !== undefined)
97
+ return true;
98
+ // Missing / suspicious UA. Real browsers always set a useful UA
99
+ // string; a missing or stub UA strongly implies a HTTP client
100
+ // (curl --user-agent "" / a python script that forgot to set one).
101
+ if (!nav.userAgent
102
+ || nav.userAgent === ''
103
+ || nav.userAgent === 'undefined'
104
+ || nav.userAgent.length < 5) {
105
+ return true;
106
+ }
107
+ const ua = nav.userAgent.toLowerCase();
108
+ // Substring match on the known automation tokens. We split into a
109
+ // single loop instead of one giant regex for readability — the cost
110
+ // difference at <20 patterns is microseconds either way.
111
+ for (const pattern of BOT_UA_PATTERNS) {
112
+ if (ua.includes(pattern))
113
+ return true;
114
+ }
115
+ // Selenium globals on window. Some frameworks reset navigator but
116
+ // forget about these.
117
+ for (const key of AUTOMATION_GLOBALS) {
118
+ if (win[key] !== undefined)
119
+ return true;
120
+ }
121
+ // documentElement attributes. Cheap belt-and-suspenders check; some
122
+ // Selenium variants annotate the HTML root for their own bookkeeping.
123
+ const docEl = document.documentElement;
124
+ if (docEl) {
125
+ for (const attr of BOT_DOC_ATTRS) {
126
+ if (docEl.getAttribute(attr))
127
+ return true;
128
+ }
129
+ }
130
+ return false;
131
+ }
132
+ catch {
133
+ // Defensive: if any of the checks throw (locked-down iframe,
134
+ // proxy-wrapped navigator, etc.), don't block the visitor. Server
135
+ // regex + Turnstile are still in place to catch whatever this is.
136
+ return false;
137
+ }
138
+ }
@@ -18,6 +18,6 @@ export declare function useAffiliateRef(): AffiliateoContextValue;
18
18
  * ```
19
19
  */
20
20
  export declare function getAttribution(): Record<string, string>;
21
- export declare function AffiliateoProvider({ campaignId, apiUrl, children, }: AffiliateoConfig & {
21
+ export declare function AffiliateoProvider({ campaignId, apiUrl, allowedHostnames, children, }: AffiliateoConfig & {
22
22
  children: React.ReactNode;
23
23
  }): import("react/jsx-runtime").JSX.Element;