onedollarstats 0.0.17 → 0.0.19

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.
package/dist/index.js CHANGED
@@ -1,64 +1,5 @@
1
1
  // src/utils/bot.ts
2
- var BOT_PATTERNS = [
3
- // Search engines
4
- { pattern: /Googlebot/i, kind: "search_engine", name: "Googlebot" },
5
- { pattern: /Google-InspectionTool/i, kind: "search_engine", name: "Googlebot" },
6
- { pattern: /Storebot-Google/i, kind: "search_engine", name: "Googlebot" },
7
- { pattern: /AdsBot-Google/i, kind: "search_engine", name: "Google Ads" },
8
- { pattern: /Mediapartners-Google/i, kind: "search_engine", name: "Google Adsense" },
9
- { pattern: /bingbot/i, kind: "search_engine", name: "Bingbot" },
10
- { pattern: /msnbot/i, kind: "search_engine", name: "MSNBot" },
11
- { pattern: /YandexBot/i, kind: "search_engine", name: "YandexBot" },
12
- { pattern: /YandexAccessibilityBot/i, kind: "search_engine", name: "YandexBot" },
13
- { pattern: /Baiduspider/i, kind: "search_engine", name: "Baidu" },
14
- { pattern: /DuckDuckBot/i, kind: "search_engine", name: "DuckDuckBot" },
15
- { pattern: /Sogou/i, kind: "search_engine", name: "Sogou" },
16
- { pattern: /Exabot/i, kind: "search_engine", name: "Exabot" },
17
- { pattern: /ia_archiver/i, kind: "search_engine", name: "Alexa" },
18
- { pattern: /SemrushBot/i, kind: "search_engine", name: "SemrushBot" },
19
- { pattern: /AhrefsBot/i, kind: "search_engine", name: "AhrefsBot" },
20
- { pattern: /MJ12bot/i, kind: "search_engine", name: "MJ12bot" },
21
- { pattern: /DotBot/i, kind: "search_engine", name: "DotBot" },
22
- { pattern: /PetalBot/i, kind: "search_engine", name: "PetalBot" },
23
- { pattern: /Applebot/i, kind: "search_engine", name: "Applebot" },
24
- { pattern: /GPTBot/i, kind: "search_engine", name: "GPTBot" },
25
- { pattern: /ChatGPT-User/i, kind: "search_engine", name: "ChatGPT" },
26
- { pattern: /ClaudeBot/i, kind: "search_engine", name: "ClaudeBot" },
27
- { pattern: /CCBot/i, kind: "search_engine", name: "Common Crawl" },
28
- { pattern: /anthropic-ai/i, kind: "search_engine", name: "Anthropic" },
29
- { pattern: /PerplexityBot/i, kind: "search_engine", name: "PerplexityBot" },
30
- // Social crawlers
31
- { pattern: /facebookexternalhit/i, kind: "social_crawler", name: "Facebook" },
32
- { pattern: /Facebot/i, kind: "social_crawler", name: "Facebook" },
33
- { pattern: /Twitterbot/i, kind: "social_crawler", name: "Twitter" },
34
- { pattern: /LinkedInBot/i, kind: "social_crawler", name: "LinkedIn" },
35
- { pattern: /Slackbot/i, kind: "social_crawler", name: "Slack" },
36
- { pattern: /Discordbot/i, kind: "social_crawler", name: "Discord" },
37
- { pattern: /TelegramBot/i, kind: "social_crawler", name: "Telegram" },
38
- { pattern: /WhatsApp/i, kind: "social_crawler", name: "WhatsApp" },
39
- { pattern: /Pinterestbot/i, kind: "social_crawler", name: "Pinterest" },
40
- { pattern: /Snapchat/i, kind: "social_crawler", name: "Snapchat" },
41
- // Headless / automation
42
- { pattern: /HeadlessChrome/i, kind: "headless", name: "Headless Chrome" },
43
- { pattern: /PhantomJS/i, kind: "headless", name: "PhantomJS" },
44
- { pattern: /Selenium/i, kind: "automation", name: "Selenium" },
45
- { pattern: /Puppeteer/i, kind: "automation", name: "Puppeteer" },
46
- // HTTP libraries
47
- { pattern: /curl\//i, kind: "library", name: "curl" },
48
- { pattern: /Wget\//i, kind: "library", name: "Wget" },
49
- { pattern: /python-requests/i, kind: "library", name: "Python Requests" },
50
- { pattern: /python-urllib/i, kind: "library", name: "Python urllib" },
51
- { pattern: /node-fetch/i, kind: "library", name: "node-fetch" },
52
- { pattern: /axios\//i, kind: "library", name: "Axios" },
53
- { pattern: /Go-http-client/i, kind: "library", name: "Go HTTP" },
54
- { pattern: /Java\//i, kind: "library", name: "Java HTTP" },
55
- { pattern: /libwww-perl/i, kind: "library", name: "Perl LWP" },
56
- { pattern: /Apache-HttpClient/i, kind: "library", name: "Apache HttpClient" },
57
- { pattern: /okhttp/i, kind: "library", name: "OkHttp" },
58
- { pattern: /Scrapy/i, kind: "library", name: "Scrapy" },
59
- // Generic catch-all (must be last)
60
- { pattern: /bot|crawl|spider|slurp|fetch|archiver/i, kind: "unknown_bot", name: "generic" }
61
- ];
2
+ var BOT_UA_PATTERN = /Google-InspectionTool|Mediapartners-Google|Sogou|ChatGPT-User|anthropic-ai|facebookexternalhit|WhatsApp|Snapchat|HeadlessChrome|PhantomJS|Selenium|Puppeteer|curl\/|Wget\/|python-requests|python-urllib|axios\/|Go-http-client|Java\/|libwww-perl|Apache-HttpClient|okhttp|Scrapy|bot|crawl|spider|slurp|fetch|archiver/i;
62
3
  var AUTOMATION_GLOBALS = [
63
4
  // Selenium
64
5
  "__selenium_unwrapped",
@@ -92,14 +33,10 @@ var AUTOMATION_GLOBALS = [
92
33
  ];
93
34
  function detectBot() {
94
35
  const signals = collectBotSignals();
95
- const isBot = signals.userAgentBot !== null || signals.webdriver || signals.headless || signals.automationGlobals.length > 0 || signals.liesDetected > 2 || signals.liesDetected > 0 && signals.hasProxy;
36
+ const isBot = signals.userAgentBot || signals.webdriver || signals.headless || signals.automationGlobals.length > 0 || signals.liesDetected > 2 || signals.liesDetected > 0 && signals.hasProxy;
96
37
  let botKind = "human";
97
38
  if (isBot) {
98
- if (signals.userAgentBot !== null) {
99
- const ua = navigator.userAgent || "";
100
- const match = BOT_PATTERNS.find((p) => p.pattern.test(ua));
101
- botKind = match?.kind ?? "unknown_bot";
102
- } else if (signals.headless) {
39
+ if (signals.headless) {
103
40
  botKind = "headless";
104
41
  } else if (signals.webdriver || signals.automationGlobals.length > 0) {
105
42
  botKind = "automation";
@@ -122,11 +59,8 @@ function collectBotSignals() {
122
59
  }
123
60
  function detectUserAgentBot() {
124
61
  const ua = navigator.userAgent || "";
125
- if (!ua) return "empty-ua";
126
- for (const { pattern, name } of BOT_PATTERNS) {
127
- if (pattern.test(ua)) return name;
128
- }
129
- return null;
62
+ if (!ua) return true;
63
+ return BOT_UA_PATTERN.test(ua);
130
64
  }
131
65
  function detectWebdriver() {
132
66
  return !!navigator.webdriver;
@@ -160,48 +94,34 @@ function detectLies() {
160
94
  let liesDetected = 0;
161
95
  let hasProxy = false;
162
96
  const apisToTest = [
163
- ["Navigator.prototype.userAgent", () => desc(Navigator.prototype, "userAgent")],
164
- ["Navigator.prototype.languages", () => desc(Navigator.prototype, "languages")],
165
- ["Navigator.prototype.platform", () => desc(Navigator.prototype, "platform")],
166
- ["Navigator.prototype.hardwareConcurrency", () => desc(Navigator.prototype, "hardwareConcurrency")],
167
- ["Navigator.prototype.webdriver", () => desc(Navigator.prototype, "webdriver")],
168
- ["HTMLCanvasElement.prototype.toDataURL", () => HTMLCanvasElement.prototype.toDataURL],
169
- ["CanvasRenderingContext2D.prototype.fillText", () => CanvasRenderingContext2D.prototype.fillText],
170
- ["Date.prototype.getTimezoneOffset", () => Date.prototype.getTimezoneOffset]
97
+ [Navigator.prototype, "userAgent"],
98
+ [Navigator.prototype, "languages"],
99
+ [Navigator.prototype, "platform"],
100
+ [Navigator.prototype, "hardwareConcurrency"],
101
+ [Navigator.prototype, "webdriver"],
102
+ [HTMLCanvasElement.prototype, "toDataURL"],
103
+ [CanvasRenderingContext2D.prototype, "fillText"],
104
+ [Date.prototype, "getTimezoneOffset"]
171
105
  ];
172
- for (const [name, accessor] of apisToTest) {
106
+ for (const [proto, prop] of apisToTest) {
173
107
  try {
174
- const val = accessor();
175
- if (val === void 0 || val === null) continue;
176
- if (typeof val === "function") {
177
- const str = Function.prototype.toString.call(val);
178
- if (!isNativeToString(str)) liesDetected++;
179
- }
180
- if (name.includes(".prototype.") && typeof val !== "function") {
181
- const parts = name.split(".");
182
- const proto = safeProto(parts[0]);
183
- const prop = parts[parts.length - 1];
184
- if (proto) {
185
- const d = Object.getOwnPropertyDescriptor(proto, prop);
186
- if (d?.get) {
187
- const gs = Function.prototype.toString.call(d.get);
188
- if (!isNativeToString(gs)) liesDetected++;
189
- }
190
- }
191
- }
192
- if (typeof val === "function") {
193
- if (val.toString !== Function.prototype.toString) {
194
- try {
195
- const native = Function.prototype.toString.call(val);
196
- const custom = val.toString();
197
- if (native !== custom) {
198
- liesDetected++;
199
- hasProxy = true;
200
- }
201
- } catch {
108
+ const d = Object.getOwnPropertyDescriptor(proto, prop);
109
+ if (!d) continue;
110
+ const fn = d.value ?? d.get;
111
+ if (typeof fn !== "function") continue;
112
+ const str = Function.prototype.toString.call(fn);
113
+ if (!isNativeToString(str)) liesDetected++;
114
+ if (d.value && fn.toString !== Function.prototype.toString) {
115
+ try {
116
+ const native = Function.prototype.toString.call(fn);
117
+ const custom = fn.toString();
118
+ if (native !== custom) {
202
119
  liesDetected++;
203
120
  hasProxy = true;
204
121
  }
122
+ } catch {
123
+ liesDetected++;
124
+ hasProxy = true;
205
125
  }
206
126
  }
207
127
  } catch {
@@ -225,16 +145,6 @@ function detectMissingPlugins() {
225
145
  function isNativeToString(str) {
226
146
  return /^function\s[^{]*\{\s*\[native code\]\s*\}$/.test(str) || str === "function () { [native code] }" || /^\(\)\s*=>\s*\{\s*\[native code\]\s*\}$/.test(str);
227
147
  }
228
- function desc(proto, prop) {
229
- return Object.getOwnPropertyDescriptor(proto, prop);
230
- }
231
- function safeProto(name) {
232
- try {
233
- return window[name]?.prototype ?? null;
234
- } catch {
235
- return null;
236
- }
237
- }
238
148
 
239
149
  // src/utils/environment.ts
240
150
  var getEnvironment = () => ({
@@ -279,144 +189,33 @@ var mergeConfig = (userConfig = {}) => {
279
189
  return { ...defaultConfig, ...userConfig, hostname, devmode };
280
190
  };
281
191
 
282
- // src/utils/create-modal.ts
283
- var createDebugModal = (debugUrl, analyticsUrl) => {
284
- if (!document.getElementById("onedollatstats-modal-styles")) {
285
- const style = document.createElement("style");
286
- style.id = "onedollatstats-modal-styles";
287
- style.textContent = CSS;
288
- document.head.appendChild(style);
289
- }
290
- const modal = document.createElement("div");
291
- modal.className = "dev-modal";
292
- modal.innerHTML = `
293
- <button class="close-btn">&times;</button>
294
- <p class="title">onedollarstats debug window</p>
295
- <p>${icons.info}<span class="text">Tracking localhost as ${debugUrl}</span></p>
296
- <div id="event-log" style="max-height: 100px; overflow-y: auto;"></div>
297
- `;
298
- document.body.appendChild(modal);
299
- modal.querySelector(".close-btn")?.addEventListener("click", () => modal.remove(), { once: true });
300
- if (analyticsUrl === defaultConfig.collectorUrl) {
301
- const img = new Image(1, 1);
302
- img.onerror = () => {
303
- if (modal.querySelector("#ad-blocker-warning")) return;
304
- const warning = document.createElement("p");
305
- warning.id = "ad-blocker-warning";
306
- warning.innerHTML = `${icons.warning}<span class="text">Health check failed - ad blocker might be interfering.</span>`;
307
- modal.querySelector(".title")?.insertAdjacentElement("afterend", warning);
308
- };
309
- img.src = "https://collector.onedollarstats.com/pixel-health";
310
- }
311
- return (message, success) => {
312
- const logContainer = modal.querySelector("#event-log");
313
- if (!logContainer || modal.querySelector("#ad-blocker-warning")) return;
314
- const entry = document.createElement("p");
315
- entry.innerHTML = `${success ? icons.success : icons.error}<span class="text">${message}</span>`;
316
- logContainer.appendChild(entry);
317
- logContainer.scrollTop = logContainer.scrollHeight;
318
- };
319
- };
320
- var icons = {
321
- info: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="gray" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>`,
322
- success: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="green" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>`,
323
- error: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="red" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>`,
324
- warning: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="orange" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>`
325
- };
326
- var CSS = `
327
- .dev-modal {
328
- position: fixed;
329
- bottom: 20px;
330
- right: 20px;
331
- background: #f6f6f7;
332
- color: #21272F;
333
- padding: 14px;
334
- border-radius: 8px;
335
- max-width: 340px;
336
- max-height: 180px;
337
- box-shadow: 0 5px 20px rgba(0,0,0,0.3);
338
- font-family: sans-serif;
339
- z-index: 99999;
340
- animation: slideIn 0.3s ease-out;
341
- }
342
-
343
- .dev-modal .title {
344
- text-transform: uppercase;
345
- font-size: 11px;
346
- font-weight: 500;
347
- margin: 0 0 6px 0;
348
- letter-spacing: 0.5px;
349
- }
350
-
351
- .dev-modal p {
352
- margin: 4px 0;
353
- font-size: 14px;
354
- display: flex;
355
- align-items: flex-start;
356
- gap: 4px;
357
- }
358
-
359
- .dev-modal .text {
360
- word-break: break-word;
361
- }
362
-
363
- .dev-modal p svg {
364
- flex-shrink: 0;
365
- width: 18px;
366
- height: 18px;
367
- margin-top: 1px;
368
- }
369
-
370
- .dev-modal .close-btn {
371
- position: absolute;
372
- top: 2px;
373
- right: 8px;
374
- background: transparent;
375
- border: none;
376
- cursor: pointer;
377
- font-size: 14px;
378
- font-weight: bold;
379
- color: #333;
380
- }
381
-
382
- @keyframes slideIn {
383
- from {
384
- opacity: 0;
385
- transform: translateX(100%);
386
- }
387
- to {
388
- opacity: 1;
389
- transform: translateX(0);
390
- }
391
- }`;
392
-
393
192
  // src/utils/parse-utm-params.ts
394
- var parseUtmParams = (urlSearchParams) => {
193
+ function parseUtmParams(urlSearchParams) {
395
194
  const utm = {};
396
195
  const keys = ["utm_campaign", "utm_source", "utm_medium", "utm_term", "utm_content"];
397
196
  for (const key of keys) {
398
197
  const raw = urlSearchParams.get(key);
399
198
  if (!raw) continue;
400
- const decoded = recursiveDecode(raw).trim();
199
+ const decoded = decodeAndTrim(raw);
401
200
  if (decoded) {
402
201
  utm[key] = decoded;
403
202
  }
404
203
  }
405
204
  return utm;
406
- };
407
- var recursiveDecode = (value) => {
408
- let current = value;
409
- try {
410
- let decoded = decodeURIComponent(current);
411
- while (decoded !== current) {
412
- current = decoded;
413
- decoded = decodeURIComponent(current);
205
+ }
206
+ function decodeAndTrim(value) {
207
+ let decoded = value;
208
+ let previous = "";
209
+ while (decoded !== previous) {
210
+ previous = decoded;
211
+ try {
212
+ decoded = decodeURIComponent(decoded);
213
+ } catch {
214
+ return decoded.trim();
414
215
  }
415
- return current;
416
- } catch {
417
- return value;
418
216
  }
419
- };
217
+ return decoded.trim();
218
+ }
420
219
 
421
220
  // src/utils/props-parser.ts
422
221
  var parseProps = (propsString) => {
@@ -436,7 +235,8 @@ var resolvePath = (pathOrProps) => {
436
235
  if (pathOrProps) return pathOrProps;
437
236
  const sources = [
438
237
  { value: document.body?.getAttribute("data-s-path"), name: "data-s-path" },
439
- { value: document.body?.getAttribute("data-s:path"), name: "data-s:path" }
238
+ { value: document.body?.getAttribute("data-s:path"), name: "data-s:path" },
239
+ { value: document.querySelector('meta[name="stonks-path"]')?.getAttribute("content"), name: "meta[stonks-path]" }
440
240
  ];
441
241
  const existing = sources.filter(({ value }) => value);
442
242
  if (existing.length > 1) {
@@ -461,17 +261,23 @@ var _AnalyticsTracker = class _AnalyticsTracker {
461
261
  constructor(userConfig = {}) {
462
262
  this.autocollectSetupDone = false;
463
263
  this.lastPage = null;
464
- this.modalLog = () => {
465
- };
466
264
  this.config = mergeConfig(userConfig);
467
265
  if (!isClient()) return;
468
266
  const { isLocalhost } = getEnvironment();
469
267
  if (isLocalhost && this.config.devmode && this.config.hostname) {
470
268
  console.log(`[onedollarstats]
471
269
  OneDollarStats connected! Tracking localhost as ${this.config.hostname}`);
472
- this.modalLog = createDebugModal(this.config.hostname, this.config.collectorUrl);
270
+ window.__stonksDebugConfig = { hostname: this.config.hostname, collectorUrl: this.config.collectorUrl };
271
+ window.__stonksModalQueue = [];
272
+ window.__stonksModalReady = false;
273
+ const debugScript = document.createElement("script");
274
+ debugScript.src = "https://assets.onedollarstats.com/stonks-debug.js";
275
+ debugScript.onerror = () => {
276
+ window.__stonksModalReady = true;
277
+ };
278
+ document.head.appendChild(debugScript);
473
279
  }
474
- if (this.config.autocollect) this.setupAutocollect();
280
+ this.setupAutocollect();
475
281
  }
476
282
  static getInstance(userConfig = {}) {
477
283
  if (!isClient()) return new _AnalyticsTracker(userConfig);
@@ -547,7 +353,14 @@ UTM: ${data.utm}`;
547
353
  const payloadBase64 = btoa(bin);
548
354
  const safeGetThreshold = 1500;
549
355
  const tryImageBeacon = payloadBase64.length <= safeGetThreshold;
550
- const onComplete = (success) => this.modalLog(`${data.type} ${success ? "sent" : "failed to send"}`, success);
356
+ const onComplete = (success) => {
357
+ const message = `${data.type} ${success ? "sent" : "failed to send"}`;
358
+ if (window.__stonksModalReady) {
359
+ window.__stonksModalLog?.(message, success);
360
+ } else {
361
+ window.__stonksModalQueue?.push([message, success]);
362
+ }
363
+ };
551
364
  if (tryImageBeacon) {
552
365
  const img = new Image(1, 1);
553
366
  img.onload = () => onComplete(true);
@@ -559,17 +372,22 @@ UTM: ${data.utm}`;
559
372
  trackPageView({ path, props }, checkBlock = false) {
560
373
  if (!isClient()) return;
561
374
  const viewPath = resolvePath(path);
562
- const viewProps = props || (() => {
563
- const newProps = {};
564
- const elements = document.querySelectorAll("[data-s\\:view-props], [data-s-view-props]");
565
- for (const el of Array.from(elements)) {
566
- const propsString = el.getAttribute("data-s-view-props") || el.getAttribute("data-s:view-props");
567
- if (!propsString) continue;
568
- const parsedProps = parseProps(propsString);
569
- Object.assign(newProps, parsedProps);
570
- }
571
- return Object.keys(newProps).length ? newProps : void 0;
572
- })();
375
+ const collectedProps = {};
376
+ const elements = document.querySelectorAll("[data-s\\:view-props], [data-s-view-props]");
377
+ for (const el of Array.from(elements)) {
378
+ const propsString = el.getAttribute("data-s-view-props") || el.getAttribute("data-s:view-props");
379
+ if (!propsString) continue;
380
+ const parsedProps = parseProps(propsString);
381
+ Object.assign(collectedProps, parsedProps);
382
+ }
383
+ const metaViewProps = document.querySelector('meta[name="stonks-props"]')?.getAttribute("content");
384
+ if (metaViewProps) {
385
+ Object.assign(collectedProps, parseProps(metaViewProps));
386
+ }
387
+ if (props) {
388
+ Object.assign(collectedProps, props);
389
+ }
390
+ const viewProps = Object.keys(collectedProps).length > 0 ? collectedProps : void 0;
573
391
  if (!this.config.hashRouting && this.lastPage === viewPath) return;
574
392
  if (checkBlock && !shouldTrackPath(viewPath, this.config)) return;
575
393
  this.lastPage = viewPath;
@@ -622,7 +440,19 @@ UTM: ${data.utm}`;
622
440
  setupAutocollect() {
623
441
  if (!isClient() || this.autocollectSetupDone) return;
624
442
  this.autocollectSetupDone = true;
625
- const handlePageView = () => this.trackPageView({}, true);
443
+ const handlePageView = () => {
444
+ const metaCollect = document.querySelector('meta[name="stonks-collect"]')?.getAttribute("content");
445
+ const bodyCollect = document.body?.getAttribute("data-s-collect") || document.body?.getAttribute("data-s:collect");
446
+ if (metaCollect === "false" || bodyCollect === "false") {
447
+ this.lastPage = null;
448
+ return;
449
+ }
450
+ if (!this.config.autocollect && metaCollect !== "true" && bodyCollect !== "true") {
451
+ this.lastPage = null;
452
+ return;
453
+ }
454
+ this.trackPageView({}, true);
455
+ };
626
456
  const onVisibility = () => {
627
457
  if (document.visibilityState === "visible") handlePageView();
628
458
  };
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/utils/bot.ts", "../src/utils/environment.ts", "../src/utils/merge-config.ts", "../src/utils/create-modal.ts", "../src/utils/parse-utm-params.ts", "../src/utils/props-parser.ts", "../src/utils/resolve-path.ts", "../src/utils/should-track.ts", "../src/index.ts"],
4
- "sourcesContent": ["/**\n * Bot & crawler detection \u2014 standalone module.\n *\n * Usage:\n * import { detectBot } from '@aspect/fingerprint/bot'\n * const result = detectBot()\n * if (result.isBot) console.log(result.botKind, result.signals)\n *\n * Detects:\n * - Known search engine crawlers (Googlebot, Bingbot, Yandex, Baidu, etc.)\n * - Social media crawlers (Facebook, Twitter, LinkedIn, etc.)\n * - Headless browsers (Puppeteer, Playwright, Selenium, PhantomJS)\n * - General automation tools via navigator.webdriver and injected globals\n * - API tampering / lie detection (prototype spoofing, proxy wrapping)\n *\n * Zero dependencies \u2014 can be imported and used independently of the\n * fingerprinting library.\n */\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type BotKind =\n | 'search_engine' // Googlebot, Bingbot, Yandex, Baidu, DuckDuckBot, etc.\n | 'social_crawler' // Facebook, Twitter/X, LinkedIn, Slack, Discord, etc.\n | 'headless' // Headless Chrome/Firefox, PhantomJS\n | 'automation' // Selenium, Puppeteer, Playwright (non-headless)\n | 'library' // curl, wget, python-requests, node-fetch, etc.\n | 'unknown_bot' // Signals say bot, but can't classify further\n | 'human' // No bot signals detected\n\nexport interface BotSignals {\n /** navigator.userAgent matched a known bot pattern. */\n userAgentBot: string | null\n /** navigator.webdriver is true. */\n webdriver: boolean\n /** Headless browser indicators detected. */\n headless: boolean\n /** Automation globals found on window. */\n automationGlobals: string[]\n /** Number of API lies (toString/proxy tampering) detected. */\n liesDetected: number\n /** Proxy wrapping detected on native functions. */\n hasProxy: boolean\n /** navigator.languages is empty or missing. */\n missingLanguages: boolean\n /** navigator.plugins is empty (non-mobile). */\n missingPlugins: boolean\n}\n\nexport interface BotDetectionResult {\n /** True when any bot signal fires. */\n isBot: boolean\n /** Classified category of the detected bot. */\n botKind: BotKind\n /** Individual signal results for debugging / logging. */\n signals: BotSignals\n}\n\n// ---------------------------------------------------------------------------\n// Known bot UA patterns\n// ---------------------------------------------------------------------------\n\ninterface BotPattern {\n pattern: RegExp\n kind: BotKind\n name: string\n}\n\nconst BOT_PATTERNS: BotPattern[] = [\n // Search engines\n { pattern: /Googlebot/i, kind: 'search_engine', name: 'Googlebot' },\n { pattern: /Google-InspectionTool/i, kind: 'search_engine', name: 'Googlebot' },\n { pattern: /Storebot-Google/i, kind: 'search_engine', name: 'Googlebot' },\n { pattern: /AdsBot-Google/i, kind: 'search_engine', name: 'Google Ads' },\n { pattern: /Mediapartners-Google/i, kind: 'search_engine', name: 'Google Adsense' },\n { pattern: /bingbot/i, kind: 'search_engine', name: 'Bingbot' },\n { pattern: /msnbot/i, kind: 'search_engine', name: 'MSNBot' },\n { pattern: /YandexBot/i, kind: 'search_engine', name: 'YandexBot' },\n { pattern: /YandexAccessibilityBot/i, kind: 'search_engine', name: 'YandexBot' },\n { pattern: /Baiduspider/i, kind: 'search_engine', name: 'Baidu' },\n { pattern: /DuckDuckBot/i, kind: 'search_engine', name: 'DuckDuckBot' },\n { pattern: /Sogou/i, kind: 'search_engine', name: 'Sogou' },\n { pattern: /Exabot/i, kind: 'search_engine', name: 'Exabot' },\n { pattern: /ia_archiver/i, kind: 'search_engine', name: 'Alexa' },\n { pattern: /SemrushBot/i, kind: 'search_engine', name: 'SemrushBot' },\n { pattern: /AhrefsBot/i, kind: 'search_engine', name: 'AhrefsBot' },\n { pattern: /MJ12bot/i, kind: 'search_engine', name: 'MJ12bot' },\n { pattern: /DotBot/i, kind: 'search_engine', name: 'DotBot' },\n { pattern: /PetalBot/i, kind: 'search_engine', name: 'PetalBot' },\n { pattern: /Applebot/i, kind: 'search_engine', name: 'Applebot' },\n { pattern: /GPTBot/i, kind: 'search_engine', name: 'GPTBot' },\n { pattern: /ChatGPT-User/i, kind: 'search_engine', name: 'ChatGPT' },\n { pattern: /ClaudeBot/i, kind: 'search_engine', name: 'ClaudeBot' },\n { pattern: /CCBot/i, kind: 'search_engine', name: 'Common Crawl' },\n { pattern: /anthropic-ai/i, kind: 'search_engine', name: 'Anthropic' },\n { pattern: /PerplexityBot/i, kind: 'search_engine', name: 'PerplexityBot' },\n\n // Social crawlers\n { pattern: /facebookexternalhit/i, kind: 'social_crawler', name: 'Facebook' },\n { pattern: /Facebot/i, kind: 'social_crawler', name: 'Facebook' },\n { pattern: /Twitterbot/i, kind: 'social_crawler', name: 'Twitter' },\n { pattern: /LinkedInBot/i, kind: 'social_crawler', name: 'LinkedIn' },\n { pattern: /Slackbot/i, kind: 'social_crawler', name: 'Slack' },\n { pattern: /Discordbot/i, kind: 'social_crawler', name: 'Discord' },\n { pattern: /TelegramBot/i, kind: 'social_crawler', name: 'Telegram' },\n { pattern: /WhatsApp/i, kind: 'social_crawler', name: 'WhatsApp' },\n { pattern: /Pinterestbot/i, kind: 'social_crawler', name: 'Pinterest' },\n { pattern: /Snapchat/i, kind: 'social_crawler', name: 'Snapchat' },\n\n // Headless / automation\n { pattern: /HeadlessChrome/i, kind: 'headless', name: 'Headless Chrome' },\n { pattern: /PhantomJS/i, kind: 'headless', name: 'PhantomJS' },\n { pattern: /Selenium/i, kind: 'automation', name: 'Selenium' },\n { pattern: /Puppeteer/i, kind: 'automation', name: 'Puppeteer' },\n\n // HTTP libraries\n { pattern: /curl\\//i, kind: 'library', name: 'curl' },\n { pattern: /Wget\\//i, kind: 'library', name: 'Wget' },\n { pattern: /python-requests/i, kind: 'library', name: 'Python Requests' },\n { pattern: /python-urllib/i, kind: 'library', name: 'Python urllib' },\n { pattern: /node-fetch/i, kind: 'library', name: 'node-fetch' },\n { pattern: /axios\\//i, kind: 'library', name: 'Axios' },\n { pattern: /Go-http-client/i, kind: 'library', name: 'Go HTTP' },\n { pattern: /Java\\//i, kind: 'library', name: 'Java HTTP' },\n { pattern: /libwww-perl/i, kind: 'library', name: 'Perl LWP' },\n { pattern: /Apache-HttpClient/i, kind: 'library', name: 'Apache HttpClient' },\n { pattern: /okhttp/i, kind: 'library', name: 'OkHttp' },\n { pattern: /Scrapy/i, kind: 'library', name: 'Scrapy' },\n\n // Generic catch-all (must be last)\n { pattern: /bot|crawl|spider|slurp|fetch|archiver/i, kind: 'unknown_bot', name: 'generic' },\n]\n\n// ---------------------------------------------------------------------------\n// Automation globals injected by common frameworks\n// ---------------------------------------------------------------------------\n\nconst AUTOMATION_GLOBALS = [\n // Selenium\n '__selenium_unwrapped',\n '__selenium_evaluate',\n '__webdriver_evaluate',\n '__webdriver_script_fn',\n '__webdriver_script_func',\n '__webdriver_script_function',\n '__fxdriver_evaluate',\n '__fxdriver_unwrapped',\n '_Selenium_IDE_Recorder',\n // Puppeteer / CDP\n '__puppeteer_evaluation_script__',\n // PhantomJS\n 'callPhantom',\n '_phantom',\n 'phantom',\n // Nightmare.js\n '__nightmare',\n // Playwright (injects page.exposeFunction bindings)\n '__playwright',\n '__pw_manual',\n // CasperJS\n '__casper',\n // TestCafe\n '__testcafe',\n // WebDriver (generic)\n 'webdriver',\n 'domAutomation',\n 'domAutomationController',\n] as const\n\n// ---------------------------------------------------------------------------\n// Core detection\n// ---------------------------------------------------------------------------\n\n/**\n * Detect whether the current browser context is a bot or crawler.\n * Synchronous and lightweight \u2014 no async APIs needed.\n */\nexport function detectBot(): BotDetectionResult {\n const signals = collectBotSignals()\n\n const isBot =\n signals.userAgentBot !== null ||\n signals.webdriver ||\n signals.headless ||\n signals.automationGlobals.length > 0 ||\n signals.liesDetected > 2 ||\n (signals.liesDetected > 0 && signals.hasProxy)\n\n let botKind: BotKind = 'human'\n if (isBot) {\n if (signals.userAgentBot !== null) {\n // Find the matching pattern to determine kind\n const ua = navigator.userAgent || ''\n const match = BOT_PATTERNS.find(p => p.pattern.test(ua))\n botKind = match?.kind ?? 'unknown_bot'\n } else if (signals.headless) {\n botKind = 'headless'\n } else if (signals.webdriver || signals.automationGlobals.length > 0) {\n botKind = 'automation'\n } else {\n botKind = 'unknown_bot'\n }\n }\n\n return { isBot, botKind, signals }\n}\n\n// ---------------------------------------------------------------------------\n// Signal collectors\n// ---------------------------------------------------------------------------\n\nfunction collectBotSignals(): BotSignals {\n return {\n userAgentBot: detectUserAgentBot(),\n webdriver: detectWebdriver(),\n headless: detectHeadless(),\n automationGlobals: detectAutomationGlobals(),\n ...detectLies(),\n missingLanguages: detectMissingLanguages(),\n missingPlugins: detectMissingPlugins(),\n }\n}\n\n/** Check UA string against known bot patterns. */\nfunction detectUserAgentBot(): string | null {\n const ua = navigator.userAgent || ''\n if (!ua) return 'empty-ua'\n for (const { pattern, name } of BOT_PATTERNS) {\n if (pattern.test(ua)) return name\n }\n return null\n}\n\n/** navigator.webdriver is set by WebDriver-based automation. */\nfunction detectWebdriver(): boolean {\n return !!(navigator as any).webdriver\n}\n\n/** Headless browser indicators. */\nfunction detectHeadless(): boolean {\n const w = window as any\n const n = navigator as any\n\n // Chrome-specific: headless mode omits the chrome runtime object\n if (/Chrome/.test(n.userAgent) && !w.chrome) return true\n\n // HeadlessChrome in UA\n if (/HeadlessChrome/.test(n.userAgent)) return true\n\n // Notification permission is always \"denied\" in headless Chrome\n // (in headed mode it defaults to \"default\")\n try {\n if (Notification.permission === 'denied' && n.permissions) {\n // Double-check: headless also lacks plugins\n if ((!n.plugins || n.plugins.length === 0) && !/Mobile|Android/i.test(n.userAgent)) {\n return true\n }\n }\n } catch { /* permissions API unavailable */ }\n\n return false\n}\n\n/** Detect automation framework globals on window. */\nfunction detectAutomationGlobals(): string[] {\n const w = window as any\n return AUTOMATION_GLOBALS.filter(key => {\n try { return key in w && w[key] !== undefined }\n catch { return false }\n }) as string[]\n}\n\n/**\n * Lie detection \u2014 detect API tampering (proxies, toString spoofing).\n * Extracted from the fingerprint lies module; self-contained here.\n */\nfunction detectLies(): { liesDetected: number; hasProxy: boolean } {\n let liesDetected = 0\n let hasProxy = false\n\n const apisToTest: Array<[string, () => unknown]> = [\n ['Navigator.prototype.userAgent', () => desc(Navigator.prototype, 'userAgent')],\n ['Navigator.prototype.languages', () => desc(Navigator.prototype, 'languages')],\n ['Navigator.prototype.platform', () => desc(Navigator.prototype, 'platform')],\n ['Navigator.prototype.hardwareConcurrency', () => desc(Navigator.prototype, 'hardwareConcurrency')],\n ['Navigator.prototype.webdriver', () => desc(Navigator.prototype, 'webdriver')],\n ['HTMLCanvasElement.prototype.toDataURL', () => HTMLCanvasElement.prototype.toDataURL],\n ['CanvasRenderingContext2D.prototype.fillText', () => CanvasRenderingContext2D.prototype.fillText],\n ['Date.prototype.getTimezoneOffset', () => Date.prototype.getTimezoneOffset],\n ]\n\n for (const [name, accessor] of apisToTest) {\n try {\n const val = accessor()\n if (val === undefined || val === null) continue\n\n // toString format check\n if (typeof val === 'function') {\n const str = Function.prototype.toString.call(val)\n if (!isNativeToString(str)) liesDetected++\n }\n\n // Getter integrity check\n if (name.includes('.prototype.') && typeof val !== 'function') {\n const parts = name.split('.')\n const proto = safeProto(parts[0])\n const prop = parts[parts.length - 1]\n if (proto) {\n const d = Object.getOwnPropertyDescriptor(proto, prop)\n if (d?.get) {\n const gs = Function.prototype.toString.call(d.get)\n if (!isNativeToString(gs)) liesDetected++\n }\n }\n }\n\n // Proxy detection\n if (typeof val === 'function') {\n if (val.toString !== Function.prototype.toString) {\n try {\n const native = Function.prototype.toString.call(val)\n const custom = val.toString()\n if (native !== custom) { liesDetected++; hasProxy = true }\n } catch { liesDetected++; hasProxy = true }\n }\n }\n } catch { /* skip inaccessible */ }\n }\n\n // Meta-test: toString integrity\n try {\n const s = Function.prototype.toString.call(Function.prototype.toString)\n if (!isNativeToString(s)) liesDetected++\n } catch { /* skip */ }\n\n return { liesDetected, hasProxy }\n}\n\n/** Missing navigator.languages is a strong headless indicator. */\nfunction detectMissingLanguages(): boolean {\n const langs = navigator.languages\n return !langs || langs.length === 0\n}\n\n/** Missing plugins on desktop is a headless indicator. */\nfunction detectMissingPlugins(): boolean {\n if (/Mobile|Android/i.test(navigator.userAgent)) return false\n return !navigator.plugins || navigator.plugins.length === 0\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction isNativeToString(str: string): boolean {\n return /^function\\s[^{]*\\{\\s*\\[native code\\]\\s*\\}$/.test(str) ||\n str === 'function () { [native code] }' ||\n /^\\(\\)\\s*=>\\s*\\{\\s*\\[native code\\]\\s*\\}$/.test(str)\n}\n\nfunction desc(proto: object, prop: string) {\n return Object.getOwnPropertyDescriptor(proto, prop)\n}\n\nfunction safeProto(name: string): object | null {\n try { return (window as any)[name]?.prototype ?? null }\n catch { return null }\n}\n", "export const getEnvironment = (): {\n isLocalhost: boolean;\n isHeadlessBrowser: boolean;\n} => ({\n isLocalhost:\n (/^localhost$|^127(\\.[0-9]+){0,2}\\.[0-9]+$|^\\[::1?\\]$/.test(location.hostname) &&\n (location.protocol === \"http:\" || location.protocol === \"https:\")) ||\n location.protocol === \"file:\",\n isHeadlessBrowser: Boolean(\n window.navigator.webdriver ||\n (\"_phantom\" in window && window._phantom) ||\n (\"__nightmare\" in window && window.__nightmare) ||\n (\"Cypress\" in window && window.Cypress)\n )\n});\nexport const isClient = (): boolean => {\n try {\n // Basic checks for window and document\n if (typeof window === \"undefined\" || typeof document === \"undefined\") return false;\n\n // Check for navigator safely\n const ua = typeof navigator !== \"undefined\" ? navigator.userAgent : \"\";\n if (/node|jsdom/i.test(ua)) return false;\n return true;\n } catch {\n return false;\n }\n};\n", "import type { AnalyticsConfig, InternalAnalyticsConfig } from \"../types\";\nimport { getEnvironment } from \"./environment\";\n\nexport const defaultConfig: InternalAnalyticsConfig = {\n hostname: null,\n devmode: false,\n collectorUrl: \"https://collector.onedollarstats.com/events\",\n hashRouting: false,\n autocollect: true,\n excludePages: [],\n includePages: []\n};\n\nexport const mergeConfig = (userConfig: AnalyticsConfig = {}): InternalAnalyticsConfig => {\n const { isLocalhost } = getEnvironment();\n\n const devmode = !isLocalhost ? false : (userConfig.devmode ?? !!userConfig.trackLocalhostAs);\n\n let hostname: string | null;\n if (userConfig.hostname) {\n const trimmed = userConfig.hostname.trim();\n hostname = trimmed || null;\n } else if (devmode && userConfig.trackLocalhostAs) {\n hostname = userConfig.trackLocalhostAs;\n } else {\n hostname = null;\n }\n\n // Merge default config, user config, and computed values\n return { ...defaultConfig, ...userConfig, hostname, devmode };\n};\n", "import { defaultConfig } from \"./merge-config\";\n\nexport const createDebugModal = (debugUrl: string, analyticsUrl: string) => {\n if (!document.getElementById(\"onedollatstats-modal-styles\")) {\n const style = document.createElement(\"style\");\n style.id = \"onedollatstats-modal-styles\";\n style.textContent = CSS;\n document.head.appendChild(style);\n }\n\n // Create modal\n const modal = document.createElement(\"div\");\n modal.className = \"dev-modal\";\n modal.innerHTML = `\n <button class=\"close-btn\">&times;</button>\n <p class=\"title\">onedollarstats debug window</p>\n <p>${icons.info}<span class=\"text\">Tracking localhost as ${debugUrl}</span></p>\n <div id=\"event-log\" style=\"max-height: 100px; overflow-y: auto;\"></div>\n `;\n document.body.appendChild(modal);\n\n // Close handler\n modal.querySelector(\".close-btn\")?.addEventListener(\"click\", () => modal.remove(), { once: true });\n\n // Health check\n if (analyticsUrl === defaultConfig.collectorUrl) {\n const img = new Image(1, 1);\n img.onerror = () => {\n if (modal.querySelector(\"#ad-blocker-warning\")) return;\n const warning = document.createElement(\"p\");\n warning.id = \"ad-blocker-warning\";\n warning.innerHTML = `${icons.warning}<span class=\"text\">Health check failed - ad blocker might be interfering.</span>`;\n modal.querySelector(\".title\")?.insertAdjacentElement(\"afterend\", warning);\n };\n img.src = \"https://collector.onedollarstats.com/pixel-health\";\n }\n\n // Log rendering function\n return (message: string, success: boolean): void => {\n const logContainer = modal.querySelector(\"#event-log\");\n if (!logContainer || modal.querySelector(\"#ad-blocker-warning\")) return;\n\n const entry = document.createElement(\"p\");\n entry.innerHTML = `${success ? icons.success : icons.error}<span class=\"text\">${message}</span>`;\n logContainer.appendChild(entry);\n logContainer.scrollTop = logContainer.scrollHeight;\n };\n};\n\nconst icons = {\n info: `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"gray\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4\"/><path d=\"M12 8h.01\"/></svg>`,\n success: `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"green\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m9 12 2 2 4-4\"/></svg>`,\n error: `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"red\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"m15 9-6 6\"/><path d=\"m9 9 6 6\"/></svg>`,\n warning: `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"orange\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg>`\n} as const;\n\nconst CSS = `\n .dev-modal {\n position: fixed;\n bottom: 20px;\n right: 20px;\n background: #f6f6f7;\n color: #21272F;\n padding: 14px;\n border-radius: 8px;\n max-width: 340px;\n max-height: 180px;\n box-shadow: 0 5px 20px rgba(0,0,0,0.3);\n font-family: sans-serif;\n z-index: 99999;\n animation: slideIn 0.3s ease-out;\n}\n\n.dev-modal .title {\n text-transform: uppercase;\n font-size: 11px;\n font-weight: 500;\n margin: 0 0 6px 0;\n letter-spacing: 0.5px;\n}\n\n.dev-modal p {\n margin: 4px 0;\n font-size: 14px;\n display: flex;\n align-items: flex-start;\n gap: 4px;\n}\n\n.dev-modal .text {\n word-break: break-word;\n}\n\n.dev-modal p svg {\n flex-shrink: 0;\n width: 18px;\n height: 18px;\n margin-top: 1px;\n}\n\n.dev-modal .close-btn {\n position: absolute;\n top: 2px;\n right: 8px;\n background: transparent;\n border: none;\n cursor: pointer;\n font-size: 14px;\n font-weight: bold;\n color: #333;\n}\n\n@keyframes slideIn {\n from {\n opacity: 0;\n transform: translateX(100%);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}`;\n", "export const parseUtmParams = (urlSearchParams: URLSearchParams) => {\n const utm: Record<string, string> = {};\n const keys = [\"utm_campaign\", \"utm_source\", \"utm_medium\", \"utm_term\", \"utm_content\"] as const;\n\n for (const key of keys) {\n const raw = urlSearchParams.get(key);\n if (!raw) continue;\n\n const decoded = recursiveDecode(raw).trim();\n if (decoded) {\n utm[key] = decoded;\n }\n }\n\n return utm;\n};\n\nconst recursiveDecode = (value: string): string => {\n let current = value;\n\n try {\n let decoded = decodeURIComponent(current);\n\n while (decoded !== current) {\n current = decoded;\n decoded = decodeURIComponent(current);\n }\n\n return current;\n } catch {\n return value;\n }\n};\n", "export const parseProps = (propsString: string): Record<string, string> | undefined => {\n if (!propsString) return undefined;\n // \"key1=value1;key2=value2\"\n\n const splittedProps = propsString.split(\";\");\n const propsObj: Record<string, string> = {};\n\n for (const keyValueString of splittedProps) {\n const keyValuePair = keyValueString.split(\"=\").map((el) => el.trim());\n if (keyValuePair.length !== 2 || keyValuePair[0] === \"\" || keyValuePair[1] === \"\") continue;\n // @ts-ignore\n propsObj[keyValuePair[0]] = keyValuePair[1];\n }\n\n return Object.keys(propsObj).length === 0 ? undefined : propsObj;\n};\n", "export const resolvePath = (pathOrProps?: string): string => {\n if (pathOrProps) return pathOrProps;\n\n const sources = [\n { value: document.body?.getAttribute(\"data-s-path\"), name: \"data-s-path\" },\n { value: document.body?.getAttribute(\"data-s:path\"), name: \"data-s:path\" }\n ];\n\n // Only keep sources that actually exist\n const existing = sources.filter(({ value }) => value);\n\n if (existing.length > 1) {\n console.warn(\"[onedollarstats] Multiple path sources found. Using priority order:\", existing.map(({ name }) => name).join(\" > \"));\n }\n\n // Return first available value, fallback to location.pathname\n return existing[0]?.value ?? location.pathname;\n};\n", "import type { InternalAnalyticsConfig } from \"../types\";\n\nconst matchesPattern = (path: string, pattern: string): boolean => {\n // Escape special regex characters except '*' which becomes '.*'\n const escaped = pattern.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(/\\*/g, \".*\");\n return new RegExp(`^${escaped}$`).test(path);\n};\n\nexport const shouldTrackPath = (path: string, config: InternalAnalyticsConfig): boolean => {\n // Exclude pages first\n if (config.excludePages.some((pattern) => matchesPattern(path, pattern))) return false;\n // If includePages is defined, only allow matching paths\n if (config.includePages.length && !config.includePages.some((pattern) => matchesPattern(path, pattern))) return false;\n return true;\n};\n", "import type { AnalyticsConfig, BaseProps, BodyToSend, Event, InternalAnalyticsConfig, ViewArguments } from \"./types\";\nimport { detectBot } from \"./utils/bot\";\nimport { createDebugModal } from \"./utils/create-modal\";\nimport { getEnvironment, isClient } from \"./utils/environment\";\nimport { mergeConfig } from \"./utils/merge-config\";\nimport { parseUtmParams } from \"./utils/parse-utm-params\";\nimport { parseProps } from \"./utils/props-parser\";\nimport { resolvePath } from \"./utils/resolve-path\";\nimport { shouldTrackPath } from \"./utils/should-track\";\n\nclass AnalyticsTracker {\n private static instance: AnalyticsTracker | null = null;\n\n private autocollectSetupDone = false;\n private config: InternalAnalyticsConfig;\n private lastPage: string | null = null;\n private modalLog: (message: string, success: boolean) => void = () => {};\n\n public static getInstance(userConfig: AnalyticsConfig = {}): AnalyticsTracker {\n // Fresh no-op instance for SSR\n if (!isClient()) return new AnalyticsTracker(userConfig);\n\n if (!AnalyticsTracker.instance) {\n AnalyticsTracker.instance = new AnalyticsTracker(userConfig);\n }\n return AnalyticsTracker.instance;\n }\n\n private constructor(userConfig: AnalyticsConfig = {}) {\n this.config = mergeConfig(userConfig);\n\n // Skip setup in non-client environments\n if (!isClient()) return;\n\n const { isLocalhost } = getEnvironment();\n\n // Debug log on localhost\n if (isLocalhost && this.config.devmode && this.config.hostname) {\n console.log(`[onedollarstats]\\nOneDollarStats connected! Tracking localhost as ${this.config.hostname}`);\n\n this.modalLog = createDebugModal(this.config.hostname, this.config.collectorUrl);\n }\n\n // Auto-start autocollect\n if (this.config.autocollect) this.setupAutocollect();\n }\n\n private async sendWithBeaconOrFetch(stringifiedBody: string, callback: (success: boolean) => void): Promise<void> {\n // First fallback: try sendBeacon\n if (navigator.sendBeacon?.(this.config.collectorUrl, stringifiedBody)) {\n callback(true);\n return;\n }\n\n // Second fallback: use fetch() with keepalive\n fetch(this.config.collectorUrl, {\n method: \"POST\",\n body: stringifiedBody,\n headers: { \"Content-Type\": \"application/json\" },\n keepalive: true\n })\n .then(({ ok }) => callback(ok))\n .catch((err: Error) => {\n console.error(\"[onedollarstats] fetch() failed:\", err.message);\n callback(false);\n });\n }\n\n // Handles localhost replacement, referrer, UTM parameters, and debug mode.\n // Uses img beacon then `navigator.sendBeacon` if available, otherwise falls back to `fetch`.\n private async send(data: Event): Promise<void> {\n const { isLocalhost, isHeadlessBrowser } = getEnvironment();\n if ((isLocalhost && !this.config.devmode) || isHeadlessBrowser) return;\n\n const { isBot, botKind } = detectBot();\n\n if (isBot && botKind !== \"human\") return;\n\n const urlToSend = new URL(this.config.hostname ? `https://${this.config.hostname}${location.pathname}` : location.href);\n\n // Clean query string unless UTM is explicitly provided\n urlToSend.search = \"\";\n if (data.path) urlToSend.pathname = data.path;\n\n const cleanUrl = urlToSend.href.replace(/\\/$/, \"\");\n\n // Determine referrer\n let referrer: string | undefined = data.referrer;\n try {\n if (!referrer && document.referrer && document.referrer !== \"null\") {\n const referrerURL = new URL(document.referrer);\n if (referrerURL.hostname !== urlToSend.hostname) referrer = referrerURL.href;\n }\n } catch {} // ignore malformed referrer\n\n // Build request body\n const body: BodyToSend = {\n u: cleanUrl,\n e: [\n {\n t: data.type,\n h: this.config.hashRouting,\n r: referrer,\n p: data.props\n }\n ],\n debug: this.config.devmode\n };\n\n if (data.utm && Object.keys(data.utm).length > 0) body.qs = data.utm;\n\n if (body.debug) {\n let logMessage = `[onedollarstats]\\nEvent name: ${data.type}\\nEvent collected from: ${cleanUrl}`;\n if (data.props && Object.keys(data.props).length > 0) logMessage += `\\nProps: ${JSON.stringify(data.props, null, 2)}`;\n if (referrer) logMessage += `\\nReferrer: ${referrer}`;\n if (this.config.hashRouting) logMessage += `\\nHashRouting: ${this.config.hashRouting}`;\n if (data.utm && Object.keys(data.utm).length > 0) logMessage += `\\nUTM: ${data.utm}`;\n\n console.log(logMessage);\n }\n\n // Prepare the event payload\n const stringifiedBody = JSON.stringify(body);\n\n // Encode for safe inclusion in a query string (UTF-8 \u2192 Base64)\n const bytes = new TextEncoder().encode(stringifiedBody); // UTF-8 \u2192 bytes\n const bin = String.fromCharCode(...bytes); // bytes \u2192 binary string\n const payloadBase64 = btoa(bin); // binary \u2192 Base64\n\n const safeGetThreshold = 1500; // limit for query-string-containing URLs\n const tryImageBeacon = payloadBase64.length <= safeGetThreshold;\n\n const onComplete = (success: boolean) => this.modalLog(`${data.type} ${success ? \"sent\" : \"failed to send\"}`, success);\n\n if (tryImageBeacon) {\n // Send via image beacon\n const img = new Image(1, 1);\n\n img.onload = () => onComplete(true);\n // If loading image fails (server unavailable, blocked, etc.)\n img.onerror = () => this.sendWithBeaconOrFetch(stringifiedBody, onComplete);\n\n // Primary attempt: send data via image beacon (GET request with query string)\n img.src = `${this.config.collectorUrl}?data=${payloadBase64}`;\n } else await this.sendWithBeaconOrFetch(stringifiedBody, onComplete);\n }\n\n // Prevents duplicate pageviews and respects include/exclude page rules. Automatically parses UTM parameters from URL.\n private trackPageView({ path, props }: ViewArguments, checkBlock: boolean = false) {\n if (!isClient()) return;\n\n const viewPath = resolvePath(path);\n\n const viewProps =\n props ||\n (() => {\n const newProps = {};\n const elements = document.querySelectorAll(\"[data-s\\\\:view-props], [data-s-view-props]\");\n\n for (const el of Array.from(elements)) {\n const propsString = el.getAttribute(\"data-s-view-props\") || el.getAttribute(\"data-s:view-props\");\n if (!propsString) continue;\n const parsedProps = parseProps(propsString);\n Object.assign(newProps, parsedProps);\n }\n\n return Object.keys(newProps).length ? newProps : undefined;\n })();\n\n // Skip duplicate pageviews or excluded pages\n if (!this.config.hashRouting && this.lastPage === viewPath) return;\n\n // Skip page if checkBlock is true and the path should be excluded\n if (checkBlock && !shouldTrackPath(viewPath, this.config)) return;\n\n this.lastPage = viewPath;\n\n const utm = parseUtmParams(new URLSearchParams(location.search));\n this.send({ type: \"PageView\", path: viewPath, props: viewProps, utm });\n }\n\n /**\n * Tracks a custom event.\n * Can accept path string or a props object.\n *\n * @param eventName Name of the event to track.\n * @param pathOrProps Optional path string or props object.\n * @param props Optional props object if path string is provided.\n */\n public async event(eventName: string, pathOrProps?: string | BaseProps, rawProps?: BaseProps) {\n if (!isClient()) return;\n\n const { isLocalhost, isHeadlessBrowser } = getEnvironment();\n if ((isLocalhost && !this.config.devmode) || isHeadlessBrowser) return;\n\n const path = resolvePath(typeof pathOrProps === \"string\" ? pathOrProps : undefined);\n const props = typeof pathOrProps === \"object\" ? pathOrProps : rawProps;\n\n this.send({ type: eventName, path, props });\n }\n\n /**\n * Records a page view.\n * Can accept path string or a props object.\n *\n * @param pathOrProps Optional path string or props object.\n * @param props Optional props when first arg is a path string.\n */\n public async view(pathOrProps?: string | BaseProps, props?: BaseProps) {\n if (!isClient()) return;\n\n const args: ViewArguments = {};\n\n if (typeof pathOrProps === \"string\") {\n args.path = pathOrProps;\n args.props = props;\n } else if (typeof pathOrProps === \"object\") {\n args.props = pathOrProps;\n }\n\n this.trackPageView(args);\n }\n\n /**\n * Installs global DOM/window listeners exactly once for:\n * - visibilitychange\n * - history.pushState\n * - popstate\n * - hashchange\n * - click autocapture for elements annotated with `data-s:event` & `data-s-event`\n *\n */\n private setupAutocollect() {\n if (!isClient() || this.autocollectSetupDone) return;\n this.autocollectSetupDone = true;\n\n const handlePageView = () => this.trackPageView({}, true);\n\n // visibilitychange\n const onVisibility = () => {\n if (document.visibilityState === \"visible\") handlePageView();\n };\n document.addEventListener(\"visibilitychange\", onVisibility);\n\n // pushState\n const origPush = history.pushState.bind(history);\n history.pushState = (...args) => {\n origPush(...args);\n requestAnimationFrame(() => {\n handlePageView();\n });\n };\n\n // popstate\n window.addEventListener(\"popstate\", handlePageView);\n\n // hashchange\n window.addEventListener(\"hashchange\", handlePageView);\n\n // click autocapture\n const onClick: EventListener = (ev: Event) => {\n const clickEvent = ev as MouseEvent;\n if (clickEvent.type === \"auxclick\" && clickEvent.button !== 1) return;\n\n const target = clickEvent.target as Element | null;\n if (!target) return;\n\n // Check if inside <a> or <button>\n const insideInteractive = !!target.closest(\"a, button\");\n\n let el: Element | null = target;\n let depth = 0;\n\n while (el) {\n const eventName = el.getAttribute(\"data-s-event\") || el.getAttribute(\"data-s:event\");\n if (eventName) {\n const propsAttr = el.getAttribute(\"data-s-event-props\") || el.getAttribute(\"data-s:event-props\");\n const props = propsAttr ? parseProps(propsAttr) : undefined;\n const path = el.getAttribute(\"data-s-event-path\") || el.getAttribute(\"data-s:event-path\") || undefined;\n\n if ((path && !shouldTrackPath(path, this.config)) || !shouldTrackPath(location.pathname, this.config)) {\n return;\n }\n\n this.event(eventName, path ?? props, props);\n return;\n }\n\n el = el.parentElement;\n depth++;\n\n // If not in <a>/<button>, stop after 3 levels\n if (!insideInteractive && depth >= 3) break;\n }\n };\n\n document.addEventListener(\"click\", onClick);\n\n // Fire initial pageview if already visible\n if (document.visibilityState === \"visible\") handlePageView();\n }\n}\n\nexport const configure = (userConfig: AnalyticsConfig = {}) => {\n AnalyticsTracker.getInstance(userConfig);\n};\n\nexport const event = async (eventName: string, pathOrProps?: string | BaseProps, props?: BaseProps) => {\n const instance = AnalyticsTracker.getInstance();\n await instance.event(eventName, pathOrProps, props);\n};\n\nexport const view = async (pathOrProps?: string | BaseProps, props?: BaseProps) => {\n const instance = AnalyticsTracker.getInstance();\n await instance.view(pathOrProps, props);\n};\n"],
5
- "mappings": "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",
3
+ "sources": ["../src/utils/bot.ts", "../src/utils/environment.ts", "../src/utils/merge-config.ts", "../src/utils/parse-utm-params.ts", "../src/utils/props-parser.ts", "../src/utils/resolve-path.ts", "../src/utils/should-track.ts", "../src/index.ts"],
4
+ "sourcesContent": ["/**\n * Bot & crawler detection module.\n *\n * Detects:\n * - Known search engine crawlers (Googlebot, Bingbot, Yandex, Baidu, etc.)\n * - Social media crawlers (Facebook, Twitter, LinkedIn, etc.)\n * - Headless browsers (Puppeteer, Playwright, Selenium, PhantomJS)\n * - General automation tools via navigator.webdriver and injected globals\n * - API tampering / lie detection (prototype spoofing, proxy wrapping)\n */\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type BotKind =\n | 'search_engine' // Googlebot, Bingbot, Yandex, Baidu, DuckDuckBot, etc.\n | 'social_crawler' // Facebook, Twitter/X, LinkedIn, Slack, Discord, etc.\n | 'headless' // Headless Chrome/Firefox, PhantomJS\n | 'automation' // Selenium, Puppeteer, Playwright (non-headless)\n | 'library' // curl, wget, python-requests, node-fetch, etc.\n | 'unknown_bot' // Signals say bot, but can't classify further\n | 'human' // No bot signals detected\n\nexport interface BotSignals {\n /** navigator.userAgent matched a known bot pattern. */\n userAgentBot: boolean\n /** navigator.webdriver is true. */\n webdriver: boolean\n /** Headless browser indicators detected. */\n headless: boolean\n /** Automation globals found on window. */\n automationGlobals: string[]\n /** Number of API lies (toString/proxy tampering) detected. */\n liesDetected: number\n /** Proxy wrapping detected on native functions. */\n hasProxy: boolean\n /** navigator.languages is empty or missing. */\n missingLanguages: boolean\n /** navigator.plugins is empty (non-mobile). */\n missingPlugins: boolean\n}\n\nexport interface BotDetectionResult {\n /** True when any bot signal fires. */\n isBot: boolean\n /** Classified category of the detected bot. */\n botKind: BotKind\n /** Individual signal results for debugging / logging. */\n signals: BotSignals\n}\n\n// ---------------------------------------------------------------------------\n// Known bot UA patterns\n// ---------------------------------------------------------------------------\n\n// Single alternation. The generic tokens (bot, crawl, spider, slurp, fetch,\n// archiver) subsume most named crawlers; only names that don't contain one of\n// those tokens are listed explicitly.\nconst BOT_UA_PATTERN =\n /Google-InspectionTool|Mediapartners-Google|Sogou|ChatGPT-User|anthropic-ai|facebookexternalhit|WhatsApp|Snapchat|HeadlessChrome|PhantomJS|Selenium|Puppeteer|curl\\/|Wget\\/|python-requests|python-urllib|axios\\/|Go-http-client|Java\\/|libwww-perl|Apache-HttpClient|okhttp|Scrapy|bot|crawl|spider|slurp|fetch|archiver/i\n\n// ---------------------------------------------------------------------------\n// Automation globals injected by common frameworks\n// ---------------------------------------------------------------------------\n\nconst AUTOMATION_GLOBALS = [\n // Selenium\n '__selenium_unwrapped',\n '__selenium_evaluate',\n '__webdriver_evaluate',\n '__webdriver_script_fn',\n '__webdriver_script_func',\n '__webdriver_script_function',\n '__fxdriver_evaluate',\n '__fxdriver_unwrapped',\n '_Selenium_IDE_Recorder',\n // Puppeteer / CDP\n '__puppeteer_evaluation_script__',\n // PhantomJS\n 'callPhantom',\n '_phantom',\n 'phantom',\n // Nightmare.js\n '__nightmare',\n // Playwright (injects page.exposeFunction bindings)\n '__playwright',\n '__pw_manual',\n // CasperJS\n '__casper',\n // TestCafe\n '__testcafe',\n // WebDriver (generic)\n 'webdriver',\n 'domAutomation',\n 'domAutomationController',\n] as const\n\n// ---------------------------------------------------------------------------\n// Core detection\n// ---------------------------------------------------------------------------\n\n/**\n * Detect whether the current browser context is a bot or crawler.\n * Synchronous and lightweight \u2014 no async APIs needed.\n */\nexport function detectBot(): BotDetectionResult {\n const signals = collectBotSignals()\n\n const isBot =\n signals.userAgentBot ||\n signals.webdriver ||\n signals.headless ||\n signals.automationGlobals.length > 0 ||\n signals.liesDetected > 2 ||\n (signals.liesDetected > 0 && signals.hasProxy)\n\n let botKind: BotKind = 'human'\n if (isBot) {\n if (signals.headless) {\n botKind = 'headless'\n } else if (signals.webdriver || signals.automationGlobals.length > 0) {\n botKind = 'automation'\n } else {\n botKind = 'unknown_bot'\n }\n }\n\n return { isBot, botKind, signals }\n}\n\n// ---------------------------------------------------------------------------\n// Signal collectors\n// ---------------------------------------------------------------------------\n\nfunction collectBotSignals(): BotSignals {\n return {\n userAgentBot: detectUserAgentBot(),\n webdriver: detectWebdriver(),\n headless: detectHeadless(),\n automationGlobals: detectAutomationGlobals(),\n ...detectLies(),\n missingLanguages: detectMissingLanguages(),\n missingPlugins: detectMissingPlugins(),\n }\n}\n\n/** Check UA string against known bot patterns. */\nfunction detectUserAgentBot(): boolean {\n const ua = navigator.userAgent || ''\n if (!ua) return true\n return BOT_UA_PATTERN.test(ua)\n}\n\n/** navigator.webdriver is set by WebDriver-based automation. */\nfunction detectWebdriver(): boolean {\n return !!(navigator as any).webdriver\n}\n\n/** Headless browser indicators. */\nfunction detectHeadless(): boolean {\n const w = window as any\n const n = navigator as any\n\n // Chrome-specific: headless mode omits the chrome runtime object\n if (/Chrome/.test(n.userAgent) && !w.chrome) return true\n\n // HeadlessChrome in UA\n if (/HeadlessChrome/.test(n.userAgent)) return true\n\n // Notification permission is always \"denied\" in headless Chrome\n // (in headed mode it defaults to \"default\")\n try {\n if (Notification.permission === 'denied' && n.permissions) {\n // Double-check: headless also lacks plugins\n if ((!n.plugins || n.plugins.length === 0) && !/Mobile|Android/i.test(n.userAgent)) {\n return true\n }\n }\n } catch { /* permissions API unavailable */ }\n\n return false\n}\n\n/** Detect automation framework globals on window. */\nfunction detectAutomationGlobals(): string[] {\n const w = window as any\n return AUTOMATION_GLOBALS.filter(key => {\n try { return key in w && w[key] !== undefined }\n catch { return false }\n }) as string[]\n}\n\n/**\n * Lie detection \u2014 detect API tampering (proxies, toString spoofing).\n * Extracted from the fingerprint lies module; self-contained here.\n */\nfunction detectLies(): { liesDetected: number; hasProxy: boolean } {\n let liesDetected = 0\n let hasProxy = false\n\n const apisToTest: Array<[object, string]> = [\n [Navigator.prototype, 'userAgent'],\n [Navigator.prototype, 'languages'],\n [Navigator.prototype, 'platform'],\n [Navigator.prototype, 'hardwareConcurrency'],\n [Navigator.prototype, 'webdriver'],\n [HTMLCanvasElement.prototype, 'toDataURL'],\n [CanvasRenderingContext2D.prototype, 'fillText'],\n [Date.prototype, 'getTimezoneOffset'],\n ]\n\n for (const [proto, prop] of apisToTest) {\n try {\n const d = Object.getOwnPropertyDescriptor(proto, prop)\n if (!d) continue\n const fn = d.value ?? d.get\n if (typeof fn !== 'function') continue\n\n // toString integrity for methods and accessor getters\n const str = Function.prototype.toString.call(fn)\n if (!isNativeToString(str)) liesDetected++\n\n // Proxy detection \u2014 only for value-bound methods (not getters)\n if (d.value && fn.toString !== Function.prototype.toString) {\n try {\n const native = Function.prototype.toString.call(fn)\n const custom = fn.toString()\n if (native !== custom) { liesDetected++; hasProxy = true }\n } catch { liesDetected++; hasProxy = true }\n }\n } catch { /* skip inaccessible */ }\n }\n\n // Meta-test: toString integrity\n try {\n const s = Function.prototype.toString.call(Function.prototype.toString)\n if (!isNativeToString(s)) liesDetected++\n } catch { /* skip */ }\n\n return { liesDetected, hasProxy }\n}\n\n/** Missing navigator.languages is a strong headless indicator. */\nfunction detectMissingLanguages(): boolean {\n const langs = navigator.languages\n return !langs || langs.length === 0\n}\n\n/** Missing plugins on desktop is a headless indicator. */\nfunction detectMissingPlugins(): boolean {\n if (/Mobile|Android/i.test(navigator.userAgent)) return false\n return !navigator.plugins || navigator.plugins.length === 0\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction isNativeToString(str: string): boolean {\n return /^function\\s[^{]*\\{\\s*\\[native code\\]\\s*\\}$/.test(str) ||\n str === 'function () { [native code] }' ||\n /^\\(\\)\\s*=>\\s*\\{\\s*\\[native code\\]\\s*\\}$/.test(str)\n}\n", "export const getEnvironment = (): {\n isLocalhost: boolean;\n isHeadlessBrowser: boolean;\n} => ({\n isLocalhost:\n (/^localhost$|^127(\\.[0-9]+){0,2}\\.[0-9]+$|^\\[::1?\\]$/.test(location.hostname) &&\n (location.protocol === \"http:\" || location.protocol === \"https:\")) ||\n location.protocol === \"file:\",\n isHeadlessBrowser: Boolean(\n window.navigator.webdriver ||\n (\"_phantom\" in window && window._phantom) ||\n (\"__nightmare\" in window && window.__nightmare) ||\n (\"Cypress\" in window && window.Cypress)\n )\n});\nexport const isClient = (): boolean => {\n try {\n // Basic checks for window and document\n if (typeof window === \"undefined\" || typeof document === \"undefined\") return false;\n\n // Check for navigator safely\n const ua = typeof navigator !== \"undefined\" ? navigator.userAgent : \"\";\n if (/node|jsdom/i.test(ua)) return false;\n return true;\n } catch {\n return false;\n }\n};\n", "import type { AnalyticsConfig, InternalAnalyticsConfig } from \"../types\";\nimport { getEnvironment } from \"./environment\";\n\nexport const defaultConfig: InternalAnalyticsConfig = {\n hostname: null,\n devmode: false,\n collectorUrl: \"https://collector.onedollarstats.com/events\",\n hashRouting: false,\n autocollect: true,\n excludePages: [],\n includePages: []\n};\n\nexport const mergeConfig = (userConfig: AnalyticsConfig = {}): InternalAnalyticsConfig => {\n const { isLocalhost } = getEnvironment();\n\n const devmode = !isLocalhost ? false : (userConfig.devmode ?? !!userConfig.trackLocalhostAs);\n\n let hostname: string | null;\n if (userConfig.hostname) {\n const trimmed = userConfig.hostname.trim();\n hostname = trimmed || null;\n } else if (devmode && userConfig.trackLocalhostAs) {\n hostname = userConfig.trackLocalhostAs;\n } else {\n hostname = null;\n }\n\n // Merge default config, user config, and computed values\n return { ...defaultConfig, ...userConfig, hostname, devmode };\n};\n", "export function parseUtmParams(urlSearchParams: URLSearchParams) {\n const utm: Record<string, string> = {};\n const keys = [\"utm_campaign\", \"utm_source\", \"utm_medium\", \"utm_term\", \"utm_content\"] as const;\n\n for (const key of keys) {\n const raw = urlSearchParams.get(key);\n if (!raw) continue;\n\n const decoded = decodeAndTrim(raw);\n if (decoded) {\n utm[key] = decoded;\n }\n }\n\n return utm;\n}\n\nfunction decodeAndTrim(value: string): string {\n let decoded = value;\n let previous = \"\";\n\n while (decoded !== previous) {\n previous = decoded;\n try {\n decoded = decodeURIComponent(decoded);\n } catch {\n return decoded.trim();\n }\n }\n\n return decoded.trim();\n}\n", "export const parseProps = (propsString: string): Record<string, string> | undefined => {\n if (!propsString) return undefined;\n // \"key1=value1;key2=value2\"\n\n const splittedProps = propsString.split(\";\");\n const propsObj: Record<string, string> = {};\n\n for (const keyValueString of splittedProps) {\n const keyValuePair = keyValueString.split(\"=\").map((el) => el.trim());\n if (keyValuePair.length !== 2 || keyValuePair[0] === \"\" || keyValuePair[1] === \"\") continue;\n // @ts-ignore\n propsObj[keyValuePair[0]] = keyValuePair[1];\n }\n\n return Object.keys(propsObj).length === 0 ? undefined : propsObj;\n};\n", "export const resolvePath = (pathOrProps?: string): string => {\n if (pathOrProps) return pathOrProps;\n\n const sources = [\n { value: document.body?.getAttribute(\"data-s-path\"), name: \"data-s-path\" },\n { value: document.body?.getAttribute(\"data-s:path\"), name: \"data-s:path\" },\n { value: document.querySelector('meta[name=\"stonks-path\"]')?.getAttribute(\"content\"), name: \"meta[stonks-path]\" }\n ];\n\n // Only keep sources that actually exist\n const existing = sources.filter(({ value }) => value);\n\n if (existing.length > 1) {\n console.warn(\"[onedollarstats] Multiple path sources found. Using priority order:\", existing.map(({ name }) => name).join(\" > \"));\n }\n\n // Return first available value, fallback to location.pathname\n return existing[0]?.value ?? location.pathname;\n};\n", "import type { InternalAnalyticsConfig } from \"../types\";\n\nconst matchesPattern = (path: string, pattern: string): boolean => {\n // Escape special regex characters except '*' which becomes '.*'\n const escaped = pattern.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(/\\*/g, \".*\");\n return new RegExp(`^${escaped}$`).test(path);\n};\n\nexport const shouldTrackPath = (path: string, config: InternalAnalyticsConfig): boolean => {\n // Exclude pages first\n if (config.excludePages.some((pattern) => matchesPattern(path, pattern))) return false;\n // If includePages is defined, only allow matching paths\n if (config.includePages.length && !config.includePages.some((pattern) => matchesPattern(path, pattern))) return false;\n return true;\n};\n", "import type { AnalyticsConfig, BaseProps, BodyToSend, Event, InternalAnalyticsConfig, ViewArguments } from \"./types\";\nimport { detectBot } from \"./utils/bot\";\nimport { getEnvironment, isClient } from \"./utils/environment\";\nimport { mergeConfig } from \"./utils/merge-config\";\nimport { parseUtmParams } from \"./utils/parse-utm-params\";\nimport { parseProps } from \"./utils/props-parser\";\nimport { resolvePath } from \"./utils/resolve-path\";\nimport { shouldTrackPath } from \"./utils/should-track\";\n\ndeclare const DEBUG_SCRIPT_URL: string;\n\nclass AnalyticsTracker {\n private static instance: AnalyticsTracker | null = null;\n\n private autocollectSetupDone = false;\n private config: InternalAnalyticsConfig;\n private lastPage: string | null = null;\n\n public static getInstance(userConfig: AnalyticsConfig = {}): AnalyticsTracker {\n // Fresh no-op instance for SSR\n if (!isClient()) return new AnalyticsTracker(userConfig);\n\n if (!AnalyticsTracker.instance) {\n AnalyticsTracker.instance = new AnalyticsTracker(userConfig);\n }\n return AnalyticsTracker.instance;\n }\n\n private constructor(userConfig: AnalyticsConfig = {}) {\n this.config = mergeConfig(userConfig);\n\n // Skip setup in non-client environments\n if (!isClient()) return;\n\n const { isLocalhost } = getEnvironment();\n\n // Debug log on localhost\n if (isLocalhost && this.config.devmode && this.config.hostname) {\n console.log(`[onedollarstats]\\nOneDollarStats connected! Tracking localhost as ${this.config.hostname}`);\n\n // Set up debug modal loading: store config + queue, then dynamically load the debug script\n window.__stonksDebugConfig = { hostname: this.config.hostname, collectorUrl: this.config.collectorUrl };\n window.__stonksModalQueue = [];\n window.__stonksModalReady = false;\n\n const debugScript = document.createElement(\"script\");\n debugScript.src = DEBUG_SCRIPT_URL;\n debugScript.onerror = () => {\n // If the debug script fails to load, mark as ready so queued events are not stuck\n window.__stonksModalReady = true;\n };\n document.head.appendChild(debugScript);\n }\n\n // Auto-start autocollect (always set up listeners; handlePageView checks config)\n this.setupAutocollect();\n }\n\n private async sendWithBeaconOrFetch(stringifiedBody: string, callback: (success: boolean) => void): Promise<void> {\n // First fallback: try sendBeacon\n if (navigator.sendBeacon?.(this.config.collectorUrl, stringifiedBody)) {\n callback(true);\n return;\n }\n\n // Second fallback: use fetch() with keepalive\n fetch(this.config.collectorUrl, {\n method: \"POST\",\n body: stringifiedBody,\n headers: { \"Content-Type\": \"application/json\" },\n keepalive: true\n })\n .then(({ ok }) => callback(ok))\n .catch((err: Error) => {\n console.error(\"[onedollarstats] fetch() failed:\", err.message);\n callback(false);\n });\n }\n\n // Handles localhost replacement, referrer, UTM parameters, and debug mode.\n // Uses img beacon then `navigator.sendBeacon` if available, otherwise falls back to `fetch`.\n private async send(data: Event): Promise<void> {\n const { isLocalhost, isHeadlessBrowser } = getEnvironment();\n if ((isLocalhost && !this.config.devmode) || isHeadlessBrowser) return;\n\n const { isBot, botKind } = detectBot();\n\n if (isBot && botKind !== \"human\") return;\n\n const urlToSend = new URL(this.config.hostname ? `https://${this.config.hostname}${location.pathname}` : location.href);\n\n // Clean query string unless UTM is explicitly provided\n urlToSend.search = \"\";\n if (data.path) urlToSend.pathname = data.path;\n\n const cleanUrl = urlToSend.href.replace(/\\/$/, \"\");\n\n // Determine referrer\n let referrer: string | undefined = data.referrer;\n try {\n if (!referrer && document.referrer && document.referrer !== \"null\") {\n const referrerURL = new URL(document.referrer);\n if (referrerURL.hostname !== urlToSend.hostname) referrer = referrerURL.href;\n }\n } catch {} // ignore malformed referrer\n\n // Build request body\n const body: BodyToSend = {\n u: cleanUrl,\n e: [\n {\n t: data.type,\n h: this.config.hashRouting,\n r: referrer,\n p: data.props\n }\n ],\n debug: this.config.devmode\n };\n\n if (data.utm && Object.keys(data.utm).length > 0) body.qs = data.utm;\n\n if (body.debug) {\n let logMessage = `[onedollarstats]\\nEvent name: ${data.type}\\nEvent collected from: ${cleanUrl}`;\n if (data.props && Object.keys(data.props).length > 0) logMessage += `\\nProps: ${JSON.stringify(data.props, null, 2)}`;\n if (referrer) logMessage += `\\nReferrer: ${referrer}`;\n if (this.config.hashRouting) logMessage += `\\nHashRouting: ${this.config.hashRouting}`;\n if (data.utm && Object.keys(data.utm).length > 0) logMessage += `\\nUTM: ${data.utm}`;\n\n console.log(logMessage);\n }\n\n // Prepare the event payload\n const stringifiedBody = JSON.stringify(body);\n\n // Encode for safe inclusion in a query string (UTF-8 \u2192 Base64)\n const bytes = new TextEncoder().encode(stringifiedBody); // UTF-8 \u2192 bytes\n const bin = String.fromCharCode(...bytes); // bytes \u2192 binary string\n const payloadBase64 = btoa(bin); // binary \u2192 Base64\n\n const safeGetThreshold = 1500; // limit for query-string-containing URLs\n const tryImageBeacon = payloadBase64.length <= safeGetThreshold;\n\n const onComplete = (success: boolean) => {\n const message = `${data.type} ${success ? \"sent\" : \"failed to send\"}`;\n if (window.__stonksModalReady) {\n window.__stonksModalLog?.(message, success);\n } else {\n window.__stonksModalQueue?.push([message, success]);\n }\n };\n\n if (tryImageBeacon) {\n // Send via image beacon\n const img = new Image(1, 1);\n\n img.onload = () => onComplete(true);\n // If loading image fails (server unavailable, blocked, etc.)\n img.onerror = () => this.sendWithBeaconOrFetch(stringifiedBody, onComplete);\n\n // Primary attempt: send data via image beacon (GET request with query string)\n img.src = `${this.config.collectorUrl}?data=${payloadBase64}`;\n } else await this.sendWithBeaconOrFetch(stringifiedBody, onComplete);\n }\n\n // Prevents duplicate pageviews and respects include/exclude page rules. Automatically parses UTM parameters from URL.\n private trackPageView({ path, props }: ViewArguments, checkBlock: boolean = false) {\n if (!isClient()) return;\n\n const viewPath = resolvePath(path);\n\n // Collect props from DOM attributes\n const collectedProps: Record<string, string> = {};\n const elements = document.querySelectorAll(\"[data-s\\\\:view-props], [data-s-view-props]\");\n\n for (const el of Array.from(elements)) {\n const propsString = el.getAttribute(\"data-s-view-props\") || el.getAttribute(\"data-s:view-props\");\n if (!propsString) continue;\n const parsedProps = parseProps(propsString);\n Object.assign(collectedProps, parsedProps);\n }\n\n // Collect props from meta tag (overrides DOM attributes)\n const metaViewProps = document\n .querySelector('meta[name=\"stonks-props\"]')\n ?.getAttribute(\"content\");\n if (metaViewProps) {\n Object.assign(collectedProps, parseProps(metaViewProps));\n }\n\n // Explicit props override everything\n if (props) {\n Object.assign(collectedProps, props);\n }\n\n const viewProps = Object.keys(collectedProps).length > 0 ? collectedProps : undefined;\n\n // Skip duplicate pageviews or excluded pages\n if (!this.config.hashRouting && this.lastPage === viewPath) return;\n\n // Skip page if checkBlock is true and the path should be excluded\n if (checkBlock && !shouldTrackPath(viewPath, this.config)) return;\n\n this.lastPage = viewPath;\n\n const utm = parseUtmParams(new URLSearchParams(location.search));\n this.send({ type: \"PageView\", path: viewPath, props: viewProps, utm });\n }\n\n /**\n * Tracks a custom event.\n * Can accept path string or a props object.\n *\n * @param eventName Name of the event to track.\n * @param pathOrProps Optional path string or props object.\n * @param props Optional props object if path string is provided.\n */\n public async event(eventName: string, pathOrProps?: string | BaseProps, rawProps?: BaseProps) {\n if (!isClient()) return;\n\n const { isLocalhost, isHeadlessBrowser } = getEnvironment();\n if ((isLocalhost && !this.config.devmode) || isHeadlessBrowser) return;\n\n const path = resolvePath(typeof pathOrProps === \"string\" ? pathOrProps : undefined);\n const props = typeof pathOrProps === \"object\" ? pathOrProps : rawProps;\n\n this.send({ type: eventName, path, props });\n }\n\n /**\n * Records a page view.\n * Can accept path string or a props object.\n *\n * @param pathOrProps Optional path string or props object.\n * @param props Optional props when first arg is a path string.\n */\n public async view(pathOrProps?: string | BaseProps, props?: BaseProps) {\n if (!isClient()) return;\n\n const args: ViewArguments = {};\n\n if (typeof pathOrProps === \"string\") {\n args.path = pathOrProps;\n args.props = props;\n } else if (typeof pathOrProps === \"object\") {\n args.props = pathOrProps;\n }\n\n this.trackPageView(args);\n }\n\n /**\n * Installs global DOM/window listeners exactly once for:\n * - visibilitychange\n * - history.pushState\n * - popstate\n * - hashchange\n * - click autocapture for elements annotated with `data-s:event` & `data-s-event`\n *\n */\n private setupAutocollect() {\n if (!isClient() || this.autocollectSetupDone) return;\n this.autocollectSetupDone = true;\n\n const handlePageView = () => {\n // Check meta tag and body attribute for collection control\n const metaCollect = document\n .querySelector('meta[name=\"stonks-collect\"]')\n ?.getAttribute(\"content\");\n const bodyCollect =\n document.body?.getAttribute(\"data-s-collect\") ||\n document.body?.getAttribute(\"data-s:collect\");\n\n // Explicitly disabled\n if (metaCollect === \"false\" || bodyCollect === \"false\") {\n this.lastPage = null;\n return;\n }\n\n // If autocollect is off, only collect if explicitly enabled via meta/body\n if (!this.config.autocollect && metaCollect !== \"true\" && bodyCollect !== \"true\") {\n this.lastPage = null;\n return;\n }\n\n this.trackPageView({}, true);\n };\n\n // visibilitychange\n const onVisibility = () => {\n if (document.visibilityState === \"visible\") handlePageView();\n };\n document.addEventListener(\"visibilitychange\", onVisibility);\n\n // pushState\n const origPush = history.pushState.bind(history);\n history.pushState = (...args) => {\n origPush(...args);\n requestAnimationFrame(() => {\n handlePageView();\n });\n };\n\n // popstate\n window.addEventListener(\"popstate\", handlePageView);\n\n // hashchange\n window.addEventListener(\"hashchange\", handlePageView);\n\n // click autocapture\n const onClick: EventListener = (ev: Event) => {\n const clickEvent = ev as MouseEvent;\n if (clickEvent.type === \"auxclick\" && clickEvent.button !== 1) return;\n\n const target = clickEvent.target as Element | null;\n if (!target) return;\n\n // Check if inside <a> or <button>\n const insideInteractive = !!target.closest(\"a, button\");\n\n let el: Element | null = target;\n let depth = 0;\n\n while (el) {\n const eventName = el.getAttribute(\"data-s-event\") || el.getAttribute(\"data-s:event\");\n if (eventName) {\n const propsAttr = el.getAttribute(\"data-s-event-props\") || el.getAttribute(\"data-s:event-props\");\n const props = propsAttr ? parseProps(propsAttr) : undefined;\n const path = el.getAttribute(\"data-s-event-path\") || el.getAttribute(\"data-s:event-path\") || undefined;\n\n if ((path && !shouldTrackPath(path, this.config)) || !shouldTrackPath(location.pathname, this.config)) {\n return;\n }\n\n this.event(eventName, path ?? props, props);\n return;\n }\n\n el = el.parentElement;\n depth++;\n\n // If not in <a>/<button>, stop after 3 levels\n if (!insideInteractive && depth >= 3) break;\n }\n };\n\n document.addEventListener(\"click\", onClick);\n\n // Fire initial pageview if already visible\n if (document.visibilityState === \"visible\") handlePageView();\n }\n}\n\nexport const configure = (userConfig: AnalyticsConfig = {}) => {\n AnalyticsTracker.getInstance(userConfig);\n};\n\nexport const event = async (eventName: string, pathOrProps?: string | BaseProps, props?: BaseProps) => {\n const instance = AnalyticsTracker.getInstance();\n await instance.event(eventName, pathOrProps, props);\n};\n\nexport const view = async (pathOrProps?: string | BaseProps, props?: BaseProps) => {\n const instance = AnalyticsTracker.getInstance();\n await instance.view(pathOrProps, props);\n};\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/types.d.ts CHANGED
@@ -1,28 +1,27 @@
1
- type UtmParams = Record<string, string | string[]>;
2
1
  export type BaseProps = Record<string, string>;
3
- type MinimizedEvent = {
4
- t: string;
5
- h?: boolean;
6
- r?: string;
7
- p?: BaseProps;
2
+ export type ViewArguments = {
3
+ path?: string;
4
+ props?: BaseProps;
8
5
  };
9
6
  export type Event = {
10
7
  type: string;
11
8
  path?: string;
12
9
  props?: BaseProps;
13
- utm?: UtmParams;
10
+ utm?: Record<string, string>;
14
11
  referrer?: string;
15
12
  };
13
+ type MinimizedEvent = {
14
+ t: string;
15
+ h?: boolean;
16
+ r?: string;
17
+ p?: BaseProps;
18
+ };
16
19
  export type BodyToSend = {
17
20
  u: string;
18
21
  e: [MinimizedEvent];
19
- qs?: UtmParams;
22
+ qs?: Record<string, string>;
20
23
  debug?: boolean;
21
24
  };
22
- export type ViewArguments = {
23
- path?: string;
24
- props?: BaseProps;
25
- };
26
25
  type BaseAnalyticsConfig = {
27
26
  collectorUrl?: string;
28
27
  /**
package/package.json CHANGED
@@ -1,16 +1,21 @@
1
1
  {
2
2
  "name": "onedollarstats",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "A lightweight, zero-dependency analytics tracker for frontend apps",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "type": "module",
8
- "scripts": {
9
- "build": "tsc --emitDeclarationOnly && esbuild src/index.ts --bundle --outfile=dist/index.js --platform=browser --format=esm --target=es2020 --sourcemap",
10
- "prepublishOnly": "pnpm build"
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
11
13
  },
12
14
  "files": [
13
- "dist"
15
+ "dist/index.js",
16
+ "dist/index.js.map",
17
+ "dist/index.d.ts",
18
+ "dist/types.d.ts"
14
19
  ],
15
20
  "keywords": [
16
21
  "analytics",
@@ -24,10 +29,29 @@
24
29
  ],
25
30
  "author": "",
26
31
  "license": "MIT",
27
- "packageManager": "pnpm@10.16.1",
28
32
  "devDependencies": {
33
+ "@types/jsdom": "^27.0.0",
29
34
  "esbuild": "^0.25.10",
35
+ "jsdom": "^26.0.0",
36
+ "puppeteer": "^24.1.1",
37
+ "tsx": "^4.19.2",
30
38
  "typescript": "^5.9.2",
31
39
  "vitest": "^4.0.18"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc -p tsconfig.build.json --emitDeclarationOnly && rm -rf dist/utils dist/script.d.ts dist/debug-modal.d.ts dist/globals.d.ts && esbuild src/index.ts --bundle --outfile=dist/index.js --platform=browser --format=esm --target=es2020 --sourcemap --legal-comments=none --define:DEBUG_SCRIPT_URL='\"https://assets.onedollarstats.com/stonks-debug.js\"'",
43
+ "bundle": "esbuild src/script.ts --bundle --minify --format=iife --outfile=build/stonks.js --legal-comments=none --define:DEBUG_SCRIPT_URL='\"https://assets.onedollarstats.com/stonks-debug.js\"'",
44
+ "bundle:dev": "esbuild src/script.ts --bundle --minify --format=iife --outfile=build/stonks.js --legal-comments=none --define:DEBUG_SCRIPT_URL='\"https://assets.onedollarstats.com/stonks-debug-dev.js\"'",
45
+ "bundle:test": "esbuild src/script.ts --bundle --format=cjs --outfile=build/stonks.js --legal-comments=none --define:DEBUG_SCRIPT_URL='\"https://assets.onedollarstats.com/stonks-debug.js\"'",
46
+ "bundle:debug": "esbuild src/debug-modal.ts --bundle --minify --format=iife --outfile=build/stonks-debug.js --legal-comments=none",
47
+ "test": "vitest run",
48
+ "test:units": "vitest run --project 'Unit Tests'",
49
+ "test:units:modal": "vitest run --project 'Unit Tests' src/utils/create-modal.test.ts",
50
+ "test:e2e:script": "vitest run --project 'Script E2E Tests'",
51
+ "test:e2e:package": "vitest run --project 'Package E2E Tests'",
52
+ "test:e2e": "vitest run --project 'Script E2E Tests' --project 'Package E2E Tests'",
53
+ "test:build&e2e:script": "pnpm bundle:test && FORCE_BUILD=1 COPY_TRACKER=1 vitest run --project 'Script E2E Tests'",
54
+ "test:build&e2e:package": "pnpm build && FORCE_BUILD=1 COPY_PACKAGE=1 vitest run --project 'Package E2E Tests'",
55
+ "test:all": "pnpm bundle:test && pnpm build && FORCE_BUILD=1 COPY_TRACKER=1 COPY_PACKAGE=1 vitest run"
32
56
  }
33
- }
57
+ }
@@ -1,50 +0,0 @@
1
- /**
2
- * Bot & crawler detection — standalone module.
3
- *
4
- * Usage:
5
- * import { detectBot } from '@aspect/fingerprint/bot'
6
- * const result = detectBot()
7
- * if (result.isBot) console.log(result.botKind, result.signals)
8
- *
9
- * Detects:
10
- * - Known search engine crawlers (Googlebot, Bingbot, Yandex, Baidu, etc.)
11
- * - Social media crawlers (Facebook, Twitter, LinkedIn, etc.)
12
- * - Headless browsers (Puppeteer, Playwright, Selenium, PhantomJS)
13
- * - General automation tools via navigator.webdriver and injected globals
14
- * - API tampering / lie detection (prototype spoofing, proxy wrapping)
15
- *
16
- * Zero dependencies — can be imported and used independently of the
17
- * fingerprinting library.
18
- */
19
- export type BotKind = 'search_engine' | 'social_crawler' | 'headless' | 'automation' | 'library' | 'unknown_bot' | 'human';
20
- export interface BotSignals {
21
- /** navigator.userAgent matched a known bot pattern. */
22
- userAgentBot: string | null;
23
- /** navigator.webdriver is true. */
24
- webdriver: boolean;
25
- /** Headless browser indicators detected. */
26
- headless: boolean;
27
- /** Automation globals found on window. */
28
- automationGlobals: string[];
29
- /** Number of API lies (toString/proxy tampering) detected. */
30
- liesDetected: number;
31
- /** Proxy wrapping detected on native functions. */
32
- hasProxy: boolean;
33
- /** navigator.languages is empty or missing. */
34
- missingLanguages: boolean;
35
- /** navigator.plugins is empty (non-mobile). */
36
- missingPlugins: boolean;
37
- }
38
- export interface BotDetectionResult {
39
- /** True when any bot signal fires. */
40
- isBot: boolean;
41
- /** Classified category of the detected bot. */
42
- botKind: BotKind;
43
- /** Individual signal results for debugging / logging. */
44
- signals: BotSignals;
45
- }
46
- /**
47
- * Detect whether the current browser context is a bot or crawler.
48
- * Synchronous and lightweight — no async APIs needed.
49
- */
50
- export declare function detectBot(): BotDetectionResult;
@@ -1 +0,0 @@
1
- export declare const createDebugModal: (debugUrl: string, analyticsUrl: string) => (message: string, success: boolean) => void;
@@ -1,5 +0,0 @@
1
- export declare const getEnvironment: () => {
2
- isLocalhost: boolean;
3
- isHeadlessBrowser: boolean;
4
- };
5
- export declare const isClient: () => boolean;
@@ -1,3 +0,0 @@
1
- import type { AnalyticsConfig, InternalAnalyticsConfig } from "../types";
2
- export declare const defaultConfig: InternalAnalyticsConfig;
3
- export declare const mergeConfig: (userConfig?: AnalyticsConfig) => InternalAnalyticsConfig;
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export declare const parseUtmParams: (urlSearchParams: URLSearchParams) => Record<string, string>;
@@ -1 +0,0 @@
1
- export declare const parseProps: (propsString: string) => Record<string, string> | undefined;
@@ -1 +0,0 @@
1
- export declare const resolvePath: (pathOrProps?: string) => string;
@@ -1,2 +0,0 @@
1
- import type { InternalAnalyticsConfig } from "../types";
2
- export declare const shouldTrackPath: (path: string, config: InternalAnalyticsConfig) => boolean;