@blotoutio/providers-evo-search-sdk 1.53.1 → 1.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/core.cjs.js CHANGED
@@ -1,5 +1,39 @@
1
1
  'use strict';
2
2
 
3
+ /**
4
+ * Known ad-network click ID query parameters and the human-readable provider
5
+ * label each one maps to. Used by the CDN worker to resolve `inSessionTouch`
6
+ * and by the analytics API to classify paid-media touch sessions.
7
+ *
8
+ * Keep this list in sync with `defaultParams` in queryParams.ts when new
9
+ * click ID providers are added.
10
+ */
11
+ const CLICK_IDS = [
12
+ { param: 'fbclid', label: 'Facebook' }, // Facebook Ads
13
+ { param: 'gclid', label: 'Google' }, // Google Ads (click)
14
+ { param: 'gbraid', label: 'Google' }, // Google Ads (iOS app)
15
+ { param: 'wbraid', label: 'Google' }, // Google Ads (iOS web)
16
+ { param: 'msclkid', label: 'Bing' }, // Microsoft Bing Ads
17
+ { param: 'ttclid', label: 'TikTok' }, // TikTok Ads
18
+ { param: 'ScCid', label: 'Snapchat' }, // Snapchat Ads
19
+ { param: 'epik', label: 'Pinterest' }, // Pinterest Ads
20
+ { param: 'li_fat_id', label: 'LinkedIn' }, // LinkedIn Ads
21
+ { param: 'twclid', label: 'Twitter' }, // X (Twitter) Ads
22
+ { param: 'rdt_cid', label: 'Reddit' }, // Reddit Ads
23
+ { param: 'aleid', label: 'AppLovin' }, // AppLovin
24
+ { param: 'tabclid', label: 'Taboola' }, // Taboola
25
+ { param: 'obclid', label: 'Outbrain' }, // Outbrain
26
+ { param: 'trybe', label: 'Trybe' }, // Trybe
27
+ { param: '_kx', label: 'Klaviyo' }, // Klaviyo email campaigns
28
+ { param: 'mc_eid', label: 'Mailchimp' }, // Mailchimp email campaigns
29
+ { param: 'ttd_id', label: 'The Trade Desk' }, // The Trade Desk programmatic
30
+ { param: 'evsclid', label: 'EvoSearch' }, // EvoSearch
31
+ { param: 'li_did', label: 'Live Intent' }, // LiveIntent device-level ad
32
+ { param: '_raclid', label: 'Rumble' }, // Rumble Ads
33
+ { param: 'ref_id', label: 'StackAdapt' }, // StackAdapt programmatic
34
+ { param: 'duel_a', label: 'Duel' }, // Duel referral/advocacy
35
+ ];
36
+
3
37
  const expand = (str) => str.split(',').flatMap((entry) => {
4
38
  if (!entry.includes('-')) {
5
39
  return entry;
@@ -356,8 +390,133 @@ const usStates = new Map([
356
390
  ]);
357
391
  new Set([...isoCountries.keys(), ...usStates.keys()]);
358
392
 
393
+ /**
394
+ * Exact utm_source normalization (lowercase key → canonical name).
395
+ *
396
+ * Use exact match when the key is short, generic, or must not accidentally
397
+ * absorb variant spellings (e.g. "impact" must not match "impact_radius").
398
+ *
399
+ * Ordering: Paid (search/social → ad networks → affiliates) →
400
+ * Organic (AI/discovery) → Retention (email → SMS → push → post-purchase)
401
+ */
402
+ const UTM_SOURCE_EXACT = {
403
+ // ── Paid – Search & Social ────────────────────────────────────────────────
404
+ google: 'Google',
405
+ adwords: 'Google',
406
+ youtube: 'Google',
407
+ yt: 'Google',
408
+ meta: 'Facebook',
409
+ facebook: 'Facebook',
410
+ instagram: 'Facebook',
411
+ ig: 'Facebook',
412
+ igshopping: 'Facebook',
413
+ threads: 'Facebook',
414
+ twitter: 'Twitter',
415
+ snapchat: 'Snapchat',
416
+ pinterest: 'Pinterest',
417
+ bing: 'Bing',
418
+ microsoft: 'Bing',
419
+ tiktok: 'TikTok',
420
+ // ── Paid – Ad Networks ───────────────────────────────────────────────────
421
+ rtbhouse: 'RTB House',
422
+ applovin: 'AppLovin',
423
+ ttd: 'The Trade Desk',
424
+ amazondsp: 'Amazon DSP',
425
+ axon: 'Axon',
426
+ duel: 'Duel',
427
+ // ── Paid – Affiliates ────────────────────────────────────────────────────
428
+ awin: 'Awin',
429
+ 'affiliate-cj': 'CJ Affiliate',
430
+ impact: 'Impact',
431
+ rakuten: 'Rakuten',
432
+ superfiliate: 'Superfiliate',
433
+ // ── Organic – AI & Discovery ─────────────────────────────────────────────
434
+ perplexity: 'Perplexity',
435
+ chatgpt: 'ChatGPT',
436
+ 'chatgpt.com': 'ChatGPT',
437
+ openai: 'ChatGPT',
438
+ 'copilot.com': 'Microsoft Copilot',
439
+ copilot: 'Microsoft Copilot',
440
+ applenews: 'Apple News',
441
+ whatsapp: 'WhatsApp',
442
+ podcast: 'Podcast',
443
+ // ── Retention – Email ────────────────────────────────────────────────────
444
+ mailchimp: 'Mailchimp',
445
+ omnisend: 'Omnisend',
446
+ iterable: 'Iterable',
447
+ listrak: 'Listrak',
448
+ sailthru: 'Sailthru',
449
+ // ── Retention – Push ─────────────────────────────────────────────────────
450
+ pushowl: 'PushOwl',
451
+ // ── Retention – Post-purchase / Payment ──────────────────────────────────
452
+ narvar: 'Narvar',
453
+ shop_app: 'Shop App',
454
+ salesforce: 'Salesforce',
455
+ yotpo: 'Yotpo',
456
+ };
457
+ /**
458
+ * Partial utm_source normalization (startsWith prefix check, lowercase).
459
+ *
460
+ * Checked after exact match so short exact keys (e.g. "ig", "yt") win first.
461
+ */
462
+ const UTM_SOURCE_PARTIAL = [
463
+ // ── Paid – Social ─────────────────────────────────────────────────────────
464
+ { prefix: 'tiktok', name: 'TikTok' },
465
+ // ── Paid – Ad Networks ───────────────────────────────────────────────────
466
+ { prefix: 'criteo', name: 'Criteo' },
467
+ // ── Retention – Email ────────────────────────────────────────────────────
468
+ { prefix: 'klaviyo', name: 'Klaviyo' },
469
+ // ── Retention – SMS ──────────────────────────────────────────────────────
470
+ { prefix: 'attentive', name: 'Attentive' },
471
+ { prefix: 'postscript', name: 'Postscript' },
472
+ // ── Retention – Post-purchase / Payment ──────────────────────────────────
473
+ { prefix: 'afterpay', name: 'Afterpay' },
474
+ { prefix: 'klarna', name: 'Klarna' },
475
+ ];
476
+ const ORGANIC_SEARCH_ENGINES = [
477
+ { match: 'google', name: 'Google' },
478
+ { match: 'bing', name: 'Bing' },
479
+ { match: 'yahoo', name: 'Yahoo' },
480
+ { match: 'duckduckgo', name: 'DuckDuckGo' },
481
+ { match: 'baidu', name: 'Baidu' },
482
+ { match: 'yandex', name: 'Yandex' },
483
+ { match: 'brave', name: 'Brave' },
484
+ ];
485
+ const KNOWN_REFERRAL_PLATFORMS = [
486
+ { match: 'facebook', name: 'Facebook' },
487
+ { match: 'instagram', name: 'Facebook' },
488
+ { match: 'twitter', name: 'Twitter' },
489
+ { match: 'x.com', name: 'Twitter' },
490
+ { match: 'tiktok', name: 'TikTok' },
491
+ { match: 'pinterest', name: 'Pinterest' },
492
+ { match: 'linkedin', name: 'LinkedIn' },
493
+ { match: 'youtube', name: 'YouTube' },
494
+ { match: 'chatgpt', name: 'ChatGPT' },
495
+ { match: 'claude', name: 'Claude' },
496
+ { match: 'perplexity', name: 'Perplexity' },
497
+ { match: 'shop.app', name: 'Shop App' },
498
+ { match: 'amazon', name: 'Amazon' },
499
+ { match: 'attentive', name: 'Attentive' },
500
+ ];
501
+ const OFFLINE_TOUCH = 'Blotout_Offline';
502
+ new Set([
503
+ ...CLICK_IDS.map((c) => c.label),
504
+ ...Object.values(UTM_SOURCE_EXACT),
505
+ ...UTM_SOURCE_PARTIAL.map((e) => e.name),
506
+ ...ORGANIC_SEARCH_ENGINES.map((e) => `Organic Search - ${e.name}`),
507
+ ...KNOWN_REFERRAL_PLATFORMS.map((e) => `Referral - ${e.name}`),
508
+ 'Referral - Other',
509
+ 'Direct Traffic',
510
+ 'Other',
511
+ OFFLINE_TOUCH,
512
+ ]);
513
+
359
514
  const uiActions = new Set([
360
515
  'evoSearchProductRecommendationClicked',
516
+ 'evoSearchProductClicked',
517
+ 'evoSearchProductAddedToCart',
518
+ 'evoSearchProductPurchased',
519
+ 'evoSearchSearchIssued',
361
520
  ]);
362
521
  new Set([...uiActions]);
363
522
 
@@ -515,7 +674,7 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
515
674
  throw new Error(`Search failed - ${response.status}`);
516
675
  }
517
676
  const data = (await response.json());
518
- logger.info(`EvoSearch API: Search success - ${data.length} products`);
677
+ logger.info(`EvoSearch API: Search success - ${data.results.length} products`);
519
678
  return data;
520
679
  };
521
680
  const sendEvent = (action, currency, actionData, clickData, beacon) => {
package/core.js CHANGED
@@ -1,6 +1,40 @@
1
1
  var ProvidersEvoSearchSdk = (function () {
2
2
  'use strict';
3
3
 
4
+ /**
5
+ * Known ad-network click ID query parameters and the human-readable provider
6
+ * label each one maps to. Used by the CDN worker to resolve `inSessionTouch`
7
+ * and by the analytics API to classify paid-media touch sessions.
8
+ *
9
+ * Keep this list in sync with `defaultParams` in queryParams.ts when new
10
+ * click ID providers are added.
11
+ */
12
+ const CLICK_IDS = [
13
+ { param: 'fbclid', label: 'Facebook' }, // Facebook Ads
14
+ { param: 'gclid', label: 'Google' }, // Google Ads (click)
15
+ { param: 'gbraid', label: 'Google' }, // Google Ads (iOS app)
16
+ { param: 'wbraid', label: 'Google' }, // Google Ads (iOS web)
17
+ { param: 'msclkid', label: 'Bing' }, // Microsoft Bing Ads
18
+ { param: 'ttclid', label: 'TikTok' }, // TikTok Ads
19
+ { param: 'ScCid', label: 'Snapchat' }, // Snapchat Ads
20
+ { param: 'epik', label: 'Pinterest' }, // Pinterest Ads
21
+ { param: 'li_fat_id', label: 'LinkedIn' }, // LinkedIn Ads
22
+ { param: 'twclid', label: 'Twitter' }, // X (Twitter) Ads
23
+ { param: 'rdt_cid', label: 'Reddit' }, // Reddit Ads
24
+ { param: 'aleid', label: 'AppLovin' }, // AppLovin
25
+ { param: 'tabclid', label: 'Taboola' }, // Taboola
26
+ { param: 'obclid', label: 'Outbrain' }, // Outbrain
27
+ { param: 'trybe', label: 'Trybe' }, // Trybe
28
+ { param: '_kx', label: 'Klaviyo' }, // Klaviyo email campaigns
29
+ { param: 'mc_eid', label: 'Mailchimp' }, // Mailchimp email campaigns
30
+ { param: 'ttd_id', label: 'The Trade Desk' }, // The Trade Desk programmatic
31
+ { param: 'evsclid', label: 'EvoSearch' }, // EvoSearch
32
+ { param: 'li_did', label: 'Live Intent' }, // LiveIntent device-level ad
33
+ { param: '_raclid', label: 'Rumble' }, // Rumble Ads
34
+ { param: 'ref_id', label: 'StackAdapt' }, // StackAdapt programmatic
35
+ { param: 'duel_a', label: 'Duel' }, // Duel referral/advocacy
36
+ ];
37
+
4
38
  const expand = (str) => str.split(',').flatMap((entry) => {
5
39
  if (!entry.includes('-')) {
6
40
  return entry;
@@ -357,8 +391,133 @@ var ProvidersEvoSearchSdk = (function () {
357
391
  ]);
358
392
  new Set([...isoCountries.keys(), ...usStates.keys()]);
359
393
 
394
+ /**
395
+ * Exact utm_source normalization (lowercase key → canonical name).
396
+ *
397
+ * Use exact match when the key is short, generic, or must not accidentally
398
+ * absorb variant spellings (e.g. "impact" must not match "impact_radius").
399
+ *
400
+ * Ordering: Paid (search/social → ad networks → affiliates) →
401
+ * Organic (AI/discovery) → Retention (email → SMS → push → post-purchase)
402
+ */
403
+ const UTM_SOURCE_EXACT = {
404
+ // ── Paid – Search & Social ────────────────────────────────────────────────
405
+ google: 'Google',
406
+ adwords: 'Google',
407
+ youtube: 'Google',
408
+ yt: 'Google',
409
+ meta: 'Facebook',
410
+ facebook: 'Facebook',
411
+ instagram: 'Facebook',
412
+ ig: 'Facebook',
413
+ igshopping: 'Facebook',
414
+ threads: 'Facebook',
415
+ twitter: 'Twitter',
416
+ snapchat: 'Snapchat',
417
+ pinterest: 'Pinterest',
418
+ bing: 'Bing',
419
+ microsoft: 'Bing',
420
+ tiktok: 'TikTok',
421
+ // ── Paid – Ad Networks ───────────────────────────────────────────────────
422
+ rtbhouse: 'RTB House',
423
+ applovin: 'AppLovin',
424
+ ttd: 'The Trade Desk',
425
+ amazondsp: 'Amazon DSP',
426
+ axon: 'Axon',
427
+ duel: 'Duel',
428
+ // ── Paid – Affiliates ────────────────────────────────────────────────────
429
+ awin: 'Awin',
430
+ 'affiliate-cj': 'CJ Affiliate',
431
+ impact: 'Impact',
432
+ rakuten: 'Rakuten',
433
+ superfiliate: 'Superfiliate',
434
+ // ── Organic – AI & Discovery ─────────────────────────────────────────────
435
+ perplexity: 'Perplexity',
436
+ chatgpt: 'ChatGPT',
437
+ 'chatgpt.com': 'ChatGPT',
438
+ openai: 'ChatGPT',
439
+ 'copilot.com': 'Microsoft Copilot',
440
+ copilot: 'Microsoft Copilot',
441
+ applenews: 'Apple News',
442
+ whatsapp: 'WhatsApp',
443
+ podcast: 'Podcast',
444
+ // ── Retention – Email ────────────────────────────────────────────────────
445
+ mailchimp: 'Mailchimp',
446
+ omnisend: 'Omnisend',
447
+ iterable: 'Iterable',
448
+ listrak: 'Listrak',
449
+ sailthru: 'Sailthru',
450
+ // ── Retention – Push ─────────────────────────────────────────────────────
451
+ pushowl: 'PushOwl',
452
+ // ── Retention – Post-purchase / Payment ──────────────────────────────────
453
+ narvar: 'Narvar',
454
+ shop_app: 'Shop App',
455
+ salesforce: 'Salesforce',
456
+ yotpo: 'Yotpo',
457
+ };
458
+ /**
459
+ * Partial utm_source normalization (startsWith prefix check, lowercase).
460
+ *
461
+ * Checked after exact match so short exact keys (e.g. "ig", "yt") win first.
462
+ */
463
+ const UTM_SOURCE_PARTIAL = [
464
+ // ── Paid – Social ─────────────────────────────────────────────────────────
465
+ { prefix: 'tiktok', name: 'TikTok' },
466
+ // ── Paid – Ad Networks ───────────────────────────────────────────────────
467
+ { prefix: 'criteo', name: 'Criteo' },
468
+ // ── Retention – Email ────────────────────────────────────────────────────
469
+ { prefix: 'klaviyo', name: 'Klaviyo' },
470
+ // ── Retention – SMS ──────────────────────────────────────────────────────
471
+ { prefix: 'attentive', name: 'Attentive' },
472
+ { prefix: 'postscript', name: 'Postscript' },
473
+ // ── Retention – Post-purchase / Payment ──────────────────────────────────
474
+ { prefix: 'afterpay', name: 'Afterpay' },
475
+ { prefix: 'klarna', name: 'Klarna' },
476
+ ];
477
+ const ORGANIC_SEARCH_ENGINES = [
478
+ { match: 'google', name: 'Google' },
479
+ { match: 'bing', name: 'Bing' },
480
+ { match: 'yahoo', name: 'Yahoo' },
481
+ { match: 'duckduckgo', name: 'DuckDuckGo' },
482
+ { match: 'baidu', name: 'Baidu' },
483
+ { match: 'yandex', name: 'Yandex' },
484
+ { match: 'brave', name: 'Brave' },
485
+ ];
486
+ const KNOWN_REFERRAL_PLATFORMS = [
487
+ { match: 'facebook', name: 'Facebook' },
488
+ { match: 'instagram', name: 'Facebook' },
489
+ { match: 'twitter', name: 'Twitter' },
490
+ { match: 'x.com', name: 'Twitter' },
491
+ { match: 'tiktok', name: 'TikTok' },
492
+ { match: 'pinterest', name: 'Pinterest' },
493
+ { match: 'linkedin', name: 'LinkedIn' },
494
+ { match: 'youtube', name: 'YouTube' },
495
+ { match: 'chatgpt', name: 'ChatGPT' },
496
+ { match: 'claude', name: 'Claude' },
497
+ { match: 'perplexity', name: 'Perplexity' },
498
+ { match: 'shop.app', name: 'Shop App' },
499
+ { match: 'amazon', name: 'Amazon' },
500
+ { match: 'attentive', name: 'Attentive' },
501
+ ];
502
+ const OFFLINE_TOUCH = 'Blotout_Offline';
503
+ new Set([
504
+ ...CLICK_IDS.map((c) => c.label),
505
+ ...Object.values(UTM_SOURCE_EXACT),
506
+ ...UTM_SOURCE_PARTIAL.map((e) => e.name),
507
+ ...ORGANIC_SEARCH_ENGINES.map((e) => `Organic Search - ${e.name}`),
508
+ ...KNOWN_REFERRAL_PLATFORMS.map((e) => `Referral - ${e.name}`),
509
+ 'Referral - Other',
510
+ 'Direct Traffic',
511
+ 'Other',
512
+ OFFLINE_TOUCH,
513
+ ]);
514
+
360
515
  const uiActions = new Set([
361
516
  'evoSearchProductRecommendationClicked',
517
+ 'evoSearchProductClicked',
518
+ 'evoSearchProductAddedToCart',
519
+ 'evoSearchProductPurchased',
520
+ 'evoSearchSearchIssued',
362
521
  ]);
363
522
  new Set([...uiActions]);
364
523
 
@@ -516,7 +675,7 @@ var ProvidersEvoSearchSdk = (function () {
516
675
  throw new Error(`Search failed - ${response.status}`);
517
676
  }
518
677
  const data = (await response.json());
519
- logger.info(`EvoSearch API: Search success - ${data.length} products`);
678
+ logger.info(`EvoSearch API: Search success - ${data.results.length} products`);
520
679
  return data;
521
680
  };
522
681
  const sendEvent = (action, currency, actionData, clickData, beacon) => {
package/core.mjs CHANGED
@@ -1,3 +1,37 @@
1
+ /**
2
+ * Known ad-network click ID query parameters and the human-readable provider
3
+ * label each one maps to. Used by the CDN worker to resolve `inSessionTouch`
4
+ * and by the analytics API to classify paid-media touch sessions.
5
+ *
6
+ * Keep this list in sync with `defaultParams` in queryParams.ts when new
7
+ * click ID providers are added.
8
+ */
9
+ const CLICK_IDS = [
10
+ { param: 'fbclid', label: 'Facebook' }, // Facebook Ads
11
+ { param: 'gclid', label: 'Google' }, // Google Ads (click)
12
+ { param: 'gbraid', label: 'Google' }, // Google Ads (iOS app)
13
+ { param: 'wbraid', label: 'Google' }, // Google Ads (iOS web)
14
+ { param: 'msclkid', label: 'Bing' }, // Microsoft Bing Ads
15
+ { param: 'ttclid', label: 'TikTok' }, // TikTok Ads
16
+ { param: 'ScCid', label: 'Snapchat' }, // Snapchat Ads
17
+ { param: 'epik', label: 'Pinterest' }, // Pinterest Ads
18
+ { param: 'li_fat_id', label: 'LinkedIn' }, // LinkedIn Ads
19
+ { param: 'twclid', label: 'Twitter' }, // X (Twitter) Ads
20
+ { param: 'rdt_cid', label: 'Reddit' }, // Reddit Ads
21
+ { param: 'aleid', label: 'AppLovin' }, // AppLovin
22
+ { param: 'tabclid', label: 'Taboola' }, // Taboola
23
+ { param: 'obclid', label: 'Outbrain' }, // Outbrain
24
+ { param: 'trybe', label: 'Trybe' }, // Trybe
25
+ { param: '_kx', label: 'Klaviyo' }, // Klaviyo email campaigns
26
+ { param: 'mc_eid', label: 'Mailchimp' }, // Mailchimp email campaigns
27
+ { param: 'ttd_id', label: 'The Trade Desk' }, // The Trade Desk programmatic
28
+ { param: 'evsclid', label: 'EvoSearch' }, // EvoSearch
29
+ { param: 'li_did', label: 'Live Intent' }, // LiveIntent device-level ad
30
+ { param: '_raclid', label: 'Rumble' }, // Rumble Ads
31
+ { param: 'ref_id', label: 'StackAdapt' }, // StackAdapt programmatic
32
+ { param: 'duel_a', label: 'Duel' }, // Duel referral/advocacy
33
+ ];
34
+
1
35
  const expand = (str) => str.split(',').flatMap((entry) => {
2
36
  if (!entry.includes('-')) {
3
37
  return entry;
@@ -354,8 +388,133 @@ const usStates = new Map([
354
388
  ]);
355
389
  new Set([...isoCountries.keys(), ...usStates.keys()]);
356
390
 
391
+ /**
392
+ * Exact utm_source normalization (lowercase key → canonical name).
393
+ *
394
+ * Use exact match when the key is short, generic, or must not accidentally
395
+ * absorb variant spellings (e.g. "impact" must not match "impact_radius").
396
+ *
397
+ * Ordering: Paid (search/social → ad networks → affiliates) →
398
+ * Organic (AI/discovery) → Retention (email → SMS → push → post-purchase)
399
+ */
400
+ const UTM_SOURCE_EXACT = {
401
+ // ── Paid – Search & Social ────────────────────────────────────────────────
402
+ google: 'Google',
403
+ adwords: 'Google',
404
+ youtube: 'Google',
405
+ yt: 'Google',
406
+ meta: 'Facebook',
407
+ facebook: 'Facebook',
408
+ instagram: 'Facebook',
409
+ ig: 'Facebook',
410
+ igshopping: 'Facebook',
411
+ threads: 'Facebook',
412
+ twitter: 'Twitter',
413
+ snapchat: 'Snapchat',
414
+ pinterest: 'Pinterest',
415
+ bing: 'Bing',
416
+ microsoft: 'Bing',
417
+ tiktok: 'TikTok',
418
+ // ── Paid – Ad Networks ───────────────────────────────────────────────────
419
+ rtbhouse: 'RTB House',
420
+ applovin: 'AppLovin',
421
+ ttd: 'The Trade Desk',
422
+ amazondsp: 'Amazon DSP',
423
+ axon: 'Axon',
424
+ duel: 'Duel',
425
+ // ── Paid – Affiliates ────────────────────────────────────────────────────
426
+ awin: 'Awin',
427
+ 'affiliate-cj': 'CJ Affiliate',
428
+ impact: 'Impact',
429
+ rakuten: 'Rakuten',
430
+ superfiliate: 'Superfiliate',
431
+ // ── Organic – AI & Discovery ─────────────────────────────────────────────
432
+ perplexity: 'Perplexity',
433
+ chatgpt: 'ChatGPT',
434
+ 'chatgpt.com': 'ChatGPT',
435
+ openai: 'ChatGPT',
436
+ 'copilot.com': 'Microsoft Copilot',
437
+ copilot: 'Microsoft Copilot',
438
+ applenews: 'Apple News',
439
+ whatsapp: 'WhatsApp',
440
+ podcast: 'Podcast',
441
+ // ── Retention – Email ────────────────────────────────────────────────────
442
+ mailchimp: 'Mailchimp',
443
+ omnisend: 'Omnisend',
444
+ iterable: 'Iterable',
445
+ listrak: 'Listrak',
446
+ sailthru: 'Sailthru',
447
+ // ── Retention – Push ─────────────────────────────────────────────────────
448
+ pushowl: 'PushOwl',
449
+ // ── Retention – Post-purchase / Payment ──────────────────────────────────
450
+ narvar: 'Narvar',
451
+ shop_app: 'Shop App',
452
+ salesforce: 'Salesforce',
453
+ yotpo: 'Yotpo',
454
+ };
455
+ /**
456
+ * Partial utm_source normalization (startsWith prefix check, lowercase).
457
+ *
458
+ * Checked after exact match so short exact keys (e.g. "ig", "yt") win first.
459
+ */
460
+ const UTM_SOURCE_PARTIAL = [
461
+ // ── Paid – Social ─────────────────────────────────────────────────────────
462
+ { prefix: 'tiktok', name: 'TikTok' },
463
+ // ── Paid – Ad Networks ───────────────────────────────────────────────────
464
+ { prefix: 'criteo', name: 'Criteo' },
465
+ // ── Retention – Email ────────────────────────────────────────────────────
466
+ { prefix: 'klaviyo', name: 'Klaviyo' },
467
+ // ── Retention – SMS ──────────────────────────────────────────────────────
468
+ { prefix: 'attentive', name: 'Attentive' },
469
+ { prefix: 'postscript', name: 'Postscript' },
470
+ // ── Retention – Post-purchase / Payment ──────────────────────────────────
471
+ { prefix: 'afterpay', name: 'Afterpay' },
472
+ { prefix: 'klarna', name: 'Klarna' },
473
+ ];
474
+ const ORGANIC_SEARCH_ENGINES = [
475
+ { match: 'google', name: 'Google' },
476
+ { match: 'bing', name: 'Bing' },
477
+ { match: 'yahoo', name: 'Yahoo' },
478
+ { match: 'duckduckgo', name: 'DuckDuckGo' },
479
+ { match: 'baidu', name: 'Baidu' },
480
+ { match: 'yandex', name: 'Yandex' },
481
+ { match: 'brave', name: 'Brave' },
482
+ ];
483
+ const KNOWN_REFERRAL_PLATFORMS = [
484
+ { match: 'facebook', name: 'Facebook' },
485
+ { match: 'instagram', name: 'Facebook' },
486
+ { match: 'twitter', name: 'Twitter' },
487
+ { match: 'x.com', name: 'Twitter' },
488
+ { match: 'tiktok', name: 'TikTok' },
489
+ { match: 'pinterest', name: 'Pinterest' },
490
+ { match: 'linkedin', name: 'LinkedIn' },
491
+ { match: 'youtube', name: 'YouTube' },
492
+ { match: 'chatgpt', name: 'ChatGPT' },
493
+ { match: 'claude', name: 'Claude' },
494
+ { match: 'perplexity', name: 'Perplexity' },
495
+ { match: 'shop.app', name: 'Shop App' },
496
+ { match: 'amazon', name: 'Amazon' },
497
+ { match: 'attentive', name: 'Attentive' },
498
+ ];
499
+ const OFFLINE_TOUCH = 'Blotout_Offline';
500
+ new Set([
501
+ ...CLICK_IDS.map((c) => c.label),
502
+ ...Object.values(UTM_SOURCE_EXACT),
503
+ ...UTM_SOURCE_PARTIAL.map((e) => e.name),
504
+ ...ORGANIC_SEARCH_ENGINES.map((e) => `Organic Search - ${e.name}`),
505
+ ...KNOWN_REFERRAL_PLATFORMS.map((e) => `Referral - ${e.name}`),
506
+ 'Referral - Other',
507
+ 'Direct Traffic',
508
+ 'Other',
509
+ OFFLINE_TOUCH,
510
+ ]);
511
+
357
512
  const uiActions = new Set([
358
513
  'evoSearchProductRecommendationClicked',
514
+ 'evoSearchProductClicked',
515
+ 'evoSearchProductAddedToCart',
516
+ 'evoSearchProductPurchased',
517
+ 'evoSearchSearchIssued',
359
518
  ]);
360
519
  new Set([...uiActions]);
361
520
 
@@ -513,7 +672,7 @@ const createEvoSearchAPI = ({ fetch: fetchImpl = window.fetch, baseURL, userId,
513
672
  throw new Error(`Search failed - ${response.status}`);
514
673
  }
515
674
  const data = (await response.json());
516
- logger.info(`EvoSearch API: Search success - ${data.length} products`);
675
+ logger.info(`EvoSearch API: Search success - ${data.results.length} products`);
517
676
  return data;
518
677
  };
519
678
  const sendEvent = (action, currency, actionData, clickData, beacon) => {