├── docs ├── images │ ├── dashboard_demo.png │ ├── fraud-journey.png │ ├── github-banner.png │ └── fraud_waste_calculator.png ├── logos │ └── github_banner.PNG └── LOGO_ASSETS.md ├── babel.config.js ├── config ├── .prettierrc ├── .editorconfig ├── rollup.config.mjs ├── .eslintrc.json └── jest.config.js ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── bug_report.md │ └── feature_request.md ├── workflows │ └── ci.yml ├── PULL_REQUEST_TEMPLATE.md ├── SECURITY.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md └── SUPPORT.md ├── .npmignore ├── .gitignore ├── LICENSE ├── examples ├── webflow │ └── README.md ├── github-pages │ └── README.md ├── integration.html ├── squarespace │ └── README.md ├── godaddy │ └── README.md ├── wordpress │ ├── README.md │ ├── demo-output.html │ └── INTEGRATION-GUIDE.md ├── wix │ ├── README.md │ └── INTEGRATION-GUIDE.md ├── README.md ├── shopify │ ├── README.md │ └── INTEGRATION-GUIDE.md └── bluehaven-coffee │ └── index.html ├── tests ├── integration │ ├── oneliner_test.html │ └── phase1_test.html ├── setup.js ├── manual │ ├── test-unload-fetch.html │ ├── simple_test.html │ ├── phase2_manual_test.html │ ├── test-unload.html │ ├── manual.html │ └── test-production.html └── unit │ ├── url.test.js │ ├── session.test.js │ └── fingerprint.test.js ├── src ├── utils │ ├── url.js │ ├── session.js │ └── fingerprint.js ├── index.js └── core │ └── tracker.js ├── package.json ├── index.d.ts └── dev ├── ground-truth ├── index.js └── impossibilities.js └── tests ├── test-merged-ground-truth.html └── test-ground-truth.html /docs/images/dashboard_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/papa-torb/adtruth/HEAD/docs/images/dashboard_demo.png -------------------------------------------------------------------------------- /docs/images/fraud-journey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/papa-torb/adtruth/HEAD/docs/images/fraud-journey.png -------------------------------------------------------------------------------- /docs/images/github-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/papa-torb/adtruth/HEAD/docs/images/github-banner.png -------------------------------------------------------------------------------- /docs/logos/github_banner.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/papa-torb/adtruth/HEAD/docs/logos/github_banner.PNG -------------------------------------------------------------------------------- /docs/images/fraud_waste_calculator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/papa-torb/adtruth/HEAD/docs/images/fraud_waste_calculator.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current' 8 | } 9 | } 10 | ] 11 | ] 12 | }; 13 | -------------------------------------------------------------------------------- /config/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "printWidth": 100, 6 | "tabWidth": 2, 7 | "useTabs": false, 8 | "arrowParens": "avoid", 9 | "bracketSpacing": true, 10 | "endOfLine": "lf", 11 | "quoteProps": "as-needed" 12 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Security Vulnerability 4 | url: https://github.com/papa-torb/adtruth/security/advisories/new 5 | about: Please report security vulnerabilities through GitHub Security Advisories 6 | - name: Question or Discussion 7 | url: https://github.com/papa-torb/adtruth/discussions 8 | about: Ask questions or start discussions about AdTruth -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Development files 2 | .github/ 3 | .vscode/ 4 | .idea/ 5 | 6 | # Testing 7 | tests/ 8 | coverage/ 9 | jest.config.js 10 | .eslintcache 11 | 12 | # Documentation (keep README, LICENSE, CHANGELOG) 13 | docs/ 14 | examples/ 15 | 16 | # Configuration 17 | .editorconfig 18 | .eslintrc.json 19 | .prettierrc 20 | .babelrc 21 | rollup.config.mjs 22 | .gitignore 23 | 24 | # Source files (only ship dist/) 25 | src/ 26 | 27 | # Misc 28 | *.log 29 | .DS_Store 30 | .env* 31 | *.tmp 32 | *.temp 33 | 34 | # Git 35 | .git/ 36 | .gitattributes 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node.js 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | lerna-debug.log* 7 | .pnpm-debug.log* 8 | 9 | # Build output 10 | # We commit dist/ for CDN access 11 | build/ 12 | 13 | # Testing 14 | coverage/ 15 | *.lcov 16 | .nyc_output 17 | 18 | # Development 19 | .env 20 | .env.local 21 | .env.*.local 22 | *.log 23 | .eslintcache 24 | 25 | # IDE 26 | .vscode/ 27 | .idea/ 28 | *.swp 29 | *.swo 30 | *~ 31 | .DS_Store 32 | 33 | # OS 34 | Thumbs.db 35 | .DS_Store 36 | .DS_Store? 37 | ._* 38 | 39 | # Documentation builds 40 | docs/_build/ 41 | 42 | # Temporary files 43 | *.tmp 44 | *.temp 45 | .tmp/ 46 | .temp/ 47 | # Credentials files - NEVER COMMIT 48 | adtruth-credentials.txt 49 | *-credentials.txt 50 | 51 | # Claude configuration 52 | .claude/ 53 | -------------------------------------------------------------------------------- /config/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps maintain consistent coding styles across different editors and IDEs 2 | # https://editorconfig.org 3 | 4 | root = true 5 | 6 | # Default settings for all files 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | # JavaScript and JSON files 16 | [*.{js,json,mjs}] 17 | indent_size = 2 18 | 19 | # Markdown files 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | max_line_length = off 23 | 24 | # YAML files 25 | [*.{yml,yaml}] 26 | indent_size = 2 27 | 28 | # HTML files 29 | [*.html] 30 | indent_size = 2 31 | 32 | # Shell scripts 33 | [*.sh] 34 | indent_size = 4 35 | 36 | # Makefile 37 | [Makefile] 38 | indent_style = tab 39 | -------------------------------------------------------------------------------- /config/rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import terser from '@rollup/plugin-terser'; 2 | 3 | export default [ 4 | // IIFE build for browser script tag 5 | { 6 | input: 'src/index.js', 7 | output: { 8 | file: 'dist/adtruth.js', 9 | format: 'iife', 10 | name: 'AdTruth', 11 | banner: '/* AdTruth SDK v0.2.1 | MIT License | https://github.com/papa-torb/adtruth */' 12 | } 13 | }, 14 | // Minified IIFE build for production 15 | { 16 | input: 'src/index.js', 17 | output: { 18 | file: 'dist/adtruth.min.js', 19 | format: 'iife', 20 | name: 'AdTruth', 21 | banner: '/* AdTruth SDK v0.2.1 | MIT */' 22 | }, 23 | plugins: [terser()] 24 | }, 25 | // ESM build for modern bundlers 26 | { 27 | input: 'src/index.js', 28 | output: { 29 | file: 'dist/adtruth.esm.js', 30 | format: 'es', 31 | banner: '/* AdTruth SDK v0.2.1 | MIT License | https://github.com/papa-torb/adtruth */' 32 | } 33 | } 34 | ]; 35 | // Note: This project is still under development. 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Hongyi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /config/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "ecmaVersion": 2021, 10 | "sourceType": "module" 11 | }, 12 | "rules": { 13 | "indent": ["error", 2], 14 | "linebreak-style": ["error", "unix"], 15 | "quotes": ["error", "single"], 16 | "semi": ["error", "always"], 17 | "no-unused-vars": ["warn", { 18 | "argsIgnorePattern": "^_", 19 | "varsIgnorePattern": "^_" 20 | }], 21 | "no-console": ["warn", { "allow": ["warn", "error"] }], 22 | "prefer-const": "error", 23 | "no-var": "error", 24 | "eqeqeq": ["error", "always"], 25 | "curly": "error", 26 | "brace-style": ["error", "1tbs"], 27 | "comma-dangle": ["error", "never"], 28 | "object-curly-spacing": ["error", "always"], 29 | "array-bracket-spacing": ["error", "never"], 30 | "arrow-spacing": "error", 31 | "keyword-spacing": "error", 32 | "space-before-blocks": "error", 33 | "space-infix-ops": "error" 34 | }, 35 | "ignorePatterns": [ 36 | "node_modules/", 37 | "dist/", 38 | "*.min.js" 39 | ] 40 | } -------------------------------------------------------------------------------- /examples/webflow/README.md: -------------------------------------------------------------------------------- 1 | # Webflow Integration (Testing Required) 2 | 3 | **Priority**: ⚪ LOW (Professional/agency focus, not SMB target market) 4 | **Status**: 🚧 Deferred - Testing after high-priority platforms 5 | 6 | This integration guide is planned but requires testing before documentation. 7 | 8 | ## Testing Checklist 9 | 10 | - [ ] Test with free Webflow account 11 | - [ ] Test with paid site plan (Custom Code feature) 12 | - [ ] Verify tracking works in preview mode 13 | - [ ] Verify tracking works on published site 14 | - [ ] Test with Webflow CMS collections 15 | - [ ] Test with custom interactions/animations 16 | - [ ] Document Custom Code settings location 17 | 18 | ## Potential Integration Methods 19 | 20 | 1. **Project Settings → Custom Code** (Site plan required) 21 | 2. **Embed Component** (limited, may not persist) 22 | 3. **Before tag in Project Settings** 23 | 24 | ## Known Considerations 25 | 26 | - Custom Code is only available on paid site plans ($14+/month) 27 | - Free accounts cannot add custom scripts 28 | - May need to handle Webflow's page transitions 29 | 30 | --- 31 | 32 | **Want to help?** Test AdTruth on your Webflow site and report findings! 33 | -------------------------------------------------------------------------------- /examples/github-pages/README.md: -------------------------------------------------------------------------------- 1 | # GitHub Pages Integration (Testing Required) 2 | 3 | **Priority**: ⚪ LOW (Developer community, not SMB target market) 4 | **Status**: 🚧 Deferred - Testing after high-priority platforms 5 | 6 | This integration guide is planned but requires testing before documentation. 7 | 8 | ## Testing Checklist 9 | 10 | - [ ] Test with static HTML (no generator) 11 | - [ ] Test with Jekyll (GitHub's default) 12 | - [ ] Test with Hugo 13 | - [ ] Test with custom domain 14 | - [ ] Test with github.io subdomain 15 | - [ ] Verify tracking works after Jekyll build 16 | - [ ] Document _includes or _layouts integration 17 | 18 | ## Potential Integration Methods 19 | 20 | 1. **Direct HTML** (static sites) 21 | 2. **Jekyll _includes partial** (reusable across pages) 22 | 3. **Jekyll _layouts/default.html** (site-wide) 23 | 4. **Hugo baseof.html template** (Hugo sites) 24 | 25 | ## Known Considerations 26 | 27 | - Jekyll may process HTML/JS during build 28 | - Need to ensure CDN URL isn't modified 29 | - May need to use `{% raw %}` and `{% endraw %}` tags in Jekyll to prevent Liquid processing 30 | - Build process might affect script loading 31 | 32 | --- 33 | 34 | **Want to help?** Test AdTruth on your GitHub Pages site and report findings! 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug or unexpected behavior 4 | title: '[BUG] ' 5 | labels: 'bug' 6 | assignees: '' 7 | --- 8 | 9 | ## Bug Description 10 | 11 | A clear and concise description of what the bug is. 12 | 13 | ## Steps to Reproduce 14 | 15 | 1. Go to '...' 16 | 2. Click on '...' 17 | 3. Scroll down to '...' 18 | 4. See error 19 | 20 | ## Expected Behavior 21 | 22 | A clear and concise description of what you expected to happen. 23 | 24 | ## Actual Behavior 25 | 26 | What actually happened instead. 27 | 28 | ## Environment 29 | 30 | - **AdTruth Version**: [e.g., v0.2.1] 31 | - **Browser**: [e.g., Chrome 120, Safari 17] 32 | - **OS**: [e.g., Windows 11, macOS 14, iOS 17] 33 | - **Platform**: [e.g., WordPress 6.4, Shopify, Wix, Static HTML] 34 | 35 | ## Screenshots 36 | 37 | If applicable, add screenshots to help explain your problem. 38 | 39 | ## Console Errors 40 | 41 | If applicable, paste any JavaScript console errors: 42 | 43 | ``` 44 | Paste console errors here 45 | ``` 46 | 47 | ## Additional Context 48 | 49 | Add any other context about the problem here (e.g., does it happen on all pages or just some?). 50 | 51 | ## Possible Solution 52 | 53 | If you have ideas on how to fix this, please share. -------------------------------------------------------------------------------- /tests/integration/oneliner_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | One-Liner Test 5 | 6 | 7 |

One-Liner Integration Test

8 |

This page uses ONLY the one-liner. No additional code.

9 |

Scroll, click, move mouse to test Phase 2 tracking!

10 | 11 | 12 | 13 | 14 |
15 |

Keep scrolling...

