#!/usr/bin/env node /** * Seed script for generating realistic analytics events. * * Usage: * node scripts/seed-events.mjs [--timeline=30] [--sessions=500] [--url=http://localhost:3333] * * Options: * --timeline=N Duration in minutes to spread events over (default: 30) * --sessions=N Number of sessions to generate (default: 500) * --url=URL API base URL (default: http://localhost:3333) * --clientId=ID Client ID to use (required or set CLIENT_ID env var) */ // --------------------------------------------------------------------------- // Config // --------------------------------------------------------------------------- const args = Object.fromEntries( process.argv.slice(2).map((a) => { const [k, v] = a.replace(/^--/, '').split('='); return [k, v ?? true]; }) ); const TIMELINE_MINUTES = Number(args.timeline ?? 30); const SESSION_COUNT = Number(args.sessions ?? 500); const BASE_URL = args.url ?? 'http://localhost:3333'; const CLIENT_ID = args.clientId ?? process.env.CLIENT_ID ?? ''; const ORIGIN = args.origin ?? process.env.ORIGIN ?? 'https://shop.example.com'; const CONCURRENCY = 20; // max parallel requests if (!CLIENT_ID) { console.error('ERROR: provide --clientId= or set CLIENT_ID env var'); process.exit(1); } const TRACK_URL = `${BASE_URL}/track`; // --------------------------------------------------------------------------- // Deterministic seeded random (mulberry32) — keeps identities stable across runs // --------------------------------------------------------------------------- function mulberry32(seed) { return () => { seed |= 0; seed = (seed + 0x6d_2b_79_f5) | 0; let t = Math.imul(seed ^ (seed >>> 15), 1 | seed); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4_294_967_296; }; } // Non-deterministic random for events (differs on each run) const eventRng = Math.random.bind(Math); function pick(arr, rng = eventRng) { return arr[Math.floor(rng() * arr.length)]; } function randInt(min, max, rng = eventRng) { return Math.floor(rng() * (max - min + 1)) + min; } function randFloat(min, max, rng = eventRng) { return rng() * (max - min) + min; } // --------------------------------------------------------------------------- // Fake data pools // --------------------------------------------------------------------------- const FIRST_NAMES = [ 'Alice', 'Bob', 'Charlie', 'Diana', 'Eve', 'Frank', 'Grace', 'Hank', 'Iris', 'Jack', 'Karen', 'Leo', 'Mia', 'Noah', 'Olivia', 'Pete', 'Quinn', 'Rachel', 'Sam', 'Tina', 'Uma', 'Victor', 'Wendy', 'Xavier', 'Yara', 'Zoe', 'Aaron', 'Bella', 'Carlos', 'Dani', 'Ethan', 'Fiona', ]; const LAST_NAMES = [ 'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis', 'Wilson', 'Taylor', 'Anderson', 'Thomas', 'Jackson', 'White', 'Harris', 'Martin', 'Thompson', 'Moore', 'Young', 'Allen', ]; const EMAIL_DOMAINS = [ 'gmail.com', 'yahoo.com', 'outlook.com', 'icloud.com', 'proton.me', ]; const USER_AGENTS = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1', 'Mozilla/5.0 (iPad; CPU OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14.3; rv:122.0) Gecko/20100101 Firefox/122.0', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', 'Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Mobile Safari/537.36', 'Mozilla/5.0 (Linux; Android 13; Samsung Galaxy S23) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/23.0 Chrome/115.0.0.0 Mobile Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 OPR/107.0.0.0', ]; // Ensure each session has a unique UA by appending a suffix function makeUniqueUA(base, index) { return `${base} Session/${index}`; } // Generate a plausible IP address (avoiding private ranges) function makeIP(index) { // Use a spread across several /8 public ranges const ranges = [ [34, 0, 0, 0], [52, 0, 0, 0], [104, 0, 0, 0], [185, 0, 0, 0], [213, 0, 0, 0], ]; const base = ranges[index % ranges.length]; const a = base[0]; const b = Math.floor(index / 65_025) % 256; const c = Math.floor(index / 255) % 256; const d = (index % 255) + 1; return `${a}.${b}.${c}.${d}`; } // --------------------------------------------------------------------------- // Products & categories // --------------------------------------------------------------------------- const PRODUCTS = [ { id: 'prod_001', name: 'Wireless Headphones', category: 'Electronics', price: 8999, }, { id: 'prod_002', name: 'Running Shoes', category: 'Sports', price: 12_999 }, { id: 'prod_003', name: 'Coffee Maker', category: 'Kitchen', price: 5499 }, { id: 'prod_004', name: 'Yoga Mat', category: 'Sports', price: 2999 }, { id: 'prod_005', name: 'Smart Watch', category: 'Electronics', price: 29_999, }, { id: 'prod_006', name: 'Blender', category: 'Kitchen', price: 7999 }, { id: 'prod_007', name: 'Backpack', category: 'Travel', price: 4999 }, { id: 'prod_008', name: 'Sunglasses', category: 'Accessories', price: 3499 }, { id: 'prod_009', name: 'Novel: The Last Algorithm', category: 'Books', price: 1499, }, { id: 'prod_010', name: 'Standing Desk', category: 'Furniture', price: 45_999, }, ]; const CATEGORIES = [ 'Electronics', 'Sports', 'Kitchen', 'Travel', 'Accessories', 'Books', 'Furniture', ]; // --------------------------------------------------------------------------- // Groups (3 pre-defined companies) // --------------------------------------------------------------------------- const GROUPS = [ { id: 'org_acme', type: 'company', name: 'Acme Corp', properties: { plan: 'enterprise', industry: 'Technology', employees: 500 }, }, { id: 'org_globex', type: 'company', name: 'Globex Inc', properties: { plan: 'pro', industry: 'Finance', employees: 120 }, }, { id: 'org_initech', type: 'company', name: 'Initech LLC', properties: { plan: 'starter', industry: 'Consulting', employees: 45 }, }, ]; // --------------------------------------------------------------------------- // Scenarios — 20 distinct user journeys // --------------------------------------------------------------------------- /** * Each scenario returns a list of event descriptors. * screen_view events use a `path` property (origin + pathname). */ const SCENARIOS = [ // 1. Full e-commerce checkout success (product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/`, title: 'Home', referrer: 'https://google.com', }, }, { name: 'screen_view', props: { path: `${ORIGIN}/products/${product.id}`, title: product.name }, }, { name: 'product_viewed', props: { product_id: product.id, product_name: product.name, price: product.price, category: product.category, }, }, { name: 'add_to_cart', props: { product_id: product.id, product_name: product.name, price: product.price, quantity: 1, }, }, { name: 'screen_view', props: { path: `${ORIGIN}/cart`, title: 'Cart' } }, { name: 'checkout_started', props: { cart_total: product.price, item_count: 1 }, }, { name: 'screen_view', props: { path: `${ORIGIN}/checkout/shipping`, title: 'Shipping Info' }, }, { name: 'shipping_info_submitted', props: { shipping_method: 'standard', estimated_days: 5 }, }, { name: 'screen_view', props: { path: `${ORIGIN}/checkout/payment`, title: 'Payment' }, }, { name: 'payment_info_submitted', props: { payment_method: 'credit_card' }, }, { name: 'screen_view', props: { path: `${ORIGIN}/checkout/review`, title: 'Order Review' }, }, { name: 'purchase', props: { order_id: `ord_${Date.now()}`, revenue: product.price, product_id: product.id, product_name: product.name, quantity: 1, }, revenue: true, }, { name: 'screen_view', props: { path: `${ORIGIN}/checkout/success`, title: 'Order Confirmed' }, }, { name: 'checkout_success', props: { order_id: `ord_${Date.now()}`, revenue: product.price }, }, ], // 2. Checkout failed (payment declined) (product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/`, title: 'Home' } }, { name: 'screen_view', props: { path: `${ORIGIN}/products/${product.id}`, title: product.name }, }, { name: 'product_viewed', props: { product_id: product.id, product_name: product.name, price: product.price, category: product.category, }, }, { name: 'add_to_cart', props: { product_id: product.id, product_name: product.name, price: product.price, quantity: 1, }, }, { name: 'checkout_started', props: { cart_total: product.price, item_count: 1 }, }, { name: 'screen_view', props: { path: `${ORIGIN}/checkout/shipping`, title: 'Shipping Info' }, }, { name: 'shipping_info_submitted', props: { shipping_method: 'express', estimated_days: 2 }, }, { name: 'screen_view', props: { path: `${ORIGIN}/checkout/payment`, title: 'Payment' }, }, { name: 'payment_info_submitted', props: { payment_method: 'credit_card' }, }, { name: 'checkout_failed', props: { reason: 'payment_declined', error_code: 'insufficient_funds' }, }, { name: 'screen_view', props: { path: `${ORIGIN}/checkout/payment`, title: 'Payment' }, }, ], // 3. Browse only — no purchase (product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/`, title: 'Home', referrer: 'https://facebook.com', }, }, { name: 'screen_view', props: { path: `${ORIGIN}/categories/${product.category.toLowerCase()}`, title: product.category, }, }, { name: 'category_viewed', props: { category: product.category } }, { name: 'screen_view', props: { path: `${ORIGIN}/products/${product.id}`, title: product.name }, }, { name: 'product_viewed', props: { product_id: product.id, product_name: product.name, price: product.price, category: product.category, }, }, { name: 'screen_view', props: { path: `${ORIGIN}/products/${PRODUCTS[1].id}`, title: PRODUCTS[1].name, }, }, { name: 'product_viewed', props: { product_id: PRODUCTS[1].id, product_name: PRODUCTS[1].name, price: PRODUCTS[1].price, category: PRODUCTS[1].category, }, }, ], // 4. Add to cart then abandon (product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/`, title: 'Home' } }, { name: 'screen_view', props: { path: `${ORIGIN}/products/${product.id}`, title: product.name }, }, { name: 'product_viewed', props: { product_id: product.id, product_name: product.name, price: product.price, category: product.category, }, }, { name: 'add_to_cart', props: { product_id: product.id, product_name: product.name, price: product.price, quantity: 1, }, }, { name: 'screen_view', props: { path: `${ORIGIN}/cart`, title: 'Cart' } }, { name: 'cart_abandoned', props: { cart_total: product.price, item_count: 1 }, }, ], // 5. Search → product → purchase (product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/`, title: 'Home' } }, { name: 'search', props: { query: product.name.split(' ')[0], result_count: randInt(3, 20), }, }, { name: 'screen_view', props: { path: `${ORIGIN}/search?q=${encodeURIComponent(product.name)}`, title: 'Search Results', }, }, { name: 'screen_view', props: { path: `${ORIGIN}/products/${product.id}`, title: product.name }, }, { name: 'product_viewed', props: { product_id: product.id, product_name: product.name, price: product.price, category: product.category, }, }, { name: 'add_to_cart', props: { product_id: product.id, product_name: product.name, price: product.price, quantity: 1, }, }, { name: 'checkout_started', props: { cart_total: product.price, item_count: 1 }, }, { name: 'shipping_info_submitted', props: { shipping_method: 'standard', estimated_days: 5 }, }, { name: 'payment_info_submitted', props: { payment_method: 'paypal' } }, { name: 'purchase', props: { order_id: `ord_${Date.now()}`, revenue: product.price, product_id: product.id, product_name: product.name, quantity: 1, }, revenue: true, }, { name: 'checkout_success', props: { order_id: `ord_${Date.now()}`, revenue: product.price }, }, ], // 6. Sign up flow (_product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/`, title: 'Home', referrer: 'https://twitter.com', }, }, { name: 'screen_view', props: { path: `${ORIGIN}/signup`, title: 'Sign Up' }, }, { name: 'signup_started', props: {} }, { name: 'signup_step_completed', props: { step: 'email', step_number: 1 } }, { name: 'signup_step_completed', props: { step: 'password', step_number: 2 }, }, { name: 'signup_step_completed', props: { step: 'profile', step_number: 3 }, }, { name: 'signup_completed', props: { method: 'email' } }, { name: 'screen_view', props: { path: `${ORIGIN}/dashboard`, title: 'Dashboard' }, }, ], // 7. Login → browse → wishlist (product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/login`, title: 'Login' } }, { name: 'login', props: { method: 'email' } }, { name: 'screen_view', props: { path: `${ORIGIN}/`, title: 'Home' } }, { name: 'screen_view', props: { path: `${ORIGIN}/products/${product.id}`, title: product.name }, }, { name: 'product_viewed', props: { product_id: product.id, product_name: product.name, price: product.price, category: product.category, }, }, { name: 'add_to_wishlist', props: { product_id: product.id, product_name: product.name, price: product.price, }, }, { name: 'screen_view', props: { path: `${ORIGIN}/wishlist`, title: 'Wishlist' }, }, ], // 8. Promo code → purchase (product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/`, title: 'Home', referrer: 'https://newsletter.example.com', }, }, { name: 'screen_view', props: { path: `${ORIGIN}/products/${product.id}`, title: product.name }, }, { name: 'product_viewed', props: { product_id: product.id, product_name: product.name, price: product.price, category: product.category, }, }, { name: 'add_to_cart', props: { product_id: product.id, product_name: product.name, price: product.price, quantity: 1, }, }, { name: 'checkout_started', props: { cart_total: product.price, item_count: 1 }, }, { name: 'promo_code_applied', props: { code: 'SAVE20', discount_percent: 20, discount_amount: Math.round(product.price * 0.2), }, }, { name: 'shipping_info_submitted', props: { shipping_method: 'standard', estimated_days: 5 }, }, { name: 'payment_info_submitted', props: { payment_method: 'credit_card' }, }, { name: 'purchase', props: { order_id: `ord_${Date.now()}`, revenue: Math.round(product.price * 0.8), product_id: product.id, product_name: product.name, quantity: 1, promo_code: 'SAVE20', }, revenue: true, }, { name: 'checkout_success', props: { order_id: `ord_${Date.now()}`, revenue: Math.round(product.price * 0.8), }, }, ], // 9. Multi-item purchase (_product) => { const p1 = PRODUCTS[0]; const p2 = PRODUCTS[3]; const total = p1.price + p2.price; return [ { name: 'screen_view', props: { path: `${ORIGIN}/`, title: 'Home' } }, { name: 'screen_view', props: { path: `${ORIGIN}/products/${p1.id}`, title: p1.name }, }, { name: 'product_viewed', props: { product_id: p1.id, product_name: p1.name, price: p1.price, category: p1.category, }, }, { name: 'add_to_cart', props: { product_id: p1.id, product_name: p1.name, price: p1.price, quantity: 1, }, }, { name: 'screen_view', props: { path: `${ORIGIN}/products/${p2.id}`, title: p2.name }, }, { name: 'product_viewed', props: { product_id: p2.id, product_name: p2.name, price: p2.price, category: p2.category, }, }, { name: 'add_to_cart', props: { product_id: p2.id, product_name: p2.name, price: p2.price, quantity: 1, }, }, { name: 'checkout_started', props: { cart_total: total, item_count: 2 } }, { name: 'shipping_info_submitted', props: { shipping_method: 'express', estimated_days: 2 }, }, { name: 'payment_info_submitted', props: { payment_method: 'credit_card' }, }, { name: 'purchase', props: { order_id: `ord_${Date.now()}`, revenue: total, item_count: 2 }, revenue: true, }, { name: 'checkout_success', props: { order_id: `ord_${Date.now()}`, revenue: total }, }, ]; }, // 10. Help center visit (_product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/`, title: 'Home' } }, { name: 'screen_view', props: { path: `${ORIGIN}/help`, title: 'Help Center' }, }, { name: 'help_search', props: { query: 'return policy', result_count: 4 } }, { name: 'screen_view', props: { path: `${ORIGIN}/help/returns`, title: 'Return Policy' }, }, { name: 'help_article_read', props: { article: 'return_policy', time_on_page: randInt(60, 180) }, }, { name: 'screen_view', props: { path: `${ORIGIN}/help/shipping`, title: 'Shipping Info' }, }, { name: 'help_article_read', props: { article: 'shipping_times', time_on_page: randInt(30, 120) }, }, ], // 11. Product review submitted (product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/`, title: 'Home' } }, { name: 'screen_view', props: { path: `${ORIGIN}/products/${product.id}`, title: product.name }, }, { name: 'product_viewed', props: { product_id: product.id, product_name: product.name, price: product.price, category: product.category, }, }, { name: 'review_started', props: { product_id: product.id } }, { name: 'review_submitted', props: { product_id: product.id, rating: randInt(3, 5), has_text: true }, }, ], // 12. Newsletter signup only (_product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/`, title: 'Home', referrer: 'https://instagram.com', }, }, { name: 'screen_view', props: { path: `${ORIGIN}/blog`, title: 'Blog' } }, { name: 'screen_view', props: { path: `${ORIGIN}/blog/top-10-gadgets-2024`, title: 'Top 10 Gadgets 2024', }, }, { name: 'newsletter_signup', props: { source: 'blog_article', campaign: 'gadgets_2024' }, }, ], // 13. Account settings update (_product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/login`, title: 'Login' } }, { name: 'login', props: { method: 'google' } }, { name: 'screen_view', props: { path: `${ORIGIN}/account`, title: 'Account' }, }, { name: 'screen_view', props: { path: `${ORIGIN}/account/settings`, title: 'Settings' }, }, { name: 'settings_updated', props: { field: 'notification_preferences', value: 'email_only' }, }, { name: 'screen_view', props: { path: `${ORIGIN}/account/address`, title: 'Addresses' }, }, { name: 'address_added', props: { is_default: true } }, ], // 14. Referral program engagement (product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/`, title: 'Home', referrer: 'https://referral.example.com/?ref=abc123', }, }, { name: 'referral_link_clicked', props: { referrer_id: 'usr_ref123', campaign: 'summer_referral' }, }, { name: 'screen_view', props: { path: `${ORIGIN}/products/${product.id}`, title: product.name }, }, { name: 'product_viewed', props: { product_id: product.id, product_name: product.name, price: product.price, category: product.category, }, }, { name: 'add_to_cart', props: { product_id: product.id, product_name: product.name, price: product.price, quantity: 1, }, }, { name: 'checkout_started', props: { cart_total: product.price, item_count: 1 }, }, { name: 'shipping_info_submitted', props: { shipping_method: 'standard', estimated_days: 5 }, }, { name: 'payment_info_submitted', props: { payment_method: 'credit_card' }, }, { name: 'purchase', props: { order_id: `ord_${Date.now()}`, revenue: product.price, referral_code: 'abc123', }, revenue: true, }, { name: 'checkout_success', props: { order_id: `ord_${Date.now()}`, revenue: product.price }, }, ], // 15. Mobile quick browse — short session (product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/`, title: 'Home' } }, { name: 'screen_view', props: { path: `${ORIGIN}/categories/${product.category.toLowerCase()}`, title: product.category, }, }, { name: 'screen_view', props: { path: `${ORIGIN}/products/${product.id}`, title: product.name }, }, { name: 'product_viewed', props: { product_id: product.id, product_name: product.name, price: product.price, category: product.category, }, }, ], // 16. Compare products (product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/`, title: 'Home' } }, { name: 'screen_view', props: { path: `${ORIGIN}/products/${product.id}`, title: product.name }, }, { name: 'product_viewed', props: { product_id: product.id, product_name: product.name, price: product.price, category: product.category, }, }, { name: 'compare_added', props: { product_id: product.id } }, { name: 'screen_view', props: { path: `${ORIGIN}/products/${PRODUCTS[4].id}`, title: PRODUCTS[4].name, }, }, { name: 'product_viewed', props: { product_id: PRODUCTS[4].id, product_name: PRODUCTS[4].name, price: PRODUCTS[4].price, category: PRODUCTS[4].category, }, }, { name: 'compare_added', props: { product_id: PRODUCTS[4].id } }, { name: 'screen_view', props: { path: `${ORIGIN}/compare?ids=${product.id},${PRODUCTS[4].id}`, title: 'Compare Products', }, }, { name: 'compare_viewed', props: { product_ids: [product.id, PRODUCTS[4].id] }, }, ], // 17. Shipping failure retry → success (product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/`, title: 'Home' } }, { name: 'screen_view', props: { path: `${ORIGIN}/products/${product.id}`, title: product.name }, }, { name: 'product_viewed', props: { product_id: product.id, product_name: product.name, price: product.price, category: product.category, }, }, { name: 'add_to_cart', props: { product_id: product.id, product_name: product.name, price: product.price, quantity: 1, }, }, { name: 'checkout_started', props: { cart_total: product.price, item_count: 1 }, }, { name: 'screen_view', props: { path: `${ORIGIN}/checkout/shipping`, title: 'Shipping Info' }, }, { name: 'shipping_info_error', props: { error: 'invalid_address', attempt: 1 }, }, { name: 'shipping_info_submitted', props: { shipping_method: 'standard', estimated_days: 5, attempt: 2 }, }, { name: 'payment_info_submitted', props: { payment_method: 'credit_card' }, }, { name: 'purchase', props: { order_id: `ord_${Date.now()}`, revenue: product.price, product_id: product.id, }, revenue: true, }, { name: 'checkout_success', props: { order_id: `ord_${Date.now()}`, revenue: product.price }, }, ], // 18. Subscription / SaaS upgrade (_product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/pricing`, title: 'Pricing' }, }, { name: 'pricing_viewed', props: {} }, { name: 'plan_selected', props: { plan: 'pro', billing: 'annual', price: 9900 }, }, { name: 'screen_view', props: { path: `${ORIGIN}/checkout/subscription`, title: 'Subscribe' }, }, { name: 'payment_info_submitted', props: { payment_method: 'credit_card' }, }, { name: 'subscription_started', props: { plan: 'pro', billing: 'annual', revenue: 9900 }, revenue: true, }, { name: 'screen_view', props: { path: `${ORIGIN}/dashboard`, title: 'Dashboard' }, }, ], // 19. Deep content engagement (blog) (_product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/blog`, title: 'Blog', referrer: 'https://google.com', }, }, { name: 'screen_view', props: { path: `${ORIGIN}/blog/buying-guide-headphones`, title: 'Headphones Buying Guide', }, }, { name: 'content_read', props: { article: 'headphones_buying_guide', reading_time: randInt(120, 480), scroll_depth: randFloat(0.6, 1.0), }, }, { name: 'screen_view', props: { path: `${ORIGIN}/blog/best-running-shoes-2024`, title: 'Best Running Shoes 2024', }, }, { name: 'content_read', props: { article: 'best_running_shoes_2024', reading_time: randInt(90, 300), scroll_depth: randFloat(0.5, 1.0), }, }, ], // 20. Error / 404 bounce (_product) => [ { name: 'screen_view', props: { path: `${ORIGIN}/products/old-discontinued-product`, title: 'Product Not Found', }, }, { name: 'page_error', props: { error_code: 404, path: '/products/old-discontinued-product' }, }, { name: 'screen_view', props: { path: `${ORIGIN}/`, title: 'Home' } }, ], ]; // --------------------------------------------------------------------------- // Identity generation (deterministic by session index) // --------------------------------------------------------------------------- function generateIdentity(sessionIndex, sessionRng) { const firstName = pick(FIRST_NAMES, sessionRng); const lastName = pick(LAST_NAMES, sessionRng); const emailDomain = pick(EMAIL_DOMAINS, sessionRng); const profileId = `user_${String(sessionIndex + 1).padStart(4, '0')}`; return { profileId, firstName, lastName, email: `${firstName.toLowerCase()}.${lastName.toLowerCase()}${sessionIndex}@${emailDomain}`, }; } // Which sessions belong to which group (roughly 1/6 each) function getGroupForSession(sessionIndex) { if (sessionIndex % 6 === 0) { return GROUPS[0]; } if (sessionIndex % 6 === 1) { return GROUPS[1]; } if (sessionIndex % 6 === 2) { return GROUPS[2]; } return null; } // --------------------------------------------------------------------------- // HTTP helper // --------------------------------------------------------------------------- async function sendEvent(payload, ua, ip) { const headers = { 'Content-Type': 'application/json', 'user-agent': ua, 'openpanel-client-id': CLIENT_ID, 'x-forwarded-for': ip, origin: ORIGIN, }; const res = await fetch(TRACK_URL, { method: 'POST', headers, body: JSON.stringify(payload), }); if (!res.ok) { const text = await res.text().catch(() => ''); console.warn( ` [WARN] ${res.status} ${payload.type}/${payload.payload?.name ?? ''}: ${text.slice(0, 120)}` ); } return res; } // --------------------------------------------------------------------------- // Build session event list // --------------------------------------------------------------------------- function buildSession(sessionIndex) { const sessionRng = mulberry32(sessionIndex * 9973 + 1337); // deterministic per session const identity = generateIdentity(sessionIndex, sessionRng); const group = getGroupForSession(sessionIndex); const ua = makeUniqueUA(pick(USER_AGENTS, sessionRng), sessionIndex); const ip = makeIP(sessionIndex); const product = pick(PRODUCTS, sessionRng); const scenarioFn = SCENARIOS[sessionIndex % SCENARIOS.length]; const events = scenarioFn(product); return { identity, group, ua, ip, events }; } // --------------------------------------------------------------------------- // Schedule events across timeline // --------------------------------------------------------------------------- function scheduleSession(session, sessionIndex, totalSessions) { const timelineMs = TIMELINE_MINUTES * 60 * 1000; const now = Date.now(); // Sessions are spread across the timeline const sessionStartOffset = (sessionIndex / totalSessions) * timelineMs; const sessionStart = now - timelineMs + sessionStartOffset; // Events within session: spread over 2-10 minutes const sessionDurationMs = randInt(2, 10) * 60 * 1000; const eventCount = session.events.length; return session.events.map((event, i) => { const eventOffset = eventCount > 1 ? (i / (eventCount - 1)) * sessionDurationMs : 0; return { ...event, timestamp: Math.round(sessionStart + eventOffset), }; }); } // --------------------------------------------------------------------------- // Concurrency limiter // --------------------------------------------------------------------------- async function withConcurrency(tasks, limit) { const results = []; const executing = []; for (const task of tasks) { const p = Promise.resolve().then(task); results.push(p); const e = p.then(() => executing.splice(executing.indexOf(e), 1)); executing.push(e); if (executing.length >= limit) { await Promise.race(executing); } } return Promise.all(results); } // --------------------------------------------------------------------------- // Main // --------------------------------------------------------------------------- async function main() { console.log( `\nSeeding ${SESSION_COUNT} sessions over ${TIMELINE_MINUTES} minutes` ); console.log(`API: ${TRACK_URL}`); console.log(`Client ID: ${CLIENT_ID}\n`); let totalEvents = 0; let errors = 0; const sessionTasks = Array.from( { length: SESSION_COUNT }, (_, i) => async () => { const session = buildSession(i); const scheduledEvents = scheduleSession(session, i, SESSION_COUNT); const { identity, group, ua, ip } = session; // 1. Identify try { await sendEvent({ type: 'identify', payload: identity }, ua, ip); } catch (e) { errors++; console.error(` [ERROR] identify session ${i}:`, e.message); } // 2. Group (if applicable) if (group) { try { await sendEvent( { type: 'group', payload: { ...group, profileId: identity.profileId }, }, ua, ip ); } catch (e) { errors++; console.error(` [ERROR] group session ${i}:`, e.message); } } // 3. Track events in order for (const ev of scheduledEvents) { const trackPayload = { name: ev.name, profileId: identity.profileId, properties: { ...ev.props, __timestamp: new Date(ev.timestamp).toISOString(), ...(group ? { __group: group.id } : {}), }, groups: group ? [group.id] : [], }; if (ev.revenue) { trackPayload.properties.__revenue = ev.props.revenue; } try { await sendEvent({ type: 'track', payload: trackPayload }, ua, ip); totalEvents++; } catch (e) { errors++; console.error(` [ERROR] track ${ev.name} session ${i}:`, e.message); } } if ((i + 1) % 50 === 0 || i + 1 === SESSION_COUNT) { console.log(` Progress: ${i + 1}/${SESSION_COUNT} sessions`); } } ); await withConcurrency(sessionTasks, CONCURRENCY); console.log('\nDone!'); console.log(` Sessions: ${SESSION_COUNT}`); console.log(` Events sent: ${totalEvents}`); console.log(` Errors: ${errors}`); } main().catch((err) => { console.error('Fatal:', err); process.exit(1); });