16 |
17 | 18 | 19 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/utils/url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parse URL parameters including UTM parameters 3 | */ 4 | export function parseUrlParams(url) { 5 | const params = {}; 6 | 7 | try { 8 | const urlObj = new URL(url); 9 | const searchParams = urlObj.searchParams; 10 | 11 | // Extract UTM parameters and other query params 12 | const utmParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']; 13 | 14 | utmParams.forEach(param => { 15 | const value = searchParams.get(param); 16 | if (value) { 17 | params[param] = value; 18 | } 19 | }); 20 | 21 | // Also get gclid (Google Click ID) if present 22 | const gclid = searchParams.get('gclid'); 23 | if (gclid) { 24 | params.gclid = gclid; 25 | } 26 | 27 | // Facebook click ID 28 | const fbclid = searchParams.get('fbclid'); 29 | if (fbclid) { 30 | params.fbclid = fbclid; 31 | } 32 | } catch (e) { 33 | // Fail silently if URL parsing fails 34 | } 35 | 36 | return params; 37 | } 38 | 39 | /** 40 | * Get the current page URL 41 | */ 42 | export function getCurrentUrl() { 43 | return window.location.href; 44 | } 45 | 46 | /** 47 | * Get the referrer URL 48 | */ 49 | export function getReferrer() { 50 | return document.referrer || ''; 51 | } 52 | // Note: This project is still under development. 53 | -------------------------------------------------------------------------------- /config/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Set root directory to project root (one level up from config/) 3 | rootDir: '..', 4 | 5 | // Test environment 6 | testEnvironment: 'jsdom', 7 | 8 | // Test match patterns 9 | testMatch: [ 10 | '/tests/unit/**/*.test.js', 11 | '/tests/integration/**/*.test.js' 12 | ], 13 | 14 | // Coverage configuration 15 | collectCoverageFrom: [ 16 | '/src/**/*.js', 17 | '!/src/**/*.test.js', 18 | '!**/node_modules/**', 19 | '!**/dist/**' 20 | ], 21 | 22 | coverageDirectory: 'coverage', 23 | 24 | coverageReporters: [ 25 | 'text', 26 | 'lcov', 27 | 'html' 28 | ], 29 | 30 | // Coverage thresholds 31 | coverageThreshold: { 32 | global: { 33 | branches: 70, 34 | functions: 70, 35 | lines: 70, 36 | statements: 70 37 | } 38 | }, 39 | 40 | // Module paths 41 | moduleDirectories: ['node_modules', 'src'], 42 | 43 | // Transform files 44 | transform: { 45 | '^.+\\.js$': 'babel-jest' 46 | }, 47 | 48 | // Setup files 49 | setupFilesAfterEnv: ['/tests/setup.js'], 50 | 51 | // Ignore patterns 52 | testPathIgnorePatterns: [ 53 | '/node_modules/', 54 | '/dist/' 55 | ], 56 | 57 | // Verbose output 58 | verbose: true, 59 | 60 | // Clear mocks between tests 61 | clearMocks: true, 62 | 63 | // Restore mocks between tests 64 | restoreMocks: true 65 | }; 66 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest a new feature or enhancement 4 | title: '[FEATURE] ' 5 | labels: 'enhancement' 6 | assignees: '' 7 | --- 8 | 9 | ## Feature Description 10 | 11 | A clear and concise description of the feature you'd like to see. 12 | 13 | ## Problem It Solves 14 | 15 | What problem does this feature solve? Why is it needed? 16 | 17 | **Example**: "I'm frustrated when [...] because [...]" 18 | 19 | ## Proposed Solution 20 | 21 | How would you like this feature to work? Be as specific as possible. 22 | 23 | ## Alternative Solutions 24 | 25 | Have you considered any alternative approaches? What are the pros/cons? 26 | 27 | ## Use Case 28 | 29 | Describe a specific use case where this feature would be valuable. 30 | 31 | **Example scenario:** 32 | - User type: [e.g., E-commerce business, SaaS company] 33 | - Current workflow: [What they do now] 34 | - With this feature: [How it would improve their workflow] 35 | 36 | ## Impact 37 | 38 | Who would benefit from this feature? 39 | - [ ] Small businesses 40 | - [ ] E-commerce sites 41 | - [ ] SaaS companies 42 | - [ ] Marketing agencies 43 | - [ ] Developers/Contributors 44 | - [ ] All users 45 | 46 | ## Additional Context 47 | 48 | Add any other context, mockups, or examples about the feature request here. 49 | 50 | ## Willingness to Contribute 51 | 52 | - [ ] I'm willing to help implement this feature 53 | - [ ] I can test this feature once implemented 54 | - [ ] I can provide feedback during development -------------------------------------------------------------------------------- /tests/setup.js: -------------------------------------------------------------------------------- 1 | // Jest setup file 2 | // This file runs before all tests 3 | 4 | // Mock browser APIs that may not exist in jsdom 5 | global.navigator = { 6 | ...global.navigator, 7 | userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 8 | language: 'en-US', 9 | languages: ['en-US', 'en'], 10 | platform: 'Win32', 11 | hardwareConcurrency: 8, 12 | maxTouchPoints: 0, 13 | vendor: 'Google Inc.', 14 | vendorSub: '', 15 | cookieEnabled: true, 16 | doNotTrack: null, 17 | appName: 'Netscape', 18 | appVersion: '5.0' 19 | }; 20 | 21 | // Mock screen 22 | global.screen = { 23 | width: 1920, 24 | height: 1080, 25 | availWidth: 1920, 26 | availHeight: 1040, 27 | colorDepth: 24, 28 | pixelDepth: 24, 29 | orientation: { 30 | type: 'landscape-primary', 31 | angle: 0 32 | } 33 | }; 34 | 35 | // Mock localStorage 36 | global.localStorage = { 37 | getItem: jest.fn(), 38 | setItem: jest.fn(), 39 | removeItem: jest.fn(), 40 | clear: jest.fn() 41 | }; 42 | 43 | // Mock sessionStorage 44 | global.sessionStorage = { 45 | getItem: jest.fn(), 46 | setItem: jest.fn(), 47 | removeItem: jest.fn(), 48 | clear: jest.fn() 49 | }; 50 | 51 | // Mock canvas for fingerprinting tests 52 | HTMLCanvasElement.prototype.getContext = jest.fn(() => ({ 53 | fillStyle: '', 54 | fillRect: jest.fn(), 55 | fillText: jest.fn(), 56 | getImageData: jest.fn(() => ({ 57 | data: new Uint8ClampedArray(4) 58 | })) 59 | })); 60 | 61 | HTMLCanvasElement.prototype.toDataURL = jest.fn(() => 'data:image/png;base64,mock'); 62 | 63 | // Suppress console errors during tests (optional) 64 | // global.console.error = jest.fn(); 65 | -------------------------------------------------------------------------------- /examples/integration.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AdTruth Integration Example 7 | 8 | 9 |

AdTruth Integration Example

10 |

This page demonstrates how to integrate AdTruth SDK into your website.

11 | 12 | 13 | 26 | 27 | 28 | 35 | 36 | 37 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [16.x, 18.x, 20.x] 16 | 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v3 20 | 21 | - name: Setup Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | cache: 'npm' 26 | 27 | - name: Upgrade npm 28 | run: npm install -g npm@9 29 | 30 | - name: Install dependencies 31 | run: npm ci 32 | 33 | - name: Run ESLint 34 | run: npm run lint 35 | 36 | - name: Check code formatting 37 | run: npm run format:check 38 | 39 | - name: Build project 40 | run: npm run build 41 | 42 | - name: Check build outputs 43 | run: | 44 | ls -lh dist/ 45 | test -f dist/adtruth.js 46 | test -f dist/adtruth.min.js 47 | test -f dist/adtruth.esm.js 48 | 49 | - name: Run tests 50 | run: npm test 51 | 52 | lint: 53 | runs-on: ubuntu-latest 54 | 55 | steps: 56 | - name: Checkout code 57 | uses: actions/checkout@v3 58 | 59 | - name: Setup Node.js 60 | uses: actions/setup-node@v3 61 | with: 62 | node-version: '18.x' 63 | cache: 'npm' 64 | 65 | - name: Upgrade npm 66 | run: npm install -g npm@9 67 | 68 | - name: Install dependencies 69 | run: npm ci 70 | 71 | - name: Run ESLint 72 | run: npm run lint 73 | 74 | - name: Run Prettier check 75 | run: npm run format:check -------------------------------------------------------------------------------- /docs/LOGO_ASSETS.md: -------------------------------------------------------------------------------- 1 | # AdTruth Logo Assets 2 | 3 | This directory contains official logo assets for the AdTruth platform. 4 | 5 | ## Files 6 | 7 | ### github_banner.PNG 8 | - **Dimensions**: 1200x300px (recommended) 9 | - **Usage**: GitHub repository banner, social media sharing 10 | - **Contains**: AdTruth logo + full branding with tagline 11 | - **Location**: Used in README.md header 12 | 13 | ## Logo Source Files 14 | 15 | The source logo files are located in the main project workspace: 16 | - **Directory**: `/docs/logos/` (workspace root) 17 | - **Files**: 18 | - `adtruth_big_logo_crop.PNG` - Main logo (detective beagle icon) 19 | - `github_banner.PNG` - Full banner with text and tagline 20 | - `name_and_tag_line_crop.PNG` - Name and tagline variant 21 | 22 | ## Logo Usage Guidelines 23 | 24 | ### Current Design (Temporary) 25 | The current logo features a detective beagle character with a targeting scope. This is a **temporary design** created for MVP launch. 26 | 27 | ### Brand Colors 28 | - **Primary**: Green cap (#4A7C59) 29 | - **Secondary**: Brown/tan beagle (#C4915A) 30 | - **Accent**: Gray targeting scope 31 | - **Background**: Cream/beige (#F5F5DC) 32 | 33 | ### Future Improvements 34 | - Logo will be refined with professional designer input 35 | - Consider simpler, more scalable icon for small sizes 36 | - Potential vector (SVG) versions for web use 37 | - High-resolution PNG exports for print materials 38 | 39 | ## Favicon Generation 40 | 41 | Favicons are generated from `adtruth_big_logo_crop.PNG` using the `generate_favicons.py` script in the workspace root. 42 | 43 | Sizes generated: 44 | - 16x16px (favicon-16x16.png) 45 | - 32x32px (favicon-32x32.png) 46 | - 180x180px (apple-touch-icon.png) 47 | - Multi-size ICO (favicon.ico) 48 | 49 | ## Notes 50 | 51 | - All logo files should remain in version control 52 | - Update this document when logo assets change 53 | - Reference these assets in documentation until professional redesign 54 | 55 | > **Note:** This project is still under development. 56 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Pull Request 2 | 3 | ## Description 4 | 5 | Please include a summary of the changes and the related issue. Include relevant motivation and context. 6 | 7 | Fixes # (issue) 8 | 9 | ## Type of Change 10 | 11 | Please delete options that are not relevant. 12 | 13 | - [ ] Bug fix (non-breaking change which fixes an issue) 14 | - [ ] New feature (non-breaking change which adds functionality) 15 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 16 | - [ ] Documentation update 17 | - [ ] Performance improvement 18 | - [ ] Code refactoring 19 | - [ ] Build/CI changes 20 | - [ ] Other (please describe): 21 | 22 | ## Changes Made 23 | 24 | Please provide a clear list of changes: 25 | 26 | - Change 1 27 | - Change 2 28 | - Change 3 29 | 30 | ## Testing 31 | 32 | Please describe the tests that you ran to verify your changes: 33 | 34 | - [ ] Test A (describe) 35 | - [ ] Test B (describe) 36 | - [ ] Manual testing performed 37 | 38 | **Test Configuration**: 39 | - Browser: [e.g., Chrome 120] 40 | - OS: [e.g., Windows 11] 41 | - Node version: [e.g., 18.x] 42 | 43 | ## Screenshots (if applicable) 44 | 45 | Add screenshots to demonstrate visual changes or new features. 46 | 47 | ## Checklist 48 | 49 | Before submitting your PR, please review and check the following: 50 | 51 | - [ ] My code follows the style guidelines of this project 52 | - [ ] I have performed a self-review of my own code 53 | - [ ] I have commented my code, particularly in hard-to-understand areas 54 | - [ ] I have made corresponding changes to the documentation 55 | - [ ] My changes generate no new warnings or errors 56 | - [ ] I have added tests that prove my fix is effective or that my feature works 57 | - [ ] New and existing unit tests pass locally with my changes 58 | - [ ] Any dependent changes have been merged and published 59 | 60 | ## Additional Notes 61 | 62 | Add any additional notes, concerns, or questions for reviewers. 63 | 64 | ## Reviewer Notes 65 | 66 | What should reviewers focus on? Any specific areas of concern? 67 | -------------------------------------------------------------------------------- /examples/squarespace/README.md: -------------------------------------------------------------------------------- 1 | # Squarespace Integration (Testing Required) 2 | 3 | **Priority**: 🟡 MEDIUM (3.4% market share, 18% among website builders) 4 | **Status**: 🚧 Not yet tested 5 | **Market Data**: 3M live sites, 9.7% YoY growth, design-focused platform 6 | 7 | This integration guide is planned but requires testing before documentation. Squarespace is popular with photographers, artists, and design-focused businesses. 8 | 9 | ## Testing Checklist 10 | 11 | - [ ] Test with Personal plan ($16/mo - has Code Injection) 12 | - [ ] Test Code Injection in Settings → Advanced → Code Injection 13 | - [ ] Verify tracking in "Footer" injection section 14 | - [ ] Verify tracking on published site 15 | - [ ] Test with different Squarespace templates 16 | - [ ] Test with Squarespace Commerce (e-commerce plans) 17 | - [ ] Test with Squarespace's built-in analytics 18 | - [ ] Document any conflicts with marketing pixels 19 | - [ ] Test page transitions (Squarespace uses AJAX) 20 | 21 | ## Potential Integration Methods 22 | 23 | 1. **Settings → Advanced → Code Injection → Footer** (Recommended) 24 | 2. **Individual Page → Settings → Advanced → Page Header Code Injection** (Page-specific) 25 | 3. **Code Block** (Not recommended, limited to single page) 26 | 27 | ## Known Considerations 28 | 29 | - Code Injection available on all paid plans (Personal $16/mo and up) 30 | - Squarespace uses AJAX page transitions (may affect tracking) 31 | - Built-in Squarespace Analytics may conflict 32 | - Templates are tightly controlled (limited customization) 33 | - Need to verify script persists across page navigations 34 | 35 | ## Market Opportunity 36 | 37 | Squarespace targets creative professionals and premium businesses: 38 | - Photographers running Instagram/Facebook ads 39 | - E-commerce boutiques (Squarespace Commerce) 40 | - Service businesses with high margins 41 | - Design-focused businesses (portfolios, agencies) 42 | 43 | These businesses often run paid ads and benefit from fraud detection. 44 | 45 | --- 46 | 47 | **Want to help?** Test AdTruth on your Squarespace site and report findings! 48 | -------------------------------------------------------------------------------- /examples/godaddy/README.md: -------------------------------------------------------------------------------- 1 | # GoDaddy Website Builder Integration (Testing Required) 2 | 3 | **Priority**: 🟡 MEDIUM (10% among website builders) 4 | **Status**: 🚧 Not yet tested 5 | **Market Data**: Popular with budget-conscious SMBs, first-time business owners 6 | 7 | This integration guide is planned but requires testing before documentation. GoDaddy's affordable pricing ($9.99/mo intro) makes it popular with new businesses. 8 | 9 | ## Testing Checklist 10 | 11 | - [ ] Test with Basic plan ($9.99/mo intro, $12.99/mo regular) 12 | - [ ] Test Settings → Advanced → HTML/Code injection 13 | - [ ] Verify tracking with GoDaddy Website Builder (not WordPress hosting) 14 | - [ ] Test with different GoDaddy templates 15 | - [ ] Test with GoDaddy Online Store (e-commerce) 16 | - [ ] Document any conflicts with GoDaddy's marketing tools 17 | - [ ] Test with GoDaddy's InSight analytics 18 | - [ ] Verify script loads after GoDaddy's page builder renders 19 | 20 | ## Potential Integration Methods 21 | 22 | 1. **Settings → Advanced → HTML Code** (If available) 23 | 2. **Section → Add Section → Custom Code** (In page builder) 24 | 3. **Footer Settings → Custom Code** (If supported by plan) 25 | 26 | ## Known Considerations 27 | 28 | - GoDaddy Website Builder is different from GoDaddy WordPress hosting 29 | - May have plan limitations on custom code (need to verify) 30 | - GoDaddy has their own marketing suite (potential conflicts) 31 | - Page builder may restrict where scripts can be placed 32 | - Need to distinguish between: 33 | - GoDaddy Website Builder (drag-and-drop) 34 | - GoDaddy Managed WordPress 35 | - GoDaddy Website + Marketing 36 | 37 | ## Market Opportunity 38 | 39 | GoDaddy targets price-sensitive, first-time business owners: 40 | - Small local businesses (restaurants, retail) 41 | - Budget-conscious entrepreneurs 42 | - Businesses just starting paid advertising 43 | - Users who want "all-in-one" solutions 44 | 45 | These businesses are experimenting with ads and need fraud protection without high costs. 46 | 47 | --- 48 | 49 | **Want to help?** Test AdTruth on your GoDaddy site and report findings! 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@adtruth/sdk", 3 | "version": "0.3.2", 4 | "description": "Open-source ad fraud detection for small businesses. Identify bot traffic, click fraud, and fake conversions from Google Ads, Facebook Ads, and more.", 5 | "main": "dist/adtruth.js", 6 | "module": "dist/adtruth.esm.js", 7 | "unpkg": "dist/adtruth.min.js", 8 | "types": "index.d.ts", 9 | "files": [ 10 | "dist", 11 | "index.d.ts" 12 | ], 13 | "scripts": { 14 | "build": "rollup -c config/rollup.config.mjs", 15 | "dev": "rollup -c config/rollup.config.mjs -w", 16 | "test": "jest --config config/jest.config.js", 17 | "lint": "eslint src/ --config config/.eslintrc.json", 18 | "lint:fix": "eslint src/ --config config/.eslintrc.json --fix", 19 | "format": "prettier --write \"src/**/*.js\" --config config/.prettierrc", 20 | "format:check": "prettier --check \"src/**/*.js\" --config config/.prettierrc" 21 | }, 22 | "prettier": "./config/.prettierrc", 23 | "keywords": [ 24 | "fraud-detection", 25 | "click-fraud", 26 | "bot-detection", 27 | "ad-fraud", 28 | "analytics", 29 | "advertising", 30 | "tracking", 31 | "adtech", 32 | "marketing", 33 | "google-ads", 34 | "facebook-ads", 35 | "traffic-analysis", 36 | "behavioral-tracking", 37 | "fingerprinting", 38 | "small-business", 39 | "open-source" 40 | ], 41 | "author": "Hongyi Shui (https://github.com/papa-torb)", 42 | "license": "MIT", 43 | "engines": { 44 | "node": ">=16.0.0" 45 | }, 46 | "repository": { 47 | "type": "git", 48 | "url": "git+https://github.com/papa-torb/adtruth.git" 49 | }, 50 | "bugs": { 51 | "url": "https://github.com/papa-torb/adtruth/issues" 52 | }, 53 | "homepage": "https://github.com/papa-torb/adtruth#readme", 54 | "devDependencies": { 55 | "@babel/core": "^7.23.9", 56 | "@babel/preset-env": "^7.23.9", 57 | "@rollup/plugin-terser": "^0.4.4", 58 | "babel-jest": "^29.7.0", 59 | "eslint": "^8.57.0", 60 | "jest": "^29.7.0", 61 | "jest-environment-jsdom": "^29.7.0", 62 | "prettier": "^3.2.5", 63 | "rollup": "^4.9.6" 64 | } 65 | } -------------------------------------------------------------------------------- /src/utils/session.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate a unique session ID 3 | * Uses crypto.randomUUID if available, falls back to Math.random 4 | */ 5 | export function generateSessionId() { 6 | // Try to use crypto.randomUUID (modern browsers) 7 | if (typeof crypto !== 'undefined' && crypto.randomUUID) { 8 | return crypto.randomUUID(); 9 | } 10 | 11 | // Fallback for older browsers 12 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 13 | const r = (Math.random() * 16) | 0; 14 | const v = c === 'x' ? r : (r & 0x3) | 0x8; 15 | return v.toString(16); 16 | }); 17 | } 18 | 19 | /** 20 | * Get or create a session ID, stored in sessionStorage 21 | */ 22 | export function getSessionId() { 23 | const SESSION_KEY = '_adtruth_session'; 24 | 25 | try { 26 | // Try to get existing session 27 | let sessionId = sessionStorage.getItem(SESSION_KEY); 28 | 29 | if (!sessionId) { 30 | sessionId = generateSessionId(); 31 | sessionStorage.setItem(SESSION_KEY, sessionId); 32 | } 33 | 34 | return sessionId; 35 | } catch (e) { 36 | // If sessionStorage is not available, generate a new ID each time 37 | return generateSessionId(); 38 | } 39 | } 40 | 41 | /** 42 | * Get or create a visitor ID, stored in localStorage (persistent) 43 | */ 44 | export function getVisitorId() { 45 | const VISITOR_KEY = '_adtruth_visitor'; 46 | 47 | try { 48 | // Try to get existing visitor ID 49 | let visitorId = localStorage.getItem(VISITOR_KEY); 50 | 51 | if (!visitorId) { 52 | visitorId = generateSessionId(); 53 | localStorage.setItem(VISITOR_KEY, visitorId); 54 | } 55 | 56 | return visitorId; 57 | } catch (e) { 58 | // If localStorage is not available, use sessionStorage as fallback 59 | try { 60 | let visitorId = sessionStorage.getItem(VISITOR_KEY); 61 | if (!visitorId) { 62 | visitorId = generateSessionId(); 63 | sessionStorage.setItem(VISITOR_KEY, visitorId); 64 | } 65 | return visitorId; 66 | } catch (e2) { 67 | // Generate a new ID each time as last resort 68 | return generateSessionId(); 69 | } 70 | } 71 | } 72 | // Note: This project is still under development. 73 | -------------------------------------------------------------------------------- /examples/wordpress/README.md: -------------------------------------------------------------------------------- 1 | # WordPress Integration 2 | 3 | **Priority**: 🔴 HIGH (43% of all websites, 63% of CMS market) 4 | **Status**: ✅ TESTED & VERIFIED (May 2024) 5 | **Market Data**: Most popular website platform, powers nearly half the internet 6 | 7 | This integration has been tested on a live WordPress site and confirmed working. Integration takes approximately 2 minutes using the WPCode plugin method. 8 | 9 | ## Integration Guide 10 | 11 | **[→ Read the Full Integration Guide](INTEGRATION-GUIDE.md)** 12 | 13 | Our comprehensive guide covers: 14 | - Method 1: Using WPCode plugin (RECOMMENDED - tested and verified) 15 | - Method 2: Theme functions (for developers) 16 | - Method 3: Direct theme edit (advanced users) 17 | 18 | **Test Results**: Script loaded successfully, page views appeared in dashboard within 2 minutes. 19 | 20 | ## Quick Start 21 | 22 | The fastest way to integrate AdTruth with WordPress: 23 | 24 | 1. Install the **WPCode** plugin from WordPress admin (Plugins > Add New) 25 | 2. Go to **Code Snippets > Header & Footer** 26 | 3. Paste the AdTruth tracking code in the **Footer** section 27 | 4. Save and verify 28 | 29 | See the [full guide](INTEGRATION-GUIDE.md) for detailed instructions. 30 | 31 | ## Test Results (May 14, 2024) 32 | 33 | Tested on TasteWP test environment: 34 | 35 | - ✅ Script loaded without errors 36 | - ✅ Page views tracked within 2 minutes 37 | - ✅ Traffic source correctly identified 38 | - ✅ All tracking data appeared in dashboard 39 | - ⚠️ Test environment traffic flagged as suspicious (expected behavior) 40 | 41 | ## WordPress.com vs WordPress.org 42 | 43 | **WordPress.org (Self-Hosted)**: All methods work. This is what most small businesses use. 44 | 45 | **WordPress.com (Hosted)**: 46 | - Free/Personal/Premium plans: Cannot add custom scripts 47 | - Business plan ($25/mo) or higher: Can use custom code 48 | 49 | ## Files in This Directory 50 | 51 | - **[INTEGRATION-GUIDE.md](INTEGRATION-GUIDE.md)** - Complete step-by-step instructions 52 | - **[demo-output.html](demo-output.html)** - Example WordPress page with AdTruth installed 53 | - **README.md** - This file 54 | 55 | ## Need Help? 56 | 57 | - Check your API key: [AdTruth Dashboard](https://adtruth.io/dashboard) 58 | - Full documentation: [GitHub Repository](https://github.com/papa-torb/adtruth) 59 | - Report issues: [GitHub Issues](https://github.com/papa-torb/adtruth/issues) 60 | -------------------------------------------------------------------------------- /examples/wordpress/demo-output.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | My Small Business - WordPress Demo 7 | 8 | 27 | 28 | 29 | 30 | 31 |
32 |
33 |

My Small Business

34 |

A typical WordPress site for a small business

35 |
36 |
37 | 38 |
39 |
40 |

Welcome to Our Business

41 |

This is a demo of what a typical WordPress page looks like after AdTruth tracking is installed.

42 |

The tracking script is loaded in the footer, just before the closing </body> tag.

43 |
44 |
45 | 46 |
47 |
48 |

© 2025 My Small Business. All rights reserved.

49 |
50 |
51 | 52 | 53 | 54 | 55 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AdTruth SDK - Open-source fraud detection for paid advertising 3 | * @version 0.3.2 4 | * @license MIT 5 | */ 6 | 7 | import tracker from './core/tracker.js'; 8 | 9 | // Public API 10 | const AdTruth = { 11 | /** 12 | * Initialize AdTruth with API key 13 | * @param {string} apiKey - Your AdTruth API key 14 | * @param {object} options - Optional configuration 15 | */ 16 | init: function (apiKey, options) { 17 | try { 18 | tracker.init(apiKey, options); 19 | } catch (error) { 20 | console.error('AdTruth: Initialization failed', error); 21 | } 22 | }, 23 | 24 | /** 25 | * Manually track a pageview 26 | */ 27 | trackPageview: function () { 28 | try { 29 | tracker.track('pageview'); 30 | } catch (error) { 31 | console.error('AdTruth: Tracking failed', error); 32 | } 33 | }, 34 | 35 | /** 36 | * Track a custom event (for future use) 37 | * @param {string} eventName - The name of the event 38 | * @param {object} data - Optional event data 39 | */ 40 | track: function (eventName, _data) { 41 | try { 42 | // For MVP, just track as pageview 43 | // Future versions will support custom events 44 | tracker.track(eventName || 'pageview'); 45 | } catch (error) { 46 | console.error('AdTruth: Tracking failed', error); 47 | } 48 | }, 49 | 50 | /** 51 | * Get current metrics (debug only) 52 | * @returns {object} Current tracking metrics 53 | */ 54 | getMetrics: function () { 55 | if (!tracker.behaviorTracker) { 56 | return { error: 'Tracker not initialized. Call AdTruth.init() first.' }; 57 | } 58 | return { 59 | behavior: tracker.behaviorTracker.getMetrics(), 60 | fingerprint: tracker.fingerprint, 61 | canvasFingerprint: tracker.canvasFingerprint 62 | }; 63 | }, 64 | 65 | /** 66 | * Version information 67 | */ 68 | version: '0.3.2', 69 | 70 | // Expose tracker for debugging (only in debug mode) 71 | get _debug() { 72 | return tracker; 73 | } 74 | }; 75 | 76 | // Auto-initialize if script tag has data-api-key attribute 77 | if (typeof document !== 'undefined') { 78 | document.addEventListener('DOMContentLoaded', function () { 79 | const script = document.querySelector('script[data-adtruth-api-key]'); 80 | if (script) { 81 | const apiKey = script.getAttribute('data-adtruth-api-key'); 82 | const debug = script.getAttribute('data-adtruth-debug') === 'true'; 83 | AdTruth.init(apiKey, { debug }); 84 | } 85 | }); 86 | } 87 | 88 | // Export for different environments 89 | export default AdTruth; 90 | // Note: This project is still under development. 91 | -------------------------------------------------------------------------------- /tests/manual/test-unload-fetch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AdTruth Unload Test (Fetch Fallback) 7 | 36 | 37 | 38 |
39 |

🧪 AdTruth Unload Test (Fetch Fallback)

40 | 41 |
42 | This version uses FETCH instead of sendBeacon for reliable localhost testing. 43 |
    44 |
  1. Open DevTools Console (F12) and enable "Persist Logs"
  2. 45 |
  3. Interact with the page (click, scroll, move mouse)
  4. 46 |
  5. Wait 10-20 seconds
  6. 47 |
  7. Click "Navigate Away" button
  8. 48 |
  9. Check Console and database
  10. 49 |
50 |
51 | 52 |
53 | 54 | 55 | 56 | 57 |
58 | 59 |
60 |

Scroll Area

61 |

Scroll to increase metrics...

62 |

Bottom reached!

63 |
64 | 65 |
66 | 67 |
68 |
69 | 70 | 71 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /examples/wix/README.md: -------------------------------------------------------------------------------- 1 | # Wix Integration 2 | 3 | **Priority**: 🟡 HIGH (4.1% market share, 32.6% YoY growth - fastest growing!) 4 | **Status**: ⏳ TEST PENDING (Documentation ready, testing needed) 5 | **Market Data**: Fastest-growing website builder, popular with small businesses and creatives 6 | 7 | This integration guide is ready but requires testing on a live Wix site. Wix is our #2 priority for testing due to its rapid growth rate and SMB-friendly platform. 8 | 9 | ## Integration Guide 10 | 11 | **[→ Read the Full Integration Guide](INTEGRATION-GUIDE.md)** 12 | 13 | Our guide covers: 14 | - Method 1: Custom Code feature (RECOMMENDED - site-wide tracking) 15 | - Method 2: HTML embed element (per-page tracking) 16 | - Plan requirements and pricing 17 | - Troubleshooting common issues 18 | 19 | **Important**: Wix requires a paid plan (Light or higher, $17/mo+) to add custom code. 20 | 21 | ## Quick Start 22 | 23 | The fastest way to integrate AdTruth with Wix: 24 | 25 | 1. Ensure you have a paid Wix plan (Light or higher) 26 | 2. Go to **Settings > Custom Code** in your Wix dashboard 27 | 3. Click **+ Add Custom Code** 28 | 4. Paste the AdTruth tracking script 29 | 5. Choose "All pages" and "Body - end" 30 | 6. Click Apply 31 | 32 | See the [full guide](INTEGRATION-GUIDE.md) for detailed instructions. 33 | 34 | ## Plan Requirements 35 | 36 | Wix custom code requires: 37 | - Paid premium plan (minimum: Light at $17/month) 38 | - Connected custom domain (no free wix.com subdomains) 39 | - Published site (won't work in preview mode) 40 | 41 | All paid Wix plans (Light, Core, Business, Business Elite) support custom code. 42 | 43 | ## Why We Haven't Tested Yet 44 | 45 | Unlike WordPress (which we tested for free), Wix requires: 46 | - Upfront payment of $17 for the Light plan 47 | - A custom domain connection 48 | - 14-day money-back guarantee (not a true free trial) 49 | 50 | We've prioritized WordPress testing first but welcome Wix users to test and report results. 51 | 52 | ## Testing Needed 53 | 54 | We need Wix users to verify this integration! If you have a Wix site on a paid plan: 55 | 56 | 1. Follow the [Integration Guide](INTEGRATION-GUIDE.md) 57 | 2. Report results on [GitHub Issues](https://github.com/papa-torb/adtruth/issues) 58 | 3. Share your experience: 59 | - Did the script load correctly? 60 | - Did data appear in your dashboard? 61 | - How long did integration take? 62 | - Any issues or conflicts? 63 | 64 | Your feedback will help us mark this guide as "Tested & Verified" and improve it for other users. 65 | 66 | ## Expected Integration Time 67 | 68 | Based on Wix's official documentation: 69 | - **Custom Code method**: ~5 minutes 70 | - **HTML Embed method**: ~2-3 minutes per page 71 | 72 | Faster than WordPress if you're already on a paid plan. 73 | 74 | ## Files in This Directory 75 | 76 | - **[INTEGRATION-GUIDE.md](INTEGRATION-GUIDE.md)** - Complete step-by-step instructions 77 | - **README.md** - This file 78 | 79 | ## Need Help? 80 | 81 | - Check your API key: [AdTruth Dashboard](https://adtruth.io/dashboard) 82 | - Wix official docs: [Custom Code Guide](https://support.wix.com/en/article/wix-editor-embedding-custom-code-on-your-site) 83 | - Report issues: [GitHub Issues](https://github.com/papa-torb/adtruth/issues) 84 | 85 | ## Want to Help Test? 86 | 87 | If you're a Wix user and would like to help verify this integration: 88 | 89 | 1. Follow our [Integration Guide](INTEGRATION-GUIDE.md) 90 | 2. Take screenshots of the process 91 | 3. Report back with results (working/not working) 92 | 4. Share any issues or improvements 93 | 94 | We'll update the status to "Tested & Verified" and credit contributors! 95 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | We actively support the following versions of AdTruth with security updates: 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 0.2.x | :white_check_mark: | 10 | | 0.1.x | :x: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | We take the security of AdTruth seriously. If you discover a security vulnerability, please follow these steps: 15 | 16 | ### How to Report 17 | 18 | **Please do NOT report security vulnerabilities through public GitHub issues.** 19 | 20 | Instead, please report them via email to: 21 | - **Email**: hongyishui92@gmail.com 22 | - **Subject**: [SECURITY] Brief description of the vulnerability 23 | 24 | Alternatively, you can use GitHub's private security advisory feature: 25 | 1. Go to the [Security tab](https://github.com/papa-torb/adtruth/security) 26 | 2. Click "Report a vulnerability" 27 | 3. Fill in the details 28 | 29 | ### What to Include 30 | 31 | When reporting a vulnerability, please include: 32 | 33 | - **Description**: Clear description of the vulnerability 34 | - **Impact**: What can an attacker do? What data is at risk? 35 | - **Reproduction**: Step-by-step instructions to reproduce the issue 36 | - **Environment**: Browser version, OS, AdTruth version 37 | - **Proof of Concept**: If applicable, provide code or screenshots 38 | - **Suggested Fix**: If you have ideas on how to fix it (optional) 39 | 40 | ### What to Expect 41 | 42 | - **Acknowledgment**: We will acknowledge receipt of your report within 48 hours 43 | - **Updates**: We will keep you informed of our progress every 5-7 days 44 | - **Timeline**: We aim to release a fix within 30 days for critical vulnerabilities 45 | - **Credit**: We will credit you in the security advisory (unless you prefer to remain anonymous) 46 | 47 | ### Security Update Process 48 | 49 | 1. **Triage**: We evaluate the severity and impact 50 | 2. **Fix Development**: We develop and test a patch 51 | 3. **Release**: We release a new version with the fix 52 | 4. **Disclosure**: We publish a security advisory with details 53 | 5. **Notification**: We notify users via GitHub releases and documentation 54 | 55 | ### Security Best Practices for Users 56 | 57 | When integrating AdTruth: 58 | 59 | - Always use the latest version (v0.2.1 or newer) 60 | - Use HTTPS for your website 61 | - Keep your API keys secure (never commit to public repositories) 62 | - Regularly review our [CHANGELOG](CHANGELOG.md) for security updates 63 | - Subscribe to GitHub releases for security notifications 64 | 65 | ## Security Features 66 | 67 | AdTruth is designed with security in mind: 68 | 69 | - **No Personal Data Collection**: We don't collect names, emails, or PII 70 | - **Client-Side Only**: All tracking happens in the browser 71 | - **No Cookies**: We use sessionStorage and localStorage only 72 | - **HTTPS Required**: Our API only accepts HTTPS connections 73 | - **API Key Authentication**: Secure authentication for all tracking requests 74 | - **Rate Limiting**: Protection against abuse (backend) 75 | - **Input Validation**: All data is validated before processing 76 | 77 | ## Scope 78 | 79 | This security policy applies to: 80 | 81 | - AdTruth tracking script (src/index.js) 82 | - Distribution files (dist/*.js) 83 | - Backend API (api.adtruth.io) 84 | - Documentation and examples 85 | 86 | Out of scope: 87 | 88 | - Issues in third-party dependencies (report to respective maintainers) 89 | - Issues in user's implementation (we can help, but it's not a vulnerability) 90 | - Theoretical vulnerabilities without proof of concept 91 | 92 | ## Contact 93 | 94 | For security-related questions or concerns: 95 | - Email: hongyishui92@gmail.com 96 | - GitHub: [@papa-torb](https://github.com/papa-torb) 97 | 98 | Thank you for helping keep AdTruth and our users safe! 99 | -------------------------------------------------------------------------------- /.github/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.2.1] - 2024-07-14 9 | 10 | ### Fixed 11 | - Page unload tracking now correctly captures session duration instead of showing 5ms 12 | - Fixed CORS issues with fetch requests by adding `credentials: 'omit'` 13 | - Fixed backend endpoint URLs to include trailing slash (prevents 307 redirects) 14 | 15 | ### Changed 16 | - Replaced `sendBeacon()` with `fetch(..., {keepalive: true})` for better CORS control 17 | - Improved reliability of behavior data transmission on page unload 18 | 19 | ## [0.2.0] - 2024-07-05 20 | 21 | ### Added 22 | - **Page unload tracking**: Captures accurate time-on-page using beforeunload/pagehide/unload events 23 | - **Platform integration guides**: Comprehensive guides for WordPress, Shopify, and Wix 24 | - Integration examples with Blue Haven Coffee demo site 25 | - CDN versioning strategy using @latest tag for automatic updates 26 | 27 | ### Changed 28 | - Improved README structure with better visual hierarchy 29 | - Enhanced documentation with production URLs (adtruth.io, api.adtruth.io) 30 | - Optimized for search discoverability with keyword-rich sections 31 | 32 | ### Fixed 33 | - Broken navigation links in README header 34 | - Core positioning clarity: AdTruth is detection/analytics, not blocking 35 | 36 | ## [0.1.2] - 2024-01-19 37 | 38 | ### Added 39 | - Initial public release with core fraud detection features 40 | - Browser fingerprinting (screen resolution, timezone, language, platform) 41 | - Basic behavior tracking (time on page, click events, scroll depth) 42 | - Canvas fingerprinting for unique device signatures 43 | - Mouse movement and touch tracking for bot detection 44 | - Session and visitor ID generation 45 | - UTM parameter and referrer tracking 46 | 47 | ### Changed 48 | - Improved build configuration with Rollup 49 | - Enhanced minification (bundle size: ~12KB) 50 | 51 | ## [0.1.1] - 2024-01-12 52 | 53 | ### Added 54 | - Initial SDK development 55 | - Basic page view tracking 56 | - API key authentication 57 | - Integration with backend API 58 | 59 | ### Changed 60 | - Core tracking infrastructure 61 | - Data transmission to api.adtruth.io 62 | 63 | ## [Unreleased] 64 | 65 | ### Planned 66 | - TypeScript definitions for better IDE support 67 | - Enhanced error handling and retry logic 68 | - Batch data transmission for performance 69 | - WebGL fingerprinting 70 | - Custom event tracking API 71 | - npm package publication 72 | 73 | --- 74 | 75 | ## Version Support 76 | 77 | | Version | Supported | Status | 78 | |---------|-----------|--------| 79 | | 0.2.x | ✅ | Active development | 80 | | 0.1.x | ❌ | No longer supported | 81 | 82 | ## Upgrade Guide 83 | 84 | ### Upgrading from 0.1.x to 0.2.x 85 | 86 | **No breaking changes** - v0.2.x is fully backward compatible with v0.1.x. 87 | 88 | The main improvements are: 89 | - More accurate time-on-page tracking 90 | - Better cross-origin request handling 91 | - Platform integration guides 92 | 93 | **Recommendation**: Update to v0.2.1 immediately for accurate behavior data. 94 | 95 | ```html 96 | 97 | 98 | ``` 99 | 100 | ## Release Notes Format 101 | 102 | We follow semantic versioning (MAJOR.MINOR.PATCH): 103 | - **MAJOR**: Breaking changes that require user action 104 | - **MINOR**: New features, backward compatible 105 | - **PATCH**: Bug fixes, backward compatible 106 | 107 | ## Contributing 108 | 109 | See [CONTRIBUTING.md](CONTRIBUTING.md) for how to suggest changes or report issues. 110 | 111 | ## Security 112 | 113 | See [SECURITY.md](SECURITY.md) for our security policy and how to report vulnerabilities. 114 | 115 | [0.2.1]: https://github.com/papa-torb/adtruth/releases/tag/v0.2.1 116 | [0.2.0]: https://github.com/papa-torb/adtruth/releases/tag/v0.2.0 117 | [0.1.2]: https://github.com/papa-torb/adtruth/releases/tag/v0.1.2 118 | [0.1.1]: https://github.com/papa-torb/adtruth/releases/tag/v0.1.1 119 | [Unreleased]: https://github.com/papa-torb/adtruth/compare/v0.2.1...HEAD 120 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // TypeScript definitions for AdTruth SDK 2 | 3 | declare module '@adtruth/sdk' { 4 | /** 5 | * Configuration options for AdTruth initialization 6 | */ 7 | export interface AdTruthConfig { 8 | /** 9 | * Enable debug mode for console logging 10 | * @default false 11 | */ 12 | debug?: boolean; 13 | 14 | /** 15 | * Custom API endpoint URL 16 | * @default 'https://api.adtruth.io/track/' 17 | */ 18 | endpoint?: string; 19 | } 20 | 21 | /** 22 | * Behavior metrics collected by the SDK 23 | */ 24 | export interface BehaviorMetrics { 25 | /** Number of clicks on the page */ 26 | clicks: number; 27 | 28 | /** Number of mouse move events detected */ 29 | mouseMoves: number; 30 | 31 | /** Maximum scroll depth as percentage (0-1) */ 32 | scrollDepth: number; 33 | 34 | /** Time spent on page in milliseconds */ 35 | timeOnPage: number; 36 | 37 | /** Time to first interaction in milliseconds */ 38 | timeToFirstInteraction: number | null; 39 | 40 | /** Average interval between clicks in milliseconds */ 41 | avgClickInterval: number | null; 42 | 43 | /** Mouse movement patterns (velocity, variance, etc.) */ 44 | mousePatterns: { 45 | avgVelocity: number; 46 | velocityVariance: number; 47 | avgAngleChange: number; 48 | sampleCount: number; 49 | hasHumanPatterns: boolean; 50 | } | null; 51 | 52 | /** Touch interaction patterns for mobile */ 53 | touchPatterns: { 54 | tapCount: number; 55 | avgTapInterval: number | null; 56 | swipeDetected: boolean; 57 | swipeVelocity: number | null; 58 | touchSampleCount: number; 59 | } | null; 60 | } 61 | 62 | /** 63 | * Device fingerprint information 64 | */ 65 | export interface DeviceFingerprint { 66 | /** Basic fingerprint hash */ 67 | hash: string; 68 | 69 | /** Detailed fingerprint data */ 70 | details: { 71 | screen: string; 72 | timezone: string; 73 | timezoneOffset: number; 74 | hardwareConcurrency: number; 75 | deviceMemory: number; 76 | }; 77 | } 78 | 79 | /** 80 | * Canvas fingerprint for enhanced bot detection 81 | */ 82 | export interface CanvasFingerprint { 83 | /** Canvas rendering hash */ 84 | hash: string; 85 | 86 | /** Partial canvas data */ 87 | partial: string; 88 | } 89 | 90 | /** 91 | * Complete metrics collected by AdTruth 92 | */ 93 | export interface AdTruthMetrics { 94 | /** Behavioral tracking metrics */ 95 | behavior: BehaviorMetrics; 96 | 97 | /** Device fingerprint hash */ 98 | fingerprint: string; 99 | 100 | /** Canvas fingerprint hash (null if unavailable) */ 101 | canvasFingerprint: string | null; 102 | 103 | /** Session ID */ 104 | sessionId: string; 105 | 106 | /** Visitor ID (persistent across sessions) */ 107 | visitorId: string; 108 | 109 | /** Current page URL */ 110 | url: string; 111 | 112 | /** Referrer URL */ 113 | referrer: string; 114 | 115 | /** UTM parameters and click IDs */ 116 | urlParams: Record; 117 | } 118 | 119 | /** 120 | * Main AdTruth SDK interface 121 | */ 122 | export interface AdTruthAPI { 123 | /** 124 | * Initialize AdTruth tracking 125 | * @param apiKey - Your AdTruth API key 126 | * @param options - Optional configuration 127 | */ 128 | init(apiKey: string, options?: AdTruthConfig): void; 129 | 130 | /** 131 | * Manually track a pageview 132 | * Usually called automatically on init 133 | */ 134 | trackPageview(): void; 135 | 136 | /** 137 | * Track a custom event 138 | * @param eventName - Name of the event 139 | * @param data - Optional event data 140 | */ 141 | track(eventName: string, data?: Record): void; 142 | 143 | /** 144 | * Get current metrics 145 | * @returns Current tracking metrics 146 | */ 147 | getMetrics(): AdTruthMetrics; 148 | 149 | /** 150 | * SDK version 151 | */ 152 | readonly version: string; 153 | } 154 | 155 | /** 156 | * Global AdTruth object available after script load 157 | */ 158 | const AdTruth: AdTruthAPI; 159 | 160 | export default AdTruth; 161 | } 162 | 163 | declare global { 164 | interface Window { 165 | AdTruth: import('@adtruth/sdk').AdTruthAPI; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /tests/manual/simple_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Simple AdTruth Test 7 | 42 | 43 | 44 |

Simple AdTruth Test

45 |

Click the button below to test the tracking system:

46 | 47 | 48 | 49 | 50 |
51 | 52 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /src/utils/fingerprint.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Browser fingerprinting utilities for fraud detection 3 | * Phase 1: Basic fingerprinting 4 | * Phase 2: Canvas fingerprinting 5 | */ 6 | 7 | /** 8 | * Generate a basic device fingerprint 9 | * @returns {object} Basic fingerprint data 10 | */ 11 | export function getBasicFingerprint() { 12 | const fingerprint = { 13 | screen: `${screen.width}x${screen.height}x${screen.colorDepth}`, 14 | timezone: getTimezone(), 15 | timezoneOffset: new Date().getTimezoneOffset(), 16 | hardwareConcurrency: navigator.hardwareConcurrency || 0, 17 | deviceMemory: navigator.deviceMemory || 0 18 | }; 19 | 20 | // Create simple hash from fingerprint data 21 | const hash = hashFingerprint(fingerprint); 22 | 23 | return { 24 | hash, 25 | details: fingerprint 26 | }; 27 | } 28 | 29 | /** 30 | * Get timezone information 31 | */ 32 | function getTimezone() { 33 | try { 34 | return Intl.DateTimeFormat().resolvedOptions().timeZone; 35 | } catch (e) { 36 | return 'unknown'; 37 | } 38 | } 39 | 40 | /** 41 | * Get enhanced browser information 42 | * @returns {object} Enhanced browser data 43 | */ 44 | export function getEnhancedBrowserInfo() { 45 | return { 46 | user_agent: navigator.userAgent, 47 | language: navigator.language, 48 | languages: navigator.languages ? Array.from(navigator.languages).join(',') : '', 49 | platform: navigator.platform, 50 | vendor: navigator.vendor || '', 51 | cookieEnabled: navigator.cookieEnabled, 52 | doNotTrack: navigator.doNotTrack || 'unknown', 53 | maxTouchPoints: navigator.maxTouchPoints || 0, 54 | hardwareConcurrency: navigator.hardwareConcurrency || 0, 55 | deviceMemory: navigator.deviceMemory || 0 56 | }; 57 | } 58 | 59 | /** 60 | * Simple hash function for fingerprint data 61 | * @param {object} data - Data to hash 62 | * @returns {string} Hash string 63 | */ 64 | function hashFingerprint(data) { 65 | const str = JSON.stringify(data); 66 | let hash = 0; 67 | 68 | for (let i = 0; i < str.length; i++) { 69 | hash = (hash << 5) - hash + str.charCodeAt(i); 70 | hash = hash & hash; // Convert to 32-bit integer 71 | } 72 | 73 | return Math.abs(hash).toString(36); 74 | } 75 | 76 | /** 77 | * Get input method detection data 78 | * @returns {object} Input method information 79 | */ 80 | export function getInputMethod() { 81 | const hasTouch = 'ontouchstart' in window; 82 | const hasMouse = 83 | typeof matchMedia !== 'undefined' ? matchMedia('(pointer: fine)').matches : false; 84 | const hasHover = typeof matchMedia !== 'undefined' ? matchMedia('(hover: hover)').matches : false; 85 | 86 | return { 87 | hasTouch, 88 | hasMouse, 89 | hasHover, 90 | maxTouchPoints: navigator.maxTouchPoints || 0, 91 | inputInconsistency: hasTouch && hasMouse && hasHover // Potential fraud signal 92 | }; 93 | } 94 | 95 | /** 96 | * Generate canvas fingerprint (Phase 2) 97 | * Creates unique hash based on how browser renders canvas 98 | * @returns {string|null} Canvas fingerprint hash 99 | */ 100 | export function getCanvasFingerprint() { 101 | try { 102 | const canvas = document.createElement('canvas'); 103 | const ctx = canvas.getContext('2d'); 104 | 105 | if (!ctx) { 106 | return null; 107 | } 108 | 109 | // Set canvas size 110 | canvas.width = 200; 111 | canvas.height = 50; 112 | 113 | // Draw text with specific styling 114 | ctx.textBaseline = 'top'; 115 | ctx.font = '14px Arial'; 116 | ctx.textBaseline = 'alphabetic'; 117 | ctx.fillStyle = '#f60'; 118 | ctx.fillRect(125, 1, 62, 20); 119 | 120 | // Draw text 121 | ctx.fillStyle = '#069'; 122 | ctx.font = '11pt no-real-font-123'; 123 | ctx.fillText('AdTruth🔒', 2, 15); 124 | ctx.fillStyle = 'rgba(102, 204, 0, 0.7)'; 125 | ctx.font = '18pt Arial'; 126 | ctx.fillText('AdTruth', 4, 17); 127 | 128 | // Get canvas data and create hash 129 | const dataURL = canvas.toDataURL(); 130 | 131 | // Use last 50 characters as fingerprint (most variable part) 132 | const canvasHash = dataURL.slice(-50); 133 | 134 | // Also create a simple numeric hash for the full data 135 | let numericHash = 0; 136 | for (let i = 0; i < dataURL.length; i++) { 137 | numericHash = (numericHash << 5) - numericHash + dataURL.charCodeAt(i); 138 | numericHash = numericHash & numericHash; 139 | } 140 | 141 | return { 142 | hash: Math.abs(numericHash).toString(36), 143 | partial: canvasHash 144 | }; 145 | } catch (e) { 146 | // Canvas fingerprinting might be blocked or fail 147 | return null; 148 | } 149 | } 150 | 151 | // Note: This project is still under development. 152 | -------------------------------------------------------------------------------- /tests/manual/phase2_manual_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Phase 2 Manual Test 5 | 45 | 46 | 47 |
Scroll: 0%
48 | 49 |
50 |

🔍 Phase 2 Manual Test

51 | 52 |
53 |

Instructions:

54 |
    55 |
  1. Scroll up and down this page
  2. 56 |
  3. Move mouse in the area below
  4. 57 |
  5. Click the test buttons
  6. 58 |
  7. Click "SEND DATA NOW" to submit to database
  8. 59 |
60 |
61 | 62 |
63 |

🖱️ Mouse Test Area

64 |
Move your mouse here in curves and circles!
65 |
66 | 67 |
68 |

🎯 Click Test

69 | 70 | 71 | 72 |

Clicks: 0

73 |
74 | 75 |
76 |

📤 Send Tracking Data

77 | 78 |

79 |
80 | 81 |
82 |
83 |

Keep scrolling to test scroll depth...

84 |
85 |
86 |
87 | 88 | 89 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # AdTruth Integration Examples 2 | 3 | This directory contains real-world integration examples showing how to implement AdTruth tracking on different platforms and website types. 4 | 5 | ## Live Examples 6 | 7 | ### 1. Blue Haven Coffee (Static HTML) 8 | **Path**: `bluehaven-coffee/` 9 | **Platform**: Vanilla HTML/CSS/JavaScript 10 | **Use Case**: Local business landing page 11 | **Live Demo**: [View on GitHub Pages](https://papa-torb.github.io/adtruth/examples/bluehaven-coffee/) 12 | 13 | A demo coffee shop website demonstrating: 14 | - Basic AdTruth integration in static HTML 15 | - One-line script implementation 16 | - Tracking across navigation and interactions 17 | 18 | **Integration Method**: Standard script tag before `` 19 | 20 | ```html 21 | 33 | ``` 34 | 35 | --- 36 | 37 | ## Platform Integrations (By Market Priority) 38 | 39 | Based on 2025 small business website builder market research, we're testing and documenting integrations in priority order: 40 | 41 | ### 🔴 HIGH PRIORITY - Testing In Progress 42 | 43 | #### 1. WordPress (43% of all websites) 44 | - **Market Share**: 63% of CMS-powered sites 45 | - **Status**: 🚧 Testing required 46 | - **Versions**: WordPress.org (self-hosted) + WordPress.com (hosted) 47 | - [View Guide →](wordpress/) 48 | 49 | #### 2. Wix (4.1% market share, 45% among builders) 50 | - **Market Share**: 8M live sites, 32.6% YoY growth 51 | - **Status**: 🚧 Testing required 52 | - **Target**: Small businesses, fastest growing platform 53 | - [View Guide →](wix/) 54 | 55 | #### 3. Shopify (5.6% overall, 26% of e-commerce) 56 | - **Market Share**: 4M live sites, dominant in e-commerce 57 | - **Status**: 🚧 Testing required 58 | - **Target**: E-commerce businesses with high ad spend 59 | - [View Guide →](shopify/) 60 | 61 | ### 🟡 MEDIUM PRIORITY - Planned 62 | 63 | #### 4. Squarespace (3.4% market share) 64 | - **Market Share**: 3M live sites, 9.7% YoY growth 65 | - **Status**: 🚧 Testing required 66 | - **Target**: Design-focused businesses (photographers, artists) 67 | - [View Guide →](squarespace/) 68 | 69 | #### 5. GoDaddy Website Builder (10% among builders) 70 | - **Market Share**: Popular with budget-conscious SMBs 71 | - **Status**: 🚧 Testing required 72 | - **Target**: First-time business owners 73 | - [View Guide →](godaddy/) 74 | 75 | ### ⚪ LOW PRIORITY - Future Consideration 76 | 77 | #### Webflow 78 | - **Target**: Professional/agency sites, not SMB-focused 79 | - **Status**: Deferred 80 | - [View Guide →](webflow/) 81 | 82 | #### GitHub Pages 83 | - **Target**: Developer community, open source projects 84 | - **Status**: Deferred 85 | - [View Guide →](github-pages/) 86 | 87 | --- 88 | 89 | ## How to Use These Examples 90 | 91 | 1. **View the Source**: Each example includes well-commented HTML showing exactly where and how AdTruth is integrated 92 | 2. **Test Locally**: Download any example and open in a browser to see it in action 93 | 3. **Copy & Adapt**: Use these as templates for your own integration 94 | 4. **Report Issues**: Found a problem? Open an issue on GitHub 95 | 96 | ## Platform Testing Matrix 97 | 98 | | Platform | Priority | Market Share | Difficulty | Integration Method | 99 | |----------|----------|-------------|-----------|-------------------| 100 | | WordPress.org | 🔴 HIGH | 43% all sites | ⭐⭐ Medium | Theme footer / Plugin | 101 | | Wix | 🔴 HIGH | 4.1% (fastest growth) | ⭐⭐ Medium | Custom Code (paid plans) | 102 | | Shopify | 🔴 HIGH | 26% e-commerce | ⭐⭐ Medium | theme.liquid / Scripts | 103 | | Squarespace | 🟡 MEDIUM | 3.4% | ⭐ Easy | Code Injection settings | 104 | | GoDaddy | 🟡 MEDIUM | 10% builders | ⭐⭐ Medium | Header/Footer scripts | 105 | | Static HTML | ✅ DONE | - | ⭐ Easy | Direct script tag | 106 | | Webflow | ⚪ LOW | Professional | ⭐ Easy | Custom Code (paid only) | 107 | | GitHub Pages | ⚪ LOW | Developers | ⭐⭐ Medium | Jekyll includes | 108 | | Next.js | ✅ TESTED | Production | ⭐⭐⭐ Advanced | See [main docs](../../README.md) | 109 | 110 | --- 111 | 112 | ## Contributing 113 | 114 | Have an integration example for a platform not listed here? We'd love to include it! Please open a pull request with: 115 | - Complete working example 116 | - README with integration steps 117 | - Any platform-specific gotchas or limitations 118 | 119 | --- 120 | 121 | **Need Help?** Check the [main documentation](../../README.md) or open an issue. 122 | -------------------------------------------------------------------------------- /examples/shopify/README.md: -------------------------------------------------------------------------------- 1 | # Shopify Integration 2 | 3 | **Status**: ✅ TESTED & VERIFIED 4 | **Difficulty**: Easy (3 minutes) 5 | **Method**: Custom Pixels (Shopify's 2025 recommended approach) 6 | 7 | ## Overview 8 | 9 | Integrate AdTruth fraud detection into your Shopify store using Custom Pixels - a secure, future-proof method that works across all pages including checkout. 10 | 11 | ## Why Shopify Stores Need Fraud Detection 12 | 13 | E-commerce businesses are prime targets for advertising fraud: 14 | 15 | - **26% of e-commerce sites** use Shopify (high-value target for fraudsters) 16 | - **Higher ad spend** compared to other small businesses 17 | - **Performance marketing** makes them vulnerable to click fraud and bot traffic 18 | - **Conversion tracking** can be manipulated by sophisticated bots 19 | - **Checkout fraud** from fake add-to-cart and abandoned cart patterns 20 | 21 | AdTruth helps you identify fraudulent traffic before it wastes your ad budget. 22 | 23 | ## Market Data 24 | 25 | - **Shopify Market Share**: 5.6% of all websites, 26% of e-commerce sites 26 | - **Total Stores**: 4.4+ million active stores worldwide 27 | - **SMB Focus**: 82% of Shopify stores are small to medium businesses 28 | - **Ad Spend**: E-commerce businesses spend 2-3x more on paid ads than other SMBs 29 | 30 | ## Quick Start 31 | 32 | ### Requirements 33 | 34 | - Shopify store (any plan, including free trial) 35 | - Access to Settings → Customer events 36 | - AdTruth API key from https://adtruth.io/dashboard 37 | 38 | ### Installation (3 minutes) 39 | 40 | 1. **Get your API key** from https://adtruth.io/dashboard 41 | 2. **Navigate** to Settings → Customer events in Shopify admin 42 | 3. **Add custom pixel** named "AdTruth Fraud Detection" 43 | 4. **Paste the code** (see Integration Guide) 44 | 5. **Save and Connect** to activate 45 | 46 | Full step-by-step instructions: [INTEGRATION-GUIDE.md](./INTEGRATION-GUIDE.md) 47 | 48 | ## What Gets Tracked 49 | 50 | - ✅ Page views across all pages (including checkout) 51 | - ✅ Mouse movement patterns (desktop fraud detection) 52 | - ✅ Touch interactions (mobile fraud detection) 53 | - ✅ Scroll depth and engagement 54 | - ✅ Time on page and session duration 55 | - ✅ UTM parameters and traffic sources 56 | - ✅ Device fingerprinting for bot detection 57 | 58 | ## Integration Method 59 | 60 | **Custom Pixels** (Settings → Customer events) 61 | 62 | This is Shopify's official 2025 method for adding tracking pixels. Benefits: 63 | 64 | - No theme code modifications required 65 | - Works in sandboxed checkout environment 66 | - Future-proof (old checkout.liquid deprecated August 2025) 67 | - Easy to add and remove 68 | - Available on all Shopify plans 69 | 70 | ## Test Results 71 | 72 | **Platform**: Shopify free trial account 73 | **Integration Time**: 3 minutes 74 | **Difficulty**: Easy (copy-paste only) 75 | **Status**: ✅ Working perfectly 76 | 77 | **What We Verified:** 78 | - Page views appear in dashboard within 2 minutes 79 | - No JavaScript errors 80 | - No theme modifications needed 81 | - Works on all pages including checkout 82 | - Fraud detection functioning correctly 83 | 84 | ## Platform-Specific Considerations 85 | 86 | ### Shopify Checkout Extensibility 87 | 88 | As of August 28, 2025, Shopify deprecated the old `checkout.liquid` method. Custom Pixels is the only way to track checkout pages now. AdTruth's integration uses Custom Pixels, so you're future-proof. 89 | 90 | ### Shopify Event Warning 91 | 92 | After installation, you may see: "Pixel will not track any customer behavior because it is not subscribed to any events." 93 | 94 | **This is normal.** Our script works independently and doesn't need Shopify's event API. All fraud detection signals are being tracked correctly. 95 | 96 | ### Development vs Production 97 | 98 | Test traffic may show as "suspicious" in your dashboard. This is expected - development/testing environments trigger fraud signals. Real customer traffic from paid ads will score more accurately. 99 | 100 | ## Support & Troubleshooting 101 | 102 | See the full [Integration Guide](./INTEGRATION-GUIDE.md) for: 103 | 104 | - Detailed setup instructions 105 | - Troubleshooting common issues 106 | - Testing your integration 107 | - Understanding the data collected 108 | 109 | ## Alternative Methods 110 | 111 | **Theme.liquid method** (older approach) is still supported but not recommended: 112 | 113 | - Requires theme code modifications 114 | - Doesn't work in checkout after August 2025 115 | - More difficult to remove/update 116 | - Not necessary with Custom Pixels available 117 | 118 | We recommend using Custom Pixels for all Shopify integrations. 119 | 120 | ## Next Steps 121 | 122 | 1. Complete the integration following the [Integration Guide](./INTEGRATION-GUIDE.md) 123 | 2. Visit your store to generate test data 124 | 3. Check your AdTruth dashboard to verify tracking 125 | 4. Start monitoring your paid ad traffic for fraud 126 | 127 | --- 128 | 129 | **Questions?** Open an issue on [GitHub](https://github.com/papa-torb/adtruth/issues) 130 | -------------------------------------------------------------------------------- /tests/integration/phase1_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AdTruth Phase 1 Test 7 | 40 | 41 | 42 |

AdTruth Phase 1 Enhancement Test

43 | 44 |
45 | Initializing AdTruth... 46 |
47 | 48 |
49 |

Phase 1 Features

50 |
    51 |
  • Enhanced User Agent & Browser Info
  • 52 |
  • Time-based Behavioral Metrics
  • 53 |
  • Basic Device Fingerprinting
  • 54 |
  • Input Method Detection
  • 55 |
56 |
57 | 58 |
59 |

Interaction Test

60 |

Click these buttons to generate behavioral data:

61 | 62 | 63 | 64 | 65 | 66 | 67 |
68 |
69 | 70 |
71 |

Expected New Data Fields

72 |
    73 |
  • browser: Enhanced with platform, vendor, cookieEnabled, doNotTrack, maxTouchPoints, hardwareConcurrency, deviceMemory
  • 74 |
  • fingerprint: hash, timezone, timezoneOffset
  • 75 |
  • input: hasTouch, hasMouse, hasHover, inputInconsistency
  • 76 |
  • behavior: timeOnPage, timeToFirstInteraction, clickCount, avgClickInterval
  • 77 |
78 |
79 | 80 | 81 | 82 | 83 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /dev/ground-truth/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ground Truth Collector - Phase 0 MVP 3 | * Collects behavioral data and detects impossibilities 4 | * Submits to backend for ML training dataset 5 | * 6 | * IMPORTANT: This is opt-in via feature flag (enableGroundTruth) 7 | * Default: OFF (only enabled for research participants) 8 | */ 9 | 10 | import { BehavioralImpossibilities } from '../utils/impossibilities.js'; 11 | 12 | /** 13 | * GroundTruthCollector - Phase 0 minimal implementation 14 | * 15 | * What it does: 16 | * 1. Collect behavioral data for 15 seconds 17 | * 2. Analyze for impossibilities 18 | * 3. Submit to /api/training-data endpoint 19 | * 20 | * What it doesn't do (yet): 21 | * - No CAPTCHA challenges (Phase 2) 22 | * - No IP reputation (Phase 1) 23 | * - No honeypots (Phase 1) 24 | */ 25 | export class GroundTruthCollector { 26 | constructor(apiKey, behaviorTracker, config = {}) { 27 | this.apiKey = apiKey; 28 | this.behaviorTracker = behaviorTracker; 29 | this.detector = new BehavioralImpossibilities(); 30 | 31 | // Configuration 32 | this.collectionTime = config.collectionTime || 15000; // 15 seconds default 33 | this.apiEndpoint = config.apiEndpoint || 'https://api.adtruth.io/api/training-data/'; 34 | 35 | // State 36 | this.started = false; 37 | this.submitted = false; 38 | this.collectionTimer = null; 39 | } 40 | 41 | /** 42 | * Start ground truth collection 43 | * Automatically submits after collectionTime (15s default) 44 | */ 45 | start() { 46 | if (this.started) { 47 | console.warn('[AdTruth GT] Collection already started'); 48 | return; 49 | } 50 | 51 | this.started = true; 52 | console.log('[AdTruth GT] Starting Phase 0 collection (15 seconds)...'); 53 | 54 | // Schedule automatic submission after collection period 55 | this.collectionTimer = setTimeout(() => { 56 | this.collect(); 57 | }, this.collectionTime); 58 | 59 | // Also submit on page unload (in case user leaves early) 60 | window.addEventListener('beforeunload', () => { 61 | if (!this.submitted) { 62 | this.collect(); 63 | } 64 | }); 65 | } 66 | 67 | /** 68 | * Collect behavioral data and submit to backend 69 | */ 70 | async collect() { 71 | if (this.submitted) { 72 | return; // Already submitted 73 | } 74 | 75 | this.submitted = true; 76 | 77 | // Clear timer if still running 78 | if (this.collectionTimer) { 79 | clearTimeout(this.collectionTimer); 80 | this.collectionTimer = null; 81 | } 82 | 83 | try { 84 | // Get behavioral metrics from tracker 85 | const behavioralData = this.behaviorTracker.getMetrics(); 86 | 87 | // Analyze for impossibilities 88 | const impossibilities = this.detector.analyze(behavioralData); 89 | 90 | // Calculate impossibility score 91 | const impossibilityScore = this.detector.calculateScore(impossibilities); 92 | 93 | // Prepare submission payload 94 | const payload = { 95 | // Behavioral features (full metrics) 96 | behavioral_features: behavioralData, 97 | 98 | // Impossibilities detected 99 | impossibilities: impossibilities, 100 | 101 | // Fraud score (for Phase 0, this is just impossibility score) 102 | fraud_score: impossibilityScore, 103 | 104 | // Phase 0: Not challenged yet 105 | challenged: false, 106 | 107 | // Timestamp 108 | timestamp: Date.now() 109 | }; 110 | 111 | // Submit to backend 112 | await this.submit(payload); 113 | 114 | console.log('[AdTruth GT] Collection complete:', { 115 | impossibilityCount: impossibilities.length, 116 | fraudScore: impossibilityScore.toFixed(2), 117 | timeOnPage: `${Math.round(behavioralData.timeOnPage / 1000)}s` 118 | }); 119 | 120 | } catch (err) { 121 | console.error('[AdTruth GT] Collection failed:', err); 122 | } 123 | } 124 | 125 | /** 126 | * Submit ground truth data to backend 127 | * @param {object} payload - Training data payload 128 | */ 129 | async submit(payload) { 130 | try { 131 | const response = await fetch(this.apiEndpoint, { 132 | method: 'POST', 133 | headers: { 134 | 'Content-Type': 'application/json', 135 | 'X-API-Key': this.apiKey 136 | }, 137 | body: JSON.stringify(payload), 138 | keepalive: true // Ensure request completes even if page unloads 139 | }); 140 | 141 | if (!response.ok) { 142 | const errorText = await response.text(); 143 | throw new Error(`HTTP ${response.status}: ${errorText}`); 144 | } 145 | 146 | const result = await response.json(); 147 | console.log('[AdTruth GT] Submitted successfully:', result); 148 | 149 | return result; 150 | 151 | } catch (err) { 152 | console.error('[AdTruth GT] Submission failed:', err); 153 | throw err; 154 | } 155 | } 156 | 157 | /** 158 | * Stop collection (cleanup) 159 | */ 160 | stop() { 161 | if (this.collectionTimer) { 162 | clearTimeout(this.collectionTimer); 163 | this.collectionTimer = null; 164 | } 165 | this.started = false; 166 | } 167 | } 168 | 169 | // Note: This project is still under development. 170 | -------------------------------------------------------------------------------- /tests/unit/url.test.js: -------------------------------------------------------------------------------- 1 | import { parseUrlParams, getCurrentUrl, getReferrer } from '../../src/utils/url'; 2 | 3 | describe('URL Utilities', () => { 4 | describe('parseUrlParams', () => { 5 | it('should extract UTM parameters from URL', () => { 6 | const url = 'https://example.com/?utm_source=google&utm_medium=cpc&utm_campaign=summer'; 7 | const params = parseUrlParams(url); 8 | 9 | expect(params).toEqual({ 10 | utm_source: 'google', 11 | utm_medium: 'cpc', 12 | utm_campaign: 'summer' 13 | }); 14 | }); 15 | 16 | it('should extract all UTM parameters when present', () => { 17 | const url = 'https://example.com/?utm_source=facebook&utm_medium=social&utm_campaign=spring&utm_term=shoes&utm_content=blue'; 18 | const params = parseUrlParams(url); 19 | 20 | expect(params).toEqual({ 21 | utm_source: 'facebook', 22 | utm_medium: 'social', 23 | utm_campaign: 'spring', 24 | utm_term: 'shoes', 25 | utm_content: 'blue' 26 | }); 27 | }); 28 | 29 | it('should extract gclid parameter', () => { 30 | const url = 'https://example.com/?gclid=abc123xyz'; 31 | const params = parseUrlParams(url); 32 | 33 | expect(params).toEqual({ 34 | gclid: 'abc123xyz' 35 | }); 36 | }); 37 | 38 | it('should extract fbclid parameter', () => { 39 | const url = 'https://example.com/?fbclid=def456uvw'; 40 | const params = parseUrlParams(url); 41 | 42 | expect(params).toEqual({ 43 | fbclid: 'def456uvw' 44 | }); 45 | }); 46 | 47 | it('should extract both UTM and click ID parameters', () => { 48 | const url = 'https://example.com/?utm_source=google&utm_campaign=test&gclid=123&fbclid=456'; 49 | const params = parseUrlParams(url); 50 | 51 | expect(params).toEqual({ 52 | utm_source: 'google', 53 | utm_campaign: 'test', 54 | gclid: '123', 55 | fbclid: '456' 56 | }); 57 | }); 58 | 59 | it('should return empty object for URL without parameters', () => { 60 | const url = 'https://example.com/'; 61 | const params = parseUrlParams(url); 62 | 63 | expect(params).toEqual({}); 64 | }); 65 | 66 | it('should ignore non-UTM parameters', () => { 67 | const url = 'https://example.com/?utm_source=google&other_param=value&random=123'; 68 | const params = parseUrlParams(url); 69 | 70 | expect(params).toEqual({ 71 | utm_source: 'google' 72 | }); 73 | }); 74 | 75 | it('should handle URL with hash fragment', () => { 76 | const url = 'https://example.com/?utm_source=twitter#section'; 77 | const params = parseUrlParams(url); 78 | 79 | expect(params).toEqual({ 80 | utm_source: 'twitter' 81 | }); 82 | }); 83 | 84 | it('should handle encoded URL parameters', () => { 85 | const url = 'https://example.com/?utm_source=google&utm_campaign=test%20campaign'; 86 | const params = parseUrlParams(url); 87 | 88 | expect(params).toEqual({ 89 | utm_source: 'google', 90 | utm_campaign: 'test campaign' 91 | }); 92 | }); 93 | 94 | it('should handle invalid URL gracefully', () => { 95 | const url = 'not-a-valid-url'; 96 | const params = parseUrlParams(url); 97 | 98 | expect(params).toEqual({}); 99 | }); 100 | 101 | it('should handle malformed URL gracefully', () => { 102 | const url = 'https://[::1]:80/path?utm_source=test'; 103 | // This might throw in some environments, should handle gracefully 104 | const params = parseUrlParams(url); 105 | 106 | // Should not throw and return object (empty or with params) 107 | expect(typeof params).toBe('object'); 108 | }); 109 | }); 110 | 111 | describe('getCurrentUrl', () => { 112 | it('should return current window location href', () => { 113 | // Set up window.location.href mock 114 | delete window.location; 115 | window.location = { href: 'https://test.example.com/page' }; 116 | 117 | const url = getCurrentUrl(); 118 | 119 | expect(url).toBe('https://test.example.com/page'); 120 | }); 121 | 122 | it('should reflect changes to window location', () => { 123 | window.location = { href: 'https://example.com/page1' }; 124 | expect(getCurrentUrl()).toBe('https://example.com/page1'); 125 | 126 | window.location = { href: 'https://example.com/page2' }; 127 | expect(getCurrentUrl()).toBe('https://example.com/page2'); 128 | }); 129 | }); 130 | 131 | describe('getReferrer', () => { 132 | it('should return document referrer', () => { 133 | // Mock document.referrer 134 | Object.defineProperty(document, 'referrer', { 135 | value: 'https://google.com', 136 | configurable: true 137 | }); 138 | 139 | const referrer = getReferrer(); 140 | 141 | expect(referrer).toBe('https://google.com'); 142 | }); 143 | 144 | it('should return empty string when no referrer', () => { 145 | Object.defineProperty(document, 'referrer', { 146 | value: '', 147 | configurable: true 148 | }); 149 | 150 | const referrer = getReferrer(); 151 | 152 | expect(referrer).toBe(''); 153 | }); 154 | 155 | it('should handle undefined referrer', () => { 156 | Object.defineProperty(document, 'referrer', { 157 | value: undefined, 158 | configurable: true 159 | }); 160 | 161 | const referrer = getReferrer(); 162 | 163 | expect(referrer).toBe(''); 164 | }); 165 | }); 166 | }); 167 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to AdTruth SDK 2 | 3 | First off, thank you for considering contributing to AdTruth! It's people like you that make AdTruth such a great tool for protecting businesses from ad fraud. 4 | 5 | ## Code of Conduct 6 | 7 | By participating in this project, you are expected to uphold our Code of Conduct: 8 | 9 | - Be respectful and inclusive 10 | - Welcome newcomers and help them get started 11 | - Focus on constructive criticism 12 | - Show empathy towards other community members 13 | 14 | ## How Can I Contribute? 15 | 16 | ### Reporting Bugs 17 | 18 | Before creating bug reports, please check existing issues to avoid duplicates. When you create a bug report, include as many details as possible: 19 | 20 | - **Use a clear and descriptive title** 21 | - **Describe the exact steps to reproduce the problem** 22 | - **Provide specific examples** 23 | - **Describe the behavior you observed and expected** 24 | - **Include screenshots if relevant** 25 | - **Include your environment details** (OS, browser, Node version) 26 | 27 | ### Suggesting Enhancements 28 | 29 | Enhancement suggestions are tracked as GitHub issues. When creating an enhancement suggestion: 30 | 31 | - **Use a clear and descriptive title** 32 | - **Provide a detailed description of the suggested enhancement** 33 | - **Provide specific examples to demonstrate the feature** 34 | - **Describe the current behavior and expected behavior** 35 | - **Explain why this enhancement would be useful** 36 | 37 | ### Pull Requests 38 | 39 | 1. Fork the repo and create your branch from `main` 40 | 2. If you've added code that should be tested, add tests 41 | 3. If you've changed APIs, update the documentation 42 | 4. Ensure the test suite passes 43 | 5. Make sure your code follows the existing style 44 | 6. Issue that pull request! 45 | 46 | ## Development Setup 47 | 48 | See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for comprehensive setup instructions. 49 | 50 | **Quick Start:** 51 | 52 | 1. **Fork and clone the repository** 53 | ```bash 54 | git clone https://github.com/your-username/adtruth.git 55 | cd adtruth 56 | ``` 57 | 58 | 2. **Install dependencies** 59 | ```bash 60 | npm install 61 | ``` 62 | 63 | 3. **Create a feature branch** 64 | ```bash 65 | git checkout -b feature/your-feature-name 66 | ``` 67 | 68 | 4. **Make your changes** 69 | - Write your code 70 | - Add tests if applicable 71 | - Update documentation 72 | 73 | 5. **Run linting and formatting** 74 | ```bash 75 | npx eslint src/ --fix 76 | npx prettier --write "src/**/*.js" 77 | ``` 78 | 79 | 6. **Build the project** 80 | ```bash 81 | npm run build 82 | ``` 83 | 84 | 7. **Test manually** (automated tests coming soon) 85 | - Create a test HTML file in `tests/` 86 | - Serve locally: `python -m http.server 8080` 87 | - Test in browser with DevTools open 88 | 89 | 8. **Commit your changes** 90 | ```bash 91 | git add . 92 | git commit -m "feat: add amazing feature" 93 | ``` 94 | 95 | ## Commit Message Guidelines 96 | 97 | We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification: 98 | 99 | - `feat:` - A new feature 100 | - `fix:` - A bug fix 101 | - `docs:` - Documentation only changes 102 | - `style:` - Changes that don't affect code meaning 103 | - `refactor:` - Code change that neither fixes a bug nor adds a feature 104 | - `perf:` - Performance improvement 105 | - `test:` - Adding missing tests 106 | - `chore:` - Changes to build process or auxiliary tools 107 | 108 | Examples: 109 | ``` 110 | feat: add browser fingerprinting support 111 | fix: resolve memory leak in tracking function 112 | docs: update API reference for init method 113 | ``` 114 | 115 | ## Code Style Guidelines 116 | 117 | ### JavaScript 118 | 119 | - Use ES6+ features 120 | - 2 spaces for indentation 121 | - Use semicolons 122 | - Use single quotes for strings 123 | - Add JSDoc comments for functions 124 | 125 | Example: 126 | ```javascript 127 | /** 128 | * Initialize AdTruth tracking 129 | * @param {string} apiKey - The API key for authentication 130 | * @param {Object} options - Configuration options 131 | * @returns {void} 132 | */ 133 | function init(apiKey, options = {}) { 134 | // Implementation 135 | } 136 | ``` 137 | 138 | ### Testing 139 | 140 | - Write unit tests for all new functions 141 | - Maintain test coverage above 80% 142 | - Use descriptive test names 143 | 144 | Example: 145 | ```javascript 146 | describe('AdTruth.init()', () => { 147 | it('should initialize with valid API key', () => { 148 | // Test implementation 149 | }); 150 | 151 | it('should throw error with invalid API key', () => { 152 | // Test implementation 153 | }); 154 | }); 155 | ``` 156 | 157 | ## Documentation 158 | 159 | - Update README.md if you change functionality 160 | - Add JSDoc comments to all public APIs 161 | - Include examples for new features 162 | - Keep documentation concise and clear 163 | 164 | ## Review Process 165 | 166 | 1. All submissions require review before merging 167 | 2. We'll review your PR as soon as possible 168 | 3. We may suggest changes or improvements 169 | 4. After approval, we'll merge your PR 170 | 171 | ## Recognition 172 | 173 | Contributors will be recognized in our: 174 | - README.md contributors section 175 | - Release notes 176 | - Project website 177 | 178 | ## Questions? 179 | 180 | Feel free to: 181 | - **GitHub Issues**: [Open an issue](https://github.com/papa-torb/adtruth/issues) for questions or bugs 182 | - **Documentation**: See [docs/API.md](docs/API.md), [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md), [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) 183 | - **Security Issues**: See [SECURITY.md](SECURITY.md) for vulnerability reporting 184 | 185 | ## License 186 | 187 | By contributing, you agree that your contributions will be licensed under the MIT License. 188 | 189 | Thank you for contributing to AdTruth! 🎉 190 | 191 | --- 192 | 193 | > **Note:** This project is still under development. -------------------------------------------------------------------------------- /tests/unit/session.test.js: -------------------------------------------------------------------------------- 1 | import { generateSessionId, getSessionId, getVisitorId } from '../../src/utils/session'; 2 | 3 | describe('Session Utilities', () => { 4 | beforeEach(() => { 5 | jest.clearAllMocks(); 6 | 7 | // Recreate mock functions with proper Jest mock capabilities 8 | Object.defineProperty(global, 'sessionStorage', { 9 | value: { 10 | getItem: jest.fn(), 11 | setItem: jest.fn(), 12 | removeItem: jest.fn(), 13 | clear: jest.fn() 14 | }, 15 | writable: true, 16 | configurable: true 17 | }); 18 | 19 | Object.defineProperty(global, 'localStorage', { 20 | value: { 21 | getItem: jest.fn(), 22 | setItem: jest.fn(), 23 | removeItem: jest.fn(), 24 | clear: jest.fn() 25 | }, 26 | writable: true, 27 | configurable: true 28 | }); 29 | }); 30 | 31 | describe('generateSessionId', () => { 32 | it('should generate a UUID format string', () => { 33 | const id = generateSessionId(); 34 | 35 | // UUID format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx 36 | const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 37 | expect(id).toMatch(uuidRegex); 38 | }); 39 | 40 | it('should generate unique IDs', () => { 41 | const id1 = generateSessionId(); 42 | const id2 = generateSessionId(); 43 | 44 | expect(id1).not.toBe(id2); 45 | }); 46 | 47 | it('should use crypto.randomUUID if available', () => { 48 | // Test that when crypto.randomUUID exists, function returns valid UUID 49 | // We can't easily mock the implementation due to module caching, 50 | // but we can verify the function produces valid output 51 | const id = generateSessionId(); 52 | 53 | // Should return a valid UUID format 54 | const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 55 | expect(id).toMatch(uuidRegex); 56 | expect(typeof id).toBe('string'); 57 | expect(id.length).toBe(36); 58 | }); 59 | 60 | it('should fall back to Math.random if crypto unavailable', () => { 61 | const originalCrypto = global.crypto; 62 | global.crypto = undefined; 63 | 64 | const id = generateSessionId(); 65 | const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 66 | expect(id).toMatch(uuidRegex); 67 | 68 | global.crypto = originalCrypto; 69 | }); 70 | }); 71 | 72 | describe('getSessionId', () => { 73 | it('should retrieve existing session ID from sessionStorage', () => { 74 | const existingId = 'existing-session-123'; 75 | sessionStorage.getItem.mockReturnValueOnce(existingId); 76 | 77 | const id = getSessionId(); 78 | 79 | expect(id).toBe(existingId); 80 | expect(sessionStorage.getItem).toHaveBeenCalledWith('_adtruth_session'); 81 | }); 82 | 83 | it('should generate and store new session ID if none exists', () => { 84 | sessionStorage.getItem.mockReturnValueOnce(null); 85 | 86 | const id = getSessionId(); 87 | 88 | expect(sessionStorage.getItem).toHaveBeenCalledWith('_adtruth_session'); 89 | expect(sessionStorage.setItem).toHaveBeenCalledWith('_adtruth_session', id); 90 | 91 | const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 92 | expect(id).toMatch(uuidRegex); 93 | }); 94 | 95 | it('should handle sessionStorage errors gracefully', () => { 96 | sessionStorage.getItem.mockImplementationOnce(() => { 97 | throw new Error('SessionStorage not available'); 98 | }); 99 | 100 | const id = getSessionId(); 101 | 102 | // Should still return a valid UUID 103 | const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 104 | expect(id).toMatch(uuidRegex); 105 | }); 106 | }); 107 | 108 | describe('getVisitorId', () => { 109 | it('should retrieve existing visitor ID from localStorage', () => { 110 | const existingId = 'existing-visitor-456'; 111 | localStorage.getItem.mockReturnValueOnce(existingId); 112 | 113 | const id = getVisitorId(); 114 | 115 | expect(id).toBe(existingId); 116 | expect(localStorage.getItem).toHaveBeenCalledWith('_adtruth_visitor'); 117 | }); 118 | 119 | it('should generate and store new visitor ID if none exists', () => { 120 | localStorage.getItem.mockReturnValueOnce(null); 121 | 122 | const id = getVisitorId(); 123 | 124 | expect(localStorage.getItem).toHaveBeenCalledWith('_adtruth_visitor'); 125 | expect(localStorage.setItem).toHaveBeenCalledWith('_adtruth_visitor', id); 126 | 127 | const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 128 | expect(id).toMatch(uuidRegex); 129 | }); 130 | 131 | it('should fall back to sessionStorage if localStorage fails', () => { 132 | const sessionId = 'session-fallback-789'; 133 | localStorage.getItem.mockImplementationOnce(() => { 134 | throw new Error('LocalStorage not available'); 135 | }); 136 | sessionStorage.getItem.mockReturnValueOnce(sessionId); 137 | 138 | const id = getVisitorId(); 139 | 140 | expect(id).toBe(sessionId); 141 | expect(sessionStorage.getItem).toHaveBeenCalledWith('_adtruth_visitor'); 142 | }); 143 | 144 | it('should generate new ID if both storages fail', () => { 145 | localStorage.getItem.mockImplementationOnce(() => { 146 | throw new Error('LocalStorage not available'); 147 | }); 148 | sessionStorage.getItem.mockImplementationOnce(() => { 149 | throw new Error('SessionStorage not available'); 150 | }); 151 | 152 | const id = getVisitorId(); 153 | 154 | // Should still return a valid UUID 155 | const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 156 | expect(id).toMatch(uuidRegex); 157 | }); 158 | }); 159 | }); 160 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [hongyishui92@gmail.com](mailto:hongyishui92@gmail.com). 64 | 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | -------------------------------------------------------------------------------- /examples/wordpress/INTEGRATION-GUIDE.md: -------------------------------------------------------------------------------- 1 | # WordPress Integration Guide 2 | 3 | **Status**: Tested and verified working 4 | **Time Required**: ~2 minutes 5 | **Last Tested**: May 2024 6 | 7 | This guide covers three ways to add AdTruth tracking to WordPress. We tested the plugin method on a live WordPress site and confirmed it works. 8 | 9 | ## Prerequisites 10 | 11 | - WordPress admin access 12 | - Your AdTruth API key from [adtruth.io/dashboard](https://adtruth.io/dashboard) 13 | 14 | ## Method 1: WPCode Plugin (Recommended) 15 | 16 | This is the easiest method and what we tested. The WPCode plugin has 2+ million active installations and is specifically designed for adding scripts safely. 17 | 18 | ### Installation 19 | 20 | 1. In your WordPress admin, go to **Plugins > Add New** 21 | 2. Search for "WPCode" 22 | 3. Install and activate **WPCode – Insert Headers and Footers** 23 | 4. Go to **Code Snippets > Header & Footer** 24 | 5. Scroll to the **Footer** section and paste this code: 25 | 26 | ```html 27 | 39 | ``` 40 | 41 | 6. Replace `YOUR_API_KEY_HERE` with your actual API key 42 | 7. Click **Save Changes** 43 | 44 | ### Verification 45 | 46 | To verify the script is loading: 47 | 48 | 1. Open your site in a private/incognito window 49 | 2. Right-click and select **View Page Source** 50 | 3. Search for "adtruth" (Ctrl+F or Cmd+F) 51 | 4. You should see the script near the bottom, before `` 52 | 53 | After visiting a few pages, check your dashboard at [adtruth.io/dashboard](https://adtruth.io/dashboard). Page views should appear within 2-5 minutes. 54 | 55 | ### Test Results 56 | 57 | We tested this method on May 14, 2024 using a TasteWP test site: 58 | 59 | - Script loaded without errors 60 | - Page views appeared in dashboard within 2 minutes 61 | - Traffic source correctly identified 62 | - All tracking data showed up properly 63 | 64 | Note: Test traffic from development environments may be flagged as suspicious, which is expected behavior. Real user traffic on production sites will show more accurate fraud scores. 65 | 66 | ## Method 2: Theme Functions (For Developers) 67 | 68 | If you're comfortable with PHP, add this to your child theme's `functions.php`: 69 | 70 | ```php 71 | 75 | 87 | Theme File Editor** 99 | 2. Open `footer.php` 100 | 3. Find `` 101 | 4. Add the tracking script right before the closing `` tag: 102 | 103 | ```php 104 | 105 | 117 | 118 | ``` 119 | 120 | 5. Replace `YOUR_API_KEY_HERE` with your actual API key 121 | 6. Click **Update File** 122 | 123 | ## Troubleshooting 124 | 125 | **Script not showing up?** 126 | - Clear your cache if you use a caching plugin (WP Super Cache, W3 Total Cache, etc.) 127 | - Try disabling other plugins temporarily to check for conflicts 128 | - Make sure the script is placed before the closing `` tag 129 | 130 | **No data in dashboard?** 131 | - Wait 24 hours - initial data may take time to appear 132 | - Verify your API key is correct (no extra spaces) 133 | - Test in an incognito window - your own visits might be filtered 134 | 135 | **Theme update removed the code?** 136 | - This happens if you used Method 3. Use Method 1 (plugin) to avoid this. 137 | 138 | ## What Gets Tracked 139 | 140 | AdTruth automatically tracks: 141 | - Page views across your entire site 142 | - Traffic sources (Google Ads, Facebook Ads, organic search, etc.) 143 | - Visitor behavior patterns 144 | - Fraud detection signals 145 | 146 | All tracking is GDPR-compliant and privacy-focused. 147 | 148 | ## WordPress.com vs WordPress.org 149 | 150 | **WordPress.org (Self-Hosted)**: All methods work. This is what most small businesses use. 151 | 152 | **WordPress.com (Hosted)**: 153 | - Free/Personal/Premium plans: Cannot add custom scripts 154 | - Business plan ($25/mo) or higher: Can use Method 1 (plugin) 155 | 156 | If you're on a WordPress.com free plan, you'll need to upgrade to add custom scripts. 157 | 158 | ## Need Help? 159 | 160 | - Check your API key: [AdTruth Dashboard](https://adtruth.io/dashboard) 161 | - Documentation: [GitHub Repository](https://github.com/papa-torb/adtruth) 162 | - Report issues: [GitHub Issues](https://github.com/papa-torb/adtruth/issues) 163 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | Need help with AdTruth? Here's how to get assistance. 4 | 5 | ## Documentation 6 | 7 | Before asking for help, please check our comprehensive documentation: 8 | 9 | - **[README](README.md)** - Project overview and quick start 10 | - **[API Reference](docs/API.md)** - Complete API documentation 11 | - **[Development Guide](docs/DEVELOPMENT.md)** - Setup and development workflow 12 | - **[Architecture](docs/ARCHITECTURE.md)** - System design and internals 13 | - **[Contributing Guide](CONTRIBUTING.md)** - How to contribute 14 | - **[Changelog](CHANGELOG.md)** - Version history and updates 15 | 16 | ## Getting Help 17 | 18 | ### GitHub Issues 19 | 20 | The best way to get help is through GitHub Issues: 21 | 22 | **[Open an Issue](https://github.com/papa-torb/adtruth/issues)** 23 | 24 | When opening an issue: 25 | - Search existing issues first to avoid duplicates 26 | - Use the appropriate issue template (Bug Report or Feature Request) 27 | - Include as much detail as possible 28 | - Provide code examples and error messages 29 | 30 | ### GitHub Discussions 31 | 32 | For questions, ideas, and general discussions: 33 | 34 | **[GitHub Discussions](https://github.com/papa-torb/adtruth/discussions)** 35 | 36 | Great for: 37 | - General questions about AdTruth 38 | - Best practices and use cases 39 | - Feature discussions 40 | - Sharing experiences 41 | 42 | ## Common Questions 43 | 44 | ### Installation Issues 45 | 46 | **Problem**: SDK not loading or initializing 47 | 48 | **Solutions**: 49 | 1. Check browser console for errors 50 | 2. Verify API key is correct 51 | 3. Ensure script is loaded before `AdTruth.init()` 52 | 4. Check CORS settings if self-hosting 53 | 54 | See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md#troubleshooting) for more. 55 | 56 | ### Integration Issues 57 | 58 | **Problem**: Data not appearing in dashboard 59 | 60 | **Solutions**: 61 | 1. Verify tracking script is on all pages 62 | 2. Check Network tab for successful API calls 63 | 3. Wait 5-10 minutes for data to process 64 | 4. Check API key matches your website 65 | 66 | See platform-specific guides in [examples/](examples/). 67 | 68 | ### Performance Concerns 69 | 70 | **Problem**: Script slowing down website 71 | 72 | **Solutions**: 73 | 1. Verify you're using the minified version (12KB) 74 | 2. Check for duplicate script tags 75 | 3. Ensure async loading 76 | 4. Review browser console for errors 77 | 78 | See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#performance-optimizations). 79 | 80 | ## Security Issues 81 | 82 | **DO NOT** report security vulnerabilities through public issues. 83 | 84 | Please follow our security policy: 85 | 86 | **[Security Policy](SECURITY.md)** 87 | 88 | Report vulnerabilities to: 89 | - **GitHub Security Advisories**: [Report a vulnerability](https://github.com/papa-torb/adtruth/security/advisories/new) 90 | - **Email**: See [SECURITY.md](SECURITY.md) for contact 91 | 92 | ## Feature Requests 93 | 94 | Have an idea for a new feature? 95 | 96 | 1. Check [existing feature requests](https://github.com/papa-torb/adtruth/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement) 97 | 2. Use the [Feature Request template](https://github.com/papa-torb/adtruth/issues/new?template=feature_request.md) 98 | 3. Explain the use case and expected behavior 99 | 4. Be specific about why it would be valuable 100 | 101 | ## Bug Reports 102 | 103 | Found a bug? 104 | 105 | 1. Check [existing bug reports](https://github.com/papa-torb/adtruth/issues?q=is%3Aissue+is%3Aopen+label%3Abug) 106 | 2. Use the [Bug Report template](https://github.com/papa-torb/adtruth/issues/new?template=bug_report.md) 107 | 3. Include reproduction steps 108 | 4. Provide environment details (browser, OS, AdTruth version) 109 | 110 | ## Contributing 111 | 112 | Want to contribute? 113 | 114 | See our [Contributing Guide](CONTRIBUTING.md) for: 115 | - Code style guidelines 116 | - Development setup 117 | - Pull request process 118 | - Testing requirements 119 | 120 | ## Community 121 | 122 | Stay connected with the AdTruth community: 123 | 124 | - **GitHub**: [papa-torb/adtruth](https://github.com/papa-torb/adtruth) 125 | - **Issues**: [Issue Tracker](https://github.com/papa-torb/adtruth/issues) 126 | - **Discussions**: [Community Discussions](https://github.com/papa-torb/adtruth/discussions) 127 | 128 | ## Commercial Support 129 | 130 | AdTruth is a free, open-source project. Community support is provided through GitHub Issues and Discussions. 131 | 132 | For enterprise support, custom integrations, or consulting: 133 | - Contact via [GitHub Issues](https://github.com/papa-torb/adtruth/issues) with your requirements 134 | - We can discuss options based on your needs 135 | 136 | ## Response Times 137 | 138 | This is an open-source project maintained by volunteers: 139 | 140 | - **Bug Reports**: We aim to respond within 1-3 business days 141 | - **Feature Requests**: Response within 1 week 142 | - **Security Issues**: Response within 48 hours (see [SECURITY.md](SECURITY.md)) 143 | - **Pull Requests**: Review within 1 week 144 | 145 | *Note: Response times are best-effort and may vary.* 146 | 147 | ## Resources 148 | 149 | ### External Resources 150 | 151 | - **jsDelivr CDN**: [AdTruth on jsDelivr](https://www.jsdelivr.com/package/gh/papa-torb/adtruth) 152 | - **Fraud Detection Research**: Industry papers and research 153 | - **Web Analytics Best Practices**: Google Analytics, Segment documentation 154 | 155 | ### Platform-Specific Guides 156 | 157 | - **[WordPress Integration](examples/wordpress/INTEGRATION-GUIDE.md)** - Tested and verified 158 | - **[Shopify Integration](examples/shopify/INTEGRATION-GUIDE.md)** - Tested and verified 159 | - **[Wix Integration](examples/wix/INTEGRATION-GUIDE.md)** - Documentation ready 160 | 161 | ## Feedback 162 | 163 | We value your feedback! Help us improve: 164 | 165 | - **Star the repository** if you find it useful 166 | - **Share your experience** in [GitHub Discussions](https://github.com/papa-torb/adtruth/discussions) 167 | - **Report issues** to help us fix bugs 168 | - **Suggest improvements** through feature requests 169 | 170 | --- 171 | 172 | ## Quick Links 173 | 174 | - [Report a Bug](https://github.com/papa-torb/adtruth/issues/new?template=bug_report.md) 175 | - [Request a Feature](https://github.com/papa-torb/adtruth/issues/new?template=feature_request.md) 176 | - [View Documentation](docs/) 177 | - [Security Policy](SECURITY.md) 178 | - [Contributing Guide](CONTRIBUTING.md) 179 | 180 | --- 181 | 182 | Thank you for using AdTruth! We're here to help. 🚀 -------------------------------------------------------------------------------- /tests/manual/test-unload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AdTruth Unload Tracking Test 7 | 65 | 66 | 67 |
68 |

🧪 AdTruth Unload Tracking Test

69 | 70 |
71 | Test Instructions: 72 |
    73 |
  1. Interact with the page (click buttons, scroll, move mouse)
  2. 74 |
  3. Open browser DevTools Console (F12)
  4. 75 |
  5. Enable "Preserve log" in Console settings
  6. 76 |
  7. Close this tab or navigate away
  8. 77 |
  9. Check Console for "Sending final event on page unload" message
  10. 78 |
  11. Check your database for the page view with rich behavior data
  12. 79 |
80 |
81 | 82 |
83 |

Behavior Data Collection

84 |

Click these buttons, scroll the page, and move your mouse around to generate behavior data:

85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 |
93 |
Loading metrics...
94 |
95 |
96 | 97 |
98 |

Scroll Area

99 |

Scroll down to increase scroll depth metric...

100 |

Middle of scroll area

101 |

Bottom of scroll area - You've scrolled 100%!

102 |
103 | 104 |
105 |

Test Navigation

106 | 107 | 108 | 109 |
110 |
111 | 112 | 113 | 114 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /tests/unit/fingerprint.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | getBasicFingerprint, 3 | getEnhancedBrowserInfo, 4 | getInputMethod, 5 | getCanvasFingerprint 6 | } from '../../src/utils/fingerprint'; 7 | 8 | describe('Fingerprint Utilities', () => { 9 | describe('getBasicFingerprint', () => { 10 | it('should return fingerprint with hash and details', () => { 11 | const result = getBasicFingerprint(); 12 | 13 | expect(result).toHaveProperty('hash'); 14 | expect(result).toHaveProperty('details'); 15 | expect(typeof result.hash).toBe('string'); 16 | expect(result.hash.length).toBeGreaterThan(0); 17 | }); 18 | 19 | it('should include screen dimensions in fingerprint', () => { 20 | const result = getBasicFingerprint(); 21 | 22 | expect(result.details.screen).toMatch(/^\d+x\d+x\d+$/); 23 | expect(result.details.timezoneOffset).toBe(new Date().getTimezoneOffset()); 24 | }); 25 | 26 | it('should include hardware concurrency', () => { 27 | const result = getBasicFingerprint(); 28 | 29 | expect(typeof result.details.hardwareConcurrency).toBe('number'); 30 | expect(result.details.hardwareConcurrency).toBeGreaterThanOrEqual(0); 31 | }); 32 | 33 | it('should generate consistent hash for same fingerprint', () => { 34 | const result1 = getBasicFingerprint(); 35 | const result2 = getBasicFingerprint(); 36 | 37 | expect(result1.hash).toBe(result2.hash); 38 | }); 39 | }); 40 | 41 | describe('getEnhancedBrowserInfo', () => { 42 | it('should return all browser information fields', () => { 43 | const info = getEnhancedBrowserInfo(); 44 | 45 | expect(info).toHaveProperty('user_agent'); 46 | expect(info).toHaveProperty('language'); 47 | expect(info).toHaveProperty('languages'); 48 | expect(info).toHaveProperty('platform'); 49 | expect(info).toHaveProperty('vendor'); 50 | expect(info).toHaveProperty('cookieEnabled'); 51 | expect(info).toHaveProperty('doNotTrack'); 52 | expect(info).toHaveProperty('maxTouchPoints'); 53 | expect(info).toHaveProperty('hardwareConcurrency'); 54 | expect(info).toHaveProperty('deviceMemory'); 55 | }); 56 | 57 | it('should return correct values from navigator', () => { 58 | const info = getEnhancedBrowserInfo(); 59 | 60 | expect(info.user_agent).toBe(navigator.userAgent); 61 | expect(info.language).toBe(navigator.language); 62 | expect(info.platform).toBe(navigator.platform); 63 | expect(info.cookieEnabled).toBe(navigator.cookieEnabled); 64 | }); 65 | 66 | it('should handle missing navigator properties', () => { 67 | const originalVendor = navigator.vendor; 68 | Object.defineProperty(navigator, 'vendor', { value: undefined, configurable: true }); 69 | 70 | const info = getEnhancedBrowserInfo(); 71 | 72 | expect(info.vendor).toBe(''); 73 | 74 | Object.defineProperty(navigator, 'vendor', { value: originalVendor, configurable: true }); 75 | }); 76 | 77 | it('should format languages as comma-separated string', () => { 78 | const info = getEnhancedBrowserInfo(); 79 | 80 | expect(info.languages).toBe('en-US,en'); 81 | }); 82 | }); 83 | 84 | describe('getInputMethod', () => { 85 | it('should detect input capabilities', () => { 86 | const input = getInputMethod(); 87 | 88 | expect(input).toHaveProperty('hasTouch'); 89 | expect(input).toHaveProperty('hasMouse'); 90 | expect(input).toHaveProperty('hasHover'); 91 | expect(input).toHaveProperty('maxTouchPoints'); 92 | expect(input).toHaveProperty('inputInconsistency'); 93 | }); 94 | 95 | it('should detect touch support', () => { 96 | const input = getInputMethod(); 97 | 98 | expect(typeof input.hasTouch).toBe('boolean'); 99 | expect(input.maxTouchPoints).toBe(0); 100 | }); 101 | 102 | it('should flag input inconsistency when all methods present', () => { 103 | // Mock matchMedia to return true for both 104 | global.matchMedia = jest.fn((query) => ({ 105 | matches: true, 106 | media: query, 107 | addEventListener: jest.fn(), 108 | removeEventListener: jest.fn() 109 | })); 110 | 111 | // Mock touch support 112 | global.window.ontouchstart = {}; 113 | 114 | const input = getInputMethod(); 115 | 116 | expect(input.inputInconsistency).toBe(true); 117 | 118 | delete global.window.ontouchstart; 119 | }); 120 | 121 | it('should handle missing matchMedia', () => { 122 | const originalMatchMedia = global.matchMedia; 123 | global.matchMedia = undefined; 124 | 125 | const input = getInputMethod(); 126 | 127 | expect(input.hasMouse).toBe(false); 128 | expect(input.hasHover).toBe(false); 129 | 130 | global.matchMedia = originalMatchMedia; 131 | }); 132 | }); 133 | 134 | describe('getCanvasFingerprint', () => { 135 | it('should return canvas fingerprint with hash and partial', () => { 136 | const fingerprint = getCanvasFingerprint(); 137 | 138 | expect(fingerprint).toHaveProperty('hash'); 139 | expect(fingerprint).toHaveProperty('partial'); 140 | expect(typeof fingerprint.hash).toBe('string'); 141 | expect(typeof fingerprint.partial).toBe('string'); 142 | }); 143 | 144 | it('should return consistent fingerprint', () => { 145 | const fp1 = getCanvasFingerprint(); 146 | const fp2 = getCanvasFingerprint(); 147 | 148 | expect(fp1.hash).toBe(fp2.hash); 149 | expect(fp1.partial).toBe(fp2.partial); 150 | }); 151 | 152 | it('should return null if canvas context unavailable', () => { 153 | const originalGetContext = HTMLCanvasElement.prototype.getContext; 154 | HTMLCanvasElement.prototype.getContext = jest.fn(() => null); 155 | 156 | const fingerprint = getCanvasFingerprint(); 157 | 158 | expect(fingerprint).toBeNull(); 159 | 160 | HTMLCanvasElement.prototype.getContext = originalGetContext; 161 | }); 162 | 163 | it('should handle canvas errors gracefully', () => { 164 | const originalGetContext = HTMLCanvasElement.prototype.getContext; 165 | HTMLCanvasElement.prototype.getContext = jest.fn(() => { 166 | throw new Error('Canvas not supported'); 167 | }); 168 | 169 | const fingerprint = getCanvasFingerprint(); 170 | 171 | expect(fingerprint).toBeNull(); 172 | 173 | HTMLCanvasElement.prototype.getContext = originalGetContext; 174 | }); 175 | 176 | it('should create 200x50 canvas', () => { 177 | const createElementSpy = jest.spyOn(document, 'createElement'); 178 | 179 | getCanvasFingerprint(); 180 | 181 | const canvasCall = createElementSpy.mock.results.find( 182 | result => result.value && result.value.tagName === 'CANVAS' 183 | ); 184 | 185 | expect(canvasCall).toBeDefined(); 186 | 187 | createElementSpy.mockRestore(); 188 | }); 189 | }); 190 | }); 191 | -------------------------------------------------------------------------------- /examples/wix/INTEGRATION-GUIDE.md: -------------------------------------------------------------------------------- 1 | # Wix Integration Guide 2 | 3 | **Status**: Test Pending (Documentation based on Wix official guidelines) 4 | **Time Required**: ~5 minutes 5 | **Requirements**: Paid Wix plan (Light or higher, starting at $17/month) 6 | 7 | This guide covers how to add AdTruth tracking to your Wix website. The instructions are based on Wix's official Custom Code documentation but have not been tested with a live Wix site yet. 8 | 9 | ## Prerequisites 10 | 11 | To add custom code to Wix, you need: 12 | - A Wix website on a **paid premium plan** (Light plan or higher) 13 | - A **connected custom domain** (not a free wix.com subdomain) 14 | - Your AdTruth API key from [adtruth.io/dashboard](https://adtruth.io/dashboard) 15 | 16 | Note: Wix's free plan does not support custom code. You must upgrade to at least the Light plan ($17/month) to add tracking scripts. 17 | 18 | ## Method 1: Custom Code Feature (Recommended) 19 | 20 | This is the official Wix method for adding tracking scripts site-wide. 21 | 22 | ### Step 1: Access Custom Code Settings 23 | 24 | 1. Log into your Wix account and go to your site's dashboard 25 | 2. Click **Settings** in the left sidebar 26 | 3. Under the **Advanced** section, click **Custom Code** 27 | 28 | ### Step 2: Add Your Tracking Code 29 | 30 | 1. Click **+ Add Custom Code** at the top right 31 | 2. In the text box, paste this code: 32 | 33 | ```html 34 | 46 | ``` 47 | 48 | 3. Replace `YOUR_API_KEY_HERE` with your actual API key from AdTruth dashboard 49 | 4. In the **Name your code** field, enter something like "AdTruth Tracking" 50 | 51 | ### Step 3: Configure Code Settings 52 | 53 | 1. **Where to place the code**: 54 | - Choose **All pages** to track across your entire site (recommended) 55 | - Or select specific pages if you only want tracking on certain pages 56 | 57 | 2. **Place Code in**: 58 | - Select **Body - end** (recommended for tracking scripts) 59 | - This loads the script at the bottom of the page for better performance 60 | 61 | 3. Click **Apply** to save your settings 62 | 63 | ### Step 4: Verify Installation 64 | 65 | After saving: 66 | 67 | 1. Visit your published website 68 | 2. Right-click anywhere on the page and select **View Page Source** 69 | 3. Press Ctrl+F (Windows) or Cmd+F (Mac) to search 70 | 4. Search for "adtruth" 71 | 5. You should see the tracking script near the bottom of the page 72 | 73 | ### Step 5: Check Your Dashboard 74 | 75 | 1. Visit a few pages on your Wix site (3-5 page views) 76 | 2. Wait 2-5 minutes for data to process 77 | 3. Go to [adtruth.io/dashboard](https://adtruth.io/dashboard) 78 | 4. You should see page views appearing 79 | 80 | ## Method 2: HTML Embed Element (Per-Page) 81 | 82 | If you only want tracking on specific pages, you can use Wix's HTML embed element. 83 | 84 | ### Adding to Individual Pages 85 | 86 | 1. Open the page in Wix Editor 87 | 2. Click **Add Elements** (+) on the left panel 88 | 3. Go to **Embed Code > HTML iframe** 89 | 4. Drag the HTML element onto your page 90 | 5. Click **Enter Code** 91 | 6. Paste the tracking script (same as Method 1) 92 | 7. Replace `YOUR_API_KEY_HERE` with your API key 93 | 8. Click **Update** 94 | 9. Position the element at the bottom of your page 95 | 10. Repeat for each page where you want tracking 96 | 97 | Note: This method is more time-consuming if you want site-wide tracking. Use Method 1 for tracking across all pages. 98 | 99 | ## Wix Plan Requirements 100 | 101 | Custom code is only available on Wix premium plans: 102 | 103 | | Plan | Price | Custom Code | Notes | 104 | |------|-------|-------------|-------| 105 | | Free | $0/mo | ❌ No | Cannot add custom scripts | 106 | | Light | $17/mo | ✅ Yes | Minimum plan for custom code | 107 | | Core | $29/mo | ✅ Yes | More storage and bandwidth | 108 | | Business | $36/mo | ✅ Yes | Accept payments online | 109 | | Business Elite | $159/mo | ✅ Yes | Full e-commerce features | 110 | 111 | All paid plans support custom code. The Light plan is sufficient for AdTruth tracking. 112 | 113 | ## Wix Studio vs Wix Editor 114 | 115 | Both Wix platforms support custom code: 116 | 117 | - **Wix Editor**: Follow Method 1 instructions above 118 | - **Wix Studio**: Similar process - Settings > Custom Code > Add Code 119 | 120 | The Custom Code feature works identically on both platforms. 121 | 122 | ## Important Notes 123 | 124 | ### What Gets Tracked 125 | 126 | AdTruth will automatically track: 127 | - Page views across your Wix site 128 | - Traffic sources (Google Ads, Facebook Ads, organic) 129 | - Visitor behavior patterns 130 | - Fraud detection signals 131 | 132 | ### Wix-Specific Considerations 133 | 134 | - Custom code only works on **published sites** with connected domains 135 | - Code will not run in Wix Editor preview mode 136 | - Wix's built-in analytics will continue working alongside AdTruth 137 | - Some Wix apps may conflict with custom JavaScript (rare) 138 | 139 | ### GDPR Compliance 140 | 141 | AdTruth tracking is privacy-focused and GDPR-compliant. However: 142 | - You may need to add AdTruth to your privacy policy 143 | - Consider adding a cookie consent banner if required in your region 144 | - Check local regulations for tracking requirements 145 | 146 | ## Troubleshooting 147 | 148 | ### Code Not Showing in Page Source 149 | 150 | - Make sure your site is published (custom code doesn't run in preview) 151 | - Verify you're on a paid Wix plan (Light or higher) 152 | - Check that you have a connected custom domain 153 | - Confirm the code is set to "All pages" or the specific page you're viewing 154 | 155 | ### No Data in Dashboard 156 | 157 | - Wait 24 hours - initial data may take time to appear 158 | - Verify your API key is correct (no extra spaces) 159 | - Check that the script is loading by viewing page source 160 | - Test in an incognito window (your own visits may be filtered) 161 | 162 | ### Script Conflicts 163 | 164 | - If you have other tracking scripts (Google Analytics, Facebook Pixel), they may interfere 165 | - Try placing AdTruth code in **Body - end** position 166 | - Test with other scripts temporarily disabled 167 | 168 | ### Performance Issues 169 | 170 | - Wix sites already load many scripts - adding another is usually fine 171 | - If you notice slowness, verify the script is in **Body - end** position 172 | - The AdTruth script is lightweight (~10KB) and loads asynchronously 173 | 174 | ## Testing Needed 175 | 176 | This guide is based on Wix's official documentation but has not been verified with a live test. If you integrate AdTruth with Wix: 177 | 178 | - Please report your results on [GitHub Issues](https://github.com/papa-torb/adtruth/issues) 179 | - Share what worked and what didn't 180 | - Help us improve this guide with real-world experience 181 | 182 | ## Need Help? 183 | 184 | - Check your API key: [AdTruth Dashboard](https://adtruth.io/dashboard) 185 | - Wix Custom Code docs: [Wix Support](https://support.wix.com/en/article/wix-editor-embedding-custom-code-on-your-site) 186 | - Report issues: [GitHub Issues](https://github.com/papa-torb/adtruth/issues) 187 | -------------------------------------------------------------------------------- /examples/shopify/INTEGRATION-GUIDE.md: -------------------------------------------------------------------------------- 1 | # Shopify Integration Guide 2 | 3 | **Status**: ✅ TESTED & VERIFIED (November 2025) 4 | 5 | This guide shows you how to integrate AdTruth fraud detection into your Shopify store using Custom Pixels - the recommended method for 2025 and beyond. 6 | 7 | ## Why Custom Pixels? 8 | 9 | As of August 2025, Shopify deprecated the old `checkout.liquid` method in favor of Custom Pixels. This new approach: 10 | 11 | - Works on all pages including checkout (sandboxed environment) 12 | - No theme code modifications required 13 | - Future-proof and officially supported by Shopify 14 | - Available on all Shopify plans including trial accounts 15 | 16 | ## Requirements 17 | 18 | - Shopify store (any plan, including free trial) 19 | - Access to Settings → Customer events in your Shopify admin 20 | - AdTruth API key from https://adtruth.io/dashboard 21 | 22 | ## Integration Steps 23 | 24 | ### Step 1: Access Customer Events 25 | 26 | 1. Log into your Shopify admin 27 | 2. Click **Settings** (bottom left sidebar) 28 | 3. Select **Customer events** 29 | 30 | ### Step 2: Create Custom Pixel 31 | 32 | 1. Click **Add custom pixel** 33 | 2. Enter a name: **AdTruth Fraud Detection** 34 | 3. Paste the following code in the code editor: 35 | 36 | ```javascript 37 | (function() { 38 | var script = document.createElement('script'); 39 | script.src = 'https://cdn.jsdelivr.net/gh/papa-torb/adtruth@latest/dist/adtruth.min.js'; 40 | script.onload = function() { 41 | if (window.AdTruth) { 42 | AdTruth.init('YOUR_API_KEY_HERE'); 43 | } 44 | }; 45 | document.head.appendChild(script); 46 | })(); 47 | ``` 48 | 49 | 4. Replace `YOUR_API_KEY_HERE` with your actual API key 50 | 5. Click **Save** 51 | 6. Click **Connect** to activate the pixel 52 | 53 | ### Step 3: Verify Installation 54 | 55 | 1. Click **View store** (top right of Shopify admin) 56 | 2. Browse your store (homepage, product pages, etc.) 57 | 3. Wait 1-2 minutes for data to transmit 58 | 4. Check your AdTruth dashboard at https://adtruth.io/dashboard 59 | 5. You should see page views appearing under your website 60 | 61 | ## Understanding the Shopify Warning 62 | 63 | After adding the pixel, you may see this message: 64 | 65 | > "Pixel will not track any customer behavior because it is not subscribed to any events." 66 | 67 | **This is normal and expected.** Here's what it means: 68 | 69 | - The warning refers to Shopify's built-in event API (cart additions, checkouts, purchases) 70 | - Our AdTruth script works independently and doesn't need Shopify events 71 | - You're already tracking all fraud detection signals: page views, mouse movements, scroll depth, touch interactions, time on page, and click patterns 72 | - No action needed - everything is working correctly 73 | 74 | ## What Gets Tracked 75 | 76 | AdTruth automatically collects fraud detection signals without requiring Shopify event subscriptions: 77 | 78 | **Behavioral Data:** 79 | - Page views and session tracking 80 | - Time spent on each page 81 | - Mouse movement patterns (desktop) 82 | - Touch interactions (mobile) 83 | - Scroll depth and reading patterns 84 | - Click frequency and intervals 85 | 86 | **Device Fingerprinting:** 87 | - Browser and device characteristics 88 | - Screen resolution and timezone 89 | - Hardware capabilities 90 | - Canvas fingerprinting for unique device signatures 91 | 92 | **Traffic Source:** 93 | - UTM parameters (utm_source, utm_medium, utm_campaign) 94 | - Referrer information 95 | - Traffic channel classification 96 | 97 | All of this data is analyzed to detect bot traffic, click fraud, and suspicious behavior patterns. 98 | 99 | ## Troubleshooting 100 | 101 | ### Not Seeing Data in Dashboard? 102 | 103 | 1. **Check API Key**: Make sure you replaced `YOUR_API_KEY_HERE` with your actual API key 104 | 2. **Pixel Status**: Verify the pixel shows "Connected" status in Customer events 105 | 3. **Browser Console**: Open browser dev tools (F12) and check for JavaScript errors 106 | 4. **Wait Time**: Data transmission can take 1-2 minutes - be patient 107 | 5. **Visit Store**: Make sure you're visiting the actual store, not the admin panel 108 | 109 | ### Pixel Not Saving? 110 | 111 | - Check for syntax errors in the code (missing quotes, brackets) 112 | - Make sure you're logged in as the store owner 113 | - Try a different browser if issues persist 114 | 115 | ### Data Shows as "Suspicious"? 116 | 117 | - This is expected for test traffic from development environments 118 | - Real customer traffic will score more accurately 119 | - Test data often triggers fraud signals (which shows the system is working!) 120 | 121 | ## Testing Your Integration 122 | 123 | After installation, test the integration: 124 | 125 | 1. **Desktop Test**: Visit your store from a desktop browser, click around, scroll through product pages 126 | 2. **Mobile Test**: Visit from a mobile device or use Chrome DevTools device emulation 127 | 3. **Different Sources**: Test with different UTM parameters to verify source tracking 128 | 4. **Dashboard Check**: Verify all test sessions appear in your AdTruth dashboard 129 | 130 | Expected timeline: Page views should appear in the dashboard within 2 minutes. 131 | 132 | ## Advanced: Subscribing to Shopify Events (Optional) 133 | 134 | If you want to track e-commerce specific events like product views, cart additions, and purchases, you can subscribe to Shopify's Customer Events API. This is optional and not required for fraud detection. 135 | 136 | To add event tracking, modify the pixel code to subscribe to events: 137 | 138 | ```javascript 139 | // This is an advanced configuration - basic setup works without this 140 | analytics.subscribe("product_viewed", (event) => { 141 | // Custom tracking logic for product views 142 | }); 143 | 144 | analytics.subscribe("product_added_to_cart", (event) => { 145 | // Custom tracking logic for cart additions 146 | }); 147 | ``` 148 | 149 | For most users, the basic setup is sufficient. E-commerce event tracking will be added in future versions of AdTruth. 150 | 151 | ## Test Results (November 2025) 152 | 153 | **Test Environment:** 154 | - Platform: Shopify free trial account 155 | - Method: Custom Pixels (Settings → Customer events) 156 | - API Key: Production key from dashboard 157 | - Integration Time: ~3 minutes 158 | 159 | **Results:** 160 | - ✅ Pixel installation successful 161 | - ✅ Script loaded without errors 162 | - ✅ Page views appeared in dashboard within 2 minutes 163 | - ✅ Traffic source correctly identified 164 | - ✅ Fraud detection working (test traffic flagged as expected) 165 | - ✅ No theme modifications required 166 | - ✅ Works on all pages including checkout 167 | 168 | **User Experience:** 169 | - Time to integrate: 3 minutes 170 | - Difficulty: Easy 171 | - Technical knowledge required: None (copy-paste only) 172 | - Issues encountered: None 173 | 174 | ## Support 175 | 176 | If you encounter issues: 177 | 178 | 1. Check this guide's troubleshooting section 179 | 2. Review browser console for JavaScript errors 180 | 3. Verify your API key is correct 181 | 4. Open an issue on GitHub: https://github.com/papa-torb/adtruth/issues 182 | 183 | ## Next Steps 184 | 185 | After successful integration: 186 | 187 | 1. Remove password protection from your store (if applicable) 188 | 2. Start running paid advertising campaigns 189 | 3. Monitor your dashboard regularly for fraud insights 190 | 4. Review traffic sources and fraud scores 191 | 5. Optimize ad spend based on detected fraud patterns 192 | 193 | --- 194 | 195 | **Integration complete!** Your Shopify store is now protected with AdTruth fraud detection. Visit your dashboard to start monitoring traffic quality. 196 | -------------------------------------------------------------------------------- /tests/manual/manual.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AdTruth SDK - Manual Test 7 | 82 | 83 | 84 |
85 |

AdTruth SDK - Manual Test Page

86 | 87 |
88 |

Test Configuration

89 |

This page loads the AdTruth SDK and allows you to test its functionality.

90 |
91 | API Key: TEST_API_KEY_12345
92 | Endpoint: https://api.adtruth.io/track (will fail - backend not built yet)
93 | Debug Mode: Enabled 94 |
95 |
96 | 97 |
98 |

Test Controls

99 | 100 | 101 | 102 | 103 |
104 | 105 |
106 |

Test URLs with Parameters

107 |

Click these links to test UTM parameter tracking:

108 | Test with Google Ads params
109 | Test with Facebook params
110 | Reset (no params) 111 |
112 | 113 |
114 | 115 |
116 | Console Output:
117 |
118 |
119 |
120 | 121 | 122 | 123 | 124 | 125 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /dev/tests/test-merged-ground-truth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Test Merged Ground Truth (Phase 0) 7 | 66 | 67 | 68 |
69 |

✅ Phase 0 Complete: Merged Ground Truth

70 |

Testing automatic impossibilities detection (no configuration required)

71 |
72 | 73 |
74 | What's different: 75 |
    76 |
  • ✅ No enableGroundTruth flag needed
  • 77 |
  • ✅ All traffic automatically analyzed
  • 78 |
  • ✅ Impossibilities detected by backend
  • 79 |
  • ✅ Ground truth labels stored in page_views table
  • 80 |
  • ✅ One-liner philosophy maintained
  • 81 |
82 |
83 | 84 |
85 | 86 | 87 | 88 |
89 | 90 |
91 | Status: Ready to test. Click a button above. 92 |
93 | 94 |
95 | 96 |
97 |

📜 Scroll Test Content

98 |

This content allows you to test natural scrolling behavior.

99 |
100 |

Phase 0 MVP Complete

101 |

The ground truth collection system is now fully integrated into the standard tracking flow. Every page view automatically gets analyzed for behavioral impossibilities.

102 | 103 |

How It Works

104 |
    105 |
  1. SDK collects behavioral data (clicks, mouse movement, scroll, time on page)
  2. 106 |
  3. SDK sends data to /track endpoint (no special configuration)
  4. 107 |
  5. Backend automatically detects impossibilities from behavioral data
  6. 108 |
  7. Backend computes ground truth label (bot, human, suspicious, unknown)
  8. 109 |
  9. Backend stores everything in page_views table with confidence scores
  10. 110 |
111 | 112 |

Benefits

113 |
    114 |
  • Zero configuration - works automatically
  • 115 |
  • Maintains one-liner philosophy
  • 116 |
  • 100% of traffic labeled (not just a sample)
  • 117 |
  • High-quality training data for ML models
  • 118 |
  • Physics-based ground truth (not subjective)
  • 119 |
120 | 121 |

Keep scrolling...

122 |

The more you interact naturally, the more human-like behavioral signals you generate!

123 | 124 |

125 | 🎉 You reached the bottom! 126 |

127 |
128 |
129 | 130 | 131 | 132 | 133 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /examples/bluehaven-coffee/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Blue Haven Coffee - Artisan Coffee & Fresh Pastries 7 | 182 | 183 | 184 |
185 | 194 |
195 | 196 |
197 |

Welcome to Blue Haven Coffee

198 |

Artisan coffee, fresh pastries, and a cozy atmosphere in the heart of downtown

199 | View Our Menu 200 |
201 | 202 |
203 |

Why Choose Blue Haven?

204 |
205 |
206 |

🌟 Premium Coffee

207 |

We source our beans directly from small farms around the world, ensuring the highest quality and ethical practices.

208 |
209 |
210 |

🥐 Fresh Pastries

211 |

All our pastries are baked fresh daily using locally sourced ingredients and traditional recipes.

212 |
213 |
214 |

🏠 Cozy Atmosphere

215 |

Relax in our comfortable seating areas, perfect for meetings, studying, or catching up with friends.

216 |
217 |
218 |
219 | 220 | 257 | 258 |
259 |
260 |

Visit Us

261 |

123 Main Street, Downtown

262 |

Open Daily: 7:00 AM - 7:00 PM

263 |

📞 (555) 123-4567

264 |
265 |
266 | 267 |
268 |

© 2024 Blue Haven Coffee. All rights reserved.

269 |

Follow us on Instagram, Facebook, and Twitter

270 |
271 | 272 | 273 | 274 | 275 | -------------------------------------------------------------------------------- /dev/ground-truth/impossibilities.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Behavioral Impossibilities Detector 3 | * Detects bot behavior through physics violations and impossible patterns 4 | * 5 | * Phase 0: Core detection logic for ground truth labeling 6 | */ 7 | 8 | /** 9 | * BehavioralImpossibilities class - Analyzes behavioral data for impossibilities 10 | */ 11 | export class BehavioralImpossibilities { 12 | constructor() { 13 | this.domContentLoadedTime = null; 14 | 15 | // Track DOMContentLoaded timing 16 | if (document.readyState === 'loading') { 17 | document.addEventListener('DOMContentLoaded', () => { 18 | this.domContentLoadedTime = Date.now(); 19 | }); 20 | } else { 21 | this.domContentLoadedTime = Date.now(); 22 | } 23 | } 24 | 25 | /** 26 | * Analyze behavioral data for impossibilities 27 | * @param {object} behavioralData - Data from BehaviorTracker.getMetrics() 28 | * @returns {Array} Array of detected impossibilities 29 | */ 30 | analyze(behavioralData) { 31 | const impossibilities = []; 32 | 33 | // 1. Fast exit detection (land and leave pattern) 34 | const fastExit = this.detectFastExit(behavioralData); 35 | if (fastExit) { 36 | impossibilities.push(fastExit); 37 | } 38 | 39 | // 2. Superhuman mouse velocity 40 | const superhumanMouse = this.detectSuperhumanMouseVelocity(behavioralData); 41 | if (superhumanMouse) { 42 | impossibilities.push(superhumanMouse); 43 | } 44 | 45 | // 3. Impossible scroll speed 46 | const impossibleScroll = this.detectImpossibleScrollSpeed(behavioralData); 47 | if (impossibleScroll) { 48 | impossibilities.push(impossibleScroll); 49 | } 50 | 51 | // 4. Click before page load 52 | const earlyClick = this.detectClickBeforeLoad(behavioralData); 53 | if (earlyClick) { 54 | impossibilities.push(earlyClick); 55 | } 56 | 57 | // 5. Zero interaction ghost 58 | const ghostUser = this.detectZeroInteractionGhost(behavioralData); 59 | if (ghostUser) { 60 | impossibilities.push(ghostUser); 61 | } 62 | 63 | // 6. Rapid fire clicks 64 | const rapidClicks = this.detectRapidFireClicks(behavioralData); 65 | if (rapidClicks) { 66 | impossibilities.push(rapidClicks); 67 | } 68 | 69 | // 7. Instant form submission 70 | const instantForm = this.detectInstantFormSubmission(behavioralData); 71 | if (instantForm) { 72 | impossibilities.push(instantForm); 73 | } 74 | 75 | return impossibilities; 76 | } 77 | 78 | /** 79 | * Detect "land and leave" pattern - exit in <3s with zero interaction 80 | * This catches 60-70% of CPC fraud bots 81 | */ 82 | detectFastExit(data) { 83 | const { timeOnPage, clickCount, mouseMovementDetected, scrollDepth } = data; 84 | 85 | // Fast exit: <3s with absolutely no interaction 86 | if ( 87 | timeOnPage < 3000 && 88 | clickCount === 0 && 89 | !mouseMovementDetected && 90 | scrollDepth === 0 91 | ) { 92 | return { 93 | type: 'fast_exit_no_interaction', 94 | confidence: 1.0, 95 | details: { 96 | timeOnPage, 97 | clickCount, 98 | mouseMovementDetected, 99 | scrollDepth 100 | }, 101 | message: `Exited in ${timeOnPage}ms with zero interaction (no clicks, mouse, or scroll)` 102 | }; 103 | } 104 | 105 | return null; 106 | } 107 | 108 | /** 109 | * Detect superhuman mouse velocity (>5000 px/s average) 110 | * Humans rarely exceed 2000 px/s sustained 111 | */ 112 | detectSuperhumanMouseVelocity(data) { 113 | const { mousePatterns } = data; 114 | 115 | if (!mousePatterns || !mousePatterns.avgVelocity) { 116 | return null; 117 | } 118 | 119 | const threshold = 5000; // px/s 120 | if (mousePatterns.avgVelocity > threshold) { 121 | return { 122 | type: 'superhuman_mouse_velocity', 123 | confidence: 0.95, 124 | details: { 125 | avgVelocity: mousePatterns.avgVelocity, 126 | threshold, 127 | sampleCount: mousePatterns.sampleCount 128 | }, 129 | message: `Mouse velocity ${mousePatterns.avgVelocity} px/s exceeds human threshold ${threshold} px/s` 130 | }; 131 | } 132 | 133 | return null; 134 | } 135 | 136 | /** 137 | * Detect impossible scroll speed (90% depth in <100ms) 138 | * Humans need at least 500ms to scroll full page 139 | */ 140 | detectImpossibleScrollSpeed(data) { 141 | const { timeOnPage, scrollDepth } = data; 142 | 143 | // Scrolled to 90%+ depth in <100ms 144 | if (scrollDepth >= 0.9 && timeOnPage < 100) { 145 | return { 146 | type: 'impossible_scroll_speed', 147 | confidence: 0.95, 148 | details: { 149 | scrollDepth, 150 | timeOnPage 151 | }, 152 | message: `Scrolled to ${Math.round(scrollDepth * 100)}% in ${timeOnPage}ms (impossible speed)` 153 | }; 154 | } 155 | 156 | return null; 157 | } 158 | 159 | /** 160 | * Detect click before DOMContentLoaded 161 | * Page content hasn't loaded yet - can't see what to click 162 | */ 163 | detectClickBeforeLoad(data) { 164 | const { timeToFirstClick } = data; 165 | 166 | if ( 167 | timeToFirstClick !== null && 168 | this.domContentLoadedTime !== null && 169 | timeToFirstClick < 0 170 | ) { 171 | return { 172 | type: 'click_before_load', 173 | confidence: 1.0, 174 | details: { 175 | timeToFirstClick, 176 | domContentLoadedTime: this.domContentLoadedTime 177 | }, 178 | message: 'Click detected before DOM loaded (impossible)' 179 | }; 180 | } 181 | 182 | return null; 183 | } 184 | 185 | /** 186 | * Detect zero interaction for extended period (10+ seconds) 187 | * Human reading/viewing should show some micro-movements 188 | */ 189 | detectZeroInteractionGhost(data) { 190 | const { timeOnPage, clickCount, mouseMovementDetected, scrollDepth, touchPatterns } = data; 191 | 192 | // 10+ seconds with absolutely no input 193 | if ( 194 | timeOnPage >= 10000 && 195 | clickCount === 0 && 196 | !mouseMovementDetected && 197 | scrollDepth === 0 && 198 | (!touchPatterns || touchPatterns.tapCount === 0) 199 | ) { 200 | return { 201 | type: 'zero_interaction_ghost', 202 | confidence: 0.90, 203 | details: { 204 | timeOnPage, 205 | clickCount, 206 | mouseMovementDetected, 207 | scrollDepth 208 | }, 209 | message: `${Math.round(timeOnPage / 1000)}s on page with zero interaction (bot likely)` 210 | }; 211 | } 212 | 213 | return null; 214 | } 215 | 216 | /** 217 | * Detect rapid fire clicks (100+ clicks in 1 second) 218 | * Humanly impossible click rate 219 | */ 220 | detectRapidFireClicks(data) { 221 | const { clickCount, timeOnPage } = data; 222 | 223 | // More than 100 clicks in 1 second 224 | const clickRate = (clickCount / timeOnPage) * 1000; // clicks per second 225 | if (clickRate > 100) { 226 | return { 227 | type: 'rapid_fire_clicks', 228 | confidence: 1.0, 229 | details: { 230 | clickCount, 231 | timeOnPage, 232 | clickRate: Math.round(clickRate) 233 | }, 234 | message: `${clickCount} clicks in ${timeOnPage}ms (${Math.round(clickRate)} clicks/sec - impossible)` 235 | }; 236 | } 237 | 238 | return null; 239 | } 240 | 241 | /** 242 | * Detect instant form submission (<200ms after page load) 243 | * Can't read and fill form that fast 244 | * 245 | * NOTE: Increased threshold to 200ms (from 500ms) to reduce false positives. 246 | * Humans can click within 200-500ms if they're expecting to click (e.g., clicking 247 | * a button immediately after page load), but <200ms is physically impossible 248 | * for human reaction time to visual stimulus. 249 | */ 250 | detectInstantFormSubmission(data) { 251 | const { timeToFirstClick, timeOnPage } = data; 252 | 253 | // This is a placeholder - would need form tracking to implement fully 254 | // For now, detect suspiciously fast first interaction 255 | // 200ms = typical human visual-motor reaction time threshold 256 | if (timeToFirstClick !== null && timeToFirstClick < 200) { 257 | return { 258 | type: 'instant_interaction', 259 | confidence: 0.95, 260 | details: { 261 | timeToFirstClick 262 | }, 263 | message: `First interaction at ${timeToFirstClick}ms (faster than human reaction time)` 264 | }; 265 | } 266 | 267 | return null; 268 | } 269 | 270 | /** 271 | * Calculate overall impossibility score (0.0-1.0) 272 | * Based on detected impossibilities and their confidence levels 273 | * 274 | * @param {Array} impossibilities - Array from analyze() 275 | * @returns {number} Score from 0.0 (likely human) to 1.0 (definitely bot) 276 | */ 277 | calculateScore(impossibilities) { 278 | if (impossibilities.length === 0) { 279 | return 0.0; 280 | } 281 | 282 | // Weight by confidence and combine 283 | const totalConfidence = impossibilities.reduce( 284 | (sum, imp) => sum + imp.confidence, 285 | 0 286 | ); 287 | 288 | // Average confidence, but cap at 1.0 289 | const avgConfidence = totalConfidence / impossibilities.length; 290 | 291 | // Boost score if multiple impossibilities detected 292 | const multiplier = Math.min(1 + (impossibilities.length - 1) * 0.1, 1.5); 293 | 294 | return Math.min(avgConfidence * multiplier, 1.0); 295 | } 296 | } 297 | 298 | // Note: This project is still under development. 299 | -------------------------------------------------------------------------------- /dev/tests/test-ground-truth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ground Truth Collection Test - Phase 0 MVP 7 | 71 | 72 | 73 |
74 |

🔬 Phase 0 MVP Test: Ground Truth Collection

75 |

Testing behavioral impossibilities detection + data submission

76 |
77 | 78 |
79 |

📋 Test Instructions:

80 |
    81 |
  1. Enable ground truth collection - Click "Initialize with Ground Truth"
  2. 82 |
  3. Interact naturally - Move mouse, scroll, click around for 15 seconds
  4. 83 |
  5. Wait for auto-submission - Data will be sent automatically after 15s
  6. 84 |
  7. Check the console below - See behavioral data and impossibilities detected
  8. 85 |
86 |

Alternative tests: Try "Bot Test" to trigger fast-exit detection, or leave page before 15s to test unload handling

87 |
88 | 89 |
90 | 91 | 92 | 93 | 94 |
95 | 96 |
97 | Status: Not initialized. Click "Initialize with Ground Truth" to start. 98 |
99 | 100 |
101 | 102 | 103 |
104 |

📜 Scroll Test Content (Scroll Down!)

105 |

This section provides content to test scroll detection. Scroll down to increase scroll depth metrics.

106 | 107 |
108 |

What is Ground Truth Labeling?

109 |

Ground truth labeling is the process of creating accurately labeled training data for machine learning models. In fraud detection, this means identifying which traffic is definitely bot vs definitely human.

110 | 111 |

Why Behavioral Impossibilities?

112 |

Behavioral impossibilities are physics violations that prove bot behavior with near 100% confidence. For example:

113 |
    114 |
  • Exiting a page in under 1 second with zero interaction
  • 115 |
  • Mouse moving faster than humanly possible (>5000 px/s)
  • 116 |
  • Scrolling to bottom of page in under 100ms
  • 117 |
  • Clicking before page content loads
  • 118 |
119 | 120 |

Phase 0 MVP Goals

121 |

The goal of Phase 0 is to validate that behavioral impossibilities can automatically label bot traffic with high confidence. We aim to collect 1,000+ baseline events in the first week.

122 | 123 |

Keep Scrolling...

124 |

Scroll depth is measured as a percentage from 0% (top) to 100% (bottom). The more you scroll, the higher your scroll depth metric!

125 | 126 |

127 | 🎉 You reached the bottom! Good scroll depth detected. 128 |

129 |
130 |
131 | 132 | 133 | 134 | 135 | 234 | 235 | 236 | -------------------------------------------------------------------------------- /src/core/tracker.js: -------------------------------------------------------------------------------- 1 | import { getSessionId, getVisitorId } from '../utils/session.js'; 2 | import { parseUrlParams, getCurrentUrl, getReferrer } from '../utils/url.js'; 3 | import { 4 | getBasicFingerprint, 5 | getEnhancedBrowserInfo, 6 | getInputMethod, 7 | getCanvasFingerprint 8 | } from '../utils/fingerprint.js'; 9 | import { BehaviorTracker } from '../utils/behavior.js'; 10 | 11 | class Tracker { 12 | constructor() { 13 | this.apiKey = null; 14 | this.apiEndpoint = 'https://api.adtruth.io/track/'; 15 | this.initialized = false; 16 | this.debug = false; 17 | this.behaviorTracker = null; 18 | this.fingerprint = null; 19 | this.hasUnloadListener = false; 20 | this.periodicTimer = null; 21 | this.unloadEventSent = false; 22 | } 23 | 24 | /** 25 | * Initialize the tracker with an API key 26 | * @param {string} apiKey - The API key for authentication 27 | * @param {object} options - Optional configuration 28 | */ 29 | init(apiKey, options = {}) { 30 | try { 31 | if (this.initialized) { 32 | return; 33 | } 34 | 35 | if (!apiKey) { 36 | this.log('AdTruth: API key is required'); 37 | return; 38 | } 39 | 40 | this.apiKey = apiKey; 41 | this.debug = options.debug || false; 42 | 43 | // Allow custom endpoint for testing 44 | if (options.endpoint) { 45 | this.apiEndpoint = options.endpoint; 46 | } 47 | 48 | this.initialized = true; 49 | this.log('AdTruth: Initialized'); 50 | 51 | // Initialize behavior tracker and generate fingerprints 52 | this.behaviorTracker = new BehaviorTracker(); 53 | this.fingerprint = getBasicFingerprint(); 54 | 55 | // Phase 2: Canvas fingerprinting with error handling 56 | try { 57 | this.canvasFingerprint = getCanvasFingerprint(); 58 | } catch (e) { 59 | this.log('AdTruth: Canvas fingerprinting failed (browser restriction)', e); 60 | this.canvasFingerprint = null; 61 | } 62 | 63 | // Set up page unload tracking for accurate behavior data 64 | this.setupUnloadTracking(); 65 | 66 | // Optional: Enable periodic updates (default: disabled) 67 | // Users can enable with: AdTruth.init(apiKey, { periodicUpdates: true }) 68 | if (options.periodicUpdates) { 69 | this.startPeriodicUpdates(options.updateInterval || 30000); 70 | } 71 | } catch (error) { 72 | // Don't let initialization errors break client sites 73 | this.log('AdTruth: Initialization error', error); 74 | } 75 | } 76 | 77 | /** 78 | * Set up page unload tracking to capture complete behavior data 79 | * Uses sendBeacon for guaranteed delivery even as page unloads 80 | */ 81 | setupUnloadTracking() { 82 | if (this.hasUnloadListener) { 83 | return; // Already set up 84 | } 85 | 86 | const sendFinalEvent = () => { 87 | try { 88 | if (!this.initialized || !this.behaviorTracker) { 89 | return; 90 | } 91 | 92 | // Use atomic flag to prevent duplicate sends (both events fire nearly simultaneously) 93 | if (this.unloadEventSent) { 94 | this.log('AdTruth: Final event already sent'); 95 | return; 96 | } 97 | this.unloadEventSent = true; 98 | 99 | // Clean up periodic updates to prevent memory leaks 100 | this.stopPeriodicUpdates(); 101 | 102 | // Collect final data with all behavior metrics 103 | const data = this.collectData('pageview'); 104 | const payload = JSON.stringify(data); 105 | const payloadSize = new Blob([payload]).size; 106 | 107 | this.log('AdTruth: Sending final event on page unload'); 108 | this.log(`AdTruth: Payload size: ${payloadSize} bytes`); 109 | this.log('AdTruth: Behavior data:', data.behavior); 110 | 111 | // Use fetch with keepalive for reliable page unload tracking 112 | // Note: We don't use sendBeacon because it always sends credentials, 113 | // which conflicts with wildcard CORS origins. Fetch with keepalive 114 | // gives us full control over credentials and works reliably. 115 | this.sendViaFetch(payload); 116 | } catch (error) { 117 | this.log('AdTruth: Error sending final event', error); 118 | } 119 | }; 120 | 121 | // Set up multiple events for cross-browser compatibility 122 | // Desktop browsers: beforeunload fires reliably 123 | // Mobile Safari: Only pagehide fires reliably 124 | // Back/Forward Cache: pagehide fires, beforeunload doesn't 125 | window.addEventListener('beforeunload', sendFinalEvent); 126 | window.addEventListener('pagehide', sendFinalEvent); 127 | window.addEventListener('unload', sendFinalEvent); // Legacy browser fallback 128 | 129 | this.hasUnloadListener = true; 130 | this.log('AdTruth: Page unload tracking enabled'); 131 | } 132 | 133 | /** 134 | * Start periodic updates for long sessions 135 | * @param {number} interval - Update interval in milliseconds (default: 30000 = 30 seconds) 136 | */ 137 | startPeriodicUpdates(interval = 30000) { 138 | if (this.periodicTimer) { 139 | return; // Already running 140 | } 141 | 142 | this.periodicTimer = setInterval(() => { 143 | try { 144 | if (this.initialized && this.behaviorTracker) { 145 | this.log('AdTruth: Sending periodic update'); 146 | this.track(); 147 | } 148 | } catch (error) { 149 | this.log('AdTruth: Error in periodic update', error); 150 | } 151 | }, interval); 152 | 153 | this.log(`AdTruth: Periodic updates enabled (every ${interval}ms)`); 154 | } 155 | 156 | /** 157 | * Stop periodic updates 158 | */ 159 | stopPeriodicUpdates() { 160 | if (this.periodicTimer) { 161 | clearInterval(this.periodicTimer); 162 | this.periodicTimer = null; 163 | this.log('AdTruth: Periodic updates stopped'); 164 | } 165 | } 166 | 167 | /** 168 | * Track a pageview event 169 | */ 170 | track(eventType = 'pageview') { 171 | try { 172 | if (!this.initialized) { 173 | this.log('AdTruth: Not initialized. Call init() first.'); 174 | return; 175 | } 176 | 177 | const data = this.collectData(eventType); 178 | this.sendData(data); 179 | } catch (error) { 180 | // Don't let tracking errors break client sites 181 | this.log('AdTruth: Tracking error', error); 182 | } 183 | } 184 | 185 | /** 186 | * Collect data for tracking 187 | */ 188 | collectData(eventType) { 189 | const url = getCurrentUrl(); 190 | const params = parseUrlParams(url); 191 | 192 | // Collect enhanced browser info and behavior metrics 193 | const enhancedBrowser = getEnhancedBrowserInfo(); 194 | const inputMethod = getInputMethod(); 195 | const behaviorMetrics = this.behaviorTracker ? this.behaviorTracker.getMetrics() : {}; 196 | 197 | const data = { 198 | event_type: eventType, 199 | timestamp: new Date().toISOString(), 200 | session_id: getSessionId(), 201 | visitor_id: getVisitorId(), 202 | page: { 203 | url: url, 204 | title: document.title || '', 205 | referrer: getReferrer() 206 | }, 207 | utm: { 208 | source: params.utm_source || null, 209 | medium: params.utm_medium || null, 210 | campaign: params.utm_campaign || null, 211 | term: params.utm_term || null, 212 | content: params.utm_content || null 213 | }, 214 | click_ids: { 215 | gclid: params.gclid || null, 216 | fbclid: params.fbclid || null 217 | }, 218 | browser: { 219 | ...enhancedBrowser, 220 | screen: { 221 | width: screen.width, 222 | height: screen.height, 223 | colorDepth: screen.colorDepth 224 | }, 225 | viewport: { 226 | width: window.innerWidth, 227 | height: window.innerHeight 228 | } 229 | }, 230 | fingerprint: { 231 | hash: this.fingerprint ? this.fingerprint.hash : null, 232 | timezone: this.fingerprint ? this.fingerprint.details.timezone : null, 233 | timezoneOffset: this.fingerprint ? this.fingerprint.details.timezoneOffset : null, 234 | // Phase 2: Canvas fingerprint 235 | canvas: this.canvasFingerprint ? this.canvasFingerprint.hash : null 236 | }, 237 | input: inputMethod, 238 | behavior: behaviorMetrics 239 | }; 240 | 241 | // Phase 0: Behavioral impossibilities are detected automatically by backend 242 | // No SDK changes needed - backend analyzes behavioral data on every /track request 243 | 244 | return data; 245 | } 246 | 247 | /** 248 | * Send data to the API endpoint 249 | */ 250 | sendData(data) { 251 | // Always use fetch for better header support 252 | // sendBeacon doesn't support custom headers well 253 | const payload = JSON.stringify(data); 254 | this.sendViaFetch(payload); 255 | } 256 | 257 | /** 258 | * Send data using fetch API with timeout 259 | */ 260 | sendViaFetch(payload) { 261 | // Validate payload size for keepalive limit (64KB browser-wide limit) 262 | const payloadSize = new Blob([payload]).size; 263 | const KEEPALIVE_LIMIT = 60 * 1024; // 60KB (leave 4KB buffer) 264 | 265 | if (payloadSize > KEEPALIVE_LIMIT) { 266 | this.log(`AdTruth: Warning - Payload size ${payloadSize} bytes exceeds keepalive limit`); 267 | // In production, consider truncating behavior arrays here 268 | } 269 | 270 | // Create abort controller for timeout (5 seconds) 271 | const controller = new AbortController(); 272 | const timeoutId = setTimeout(() => controller.abort(), 5000); 273 | 274 | fetch(this.apiEndpoint, { 275 | method: 'POST', 276 | headers: { 277 | 'Content-Type': 'application/json', 278 | 'X-API-Key': this.apiKey 279 | }, 280 | body: payload, 281 | keepalive: true, 282 | credentials: 'omit', // Don't send cookies (required for CORS wildcard origins) 283 | signal: controller.signal 284 | }) 285 | .then(() => { 286 | clearTimeout(timeoutId); 287 | this.log('AdTruth: Data sent via fetch'); 288 | }) 289 | .catch(error => { 290 | clearTimeout(timeoutId); 291 | // Fail silently in production 292 | if (error.name === 'AbortError') { 293 | this.log('AdTruth: Request timeout'); 294 | } else { 295 | this.log('AdTruth: Failed to send data', error); 296 | } 297 | // TODO: For production, queue in IndexedDB for retry on next page load 298 | }); 299 | } 300 | 301 | /** 302 | * Log messages if debug mode is enabled 303 | */ 304 | log(...args) { 305 | // eslint-disable-next-line no-console 306 | if (this.debug && typeof console !== 'undefined' && console.log) { 307 | // eslint-disable-next-line no-console 308 | console.log(...args); 309 | } 310 | } 311 | } 312 | 313 | // Create singleton instance 314 | const tracker = new Tracker(); 315 | 316 | export default tracker; 317 | // Note: This project is still under development. 318 | -------------------------------------------------------------------------------- /tests/manual/test-production.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AdTruth v0.2.0 Production Test 7 | 108 | 109 | 110 |
111 |

🧪 AdTruth v0.2.0 Production Test

112 |
Testing SDK from jsDelivr CDN (latest version)
113 | 114 |
115 |

📋 Test Instructions

116 |
    117 |
  1. Open DevTools Console (F12) and enable "Persist Logs"
  2. 118 |
  3. Interact with the page: 119 |
      120 |
    • Click the buttons below multiple times
    • 121 |
    • Scroll down and back up
    • 122 |
    • Move your mouse around the page
    • 123 |
    • Stay on the page for at least 10-15 seconds
    • 124 |
    125 |
  4. 126 |
  5. Watch the Live Metrics update every 2 seconds
  6. 127 |
  7. Navigate Away using one of the buttons below
  8. 128 |
  9. Check Console for "Sending final event on page unload" message
  10. 129 |
  11. Verify in Database that behavior data was saved
  12. 130 |
131 |
132 | 133 |
134 |

🎮 Interaction Area

135 |

Click these buttons, scroll the page, and move your mouse to generate behavior data:

136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 |
144 |
Loading metrics...
145 |
146 |
147 | 148 |
149 |

📜 Scroll Area

150 |

Scroll down to increase scroll depth metric...

151 |

🎯 25% scrolled

152 |

🎯 50% scrolled

153 |

🎯 75% scrolled

154 |

🎉 100% - You've reached the bottom!

155 |
156 | 157 | 164 | 165 |
166 |

✅ What to Expect

167 |

Expected in Console:

168 |
    169 |
  • "AdTruth: Page unload tracking enabled"
  • 170 |
  • "AdTruth: Sending final event on page unload"
  • 171 |
  • "AdTruth: Payload size: [X] bytes"
  • 172 |
  • "AdTruth: sendBeacon succeeded" (or "Data sent via fetch")
  • 173 |
174 |

Expected in Database:

175 |
    176 |
  • timeOnPage: 10,000+ ms (10+ seconds, not 5ms!)
  • 177 |
  • clickCount: Number of button clicks
  • 178 |
  • scrollDepth: 0.0 to 1.0 (percentage scrolled)
  • 179 |
  • mouseMovementDetected: true
  • 180 |
  • mousePatterns: velocity, jitter, distance data
  • 181 |
182 |
183 |
184 | 185 | 186 | 187 | 249 | 250 | 251 | --------------------------------------------------------------------------------