@shware/analytics 11.11.1 → 11.12.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/dist/attribution/vocabulary.cjs +89 -0
- package/dist/attribution/vocabulary.cjs.map +1 -1
- package/dist/attribution/vocabulary.d.cts +12 -5
- package/dist/attribution/vocabulary.d.cts.map +1 -1
- package/dist/attribution/vocabulary.d.mts +12 -5
- package/dist/attribution/vocabulary.d.mts.map +1 -1
- package/dist/attribution/vocabulary.mjs +89 -0
- package/dist/attribution/vocabulary.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -31,10 +31,23 @@ const CHANNELS = [
|
|
|
31
31
|
"ecosia",
|
|
32
32
|
"naver",
|
|
33
33
|
"brave",
|
|
34
|
+
"qwant",
|
|
34
35
|
"pinterest",
|
|
35
36
|
"youtube",
|
|
36
37
|
"telegram",
|
|
37
38
|
"gmail",
|
|
39
|
+
"outlook",
|
|
40
|
+
"yahoo_mail",
|
|
41
|
+
"yandex_mail",
|
|
42
|
+
"naver_mail",
|
|
43
|
+
"qq_mail",
|
|
44
|
+
"netease_mail",
|
|
45
|
+
"proton_mail",
|
|
46
|
+
"seznam_mail",
|
|
47
|
+
"gmx",
|
|
48
|
+
"aol_mail",
|
|
49
|
+
"zoho_mail",
|
|
50
|
+
"email",
|
|
38
51
|
"chatgpt",
|
|
39
52
|
"perplexity",
|
|
40
53
|
"gemini",
|
|
@@ -279,6 +292,66 @@ const REFERRER_SITES = [
|
|
|
279
292
|
"email",
|
|
280
293
|
/^mail\.google\.com$/
|
|
281
294
|
],
|
|
295
|
+
[
|
|
296
|
+
"outlook",
|
|
297
|
+
"email",
|
|
298
|
+
/^outlook\.(live|office|office365)\.com$|^outlook\.cloud\.microsoft$|(^|\.)safelinks\.protection\.outlook\.com$/
|
|
299
|
+
],
|
|
300
|
+
[
|
|
301
|
+
"yahoo_mail",
|
|
302
|
+
"email",
|
|
303
|
+
/^mail\.yahoo\.[a-z]{2,}(\.[a-z]{2,3})?$/
|
|
304
|
+
],
|
|
305
|
+
[
|
|
306
|
+
"yandex_mail",
|
|
307
|
+
"email",
|
|
308
|
+
/^mail\.yandex\.[a-z]{2,}(\.[a-z]{2,3})?$/
|
|
309
|
+
],
|
|
310
|
+
[
|
|
311
|
+
"naver_mail",
|
|
312
|
+
"email",
|
|
313
|
+
/^mail\.naver\.com$/
|
|
314
|
+
],
|
|
315
|
+
[
|
|
316
|
+
"qq_mail",
|
|
317
|
+
"email",
|
|
318
|
+
/^(wx\.)?mail\.qq\.com$|^exmail\.qq\.com$/
|
|
319
|
+
],
|
|
320
|
+
[
|
|
321
|
+
"netease_mail",
|
|
322
|
+
"email",
|
|
323
|
+
/^mail\.(163|126)\.com$/
|
|
324
|
+
],
|
|
325
|
+
[
|
|
326
|
+
"proton_mail",
|
|
327
|
+
"email",
|
|
328
|
+
/^mail\.proton(mail)?\.(me|com)$/
|
|
329
|
+
],
|
|
330
|
+
[
|
|
331
|
+
"seznam_mail",
|
|
332
|
+
"email",
|
|
333
|
+
/^email\.seznam\.cz$/
|
|
334
|
+
],
|
|
335
|
+
[
|
|
336
|
+
"gmx",
|
|
337
|
+
"email",
|
|
338
|
+
/^deref-(gmx|web|mail)(-\d+)?\.(net|de|com)$|^navigator\.(gmx\.net|web\.de)$/
|
|
339
|
+
],
|
|
340
|
+
[
|
|
341
|
+
"aol_mail",
|
|
342
|
+
"email",
|
|
343
|
+
/^mail\.aol\.com$/
|
|
344
|
+
],
|
|
345
|
+
[
|
|
346
|
+
"zoho_mail",
|
|
347
|
+
"email",
|
|
348
|
+
/^mail\.zoho\.[a-z]{2,}(\.[a-z]{2,3})?$/
|
|
349
|
+
],
|
|
350
|
+
[
|
|
351
|
+
"email",
|
|
352
|
+
"email",
|
|
353
|
+
/^urldefense\.(com|proofpoint\.com)$|^protect(-[a-z]+)?\.mimecast\.com$|(^|\.)mimecastprotect\.com$|^linkprotect\.cudasvc\.com$|^url\.emailprotection\.link$/
|
|
354
|
+
],
|
|
282
355
|
[
|
|
283
356
|
"google",
|
|
284
357
|
"organic",
|
|
@@ -324,6 +397,11 @@ const REFERRER_SITES = [
|
|
|
324
397
|
"organic",
|
|
325
398
|
/^search\.brave\.com$/
|
|
326
399
|
],
|
|
400
|
+
[
|
|
401
|
+
"qwant",
|
|
402
|
+
"organic",
|
|
403
|
+
/(^|\.)qwant\.com$/
|
|
404
|
+
],
|
|
327
405
|
[
|
|
328
406
|
"meta",
|
|
329
407
|
"social",
|
|
@@ -380,6 +458,11 @@ const REFERRER_SITES = [
|
|
|
380
458
|
const ANDROID_APP_HOSTS = {
|
|
381
459
|
"com.google.android.googlequicksearchbox": "www.google.com",
|
|
382
460
|
"com.google.android.gm": "mail.google.com",
|
|
461
|
+
"com.microsoft.office.outlook": "outlook.live.com",
|
|
462
|
+
"com.yahoo.mobile.client.android.mail": "mail.yahoo.com",
|
|
463
|
+
"ru.yandex.mail": "mail.yandex.ru",
|
|
464
|
+
"com.tencent.androidqqmail": "mail.qq.com",
|
|
465
|
+
"ch.protonmail.android": "mail.proton.me",
|
|
383
466
|
"com.google.android.youtube": "www.youtube.com",
|
|
384
467
|
"com.facebook.katana": "www.facebook.com",
|
|
385
468
|
"com.instagram.android": "www.instagram.com",
|
|
@@ -395,6 +478,7 @@ const ANDROID_APP_HOSTS = {
|
|
|
395
478
|
/**
|
|
396
479
|
* Referrer hosts that are navigation, not acquisition, when a session happens to start on the way
|
|
397
480
|
* back from them: the local dev host; the payment providers a visitor is bounced through; the
|
|
481
|
+
* bot challenges (Cloudflare Turnstile, hCaptcha) a visitor passes before the page; the
|
|
398
482
|
* sign-in providers whose login lives on a host of its own (Google, Apple, Microsoft, WeChat,
|
|
399
483
|
* Kakao, LINE, Naver, Yahoo, Twitch, X's OAuth 1.0a endpoint); and the hosted auth services a
|
|
400
484
|
* product may sit behind (Auth0, Okta, Supabase, Firebase, Clerk). A product appends its own
|
|
@@ -406,6 +490,7 @@ const ANDROID_APP_HOSTS = {
|
|
|
406
490
|
*/
|
|
407
491
|
const REFERRERS_NOT_A_TOUCH = [
|
|
408
492
|
/^localhost(:|$)/,
|
|
493
|
+
/^127\.0\.0\.1$/,
|
|
409
494
|
/(^|\.)stripe\.com$/,
|
|
410
495
|
/(^|\.)link\.com$/,
|
|
411
496
|
/^pay\.google\.com$/,
|
|
@@ -413,6 +498,10 @@ const REFERRERS_NOT_A_TOUCH = [
|
|
|
413
498
|
/(^|\.)alipay\.com$/,
|
|
414
499
|
/(^|\.)paddle\.com$/,
|
|
415
500
|
/(^|\.)lemonsqueezy\.com$/,
|
|
501
|
+
/^pay\.amazon\.com$/,
|
|
502
|
+
/^pay\.weixin\.qq\.com$/,
|
|
503
|
+
/^challenges\.cloudflare\.com$/,
|
|
504
|
+
/(^|\.)hcaptcha\.com$/,
|
|
416
505
|
/^apps\.apple\.com$/,
|
|
417
506
|
/^play\.google\.com$/,
|
|
418
507
|
/^accounts\.google\.com$/,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vocabulary.cjs","names":[],"sources":["../../src/attribution/vocabulary.ts"],"sourcesContent":["import type { AdvertisingInfo } from '../track/types';\n\n/**\n * The vocabulary attribution is built from, shared by every product's `session` table and by the\n * dashboards that read it. Data for `classifyTouch` (./classify), which turns it into a channel;\n * a product's dashboards read the same names back. What stays with the product is its own hostnames\n * (referrer exclusions) and its landing-page conventions.\n */\n\n/** A session that arrived through nothing we can name. GA's spelling, so dashboards read as GA. */\nexport const DIRECT_CHANNEL = '(direct)';\n/** The medium of a direct session. */\nexport const NO_MEDIUM = '(none)';\n/** The medium of a tagged link that named a source but no medium. */\nexport const MEDIUM_NOT_SET = '(not set)';\n\n/** The channels the rules below can name. A `utm_source` outside this list is its own channel. */\nexport const CHANNELS = [\n 'meta',\n 'google',\n 'dv360',\n 'microsoft',\n 'tiktok',\n 'reddit',\n 'linkedin',\n 'snapchat',\n 'x',\n 'kakao',\n 'yandex',\n 'yahoo',\n 'duckduckgo',\n 'baidu',\n 'ecosia',\n 'naver',\n 'brave',\n 'pinterest',\n 'youtube',\n 'telegram',\n /** Gmail's web and Android app: a link in a mail that carried no utm. */\n 'gmail',\n /** AI assistants that send people to sites: ChatGPT tags its links `utm_source=chatgpt.com`, the\n * others show as a referrer. Their own channel group, `organic_ai`, since GA4's default groups\n * would file them under Referral / Unassigned and the question \"how much does AI bring\" comes up. */\n 'chatgpt',\n 'perplexity',\n 'gemini',\n 'claude',\n 'copilot',\n 'grok',\n /** and the Chinese ones — DeepSeek, 豆包, Kimi, 通义, 腾讯元宝, 文心一言, 智谱清言 */\n 'deepseek',\n 'doubao',\n 'kimi',\n 'qwen',\n 'yuanbao',\n 'ernie',\n 'zhipu',\n /** the product's own referral programme: the session came through a member's link or code.\n * How a product recognises one (a `/refer/<code>` path, a code at sign-up) is its own rule. Not\n * `referral`: that word is the channel group of any outside site, and a channel of the same name\n * would be impossible to tell apart in queries. */\n 'referral_program',\n] as const;\n\nexport type Channel = (typeof CHANNELS)[number];\n\n/** GA4's default channel groups, as identifiers; a dashboard maps them to display names. */\nexport const CHANNEL_GROUPS = [\n 'direct',\n 'paid_search',\n 'paid_social',\n 'paid_other',\n 'display',\n 'organic_search',\n 'organic_social',\n 'organic_video',\n /** not in GA4's default grouping: sessions an AI assistant sent, by referrer or by ChatGPT's utm */\n 'organic_ai',\n 'referral',\n 'email',\n 'affiliate',\n /** GA4's SMS: `sms` as the source or the medium */\n 'sms',\n /** GA4's Mobile Push Notifications: a medium ending in `push` or naming mobile or a notification, or `firebase` as the source */\n 'mobile_push',\n 'unassigned',\n] as const;\n\nexport type ChannelGroup = (typeof CHANNEL_GROUPS)[number];\n\n/** `utm_source` spellings that mean the same channel: Meta's `{{site_source_name}}` values and the usual synonyms. */\nexport const SOURCE_ALIASES = {\n fb: 'meta',\n ig: 'meta',\n an: 'meta',\n msg: 'meta',\n th: 'meta',\n facebook: 'meta',\n instagram: 'meta',\n bing: 'microsoft',\n twitter: 'x',\n 'google ads': 'google',\n googleads: 'google',\n adwords: 'google',\n 'chatgpt.com': 'chatgpt',\n 'chat.openai.com': 'chatgpt',\n openai: 'chatgpt',\n 'perplexity.ai': 'perplexity',\n 'claude.ai': 'claude',\n 'copilot.com': 'copilot',\n 'deepseek.com': 'deepseek',\n 'doubao.com': 'doubao',\n 'kimi.com': 'kimi',\n} as const satisfies Record<string, Channel>;\n\n/**\n * Which click id belongs to which channel, in priority order, and whether the platform puts it on\n * its ad clicks only (`'ads'`) or on other links as well (`'any'`). Every key is a field this SDK\n * collects (`AdvertisingInfo`).\n *\n * The third column decides whether the id can say a click was paid when a `utm_source` names the\n * channel but no `utm_medium` says how (a Reddit ad tagged `utm_source=reddit` and nothing more).\n * `fbclid` is `'any'`: Meta decorates every outbound link with it, organic posts, profile links\n * and shares included. `twclid` and `ko_click_id` are `'any'` until it is clear they never\n * decorate an organic link.\n *\n * Only URL parameters, never the cookies the tags keep beside them (`_fbc`, `_gcl_aw`, … — every\n * underscore key of `AdvertisingInfo`): a click cookie lives up to 90 days after the click, so\n * counting it named every later visit — typed in, from search, from an email — a new ad click.\n */\nexport const CLICK_ID_CHANNELS = [\n ['fbclid', 'meta', 'any'],\n ['gclid', 'google', 'ads'],\n ['gbraid', 'google', 'ads'],\n ['wbraid', 'google', 'ads'],\n ['gad_source', 'google', 'ads'],\n ['gad_campaignid', 'google', 'ads'],\n ['dclid', 'dv360', 'ads'],\n ['msclkid', 'microsoft', 'ads'],\n ['ttclid', 'tiktok', 'ads'],\n ['rdt_cid', 'reddit', 'ads'],\n ['li_fat_id', 'linkedin', 'ads'],\n ['ScCid', 'snapchat', 'ads'],\n ['twclid', 'x', 'any'],\n ['ko_click_id', 'kakao', 'any'],\n ['yclid', 'yandex', 'ads'],\n ['oppref', 'chatgpt', 'ads'],\n ['epik', 'pinterest', 'ads'],\n] as const satisfies readonly (readonly [keyof AdvertisingInfo, Channel, 'ads' | 'any'])[];\n\n/**\n * A landing page reserved for one channel's ads, for ads that lost their parameters:\n * `/lp/<channel>`, with or without host, query and hash; the capture is the channel. A product\n * that names its landing pages differently replaces this.\n */\nexport const AD_LANDING_PAGE = /^(?:https?:\\/\\/[^/]+)?\\/lp\\/([a-z]+)(?:[/?#]|$)/;\n\nexport type ReferrerMedium = 'organic' | 'social' | 'video' | 'ai' | 'email';\n\n/**\n * Referrer hosts that name a channel, as regular expressions over the lower-cased host,\n * with the medium GA4 gives that kind of site. Anything else that refers is `referral`, kept as\n * its host. A country TLD is one or two labels (`google.com`, `google.co.uk`, `google.com.hk`),\n * never an open tail, so `google.com.evil.io` is not Google. In match order: the AI assistants\n * first, because `gemini.google.com`, `copilot.microsoft.com` and `yiyan.baidu.com` would\n * otherwise be Google, Microsoft and Baidu search.\n */\nexport const REFERRER_SITES = [\n ['chatgpt', 'ai', /(^|\\.)(chatgpt\\.com|chat\\.openai\\.com)$/],\n ['perplexity', 'ai', /(^|\\.)perplexity\\.ai$/],\n ['gemini', 'ai', /^gemini\\.google\\.com$/],\n ['claude', 'ai', /(^|\\.)claude\\.ai$/],\n ['copilot', 'ai', /^copilot\\.microsoft\\.com$|(^|\\.)copilot\\.com$/],\n ['grok', 'ai', /(^|\\.)(grok\\.com|x\\.ai)$/],\n ['deepseek', 'ai', /(^|\\.)deepseek\\.com$/],\n ['doubao', 'ai', /(^|\\.)doubao\\.com$/],\n ['kimi', 'ai', /(^|\\.)(kimi\\.com|kimi\\.moonshot\\.cn|moonshot\\.cn)$/],\n ['qwen', 'ai', /^tongyi\\.aliyun\\.com$|(^|\\.)(tongyi\\.com|qianwen\\.com|qwen\\.ai)$/],\n ['yuanbao', 'ai', /^yuanbao\\.tencent\\.com$/],\n ['ernie', 'ai', /^yiyan\\.baidu\\.com$|(^|\\.)ernie\\.baidu\\.com$/],\n ['zhipu', 'ai', /(^|\\.)(chatglm\\.cn|zhipuai\\.cn|bigmodel\\.cn)$/],\n // Before google, whose pattern it would match.\n ['gmail', 'email', /^mail\\.google\\.com$/],\n ['google', 'organic', /(^|\\.)google\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['microsoft', 'organic', /(^|\\.)bing\\.com$/],\n ['yahoo', 'organic', /(^|\\.)yahoo\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['duckduckgo', 'organic', /(^|\\.)duckduckgo\\.com$/],\n ['baidu', 'organic', /(^|\\.)baidu\\.com$/],\n ['yandex', 'organic', /(^|\\.)yandex\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['ecosia', 'organic', /(^|\\.)ecosia\\.org$/],\n ['naver', 'organic', /(^|\\.)naver\\.com$/],\n ['brave', 'organic', /^search\\.brave\\.com$/],\n ['meta', 'social', /(^|\\.)(facebook\\.com|instagram\\.com|fb\\.com|threads\\.net)$/],\n ['x', 'social', /(^|\\.)(twitter\\.com|x\\.com|t\\.co)$/],\n ['linkedin', 'social', /(^|\\.)(linkedin\\.com|lnkd\\.in)$/],\n ['reddit', 'social', /(^|\\.)reddit\\.com$/],\n ['tiktok', 'social', /(^|\\.)tiktok\\.com$/],\n ['pinterest', 'social', /(^|\\.)pinterest\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['snapchat', 'social', /(^|\\.)snapchat\\.com$/],\n ['telegram', 'social', /^(t\\.me|telegram\\.me|(web\\.)?telegram\\.org)$/],\n ['youtube', 'video', /(^|\\.)(youtube\\.com|youtu\\.be)$/],\n] as const satisfies readonly (readonly [Channel, ReferrerMedium, RegExp])[];\n\n/**\n * The site an Android app's referrer stands for. A link opened from an app on Android arrives with\n * `android-app://<package>/` as its referrer rather than the site's URL; the apps below are read as\n * their sites, so the Google app is a Google search and the LinkedIn app is LinkedIn. A package\n * not listed is kept as the referring host, as GA4 shows it; Meta's apps send their mobile host\n * (`android-app://m.facebook.com`), which needs no entry.\n */\nexport const ANDROID_APP_HOSTS: Readonly<Record<string, string>> = {\n 'com.google.android.googlequicksearchbox': 'www.google.com',\n 'com.google.android.gm': 'mail.google.com',\n 'com.google.android.youtube': 'www.youtube.com',\n 'com.facebook.katana': 'www.facebook.com',\n 'com.instagram.android': 'www.instagram.com',\n 'com.linkedin.android': 'www.linkedin.com',\n 'com.reddit.frontpage': 'www.reddit.com',\n 'com.twitter.android': 'x.com',\n 'com.zhiliaoapp.musically': 'www.tiktok.com',\n 'com.ss.android.ugc.trill': 'www.tiktok.com',\n 'com.pinterest': 'www.pinterest.com',\n 'com.snapchat.android': 'www.snapchat.com',\n 'org.telegram.messenger': 't.me',\n};\n\n/**\n * Referrer hosts that are navigation, not acquisition, when a session happens to start on the way\n * back from them: the local dev host; the payment providers a visitor is bounced through; the\n * sign-in providers whose login lives on a host of its own (Google, Apple, Microsoft, WeChat,\n * Kakao, LINE, Naver, Yahoo, Twitch, X's OAuth 1.0a endpoint); and the hosted auth services a\n * product may sit behind (Auth0, Okta, Supabase, Firebase, Clerk). A product appends its own\n * hostnames — a session that starts from an internal link after the session timeout refers to\n * them. Matched before `REFERRER_SITES`, so `nid.naver.com` and `login.yahoo.com` are not read as\n * a search. Providers whose login shares the host with their content — Facebook, X's OAuth 2.0,\n * LinkedIn, GitHub, Discord — cannot be told apart by host (referrer policies strip the path) and\n * are left as the referrers they usually are. Regular expressions over the lower-cased host.\n */\nexport const REFERRERS_NOT_A_TOUCH = [\n /^localhost(:|$)/,\n // payment\n /(^|\\.)stripe\\.com$/,\n // Link, Stripe's wallet: a customer opens a merchant's site from their purchases on app.link.com\n /(^|\\.)link\\.com$/,\n /^pay\\.google\\.com$/,\n /(^|\\.)paypal\\.com$/,\n /(^|\\.)alipay\\.com$/,\n /(^|\\.)paddle\\.com$/,\n /(^|\\.)lemonsqueezy\\.com$/,\n // app stores\n /^apps\\.apple\\.com$/,\n /^play\\.google\\.com$/,\n // sign-in providers with a login host of their own\n /^accounts\\.google\\.com$/,\n /^appleid\\.apple\\.com$/,\n /^login\\.(microsoftonline|live)\\.com$/,\n /^open\\.weixin\\.qq\\.com$/,\n /^(kauth|accounts)\\.kakao\\.com$/,\n /^access\\.line\\.me$/,\n /^nid\\.naver\\.com$/,\n /^login\\.yahoo\\.com$/,\n /^id\\.twitch\\.tv$/,\n /^api\\.twitter\\.com$/,\n // hosted auth services\n /(^|\\.)auth0\\.com$/,\n /(^|\\.)okta\\.com$/,\n /(^|\\.)supabase\\.co$/,\n /(^|\\.)firebaseapp\\.com$/,\n /(^|\\.)clerk\\.accounts\\.dev$/,\n] as const;\n\n/** GA4's paid mediums — cpc, cpm, ppc, retargeting, paid_social, … — plus Performance Max. */\nexport const PAID_MEDIUM = /^(.*cp.*|ppc|retargeting|paid.*|pmax|performance_max)$/;\n/**\n * Meta's `{{placement}}` values, lower-cased, which an ad URL template of `utm_medium={{placement}}`\n * puts in the medium. Only an ad carries one — an organic post arrives as a referrer, with no utm\n * at all — so a medium in this list is a paid Meta click whatever the source says (a template with\n * `utm_source=meta-websitekeyinfo` was seen). Listed one by one rather than matched by prefix, so\n * that a hand-tagged `instagram_stories` post cannot be mistaken for an ad only because of how it\n * is spelled … which means a placement Meta adds later shows up as organic social (or unassigned,\n * under a source that is no social site) until it is added here. Meta's documented values plus the\n * ones seen in the wild (`facebook_mobile_reels`, `facebook_notification`, `an`), and the literal\n * macro for a template that did not expand it.\n *\n * Accepted for the ads already running with that template; new ads should rather send\n * `utm_medium=cpc` and the placement in a `placement` parameter of its own (collected as the\n * `placement` tag): GA4 matches its paid rule on the medium alone, and files a placement medium\n * under Organic Social.\n */\nexport const META_PLACEMENTS = [\n 'facebook_feed',\n 'facebook_instream',\n 'facebook_desktop_feed',\n 'facebook_mobile_feed',\n 'facebook_mobile_reels',\n 'facebook_reels',\n 'facebook_reels_overlay',\n 'facebook_right_column',\n 'facebook_marketplace',\n 'facebook_video_feeds',\n 'facebook_stories',\n 'facebook_search',\n 'facebook_instream_video',\n 'facebook_instant_article',\n 'facebook_groups_feed',\n 'facebook_profile_feed',\n 'facebook_notification',\n 'facebook_notifications',\n 'facebook_business_explore',\n 'instagram_feed',\n 'instagram_stories',\n 'instagram_reels',\n 'instagram_reels_overlay',\n 'instagram_explore',\n 'instagram_explore_home',\n 'instagram_search',\n 'instagram_shop',\n 'instagram_profile_feed',\n 'instagram_profile_reels',\n 'instagram_igtv',\n 'messenger_inbox',\n 'messenger_stories',\n 'messenger_sponsored_messages',\n 'threads_feed',\n 'threads_stream',\n 'whatsapp_status',\n 'audience_network_classic',\n 'audience_network_rewarded_video',\n 'audience_network_native_banner_and_interstitial',\n 'an',\n 'others',\n /** the macro itself, when Meta did not expand it (written in a field it does not substitute, or a\n * preview click): still only ever produced by a Meta ad URL, so still a paid Meta click */\n '{{placement}}',\n] as const;\nexport const DISPLAY_MEDIUMS = ['display', 'banner', 'expandable', 'interstitial'] as const;\n/** GA4's email spellings, as a source or a medium. */\nexport const EMAIL_MEDIUMS = ['email', 'e-mail', 'e_mail', 'e mail'] as const;\n/** GA4's Mobile Push Notifications medium: ending in `push`, or containing `mobile` or `notification`. */\nexport const PUSH_MEDIUM = /(push$|mobile|notification)/;\n/** A medium that says email or newsletter anywhere in it: `outbound email`, `cold_email`, `newsletter`, and GA4's four. */\nexport const EMAIL_MEDIUM = /(^|[^a-z])(e[-_ ]?mail|newsletter)([^a-z]|$)/;\n\n/** Where a touch came from: a tracked session, or a report by the user or staff. */\nexport const TOUCH_SOURCES = ['tracked', 'reported'] as const;\nexport type TouchSource = (typeof TOUCH_SOURCES)[number];\n\n/**\n * How a reported touch was learned:\n * - survey: the user answered \"how did you hear about us\" (onboarding, checkout)\n * - promo_code: the user redeemed a code that belongs to a channel\n * - phone: a call came in on a channel's dedicated number, marked by staff or the phone system\n * - manual: staff recorded what a customer told them\n */\nexport const REPORTED_TOUCH_KINDS = ['survey', 'promo_code', 'phone', 'manual'] as const;\nexport type ReportedTouchKind = (typeof REPORTED_TOUCH_KINDS)[number];\n\n/**\n * How much a touch is worth against another when attribution has to choose between sessions;\n * lower wins, and the latest wins among equals. Stored on the session when it is classified, so\n * renumbering means reclassifying.\n *\n * This ranks touches against each other. Within one session the rules that name its channel have\n * their own order — `utm_source`, then a click id, then the ad landing page, then the referrer —\n * and the first that says something wins; the session's priority is then the tier of that rule\n * (a campaign rule whose touch is organic by its own tag ranking as a referrer, see `classifyTouch`).\n * So two sessions tagged with utm and gclid respectively are equal here (both `campaign`) and the\n * later one is credited, while one session carrying both is named by its utm.\n */\nexport const TOUCH_PRIORITY = {\n /**\n * utm, click id, ad landing page: someone tagged that link (in that order within a session) —\n * unless the tag itself calls the touch organic, a referral or email, which then ranks as `referrer`\n */\n campaign: 1,\n /** a reported touch staff stand behind: a call on a channel's number, a recorded conversation */\n verified: 1,\n /** the browser said where the visitor came from; nobody chose it */\n referrer: 2,\n /** a reported touch that is the user's claim: a survey answer, a code that may have been shared */\n claimed: 3,\n} as const satisfies Record<string, number>;\n\n/** Which reported kinds are staff facts and which are the user's claims. */\nexport const REPORTED_TOUCH_PRIORITY = {\n phone: TOUCH_PRIORITY.verified,\n manual: TOUCH_PRIORITY.verified,\n survey: TOUCH_PRIORITY.claimed,\n promo_code: TOUCH_PRIORITY.claimed,\n} as const satisfies Record<ReportedTouchKind, number>;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vocabulary.cjs","names":[],"sources":["../../src/attribution/vocabulary.ts"],"sourcesContent":["import type { AdvertisingInfo } from '../track/types';\n\n/**\n * The vocabulary attribution is built from, shared by every product's `session` table and by the\n * dashboards that read it. Data for `classifyTouch` (./classify), which turns it into a channel;\n * a product's dashboards read the same names back. What stays with the product is its own hostnames\n * (referrer exclusions) and its landing-page conventions.\n */\n\n/** A session that arrived through nothing we can name. GA's spelling, so dashboards read as GA. */\nexport const DIRECT_CHANNEL = '(direct)';\n/** The medium of a direct session. */\nexport const NO_MEDIUM = '(none)';\n/** The medium of a tagged link that named a source but no medium. */\nexport const MEDIUM_NOT_SET = '(not set)';\n\n/** The channels the rules below can name. A `utm_source` outside this list is its own channel. */\nexport const CHANNELS = [\n 'meta',\n 'google',\n 'dv360',\n 'microsoft',\n 'tiktok',\n 'reddit',\n 'linkedin',\n 'snapchat',\n 'x',\n 'kakao',\n 'yandex',\n 'yahoo',\n 'duckduckgo',\n 'baidu',\n 'ecosia',\n 'naver',\n 'brave',\n 'qwant',\n 'pinterest',\n 'youtube',\n 'telegram',\n /** Webmail, by its web and Android app: a link in a mail that carried no utm. Outlook includes\n * Microsoft 365's new `outlook.cloud.microsoft` and the links its Safe Links rewrote. */\n 'gmail',\n 'outlook',\n 'yahoo_mail',\n 'yandex_mail',\n 'naver_mail',\n 'qq_mail',\n 'netease_mail',\n 'proton_mail',\n 'seznam_mail',\n /** GMX and WEB.DE, one company's webmail, whose links leave through its `deref-*` redirector */\n 'gmx',\n 'aol_mail',\n 'zoho_mail',\n /** A mail whose client cannot be told: the link was rewritten by an email security gateway\n * (Proofpoint URL Defense, Mimecast, Barracuda, …). */\n 'email',\n /** AI assistants that send people to sites: ChatGPT tags its links `utm_source=chatgpt.com`, the\n * others show as a referrer. Their own channel group, `organic_ai`, since GA4's default groups\n * would file them under Referral / Unassigned and the question \"how much does AI bring\" comes up. */\n 'chatgpt',\n 'perplexity',\n 'gemini',\n 'claude',\n 'copilot',\n 'grok',\n /** and the Chinese ones — DeepSeek, 豆包, Kimi, 通义, 腾讯元宝, 文心一言, 智谱清言 */\n 'deepseek',\n 'doubao',\n 'kimi',\n 'qwen',\n 'yuanbao',\n 'ernie',\n 'zhipu',\n /** the product's own referral programme: the session came through a member's link or code.\n * How a product recognises one (a `/refer/<code>` path, a code at sign-up) is its own rule. Not\n * `referral`: that word is the channel group of any outside site, and a channel of the same name\n * would be impossible to tell apart in queries. */\n 'referral_program',\n] as const;\n\nexport type Channel = (typeof CHANNELS)[number];\n\n/** GA4's default channel groups, as identifiers; a dashboard maps them to display names. */\nexport const CHANNEL_GROUPS = [\n 'direct',\n 'paid_search',\n 'paid_social',\n 'paid_other',\n 'display',\n 'organic_search',\n 'organic_social',\n 'organic_video',\n /** not in GA4's default grouping: sessions an AI assistant sent, by referrer or by ChatGPT's utm */\n 'organic_ai',\n 'referral',\n 'email',\n 'affiliate',\n /** GA4's SMS: `sms` as the source or the medium */\n 'sms',\n /** GA4's Mobile Push Notifications: a medium ending in `push` or naming mobile or a notification, or `firebase` as the source */\n 'mobile_push',\n 'unassigned',\n] as const;\n\nexport type ChannelGroup = (typeof CHANNEL_GROUPS)[number];\n\n/** `utm_source` spellings that mean the same channel: Meta's `{{site_source_name}}` values and the usual synonyms. */\nexport const SOURCE_ALIASES = {\n fb: 'meta',\n ig: 'meta',\n an: 'meta',\n msg: 'meta',\n th: 'meta',\n facebook: 'meta',\n instagram: 'meta',\n bing: 'microsoft',\n twitter: 'x',\n 'google ads': 'google',\n googleads: 'google',\n adwords: 'google',\n 'chatgpt.com': 'chatgpt',\n 'chat.openai.com': 'chatgpt',\n openai: 'chatgpt',\n 'perplexity.ai': 'perplexity',\n 'claude.ai': 'claude',\n 'copilot.com': 'copilot',\n 'deepseek.com': 'deepseek',\n 'doubao.com': 'doubao',\n 'kimi.com': 'kimi',\n} as const satisfies Record<string, Channel>;\n\n/**\n * Which click id belongs to which channel, in priority order, and whether the platform puts it on\n * its ad clicks only (`'ads'`) or on other links as well (`'any'`). Every key is a field this SDK\n * collects (`AdvertisingInfo`).\n *\n * The third column decides whether the id can say a click was paid when a `utm_source` names the\n * channel but no `utm_medium` says how (a Reddit ad tagged `utm_source=reddit` and nothing more).\n * `fbclid` is `'any'`: Meta decorates every outbound link with it, organic posts, profile links\n * and shares included. `twclid` and `ko_click_id` are `'any'` until it is clear they never\n * decorate an organic link.\n *\n * Only URL parameters, never the cookies the tags keep beside them (`_fbc`, `_gcl_aw`, … — every\n * underscore key of `AdvertisingInfo`): a click cookie lives up to 90 days after the click, so\n * counting it named every later visit — typed in, from search, from an email — a new ad click.\n */\nexport const CLICK_ID_CHANNELS = [\n ['fbclid', 'meta', 'any'],\n ['gclid', 'google', 'ads'],\n ['gbraid', 'google', 'ads'],\n ['wbraid', 'google', 'ads'],\n ['gad_source', 'google', 'ads'],\n ['gad_campaignid', 'google', 'ads'],\n ['dclid', 'dv360', 'ads'],\n ['msclkid', 'microsoft', 'ads'],\n ['ttclid', 'tiktok', 'ads'],\n ['rdt_cid', 'reddit', 'ads'],\n ['li_fat_id', 'linkedin', 'ads'],\n ['ScCid', 'snapchat', 'ads'],\n ['twclid', 'x', 'any'],\n ['ko_click_id', 'kakao', 'any'],\n ['yclid', 'yandex', 'ads'],\n ['oppref', 'chatgpt', 'ads'],\n ['epik', 'pinterest', 'ads'],\n] as const satisfies readonly (readonly [keyof AdvertisingInfo, Channel, 'ads' | 'any'])[];\n\n/**\n * A landing page reserved for one channel's ads, for ads that lost their parameters:\n * `/lp/<channel>`, with or without host, query and hash; the capture is the channel. A product\n * that names its landing pages differently replaces this.\n */\nexport const AD_LANDING_PAGE = /^(?:https?:\\/\\/[^/]+)?\\/lp\\/([a-z]+)(?:[/?#]|$)/;\n\nexport type ReferrerMedium = 'organic' | 'social' | 'video' | 'ai' | 'email';\n\n/**\n * Referrer hosts that name a channel, as regular expressions over the lower-cased host,\n * with the medium GA4 gives that kind of site. Anything else that refers is `referral`, kept as\n * its host. A country TLD is one or two labels (`google.com`, `google.co.uk`, `google.com.hk`),\n * never an open tail, so `google.com.evil.io` is not Google. In match order: the AI assistants\n * first, because `gemini.google.com`, `copilot.microsoft.com` and `yiyan.baidu.com` would\n * otherwise be Google, Microsoft and Baidu search.\n */\nexport const REFERRER_SITES = [\n ['chatgpt', 'ai', /(^|\\.)(chatgpt\\.com|chat\\.openai\\.com)$/],\n ['perplexity', 'ai', /(^|\\.)perplexity\\.ai$/],\n ['gemini', 'ai', /^gemini\\.google\\.com$/],\n ['claude', 'ai', /(^|\\.)claude\\.ai$/],\n ['copilot', 'ai', /^copilot\\.microsoft\\.com$|(^|\\.)copilot\\.com$/],\n ['grok', 'ai', /(^|\\.)(grok\\.com|x\\.ai)$/],\n ['deepseek', 'ai', /(^|\\.)deepseek\\.com$/],\n ['doubao', 'ai', /(^|\\.)doubao\\.com$/],\n ['kimi', 'ai', /(^|\\.)(kimi\\.com|kimi\\.moonshot\\.cn|moonshot\\.cn)$/],\n ['qwen', 'ai', /^tongyi\\.aliyun\\.com$|(^|\\.)(tongyi\\.com|qianwen\\.com|qwen\\.ai)$/],\n ['yuanbao', 'ai', /^yuanbao\\.tencent\\.com$/],\n ['ernie', 'ai', /^yiyan\\.baidu\\.com$|(^|\\.)ernie\\.baidu\\.com$/],\n ['zhipu', 'ai', /(^|\\.)(chatglm\\.cn|zhipuai\\.cn|bigmodel\\.cn)$/],\n // Webmail before the search engines whose patterns would match it: Gmail is on google.*,\n // Yahoo Mail on yahoo.*, Yandex Mail on yandex.*, Naver Mail on naver.com.\n ['gmail', 'email', /^mail\\.google\\.com$/],\n [\n 'outlook',\n 'email',\n /^outlook\\.(live|office|office365)\\.com$|^outlook\\.cloud\\.microsoft$|(^|\\.)safelinks\\.protection\\.outlook\\.com$/,\n ],\n ['yahoo_mail', 'email', /^mail\\.yahoo\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['yandex_mail', 'email', /^mail\\.yandex\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['naver_mail', 'email', /^mail\\.naver\\.com$/],\n ['qq_mail', 'email', /^(wx\\.)?mail\\.qq\\.com$|^exmail\\.qq\\.com$/],\n ['netease_mail', 'email', /^mail\\.(163|126)\\.com$/],\n ['proton_mail', 'email', /^mail\\.proton(mail)?\\.(me|com)$/],\n ['seznam_mail', 'email', /^email\\.seznam\\.cz$/],\n ['gmx', 'email', /^deref-(gmx|web|mail)(-\\d+)?\\.(net|de|com)$|^navigator\\.(gmx\\.net|web\\.de)$/],\n ['aol_mail', 'email', /^mail\\.aol\\.com$/],\n ['zoho_mail', 'email', /^mail\\.zoho\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n [\n 'email',\n 'email',\n /^urldefense\\.(com|proofpoint\\.com)$|^protect(-[a-z]+)?\\.mimecast\\.com$|(^|\\.)mimecastprotect\\.com$|^linkprotect\\.cudasvc\\.com$|^url\\.emailprotection\\.link$/,\n ],\n ['google', 'organic', /(^|\\.)google\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['microsoft', 'organic', /(^|\\.)bing\\.com$/],\n ['yahoo', 'organic', /(^|\\.)yahoo\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['duckduckgo', 'organic', /(^|\\.)duckduckgo\\.com$/],\n ['baidu', 'organic', /(^|\\.)baidu\\.com$/],\n ['yandex', 'organic', /(^|\\.)yandex\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['ecosia', 'organic', /(^|\\.)ecosia\\.org$/],\n ['naver', 'organic', /(^|\\.)naver\\.com$/],\n ['brave', 'organic', /^search\\.brave\\.com$/],\n ['qwant', 'organic', /(^|\\.)qwant\\.com$/],\n ['meta', 'social', /(^|\\.)(facebook\\.com|instagram\\.com|fb\\.com|threads\\.net)$/],\n ['x', 'social', /(^|\\.)(twitter\\.com|x\\.com|t\\.co)$/],\n ['linkedin', 'social', /(^|\\.)(linkedin\\.com|lnkd\\.in)$/],\n ['reddit', 'social', /(^|\\.)reddit\\.com$/],\n ['tiktok', 'social', /(^|\\.)tiktok\\.com$/],\n ['pinterest', 'social', /(^|\\.)pinterest\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['snapchat', 'social', /(^|\\.)snapchat\\.com$/],\n ['telegram', 'social', /^(t\\.me|telegram\\.me|(web\\.)?telegram\\.org)$/],\n ['youtube', 'video', /(^|\\.)(youtube\\.com|youtu\\.be)$/],\n] as const satisfies readonly (readonly [Channel, ReferrerMedium, RegExp])[];\n\n/**\n * The site an Android app's referrer stands for. A link opened from an app on Android arrives with\n * `android-app://<package>/` as its referrer rather than the site's URL; the apps below are read as\n * their sites, so the Google app is a Google search and the LinkedIn app is LinkedIn. A package\n * not listed is kept as the referring host, as GA4 shows it; Meta's apps send their mobile host\n * (`android-app://m.facebook.com`), which needs no entry.\n */\nexport const ANDROID_APP_HOSTS: Readonly<Record<string, string>> = {\n 'com.google.android.googlequicksearchbox': 'www.google.com',\n 'com.google.android.gm': 'mail.google.com',\n 'com.microsoft.office.outlook': 'outlook.live.com',\n 'com.yahoo.mobile.client.android.mail': 'mail.yahoo.com',\n 'ru.yandex.mail': 'mail.yandex.ru',\n 'com.tencent.androidqqmail': 'mail.qq.com',\n 'ch.protonmail.android': 'mail.proton.me',\n 'com.google.android.youtube': 'www.youtube.com',\n 'com.facebook.katana': 'www.facebook.com',\n 'com.instagram.android': 'www.instagram.com',\n 'com.linkedin.android': 'www.linkedin.com',\n 'com.reddit.frontpage': 'www.reddit.com',\n 'com.twitter.android': 'x.com',\n 'com.zhiliaoapp.musically': 'www.tiktok.com',\n 'com.ss.android.ugc.trill': 'www.tiktok.com',\n 'com.pinterest': 'www.pinterest.com',\n 'com.snapchat.android': 'www.snapchat.com',\n 'org.telegram.messenger': 't.me',\n};\n\n/**\n * Referrer hosts that are navigation, not acquisition, when a session happens to start on the way\n * back from them: the local dev host; the payment providers a visitor is bounced through; the\n * bot challenges (Cloudflare Turnstile, hCaptcha) a visitor passes before the page; the\n * sign-in providers whose login lives on a host of its own (Google, Apple, Microsoft, WeChat,\n * Kakao, LINE, Naver, Yahoo, Twitch, X's OAuth 1.0a endpoint); and the hosted auth services a\n * product may sit behind (Auth0, Okta, Supabase, Firebase, Clerk). A product appends its own\n * hostnames — a session that starts from an internal link after the session timeout refers to\n * them. Matched before `REFERRER_SITES`, so `nid.naver.com` and `login.yahoo.com` are not read as\n * a search. Providers whose login shares the host with their content — Facebook, X's OAuth 2.0,\n * LinkedIn, GitHub, Discord — cannot be told apart by host (referrer policies strip the path) and\n * are left as the referrers they usually are. Regular expressions over the lower-cased host.\n */\nexport const REFERRERS_NOT_A_TOUCH = [\n /^localhost(:|$)/,\n /^127\\.0\\.0\\.1$/,\n // payment\n /(^|\\.)stripe\\.com$/,\n // Link, Stripe's wallet: a customer opens a merchant's site from their purchases on app.link.com\n /(^|\\.)link\\.com$/,\n /^pay\\.google\\.com$/,\n /(^|\\.)paypal\\.com$/,\n /(^|\\.)alipay\\.com$/,\n /(^|\\.)paddle\\.com$/,\n /(^|\\.)lemonsqueezy\\.com$/,\n /^pay\\.amazon\\.com$/,\n /^pay\\.weixin\\.qq\\.com$/,\n // bot challenges a visitor is sent through before the page\n /^challenges\\.cloudflare\\.com$/,\n /(^|\\.)hcaptcha\\.com$/,\n // app stores\n /^apps\\.apple\\.com$/,\n /^play\\.google\\.com$/,\n // sign-in providers with a login host of their own\n /^accounts\\.google\\.com$/,\n /^appleid\\.apple\\.com$/,\n /^login\\.(microsoftonline|live)\\.com$/,\n /^open\\.weixin\\.qq\\.com$/,\n /^(kauth|accounts)\\.kakao\\.com$/,\n /^access\\.line\\.me$/,\n /^nid\\.naver\\.com$/,\n /^login\\.yahoo\\.com$/,\n /^id\\.twitch\\.tv$/,\n /^api\\.twitter\\.com$/,\n // hosted auth services\n /(^|\\.)auth0\\.com$/,\n /(^|\\.)okta\\.com$/,\n /(^|\\.)supabase\\.co$/,\n /(^|\\.)firebaseapp\\.com$/,\n /(^|\\.)clerk\\.accounts\\.dev$/,\n] as const;\n\n/** GA4's paid mediums — cpc, cpm, ppc, retargeting, paid_social, … — plus Performance Max. */\nexport const PAID_MEDIUM = /^(.*cp.*|ppc|retargeting|paid.*|pmax|performance_max)$/;\n/**\n * Meta's `{{placement}}` values, lower-cased, which an ad URL template of `utm_medium={{placement}}`\n * puts in the medium. Only an ad carries one — an organic post arrives as a referrer, with no utm\n * at all — so a medium in this list is a paid Meta click whatever the source says (a template with\n * `utm_source=meta-websitekeyinfo` was seen). Listed one by one rather than matched by prefix, so\n * that a hand-tagged `instagram_stories` post cannot be mistaken for an ad only because of how it\n * is spelled … which means a placement Meta adds later shows up as organic social (or unassigned,\n * under a source that is no social site) until it is added here. Meta's documented values plus the\n * ones seen in the wild (`facebook_mobile_reels`, `facebook_notification`, `an`), and the literal\n * macro for a template that did not expand it.\n *\n * Accepted for the ads already running with that template; new ads should rather send\n * `utm_medium=cpc` and the placement in a `placement` parameter of its own (collected as the\n * `placement` tag): GA4 matches its paid rule on the medium alone, and files a placement medium\n * under Organic Social.\n */\nexport const META_PLACEMENTS = [\n 'facebook_feed',\n 'facebook_instream',\n 'facebook_desktop_feed',\n 'facebook_mobile_feed',\n 'facebook_mobile_reels',\n 'facebook_reels',\n 'facebook_reels_overlay',\n 'facebook_right_column',\n 'facebook_marketplace',\n 'facebook_video_feeds',\n 'facebook_stories',\n 'facebook_search',\n 'facebook_instream_video',\n 'facebook_instant_article',\n 'facebook_groups_feed',\n 'facebook_profile_feed',\n 'facebook_notification',\n 'facebook_notifications',\n 'facebook_business_explore',\n 'instagram_feed',\n 'instagram_stories',\n 'instagram_reels',\n 'instagram_reels_overlay',\n 'instagram_explore',\n 'instagram_explore_home',\n 'instagram_search',\n 'instagram_shop',\n 'instagram_profile_feed',\n 'instagram_profile_reels',\n 'instagram_igtv',\n 'messenger_inbox',\n 'messenger_stories',\n 'messenger_sponsored_messages',\n 'threads_feed',\n 'threads_stream',\n 'whatsapp_status',\n 'audience_network_classic',\n 'audience_network_rewarded_video',\n 'audience_network_native_banner_and_interstitial',\n 'an',\n 'others',\n /** the macro itself, when Meta did not expand it (written in a field it does not substitute, or a\n * preview click): still only ever produced by a Meta ad URL, so still a paid Meta click */\n '{{placement}}',\n] as const;\nexport const DISPLAY_MEDIUMS = ['display', 'banner', 'expandable', 'interstitial'] as const;\n/** GA4's email spellings, as a source or a medium. */\nexport const EMAIL_MEDIUMS = ['email', 'e-mail', 'e_mail', 'e mail'] as const;\n/** GA4's Mobile Push Notifications medium: ending in `push`, or containing `mobile` or `notification`. */\nexport const PUSH_MEDIUM = /(push$|mobile|notification)/;\n/** A medium that says email or newsletter anywhere in it: `outbound email`, `cold_email`, `newsletter`, and GA4's four. */\nexport const EMAIL_MEDIUM = /(^|[^a-z])(e[-_ ]?mail|newsletter)([^a-z]|$)/;\n\n/** Where a touch came from: a tracked session, or a report by the user or staff. */\nexport const TOUCH_SOURCES = ['tracked', 'reported'] as const;\nexport type TouchSource = (typeof TOUCH_SOURCES)[number];\n\n/**\n * How a reported touch was learned:\n * - survey: the user answered \"how did you hear about us\" (onboarding, checkout)\n * - promo_code: the user redeemed a code that belongs to a channel\n * - phone: a call came in on a channel's dedicated number, marked by staff or the phone system\n * - manual: staff recorded what a customer told them\n */\nexport const REPORTED_TOUCH_KINDS = ['survey', 'promo_code', 'phone', 'manual'] as const;\nexport type ReportedTouchKind = (typeof REPORTED_TOUCH_KINDS)[number];\n\n/**\n * How much a touch is worth against another when attribution has to choose between sessions;\n * lower wins, and the latest wins among equals. Stored on the session when it is classified, so\n * renumbering means reclassifying.\n *\n * This ranks touches against each other. Within one session the rules that name its channel have\n * their own order — `utm_source`, then a click id, then the ad landing page, then the referrer —\n * and the first that says something wins; the session's priority is then the tier of that rule\n * (a campaign rule whose touch is organic by its own tag ranking as a referrer, see `classifyTouch`).\n * So two sessions tagged with utm and gclid respectively are equal here (both `campaign`) and the\n * later one is credited, while one session carrying both is named by its utm.\n */\nexport const TOUCH_PRIORITY = {\n /**\n * utm, click id, ad landing page: someone tagged that link (in that order within a session) —\n * unless the tag itself calls the touch organic, a referral or email, which then ranks as `referrer`\n */\n campaign: 1,\n /** a reported touch staff stand behind: a call on a channel's number, a recorded conversation */\n verified: 1,\n /** the browser said where the visitor came from; nobody chose it */\n referrer: 2,\n /** a reported touch that is the user's claim: a survey answer, a code that may have been shared */\n claimed: 3,\n} as const satisfies Record<string, number>;\n\n/** Which reported kinds are staff facts and which are the user's claims. */\nexport const REPORTED_TOUCH_PRIORITY = {\n phone: TOUCH_PRIORITY.verified,\n manual: TOUCH_PRIORITY.verified,\n survey: TOUCH_PRIORITY.claimed,\n promo_code: TOUCH_PRIORITY.claimed,\n} as const satisfies Record<ReportedTouchKind, number>;\n"],"mappings":"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"}
|
|
@@ -12,9 +12,15 @@ export declare const NO_MEDIUM = "(none)";
|
|
|
12
12
|
/** The medium of a tagged link that named a source but no medium. */
|
|
13
13
|
export declare const MEDIUM_NOT_SET = "(not set)";
|
|
14
14
|
/** The channels the rules below can name. A `utm_source` outside this list is its own channel. */
|
|
15
|
-
export declare const CHANNELS: readonly ['meta', 'google', 'dv360', 'microsoft', 'tiktok', 'reddit', 'linkedin', 'snapchat', 'x', 'kakao', 'yandex', 'yahoo', 'duckduckgo', 'baidu', 'ecosia', 'naver', 'brave', 'pinterest', 'youtube', 'telegram',
|
|
16
|
-
/**
|
|
17
|
-
'
|
|
15
|
+
export declare const CHANNELS: readonly ['meta', 'google', 'dv360', 'microsoft', 'tiktok', 'reddit', 'linkedin', 'snapchat', 'x', 'kakao', 'yandex', 'yahoo', 'duckduckgo', 'baidu', 'ecosia', 'naver', 'brave', 'qwant', 'pinterest', 'youtube', 'telegram',
|
|
16
|
+
/** Webmail, by its web and Android app: a link in a mail that carried no utm. Outlook includes
|
|
17
|
+
* Microsoft 365's new `outlook.cloud.microsoft` and the links its Safe Links rewrote. */
|
|
18
|
+
'gmail', 'outlook', 'yahoo_mail', 'yandex_mail', 'naver_mail', 'qq_mail', 'netease_mail', 'proton_mail', 'seznam_mail',
|
|
19
|
+
/** GMX and WEB.DE, one company's webmail, whose links leave through its `deref-*` redirector */
|
|
20
|
+
'gmx', 'aol_mail', 'zoho_mail',
|
|
21
|
+
/** A mail whose client cannot be told: the link was rewritten by an email security gateway
|
|
22
|
+
* (Proofpoint URL Defense, Mimecast, Barracuda, …). */
|
|
23
|
+
'email',
|
|
18
24
|
/** AI assistants that send people to sites: ChatGPT tags its links `utm_source=chatgpt.com`, the
|
|
19
25
|
* others show as a referrer. Their own channel group, `organic_ai`, since GA4's default groups
|
|
20
26
|
* would file them under Referral / Unassigned and the question "how much does AI bring" comes up. */
|
|
@@ -91,7 +97,7 @@ export type ReferrerMedium = 'organic' | 'social' | 'video' | 'ai' | 'email';
|
|
|
91
97
|
* first, because `gemini.google.com`, `copilot.microsoft.com` and `yiyan.baidu.com` would
|
|
92
98
|
* otherwise be Google, Microsoft and Baidu search.
|
|
93
99
|
*/
|
|
94
|
-
export declare const REFERRER_SITES: readonly [readonly ["chatgpt", "ai", RegExp], readonly ["perplexity", "ai", RegExp], readonly ["gemini", "ai", RegExp], readonly ["claude", "ai", RegExp], readonly ["copilot", "ai", RegExp], readonly ["grok", "ai", RegExp], readonly ["deepseek", "ai", RegExp], readonly ["doubao", "ai", RegExp], readonly ["kimi", "ai", RegExp], readonly ["qwen", "ai", RegExp], readonly ["yuanbao", "ai", RegExp], readonly ["ernie", "ai", RegExp], readonly ["zhipu", "ai", RegExp], readonly ["gmail", "email", RegExp], readonly ["google", "organic", RegExp], readonly ["microsoft", "organic", RegExp], readonly ["yahoo", "organic", RegExp], readonly ["duckduckgo", "organic", RegExp], readonly ["baidu", "organic", RegExp], readonly ["yandex", "organic", RegExp], readonly ["ecosia", "organic", RegExp], readonly ["naver", "organic", RegExp], readonly ["brave", "organic", RegExp], readonly ["meta", "social", RegExp], readonly ["x", "social", RegExp], readonly ["linkedin", "social", RegExp], readonly ["reddit", "social", RegExp], readonly ["tiktok", "social", RegExp], readonly ["pinterest", "social", RegExp], readonly ["snapchat", "social", RegExp], readonly ["telegram", "social", RegExp], readonly ["youtube", "video", RegExp]];
|
|
100
|
+
export declare const REFERRER_SITES: readonly [readonly ["chatgpt", "ai", RegExp], readonly ["perplexity", "ai", RegExp], readonly ["gemini", "ai", RegExp], readonly ["claude", "ai", RegExp], readonly ["copilot", "ai", RegExp], readonly ["grok", "ai", RegExp], readonly ["deepseek", "ai", RegExp], readonly ["doubao", "ai", RegExp], readonly ["kimi", "ai", RegExp], readonly ["qwen", "ai", RegExp], readonly ["yuanbao", "ai", RegExp], readonly ["ernie", "ai", RegExp], readonly ["zhipu", "ai", RegExp], readonly ["gmail", "email", RegExp], readonly ["outlook", "email", RegExp], readonly ["yahoo_mail", "email", RegExp], readonly ["yandex_mail", "email", RegExp], readonly ["naver_mail", "email", RegExp], readonly ["qq_mail", "email", RegExp], readonly ["netease_mail", "email", RegExp], readonly ["proton_mail", "email", RegExp], readonly ["seznam_mail", "email", RegExp], readonly ["gmx", "email", RegExp], readonly ["aol_mail", "email", RegExp], readonly ["zoho_mail", "email", RegExp], readonly ["email", "email", RegExp], readonly ["google", "organic", RegExp], readonly ["microsoft", "organic", RegExp], readonly ["yahoo", "organic", RegExp], readonly ["duckduckgo", "organic", RegExp], readonly ["baidu", "organic", RegExp], readonly ["yandex", "organic", RegExp], readonly ["ecosia", "organic", RegExp], readonly ["naver", "organic", RegExp], readonly ["brave", "organic", RegExp], readonly ["qwant", "organic", RegExp], readonly ["meta", "social", RegExp], readonly ["x", "social", RegExp], readonly ["linkedin", "social", RegExp], readonly ["reddit", "social", RegExp], readonly ["tiktok", "social", RegExp], readonly ["pinterest", "social", RegExp], readonly ["snapchat", "social", RegExp], readonly ["telegram", "social", RegExp], readonly ["youtube", "video", RegExp]];
|
|
95
101
|
/**
|
|
96
102
|
* The site an Android app's referrer stands for. A link opened from an app on Android arrives with
|
|
97
103
|
* `android-app://<package>/` as its referrer rather than the site's URL; the apps below are read as
|
|
@@ -103,6 +109,7 @@ export declare const ANDROID_APP_HOSTS: Readonly<Record<string, string>>;
|
|
|
103
109
|
/**
|
|
104
110
|
* Referrer hosts that are navigation, not acquisition, when a session happens to start on the way
|
|
105
111
|
* back from them: the local dev host; the payment providers a visitor is bounced through; the
|
|
112
|
+
* bot challenges (Cloudflare Turnstile, hCaptcha) a visitor passes before the page; the
|
|
106
113
|
* sign-in providers whose login lives on a host of its own (Google, Apple, Microsoft, WeChat,
|
|
107
114
|
* Kakao, LINE, Naver, Yahoo, Twitch, X's OAuth 1.0a endpoint); and the hosted auth services a
|
|
108
115
|
* product may sit behind (Auth0, Okta, Supabase, Firebase, Clerk). A product appends its own
|
|
@@ -112,7 +119,7 @@ export declare const ANDROID_APP_HOSTS: Readonly<Record<string, string>>;
|
|
|
112
119
|
* LinkedIn, GitHub, Discord — cannot be told apart by host (referrer policies strip the path) and
|
|
113
120
|
* are left as the referrers they usually are. Regular expressions over the lower-cased host.
|
|
114
121
|
*/
|
|
115
|
-
export declare const REFERRERS_NOT_A_TOUCH: readonly [RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp];
|
|
122
|
+
export declare const REFERRERS_NOT_A_TOUCH: readonly [RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp];
|
|
116
123
|
/** GA4's paid mediums — cpc, cpm, ppc, retargeting, paid_social, … — plus Performance Max. */
|
|
117
124
|
export declare const PAID_MEDIUM: RegExp;
|
|
118
125
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vocabulary.d.cts","names":[],"sources":["../../src/attribution/vocabulary.ts"],"mappings":";;;;;;;;qBAUa;;qBAEA;;qBAEA;;qBAGA
|
|
1
|
+
{"version":3,"file":"vocabulary.d.cts","names":[],"sources":["../../src/attribution/vocabulary.ts"],"mappings":";;;;;;;;qBAUa;;qBAEA;;qBAEA;;qBAGA;;;;;;;;;;;;;;;;;;;;YAgED,kBAAkB;;qBAGjB;;;;;;;YAqBD,uBAAuB;;qBAGtB;WACP;WACA;WACA;WACC;WACD;WACM;WACC;WACL;WACG;WACK;WACH;WACF;WACM;WACI;WACX;WACS;WACJ;WACE;WACC;WACF;WACF;;;;;;;;;;;;;;;;;qBAkBD;;;;;;qBAyBA,iBAAe;YAEhB;;;;;;;;;qBAUC,qDAAc,uCAAA,mCAAA,mCAAA,oCAAA,iCAAA,qCAAA,mCAAA,iCAAA,iCAAA,oCAAA,kCAAA,kCAAA,qCAAA,uCAAA,0CAAA,2CAAA,0CAAA,uCAAA,4CAAA,2CAAA,2CAAA,mCAAA,wCAAA,yCAAA,qCAAA,wCAAA,2CAAA,uCAAA,4CAAA,uCAAA,wCAAA,wCAAA,uCAAA,uCAAA,uCAAA,qCAAA,kCAAA,yCAAA,uCAAA,uCAAA,0CAAA,yCAAA,yCAAA,uCAAA;;;;;;;;qBAiEd,mBAAmB,SAAS;;;;;;;;;;;;;;qBAkC5B,iCAAqB,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA;;qBAwCrB,aAAW;;;;;;;;;;;;;;;;;qBAiBX;;;;qBA8CA;;qBAEA;;qBAEA,aAAW;;qBAEX,cAAY;;qBAGZ;YACD,sBAAsB;;;;;;;;qBASrB;YACD,4BAA4B;;;;;;;;;;;;;qBAc3B;;;;;WAKD;;WAEA;;WAEA;;WAED;;;qBAIE"}
|
|
@@ -12,9 +12,15 @@ export declare const NO_MEDIUM = "(none)";
|
|
|
12
12
|
/** The medium of a tagged link that named a source but no medium. */
|
|
13
13
|
export declare const MEDIUM_NOT_SET = "(not set)";
|
|
14
14
|
/** The channels the rules below can name. A `utm_source` outside this list is its own channel. */
|
|
15
|
-
export declare const CHANNELS: readonly ['meta', 'google', 'dv360', 'microsoft', 'tiktok', 'reddit', 'linkedin', 'snapchat', 'x', 'kakao', 'yandex', 'yahoo', 'duckduckgo', 'baidu', 'ecosia', 'naver', 'brave', 'pinterest', 'youtube', 'telegram',
|
|
16
|
-
/**
|
|
17
|
-
'
|
|
15
|
+
export declare const CHANNELS: readonly ['meta', 'google', 'dv360', 'microsoft', 'tiktok', 'reddit', 'linkedin', 'snapchat', 'x', 'kakao', 'yandex', 'yahoo', 'duckduckgo', 'baidu', 'ecosia', 'naver', 'brave', 'qwant', 'pinterest', 'youtube', 'telegram',
|
|
16
|
+
/** Webmail, by its web and Android app: a link in a mail that carried no utm. Outlook includes
|
|
17
|
+
* Microsoft 365's new `outlook.cloud.microsoft` and the links its Safe Links rewrote. */
|
|
18
|
+
'gmail', 'outlook', 'yahoo_mail', 'yandex_mail', 'naver_mail', 'qq_mail', 'netease_mail', 'proton_mail', 'seznam_mail',
|
|
19
|
+
/** GMX and WEB.DE, one company's webmail, whose links leave through its `deref-*` redirector */
|
|
20
|
+
'gmx', 'aol_mail', 'zoho_mail',
|
|
21
|
+
/** A mail whose client cannot be told: the link was rewritten by an email security gateway
|
|
22
|
+
* (Proofpoint URL Defense, Mimecast, Barracuda, …). */
|
|
23
|
+
'email',
|
|
18
24
|
/** AI assistants that send people to sites: ChatGPT tags its links `utm_source=chatgpt.com`, the
|
|
19
25
|
* others show as a referrer. Their own channel group, `organic_ai`, since GA4's default groups
|
|
20
26
|
* would file them under Referral / Unassigned and the question "how much does AI bring" comes up. */
|
|
@@ -91,7 +97,7 @@ export type ReferrerMedium = 'organic' | 'social' | 'video' | 'ai' | 'email';
|
|
|
91
97
|
* first, because `gemini.google.com`, `copilot.microsoft.com` and `yiyan.baidu.com` would
|
|
92
98
|
* otherwise be Google, Microsoft and Baidu search.
|
|
93
99
|
*/
|
|
94
|
-
export declare const REFERRER_SITES: readonly [readonly ["chatgpt", "ai", RegExp], readonly ["perplexity", "ai", RegExp], readonly ["gemini", "ai", RegExp], readonly ["claude", "ai", RegExp], readonly ["copilot", "ai", RegExp], readonly ["grok", "ai", RegExp], readonly ["deepseek", "ai", RegExp], readonly ["doubao", "ai", RegExp], readonly ["kimi", "ai", RegExp], readonly ["qwen", "ai", RegExp], readonly ["yuanbao", "ai", RegExp], readonly ["ernie", "ai", RegExp], readonly ["zhipu", "ai", RegExp], readonly ["gmail", "email", RegExp], readonly ["google", "organic", RegExp], readonly ["microsoft", "organic", RegExp], readonly ["yahoo", "organic", RegExp], readonly ["duckduckgo", "organic", RegExp], readonly ["baidu", "organic", RegExp], readonly ["yandex", "organic", RegExp], readonly ["ecosia", "organic", RegExp], readonly ["naver", "organic", RegExp], readonly ["brave", "organic", RegExp], readonly ["meta", "social", RegExp], readonly ["x", "social", RegExp], readonly ["linkedin", "social", RegExp], readonly ["reddit", "social", RegExp], readonly ["tiktok", "social", RegExp], readonly ["pinterest", "social", RegExp], readonly ["snapchat", "social", RegExp], readonly ["telegram", "social", RegExp], readonly ["youtube", "video", RegExp]];
|
|
100
|
+
export declare const REFERRER_SITES: readonly [readonly ["chatgpt", "ai", RegExp], readonly ["perplexity", "ai", RegExp], readonly ["gemini", "ai", RegExp], readonly ["claude", "ai", RegExp], readonly ["copilot", "ai", RegExp], readonly ["grok", "ai", RegExp], readonly ["deepseek", "ai", RegExp], readonly ["doubao", "ai", RegExp], readonly ["kimi", "ai", RegExp], readonly ["qwen", "ai", RegExp], readonly ["yuanbao", "ai", RegExp], readonly ["ernie", "ai", RegExp], readonly ["zhipu", "ai", RegExp], readonly ["gmail", "email", RegExp], readonly ["outlook", "email", RegExp], readonly ["yahoo_mail", "email", RegExp], readonly ["yandex_mail", "email", RegExp], readonly ["naver_mail", "email", RegExp], readonly ["qq_mail", "email", RegExp], readonly ["netease_mail", "email", RegExp], readonly ["proton_mail", "email", RegExp], readonly ["seznam_mail", "email", RegExp], readonly ["gmx", "email", RegExp], readonly ["aol_mail", "email", RegExp], readonly ["zoho_mail", "email", RegExp], readonly ["email", "email", RegExp], readonly ["google", "organic", RegExp], readonly ["microsoft", "organic", RegExp], readonly ["yahoo", "organic", RegExp], readonly ["duckduckgo", "organic", RegExp], readonly ["baidu", "organic", RegExp], readonly ["yandex", "organic", RegExp], readonly ["ecosia", "organic", RegExp], readonly ["naver", "organic", RegExp], readonly ["brave", "organic", RegExp], readonly ["qwant", "organic", RegExp], readonly ["meta", "social", RegExp], readonly ["x", "social", RegExp], readonly ["linkedin", "social", RegExp], readonly ["reddit", "social", RegExp], readonly ["tiktok", "social", RegExp], readonly ["pinterest", "social", RegExp], readonly ["snapchat", "social", RegExp], readonly ["telegram", "social", RegExp], readonly ["youtube", "video", RegExp]];
|
|
95
101
|
/**
|
|
96
102
|
* The site an Android app's referrer stands for. A link opened from an app on Android arrives with
|
|
97
103
|
* `android-app://<package>/` as its referrer rather than the site's URL; the apps below are read as
|
|
@@ -103,6 +109,7 @@ export declare const ANDROID_APP_HOSTS: Readonly<Record<string, string>>;
|
|
|
103
109
|
/**
|
|
104
110
|
* Referrer hosts that are navigation, not acquisition, when a session happens to start on the way
|
|
105
111
|
* back from them: the local dev host; the payment providers a visitor is bounced through; the
|
|
112
|
+
* bot challenges (Cloudflare Turnstile, hCaptcha) a visitor passes before the page; the
|
|
106
113
|
* sign-in providers whose login lives on a host of its own (Google, Apple, Microsoft, WeChat,
|
|
107
114
|
* Kakao, LINE, Naver, Yahoo, Twitch, X's OAuth 1.0a endpoint); and the hosted auth services a
|
|
108
115
|
* product may sit behind (Auth0, Okta, Supabase, Firebase, Clerk). A product appends its own
|
|
@@ -112,7 +119,7 @@ export declare const ANDROID_APP_HOSTS: Readonly<Record<string, string>>;
|
|
|
112
119
|
* LinkedIn, GitHub, Discord — cannot be told apart by host (referrer policies strip the path) and
|
|
113
120
|
* are left as the referrers they usually are. Regular expressions over the lower-cased host.
|
|
114
121
|
*/
|
|
115
|
-
export declare const REFERRERS_NOT_A_TOUCH: readonly [RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp];
|
|
122
|
+
export declare const REFERRERS_NOT_A_TOUCH: readonly [RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp];
|
|
116
123
|
/** GA4's paid mediums — cpc, cpm, ppc, retargeting, paid_social, … — plus Performance Max. */
|
|
117
124
|
export declare const PAID_MEDIUM: RegExp;
|
|
118
125
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vocabulary.d.mts","names":[],"sources":["../../src/attribution/vocabulary.ts"],"mappings":";;;;;;;;qBAUa;;qBAEA;;qBAEA;;qBAGA
|
|
1
|
+
{"version":3,"file":"vocabulary.d.mts","names":[],"sources":["../../src/attribution/vocabulary.ts"],"mappings":";;;;;;;;qBAUa;;qBAEA;;qBAEA;;qBAGA;;;;;;;;;;;;;;;;;;;;YAgED,kBAAkB;;qBAGjB;;;;;;;YAqBD,uBAAuB;;qBAGtB;WACP;WACA;WACA;WACC;WACD;WACM;WACC;WACL;WACG;WACK;WACH;WACF;WACM;WACI;WACX;WACS;WACJ;WACE;WACC;WACF;WACF;;;;;;;;;;;;;;;;;qBAkBD;;;;;;qBAyBA,iBAAe;YAEhB;;;;;;;;;qBAUC,qDAAc,uCAAA,mCAAA,mCAAA,oCAAA,iCAAA,qCAAA,mCAAA,iCAAA,iCAAA,oCAAA,kCAAA,kCAAA,qCAAA,uCAAA,0CAAA,2CAAA,0CAAA,uCAAA,4CAAA,2CAAA,2CAAA,mCAAA,wCAAA,yCAAA,qCAAA,wCAAA,2CAAA,uCAAA,4CAAA,uCAAA,wCAAA,wCAAA,uCAAA,uCAAA,uCAAA,qCAAA,kCAAA,yCAAA,uCAAA,uCAAA,0CAAA,yCAAA,yCAAA,uCAAA;;;;;;;;qBAiEd,mBAAmB,SAAS;;;;;;;;;;;;;;qBAkC5B,iCAAqB,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA,QAAA;;qBAwCrB,aAAW;;;;;;;;;;;;;;;;;qBAiBX;;;;qBA8CA;;qBAEA;;qBAEA,aAAW;;qBAEX,cAAY;;qBAGZ;YACD,sBAAsB;;;;;;;;qBASrB;YACD,4BAA4B;;;;;;;;;;;;;qBAc3B;;;;;WAKD;;WAEA;;WAEA;;WAED;;;qBAIE"}
|
|
@@ -30,10 +30,23 @@ const CHANNELS = [
|
|
|
30
30
|
"ecosia",
|
|
31
31
|
"naver",
|
|
32
32
|
"brave",
|
|
33
|
+
"qwant",
|
|
33
34
|
"pinterest",
|
|
34
35
|
"youtube",
|
|
35
36
|
"telegram",
|
|
36
37
|
"gmail",
|
|
38
|
+
"outlook",
|
|
39
|
+
"yahoo_mail",
|
|
40
|
+
"yandex_mail",
|
|
41
|
+
"naver_mail",
|
|
42
|
+
"qq_mail",
|
|
43
|
+
"netease_mail",
|
|
44
|
+
"proton_mail",
|
|
45
|
+
"seznam_mail",
|
|
46
|
+
"gmx",
|
|
47
|
+
"aol_mail",
|
|
48
|
+
"zoho_mail",
|
|
49
|
+
"email",
|
|
37
50
|
"chatgpt",
|
|
38
51
|
"perplexity",
|
|
39
52
|
"gemini",
|
|
@@ -278,6 +291,66 @@ const REFERRER_SITES = [
|
|
|
278
291
|
"email",
|
|
279
292
|
/^mail\.google\.com$/
|
|
280
293
|
],
|
|
294
|
+
[
|
|
295
|
+
"outlook",
|
|
296
|
+
"email",
|
|
297
|
+
/^outlook\.(live|office|office365)\.com$|^outlook\.cloud\.microsoft$|(^|\.)safelinks\.protection\.outlook\.com$/
|
|
298
|
+
],
|
|
299
|
+
[
|
|
300
|
+
"yahoo_mail",
|
|
301
|
+
"email",
|
|
302
|
+
/^mail\.yahoo\.[a-z]{2,}(\.[a-z]{2,3})?$/
|
|
303
|
+
],
|
|
304
|
+
[
|
|
305
|
+
"yandex_mail",
|
|
306
|
+
"email",
|
|
307
|
+
/^mail\.yandex\.[a-z]{2,}(\.[a-z]{2,3})?$/
|
|
308
|
+
],
|
|
309
|
+
[
|
|
310
|
+
"naver_mail",
|
|
311
|
+
"email",
|
|
312
|
+
/^mail\.naver\.com$/
|
|
313
|
+
],
|
|
314
|
+
[
|
|
315
|
+
"qq_mail",
|
|
316
|
+
"email",
|
|
317
|
+
/^(wx\.)?mail\.qq\.com$|^exmail\.qq\.com$/
|
|
318
|
+
],
|
|
319
|
+
[
|
|
320
|
+
"netease_mail",
|
|
321
|
+
"email",
|
|
322
|
+
/^mail\.(163|126)\.com$/
|
|
323
|
+
],
|
|
324
|
+
[
|
|
325
|
+
"proton_mail",
|
|
326
|
+
"email",
|
|
327
|
+
/^mail\.proton(mail)?\.(me|com)$/
|
|
328
|
+
],
|
|
329
|
+
[
|
|
330
|
+
"seznam_mail",
|
|
331
|
+
"email",
|
|
332
|
+
/^email\.seznam\.cz$/
|
|
333
|
+
],
|
|
334
|
+
[
|
|
335
|
+
"gmx",
|
|
336
|
+
"email",
|
|
337
|
+
/^deref-(gmx|web|mail)(-\d+)?\.(net|de|com)$|^navigator\.(gmx\.net|web\.de)$/
|
|
338
|
+
],
|
|
339
|
+
[
|
|
340
|
+
"aol_mail",
|
|
341
|
+
"email",
|
|
342
|
+
/^mail\.aol\.com$/
|
|
343
|
+
],
|
|
344
|
+
[
|
|
345
|
+
"zoho_mail",
|
|
346
|
+
"email",
|
|
347
|
+
/^mail\.zoho\.[a-z]{2,}(\.[a-z]{2,3})?$/
|
|
348
|
+
],
|
|
349
|
+
[
|
|
350
|
+
"email",
|
|
351
|
+
"email",
|
|
352
|
+
/^urldefense\.(com|proofpoint\.com)$|^protect(-[a-z]+)?\.mimecast\.com$|(^|\.)mimecastprotect\.com$|^linkprotect\.cudasvc\.com$|^url\.emailprotection\.link$/
|
|
353
|
+
],
|
|
281
354
|
[
|
|
282
355
|
"google",
|
|
283
356
|
"organic",
|
|
@@ -323,6 +396,11 @@ const REFERRER_SITES = [
|
|
|
323
396
|
"organic",
|
|
324
397
|
/^search\.brave\.com$/
|
|
325
398
|
],
|
|
399
|
+
[
|
|
400
|
+
"qwant",
|
|
401
|
+
"organic",
|
|
402
|
+
/(^|\.)qwant\.com$/
|
|
403
|
+
],
|
|
326
404
|
[
|
|
327
405
|
"meta",
|
|
328
406
|
"social",
|
|
@@ -379,6 +457,11 @@ const REFERRER_SITES = [
|
|
|
379
457
|
const ANDROID_APP_HOSTS = {
|
|
380
458
|
"com.google.android.googlequicksearchbox": "www.google.com",
|
|
381
459
|
"com.google.android.gm": "mail.google.com",
|
|
460
|
+
"com.microsoft.office.outlook": "outlook.live.com",
|
|
461
|
+
"com.yahoo.mobile.client.android.mail": "mail.yahoo.com",
|
|
462
|
+
"ru.yandex.mail": "mail.yandex.ru",
|
|
463
|
+
"com.tencent.androidqqmail": "mail.qq.com",
|
|
464
|
+
"ch.protonmail.android": "mail.proton.me",
|
|
382
465
|
"com.google.android.youtube": "www.youtube.com",
|
|
383
466
|
"com.facebook.katana": "www.facebook.com",
|
|
384
467
|
"com.instagram.android": "www.instagram.com",
|
|
@@ -394,6 +477,7 @@ const ANDROID_APP_HOSTS = {
|
|
|
394
477
|
/**
|
|
395
478
|
* Referrer hosts that are navigation, not acquisition, when a session happens to start on the way
|
|
396
479
|
* back from them: the local dev host; the payment providers a visitor is bounced through; the
|
|
480
|
+
* bot challenges (Cloudflare Turnstile, hCaptcha) a visitor passes before the page; the
|
|
397
481
|
* sign-in providers whose login lives on a host of its own (Google, Apple, Microsoft, WeChat,
|
|
398
482
|
* Kakao, LINE, Naver, Yahoo, Twitch, X's OAuth 1.0a endpoint); and the hosted auth services a
|
|
399
483
|
* product may sit behind (Auth0, Okta, Supabase, Firebase, Clerk). A product appends its own
|
|
@@ -405,6 +489,7 @@ const ANDROID_APP_HOSTS = {
|
|
|
405
489
|
*/
|
|
406
490
|
const REFERRERS_NOT_A_TOUCH = [
|
|
407
491
|
/^localhost(:|$)/,
|
|
492
|
+
/^127\.0\.0\.1$/,
|
|
408
493
|
/(^|\.)stripe\.com$/,
|
|
409
494
|
/(^|\.)link\.com$/,
|
|
410
495
|
/^pay\.google\.com$/,
|
|
@@ -412,6 +497,10 @@ const REFERRERS_NOT_A_TOUCH = [
|
|
|
412
497
|
/(^|\.)alipay\.com$/,
|
|
413
498
|
/(^|\.)paddle\.com$/,
|
|
414
499
|
/(^|\.)lemonsqueezy\.com$/,
|
|
500
|
+
/^pay\.amazon\.com$/,
|
|
501
|
+
/^pay\.weixin\.qq\.com$/,
|
|
502
|
+
/^challenges\.cloudflare\.com$/,
|
|
503
|
+
/(^|\.)hcaptcha\.com$/,
|
|
415
504
|
/^apps\.apple\.com$/,
|
|
416
505
|
/^play\.google\.com$/,
|
|
417
506
|
/^accounts\.google\.com$/,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vocabulary.mjs","names":[],"sources":["../../src/attribution/vocabulary.ts"],"sourcesContent":["import type { AdvertisingInfo } from '../track/types';\n\n/**\n * The vocabulary attribution is built from, shared by every product's `session` table and by the\n * dashboards that read it. Data for `classifyTouch` (./classify), which turns it into a channel;\n * a product's dashboards read the same names back. What stays with the product is its own hostnames\n * (referrer exclusions) and its landing-page conventions.\n */\n\n/** A session that arrived through nothing we can name. GA's spelling, so dashboards read as GA. */\nexport const DIRECT_CHANNEL = '(direct)';\n/** The medium of a direct session. */\nexport const NO_MEDIUM = '(none)';\n/** The medium of a tagged link that named a source but no medium. */\nexport const MEDIUM_NOT_SET = '(not set)';\n\n/** The channels the rules below can name. A `utm_source` outside this list is its own channel. */\nexport const CHANNELS = [\n 'meta',\n 'google',\n 'dv360',\n 'microsoft',\n 'tiktok',\n 'reddit',\n 'linkedin',\n 'snapchat',\n 'x',\n 'kakao',\n 'yandex',\n 'yahoo',\n 'duckduckgo',\n 'baidu',\n 'ecosia',\n 'naver',\n 'brave',\n 'pinterest',\n 'youtube',\n 'telegram',\n /** Gmail's web and Android app: a link in a mail that carried no utm. */\n 'gmail',\n /** AI assistants that send people to sites: ChatGPT tags its links `utm_source=chatgpt.com`, the\n * others show as a referrer. Their own channel group, `organic_ai`, since GA4's default groups\n * would file them under Referral / Unassigned and the question \"how much does AI bring\" comes up. */\n 'chatgpt',\n 'perplexity',\n 'gemini',\n 'claude',\n 'copilot',\n 'grok',\n /** and the Chinese ones — DeepSeek, 豆包, Kimi, 通义, 腾讯元宝, 文心一言, 智谱清言 */\n 'deepseek',\n 'doubao',\n 'kimi',\n 'qwen',\n 'yuanbao',\n 'ernie',\n 'zhipu',\n /** the product's own referral programme: the session came through a member's link or code.\n * How a product recognises one (a `/refer/<code>` path, a code at sign-up) is its own rule. Not\n * `referral`: that word is the channel group of any outside site, and a channel of the same name\n * would be impossible to tell apart in queries. */\n 'referral_program',\n] as const;\n\nexport type Channel = (typeof CHANNELS)[number];\n\n/** GA4's default channel groups, as identifiers; a dashboard maps them to display names. */\nexport const CHANNEL_GROUPS = [\n 'direct',\n 'paid_search',\n 'paid_social',\n 'paid_other',\n 'display',\n 'organic_search',\n 'organic_social',\n 'organic_video',\n /** not in GA4's default grouping: sessions an AI assistant sent, by referrer or by ChatGPT's utm */\n 'organic_ai',\n 'referral',\n 'email',\n 'affiliate',\n /** GA4's SMS: `sms` as the source or the medium */\n 'sms',\n /** GA4's Mobile Push Notifications: a medium ending in `push` or naming mobile or a notification, or `firebase` as the source */\n 'mobile_push',\n 'unassigned',\n] as const;\n\nexport type ChannelGroup = (typeof CHANNEL_GROUPS)[number];\n\n/** `utm_source` spellings that mean the same channel: Meta's `{{site_source_name}}` values and the usual synonyms. */\nexport const SOURCE_ALIASES = {\n fb: 'meta',\n ig: 'meta',\n an: 'meta',\n msg: 'meta',\n th: 'meta',\n facebook: 'meta',\n instagram: 'meta',\n bing: 'microsoft',\n twitter: 'x',\n 'google ads': 'google',\n googleads: 'google',\n adwords: 'google',\n 'chatgpt.com': 'chatgpt',\n 'chat.openai.com': 'chatgpt',\n openai: 'chatgpt',\n 'perplexity.ai': 'perplexity',\n 'claude.ai': 'claude',\n 'copilot.com': 'copilot',\n 'deepseek.com': 'deepseek',\n 'doubao.com': 'doubao',\n 'kimi.com': 'kimi',\n} as const satisfies Record<string, Channel>;\n\n/**\n * Which click id belongs to which channel, in priority order, and whether the platform puts it on\n * its ad clicks only (`'ads'`) or on other links as well (`'any'`). Every key is a field this SDK\n * collects (`AdvertisingInfo`).\n *\n * The third column decides whether the id can say a click was paid when a `utm_source` names the\n * channel but no `utm_medium` says how (a Reddit ad tagged `utm_source=reddit` and nothing more).\n * `fbclid` is `'any'`: Meta decorates every outbound link with it, organic posts, profile links\n * and shares included. `twclid` and `ko_click_id` are `'any'` until it is clear they never\n * decorate an organic link.\n *\n * Only URL parameters, never the cookies the tags keep beside them (`_fbc`, `_gcl_aw`, … — every\n * underscore key of `AdvertisingInfo`): a click cookie lives up to 90 days after the click, so\n * counting it named every later visit — typed in, from search, from an email — a new ad click.\n */\nexport const CLICK_ID_CHANNELS = [\n ['fbclid', 'meta', 'any'],\n ['gclid', 'google', 'ads'],\n ['gbraid', 'google', 'ads'],\n ['wbraid', 'google', 'ads'],\n ['gad_source', 'google', 'ads'],\n ['gad_campaignid', 'google', 'ads'],\n ['dclid', 'dv360', 'ads'],\n ['msclkid', 'microsoft', 'ads'],\n ['ttclid', 'tiktok', 'ads'],\n ['rdt_cid', 'reddit', 'ads'],\n ['li_fat_id', 'linkedin', 'ads'],\n ['ScCid', 'snapchat', 'ads'],\n ['twclid', 'x', 'any'],\n ['ko_click_id', 'kakao', 'any'],\n ['yclid', 'yandex', 'ads'],\n ['oppref', 'chatgpt', 'ads'],\n ['epik', 'pinterest', 'ads'],\n] as const satisfies readonly (readonly [keyof AdvertisingInfo, Channel, 'ads' | 'any'])[];\n\n/**\n * A landing page reserved for one channel's ads, for ads that lost their parameters:\n * `/lp/<channel>`, with or without host, query and hash; the capture is the channel. A product\n * that names its landing pages differently replaces this.\n */\nexport const AD_LANDING_PAGE = /^(?:https?:\\/\\/[^/]+)?\\/lp\\/([a-z]+)(?:[/?#]|$)/;\n\nexport type ReferrerMedium = 'organic' | 'social' | 'video' | 'ai' | 'email';\n\n/**\n * Referrer hosts that name a channel, as regular expressions over the lower-cased host,\n * with the medium GA4 gives that kind of site. Anything else that refers is `referral`, kept as\n * its host. A country TLD is one or two labels (`google.com`, `google.co.uk`, `google.com.hk`),\n * never an open tail, so `google.com.evil.io` is not Google. In match order: the AI assistants\n * first, because `gemini.google.com`, `copilot.microsoft.com` and `yiyan.baidu.com` would\n * otherwise be Google, Microsoft and Baidu search.\n */\nexport const REFERRER_SITES = [\n ['chatgpt', 'ai', /(^|\\.)(chatgpt\\.com|chat\\.openai\\.com)$/],\n ['perplexity', 'ai', /(^|\\.)perplexity\\.ai$/],\n ['gemini', 'ai', /^gemini\\.google\\.com$/],\n ['claude', 'ai', /(^|\\.)claude\\.ai$/],\n ['copilot', 'ai', /^copilot\\.microsoft\\.com$|(^|\\.)copilot\\.com$/],\n ['grok', 'ai', /(^|\\.)(grok\\.com|x\\.ai)$/],\n ['deepseek', 'ai', /(^|\\.)deepseek\\.com$/],\n ['doubao', 'ai', /(^|\\.)doubao\\.com$/],\n ['kimi', 'ai', /(^|\\.)(kimi\\.com|kimi\\.moonshot\\.cn|moonshot\\.cn)$/],\n ['qwen', 'ai', /^tongyi\\.aliyun\\.com$|(^|\\.)(tongyi\\.com|qianwen\\.com|qwen\\.ai)$/],\n ['yuanbao', 'ai', /^yuanbao\\.tencent\\.com$/],\n ['ernie', 'ai', /^yiyan\\.baidu\\.com$|(^|\\.)ernie\\.baidu\\.com$/],\n ['zhipu', 'ai', /(^|\\.)(chatglm\\.cn|zhipuai\\.cn|bigmodel\\.cn)$/],\n // Before google, whose pattern it would match.\n ['gmail', 'email', /^mail\\.google\\.com$/],\n ['google', 'organic', /(^|\\.)google\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['microsoft', 'organic', /(^|\\.)bing\\.com$/],\n ['yahoo', 'organic', /(^|\\.)yahoo\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['duckduckgo', 'organic', /(^|\\.)duckduckgo\\.com$/],\n ['baidu', 'organic', /(^|\\.)baidu\\.com$/],\n ['yandex', 'organic', /(^|\\.)yandex\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['ecosia', 'organic', /(^|\\.)ecosia\\.org$/],\n ['naver', 'organic', /(^|\\.)naver\\.com$/],\n ['brave', 'organic', /^search\\.brave\\.com$/],\n ['meta', 'social', /(^|\\.)(facebook\\.com|instagram\\.com|fb\\.com|threads\\.net)$/],\n ['x', 'social', /(^|\\.)(twitter\\.com|x\\.com|t\\.co)$/],\n ['linkedin', 'social', /(^|\\.)(linkedin\\.com|lnkd\\.in)$/],\n ['reddit', 'social', /(^|\\.)reddit\\.com$/],\n ['tiktok', 'social', /(^|\\.)tiktok\\.com$/],\n ['pinterest', 'social', /(^|\\.)pinterest\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['snapchat', 'social', /(^|\\.)snapchat\\.com$/],\n ['telegram', 'social', /^(t\\.me|telegram\\.me|(web\\.)?telegram\\.org)$/],\n ['youtube', 'video', /(^|\\.)(youtube\\.com|youtu\\.be)$/],\n] as const satisfies readonly (readonly [Channel, ReferrerMedium, RegExp])[];\n\n/**\n * The site an Android app's referrer stands for. A link opened from an app on Android arrives with\n * `android-app://<package>/` as its referrer rather than the site's URL; the apps below are read as\n * their sites, so the Google app is a Google search and the LinkedIn app is LinkedIn. A package\n * not listed is kept as the referring host, as GA4 shows it; Meta's apps send their mobile host\n * (`android-app://m.facebook.com`), which needs no entry.\n */\nexport const ANDROID_APP_HOSTS: Readonly<Record<string, string>> = {\n 'com.google.android.googlequicksearchbox': 'www.google.com',\n 'com.google.android.gm': 'mail.google.com',\n 'com.google.android.youtube': 'www.youtube.com',\n 'com.facebook.katana': 'www.facebook.com',\n 'com.instagram.android': 'www.instagram.com',\n 'com.linkedin.android': 'www.linkedin.com',\n 'com.reddit.frontpage': 'www.reddit.com',\n 'com.twitter.android': 'x.com',\n 'com.zhiliaoapp.musically': 'www.tiktok.com',\n 'com.ss.android.ugc.trill': 'www.tiktok.com',\n 'com.pinterest': 'www.pinterest.com',\n 'com.snapchat.android': 'www.snapchat.com',\n 'org.telegram.messenger': 't.me',\n};\n\n/**\n * Referrer hosts that are navigation, not acquisition, when a session happens to start on the way\n * back from them: the local dev host; the payment providers a visitor is bounced through; the\n * sign-in providers whose login lives on a host of its own (Google, Apple, Microsoft, WeChat,\n * Kakao, LINE, Naver, Yahoo, Twitch, X's OAuth 1.0a endpoint); and the hosted auth services a\n * product may sit behind (Auth0, Okta, Supabase, Firebase, Clerk). A product appends its own\n * hostnames — a session that starts from an internal link after the session timeout refers to\n * them. Matched before `REFERRER_SITES`, so `nid.naver.com` and `login.yahoo.com` are not read as\n * a search. Providers whose login shares the host with their content — Facebook, X's OAuth 2.0,\n * LinkedIn, GitHub, Discord — cannot be told apart by host (referrer policies strip the path) and\n * are left as the referrers they usually are. Regular expressions over the lower-cased host.\n */\nexport const REFERRERS_NOT_A_TOUCH = [\n /^localhost(:|$)/,\n // payment\n /(^|\\.)stripe\\.com$/,\n // Link, Stripe's wallet: a customer opens a merchant's site from their purchases on app.link.com\n /(^|\\.)link\\.com$/,\n /^pay\\.google\\.com$/,\n /(^|\\.)paypal\\.com$/,\n /(^|\\.)alipay\\.com$/,\n /(^|\\.)paddle\\.com$/,\n /(^|\\.)lemonsqueezy\\.com$/,\n // app stores\n /^apps\\.apple\\.com$/,\n /^play\\.google\\.com$/,\n // sign-in providers with a login host of their own\n /^accounts\\.google\\.com$/,\n /^appleid\\.apple\\.com$/,\n /^login\\.(microsoftonline|live)\\.com$/,\n /^open\\.weixin\\.qq\\.com$/,\n /^(kauth|accounts)\\.kakao\\.com$/,\n /^access\\.line\\.me$/,\n /^nid\\.naver\\.com$/,\n /^login\\.yahoo\\.com$/,\n /^id\\.twitch\\.tv$/,\n /^api\\.twitter\\.com$/,\n // hosted auth services\n /(^|\\.)auth0\\.com$/,\n /(^|\\.)okta\\.com$/,\n /(^|\\.)supabase\\.co$/,\n /(^|\\.)firebaseapp\\.com$/,\n /(^|\\.)clerk\\.accounts\\.dev$/,\n] as const;\n\n/** GA4's paid mediums — cpc, cpm, ppc, retargeting, paid_social, … — plus Performance Max. */\nexport const PAID_MEDIUM = /^(.*cp.*|ppc|retargeting|paid.*|pmax|performance_max)$/;\n/**\n * Meta's `{{placement}}` values, lower-cased, which an ad URL template of `utm_medium={{placement}}`\n * puts in the medium. Only an ad carries one — an organic post arrives as a referrer, with no utm\n * at all — so a medium in this list is a paid Meta click whatever the source says (a template with\n * `utm_source=meta-websitekeyinfo` was seen). Listed one by one rather than matched by prefix, so\n * that a hand-tagged `instagram_stories` post cannot be mistaken for an ad only because of how it\n * is spelled … which means a placement Meta adds later shows up as organic social (or unassigned,\n * under a source that is no social site) until it is added here. Meta's documented values plus the\n * ones seen in the wild (`facebook_mobile_reels`, `facebook_notification`, `an`), and the literal\n * macro for a template that did not expand it.\n *\n * Accepted for the ads already running with that template; new ads should rather send\n * `utm_medium=cpc` and the placement in a `placement` parameter of its own (collected as the\n * `placement` tag): GA4 matches its paid rule on the medium alone, and files a placement medium\n * under Organic Social.\n */\nexport const META_PLACEMENTS = [\n 'facebook_feed',\n 'facebook_instream',\n 'facebook_desktop_feed',\n 'facebook_mobile_feed',\n 'facebook_mobile_reels',\n 'facebook_reels',\n 'facebook_reels_overlay',\n 'facebook_right_column',\n 'facebook_marketplace',\n 'facebook_video_feeds',\n 'facebook_stories',\n 'facebook_search',\n 'facebook_instream_video',\n 'facebook_instant_article',\n 'facebook_groups_feed',\n 'facebook_profile_feed',\n 'facebook_notification',\n 'facebook_notifications',\n 'facebook_business_explore',\n 'instagram_feed',\n 'instagram_stories',\n 'instagram_reels',\n 'instagram_reels_overlay',\n 'instagram_explore',\n 'instagram_explore_home',\n 'instagram_search',\n 'instagram_shop',\n 'instagram_profile_feed',\n 'instagram_profile_reels',\n 'instagram_igtv',\n 'messenger_inbox',\n 'messenger_stories',\n 'messenger_sponsored_messages',\n 'threads_feed',\n 'threads_stream',\n 'whatsapp_status',\n 'audience_network_classic',\n 'audience_network_rewarded_video',\n 'audience_network_native_banner_and_interstitial',\n 'an',\n 'others',\n /** the macro itself, when Meta did not expand it (written in a field it does not substitute, or a\n * preview click): still only ever produced by a Meta ad URL, so still a paid Meta click */\n '{{placement}}',\n] as const;\nexport const DISPLAY_MEDIUMS = ['display', 'banner', 'expandable', 'interstitial'] as const;\n/** GA4's email spellings, as a source or a medium. */\nexport const EMAIL_MEDIUMS = ['email', 'e-mail', 'e_mail', 'e mail'] as const;\n/** GA4's Mobile Push Notifications medium: ending in `push`, or containing `mobile` or `notification`. */\nexport const PUSH_MEDIUM = /(push$|mobile|notification)/;\n/** A medium that says email or newsletter anywhere in it: `outbound email`, `cold_email`, `newsletter`, and GA4's four. */\nexport const EMAIL_MEDIUM = /(^|[^a-z])(e[-_ ]?mail|newsletter)([^a-z]|$)/;\n\n/** Where a touch came from: a tracked session, or a report by the user or staff. */\nexport const TOUCH_SOURCES = ['tracked', 'reported'] as const;\nexport type TouchSource = (typeof TOUCH_SOURCES)[number];\n\n/**\n * How a reported touch was learned:\n * - survey: the user answered \"how did you hear about us\" (onboarding, checkout)\n * - promo_code: the user redeemed a code that belongs to a channel\n * - phone: a call came in on a channel's dedicated number, marked by staff or the phone system\n * - manual: staff recorded what a customer told them\n */\nexport const REPORTED_TOUCH_KINDS = ['survey', 'promo_code', 'phone', 'manual'] as const;\nexport type ReportedTouchKind = (typeof REPORTED_TOUCH_KINDS)[number];\n\n/**\n * How much a touch is worth against another when attribution has to choose between sessions;\n * lower wins, and the latest wins among equals. Stored on the session when it is classified, so\n * renumbering means reclassifying.\n *\n * This ranks touches against each other. Within one session the rules that name its channel have\n * their own order — `utm_source`, then a click id, then the ad landing page, then the referrer —\n * and the first that says something wins; the session's priority is then the tier of that rule\n * (a campaign rule whose touch is organic by its own tag ranking as a referrer, see `classifyTouch`).\n * So two sessions tagged with utm and gclid respectively are equal here (both `campaign`) and the\n * later one is credited, while one session carrying both is named by its utm.\n */\nexport const TOUCH_PRIORITY = {\n /**\n * utm, click id, ad landing page: someone tagged that link (in that order within a session) —\n * unless the tag itself calls the touch organic, a referral or email, which then ranks as `referrer`\n */\n campaign: 1,\n /** a reported touch staff stand behind: a call on a channel's number, a recorded conversation */\n verified: 1,\n /** the browser said where the visitor came from; nobody chose it */\n referrer: 2,\n /** a reported touch that is the user's claim: a survey answer, a code that may have been shared */\n claimed: 3,\n} as const satisfies Record<string, number>;\n\n/** Which reported kinds are staff facts and which are the user's claims. */\nexport const REPORTED_TOUCH_PRIORITY = {\n phone: TOUCH_PRIORITY.verified,\n manual: TOUCH_PRIORITY.verified,\n survey: TOUCH_PRIORITY.claimed,\n promo_code: TOUCH_PRIORITY.claimed,\n} as const satisfies Record<ReportedTouchKind, number>;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vocabulary.mjs","names":[],"sources":["../../src/attribution/vocabulary.ts"],"sourcesContent":["import type { AdvertisingInfo } from '../track/types';\n\n/**\n * The vocabulary attribution is built from, shared by every product's `session` table and by the\n * dashboards that read it. Data for `classifyTouch` (./classify), which turns it into a channel;\n * a product's dashboards read the same names back. What stays with the product is its own hostnames\n * (referrer exclusions) and its landing-page conventions.\n */\n\n/** A session that arrived through nothing we can name. GA's spelling, so dashboards read as GA. */\nexport const DIRECT_CHANNEL = '(direct)';\n/** The medium of a direct session. */\nexport const NO_MEDIUM = '(none)';\n/** The medium of a tagged link that named a source but no medium. */\nexport const MEDIUM_NOT_SET = '(not set)';\n\n/** The channels the rules below can name. A `utm_source` outside this list is its own channel. */\nexport const CHANNELS = [\n 'meta',\n 'google',\n 'dv360',\n 'microsoft',\n 'tiktok',\n 'reddit',\n 'linkedin',\n 'snapchat',\n 'x',\n 'kakao',\n 'yandex',\n 'yahoo',\n 'duckduckgo',\n 'baidu',\n 'ecosia',\n 'naver',\n 'brave',\n 'qwant',\n 'pinterest',\n 'youtube',\n 'telegram',\n /** Webmail, by its web and Android app: a link in a mail that carried no utm. Outlook includes\n * Microsoft 365's new `outlook.cloud.microsoft` and the links its Safe Links rewrote. */\n 'gmail',\n 'outlook',\n 'yahoo_mail',\n 'yandex_mail',\n 'naver_mail',\n 'qq_mail',\n 'netease_mail',\n 'proton_mail',\n 'seznam_mail',\n /** GMX and WEB.DE, one company's webmail, whose links leave through its `deref-*` redirector */\n 'gmx',\n 'aol_mail',\n 'zoho_mail',\n /** A mail whose client cannot be told: the link was rewritten by an email security gateway\n * (Proofpoint URL Defense, Mimecast, Barracuda, …). */\n 'email',\n /** AI assistants that send people to sites: ChatGPT tags its links `utm_source=chatgpt.com`, the\n * others show as a referrer. Their own channel group, `organic_ai`, since GA4's default groups\n * would file them under Referral / Unassigned and the question \"how much does AI bring\" comes up. */\n 'chatgpt',\n 'perplexity',\n 'gemini',\n 'claude',\n 'copilot',\n 'grok',\n /** and the Chinese ones — DeepSeek, 豆包, Kimi, 通义, 腾讯元宝, 文心一言, 智谱清言 */\n 'deepseek',\n 'doubao',\n 'kimi',\n 'qwen',\n 'yuanbao',\n 'ernie',\n 'zhipu',\n /** the product's own referral programme: the session came through a member's link or code.\n * How a product recognises one (a `/refer/<code>` path, a code at sign-up) is its own rule. Not\n * `referral`: that word is the channel group of any outside site, and a channel of the same name\n * would be impossible to tell apart in queries. */\n 'referral_program',\n] as const;\n\nexport type Channel = (typeof CHANNELS)[number];\n\n/** GA4's default channel groups, as identifiers; a dashboard maps them to display names. */\nexport const CHANNEL_GROUPS = [\n 'direct',\n 'paid_search',\n 'paid_social',\n 'paid_other',\n 'display',\n 'organic_search',\n 'organic_social',\n 'organic_video',\n /** not in GA4's default grouping: sessions an AI assistant sent, by referrer or by ChatGPT's utm */\n 'organic_ai',\n 'referral',\n 'email',\n 'affiliate',\n /** GA4's SMS: `sms` as the source or the medium */\n 'sms',\n /** GA4's Mobile Push Notifications: a medium ending in `push` or naming mobile or a notification, or `firebase` as the source */\n 'mobile_push',\n 'unassigned',\n] as const;\n\nexport type ChannelGroup = (typeof CHANNEL_GROUPS)[number];\n\n/** `utm_source` spellings that mean the same channel: Meta's `{{site_source_name}}` values and the usual synonyms. */\nexport const SOURCE_ALIASES = {\n fb: 'meta',\n ig: 'meta',\n an: 'meta',\n msg: 'meta',\n th: 'meta',\n facebook: 'meta',\n instagram: 'meta',\n bing: 'microsoft',\n twitter: 'x',\n 'google ads': 'google',\n googleads: 'google',\n adwords: 'google',\n 'chatgpt.com': 'chatgpt',\n 'chat.openai.com': 'chatgpt',\n openai: 'chatgpt',\n 'perplexity.ai': 'perplexity',\n 'claude.ai': 'claude',\n 'copilot.com': 'copilot',\n 'deepseek.com': 'deepseek',\n 'doubao.com': 'doubao',\n 'kimi.com': 'kimi',\n} as const satisfies Record<string, Channel>;\n\n/**\n * Which click id belongs to which channel, in priority order, and whether the platform puts it on\n * its ad clicks only (`'ads'`) or on other links as well (`'any'`). Every key is a field this SDK\n * collects (`AdvertisingInfo`).\n *\n * The third column decides whether the id can say a click was paid when a `utm_source` names the\n * channel but no `utm_medium` says how (a Reddit ad tagged `utm_source=reddit` and nothing more).\n * `fbclid` is `'any'`: Meta decorates every outbound link with it, organic posts, profile links\n * and shares included. `twclid` and `ko_click_id` are `'any'` until it is clear they never\n * decorate an organic link.\n *\n * Only URL parameters, never the cookies the tags keep beside them (`_fbc`, `_gcl_aw`, … — every\n * underscore key of `AdvertisingInfo`): a click cookie lives up to 90 days after the click, so\n * counting it named every later visit — typed in, from search, from an email — a new ad click.\n */\nexport const CLICK_ID_CHANNELS = [\n ['fbclid', 'meta', 'any'],\n ['gclid', 'google', 'ads'],\n ['gbraid', 'google', 'ads'],\n ['wbraid', 'google', 'ads'],\n ['gad_source', 'google', 'ads'],\n ['gad_campaignid', 'google', 'ads'],\n ['dclid', 'dv360', 'ads'],\n ['msclkid', 'microsoft', 'ads'],\n ['ttclid', 'tiktok', 'ads'],\n ['rdt_cid', 'reddit', 'ads'],\n ['li_fat_id', 'linkedin', 'ads'],\n ['ScCid', 'snapchat', 'ads'],\n ['twclid', 'x', 'any'],\n ['ko_click_id', 'kakao', 'any'],\n ['yclid', 'yandex', 'ads'],\n ['oppref', 'chatgpt', 'ads'],\n ['epik', 'pinterest', 'ads'],\n] as const satisfies readonly (readonly [keyof AdvertisingInfo, Channel, 'ads' | 'any'])[];\n\n/**\n * A landing page reserved for one channel's ads, for ads that lost their parameters:\n * `/lp/<channel>`, with or without host, query and hash; the capture is the channel. A product\n * that names its landing pages differently replaces this.\n */\nexport const AD_LANDING_PAGE = /^(?:https?:\\/\\/[^/]+)?\\/lp\\/([a-z]+)(?:[/?#]|$)/;\n\nexport type ReferrerMedium = 'organic' | 'social' | 'video' | 'ai' | 'email';\n\n/**\n * Referrer hosts that name a channel, as regular expressions over the lower-cased host,\n * with the medium GA4 gives that kind of site. Anything else that refers is `referral`, kept as\n * its host. A country TLD is one or two labels (`google.com`, `google.co.uk`, `google.com.hk`),\n * never an open tail, so `google.com.evil.io` is not Google. In match order: the AI assistants\n * first, because `gemini.google.com`, `copilot.microsoft.com` and `yiyan.baidu.com` would\n * otherwise be Google, Microsoft and Baidu search.\n */\nexport const REFERRER_SITES = [\n ['chatgpt', 'ai', /(^|\\.)(chatgpt\\.com|chat\\.openai\\.com)$/],\n ['perplexity', 'ai', /(^|\\.)perplexity\\.ai$/],\n ['gemini', 'ai', /^gemini\\.google\\.com$/],\n ['claude', 'ai', /(^|\\.)claude\\.ai$/],\n ['copilot', 'ai', /^copilot\\.microsoft\\.com$|(^|\\.)copilot\\.com$/],\n ['grok', 'ai', /(^|\\.)(grok\\.com|x\\.ai)$/],\n ['deepseek', 'ai', /(^|\\.)deepseek\\.com$/],\n ['doubao', 'ai', /(^|\\.)doubao\\.com$/],\n ['kimi', 'ai', /(^|\\.)(kimi\\.com|kimi\\.moonshot\\.cn|moonshot\\.cn)$/],\n ['qwen', 'ai', /^tongyi\\.aliyun\\.com$|(^|\\.)(tongyi\\.com|qianwen\\.com|qwen\\.ai)$/],\n ['yuanbao', 'ai', /^yuanbao\\.tencent\\.com$/],\n ['ernie', 'ai', /^yiyan\\.baidu\\.com$|(^|\\.)ernie\\.baidu\\.com$/],\n ['zhipu', 'ai', /(^|\\.)(chatglm\\.cn|zhipuai\\.cn|bigmodel\\.cn)$/],\n // Webmail before the search engines whose patterns would match it: Gmail is on google.*,\n // Yahoo Mail on yahoo.*, Yandex Mail on yandex.*, Naver Mail on naver.com.\n ['gmail', 'email', /^mail\\.google\\.com$/],\n [\n 'outlook',\n 'email',\n /^outlook\\.(live|office|office365)\\.com$|^outlook\\.cloud\\.microsoft$|(^|\\.)safelinks\\.protection\\.outlook\\.com$/,\n ],\n ['yahoo_mail', 'email', /^mail\\.yahoo\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['yandex_mail', 'email', /^mail\\.yandex\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['naver_mail', 'email', /^mail\\.naver\\.com$/],\n ['qq_mail', 'email', /^(wx\\.)?mail\\.qq\\.com$|^exmail\\.qq\\.com$/],\n ['netease_mail', 'email', /^mail\\.(163|126)\\.com$/],\n ['proton_mail', 'email', /^mail\\.proton(mail)?\\.(me|com)$/],\n ['seznam_mail', 'email', /^email\\.seznam\\.cz$/],\n ['gmx', 'email', /^deref-(gmx|web|mail)(-\\d+)?\\.(net|de|com)$|^navigator\\.(gmx\\.net|web\\.de)$/],\n ['aol_mail', 'email', /^mail\\.aol\\.com$/],\n ['zoho_mail', 'email', /^mail\\.zoho\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n [\n 'email',\n 'email',\n /^urldefense\\.(com|proofpoint\\.com)$|^protect(-[a-z]+)?\\.mimecast\\.com$|(^|\\.)mimecastprotect\\.com$|^linkprotect\\.cudasvc\\.com$|^url\\.emailprotection\\.link$/,\n ],\n ['google', 'organic', /(^|\\.)google\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['microsoft', 'organic', /(^|\\.)bing\\.com$/],\n ['yahoo', 'organic', /(^|\\.)yahoo\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['duckduckgo', 'organic', /(^|\\.)duckduckgo\\.com$/],\n ['baidu', 'organic', /(^|\\.)baidu\\.com$/],\n ['yandex', 'organic', /(^|\\.)yandex\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['ecosia', 'organic', /(^|\\.)ecosia\\.org$/],\n ['naver', 'organic', /(^|\\.)naver\\.com$/],\n ['brave', 'organic', /^search\\.brave\\.com$/],\n ['qwant', 'organic', /(^|\\.)qwant\\.com$/],\n ['meta', 'social', /(^|\\.)(facebook\\.com|instagram\\.com|fb\\.com|threads\\.net)$/],\n ['x', 'social', /(^|\\.)(twitter\\.com|x\\.com|t\\.co)$/],\n ['linkedin', 'social', /(^|\\.)(linkedin\\.com|lnkd\\.in)$/],\n ['reddit', 'social', /(^|\\.)reddit\\.com$/],\n ['tiktok', 'social', /(^|\\.)tiktok\\.com$/],\n ['pinterest', 'social', /(^|\\.)pinterest\\.[a-z]{2,}(\\.[a-z]{2,3})?$/],\n ['snapchat', 'social', /(^|\\.)snapchat\\.com$/],\n ['telegram', 'social', /^(t\\.me|telegram\\.me|(web\\.)?telegram\\.org)$/],\n ['youtube', 'video', /(^|\\.)(youtube\\.com|youtu\\.be)$/],\n] as const satisfies readonly (readonly [Channel, ReferrerMedium, RegExp])[];\n\n/**\n * The site an Android app's referrer stands for. A link opened from an app on Android arrives with\n * `android-app://<package>/` as its referrer rather than the site's URL; the apps below are read as\n * their sites, so the Google app is a Google search and the LinkedIn app is LinkedIn. A package\n * not listed is kept as the referring host, as GA4 shows it; Meta's apps send their mobile host\n * (`android-app://m.facebook.com`), which needs no entry.\n */\nexport const ANDROID_APP_HOSTS: Readonly<Record<string, string>> = {\n 'com.google.android.googlequicksearchbox': 'www.google.com',\n 'com.google.android.gm': 'mail.google.com',\n 'com.microsoft.office.outlook': 'outlook.live.com',\n 'com.yahoo.mobile.client.android.mail': 'mail.yahoo.com',\n 'ru.yandex.mail': 'mail.yandex.ru',\n 'com.tencent.androidqqmail': 'mail.qq.com',\n 'ch.protonmail.android': 'mail.proton.me',\n 'com.google.android.youtube': 'www.youtube.com',\n 'com.facebook.katana': 'www.facebook.com',\n 'com.instagram.android': 'www.instagram.com',\n 'com.linkedin.android': 'www.linkedin.com',\n 'com.reddit.frontpage': 'www.reddit.com',\n 'com.twitter.android': 'x.com',\n 'com.zhiliaoapp.musically': 'www.tiktok.com',\n 'com.ss.android.ugc.trill': 'www.tiktok.com',\n 'com.pinterest': 'www.pinterest.com',\n 'com.snapchat.android': 'www.snapchat.com',\n 'org.telegram.messenger': 't.me',\n};\n\n/**\n * Referrer hosts that are navigation, not acquisition, when a session happens to start on the way\n * back from them: the local dev host; the payment providers a visitor is bounced through; the\n * bot challenges (Cloudflare Turnstile, hCaptcha) a visitor passes before the page; the\n * sign-in providers whose login lives on a host of its own (Google, Apple, Microsoft, WeChat,\n * Kakao, LINE, Naver, Yahoo, Twitch, X's OAuth 1.0a endpoint); and the hosted auth services a\n * product may sit behind (Auth0, Okta, Supabase, Firebase, Clerk). A product appends its own\n * hostnames — a session that starts from an internal link after the session timeout refers to\n * them. Matched before `REFERRER_SITES`, so `nid.naver.com` and `login.yahoo.com` are not read as\n * a search. Providers whose login shares the host with their content — Facebook, X's OAuth 2.0,\n * LinkedIn, GitHub, Discord — cannot be told apart by host (referrer policies strip the path) and\n * are left as the referrers they usually are. Regular expressions over the lower-cased host.\n */\nexport const REFERRERS_NOT_A_TOUCH = [\n /^localhost(:|$)/,\n /^127\\.0\\.0\\.1$/,\n // payment\n /(^|\\.)stripe\\.com$/,\n // Link, Stripe's wallet: a customer opens a merchant's site from their purchases on app.link.com\n /(^|\\.)link\\.com$/,\n /^pay\\.google\\.com$/,\n /(^|\\.)paypal\\.com$/,\n /(^|\\.)alipay\\.com$/,\n /(^|\\.)paddle\\.com$/,\n /(^|\\.)lemonsqueezy\\.com$/,\n /^pay\\.amazon\\.com$/,\n /^pay\\.weixin\\.qq\\.com$/,\n // bot challenges a visitor is sent through before the page\n /^challenges\\.cloudflare\\.com$/,\n /(^|\\.)hcaptcha\\.com$/,\n // app stores\n /^apps\\.apple\\.com$/,\n /^play\\.google\\.com$/,\n // sign-in providers with a login host of their own\n /^accounts\\.google\\.com$/,\n /^appleid\\.apple\\.com$/,\n /^login\\.(microsoftonline|live)\\.com$/,\n /^open\\.weixin\\.qq\\.com$/,\n /^(kauth|accounts)\\.kakao\\.com$/,\n /^access\\.line\\.me$/,\n /^nid\\.naver\\.com$/,\n /^login\\.yahoo\\.com$/,\n /^id\\.twitch\\.tv$/,\n /^api\\.twitter\\.com$/,\n // hosted auth services\n /(^|\\.)auth0\\.com$/,\n /(^|\\.)okta\\.com$/,\n /(^|\\.)supabase\\.co$/,\n /(^|\\.)firebaseapp\\.com$/,\n /(^|\\.)clerk\\.accounts\\.dev$/,\n] as const;\n\n/** GA4's paid mediums — cpc, cpm, ppc, retargeting, paid_social, … — plus Performance Max. */\nexport const PAID_MEDIUM = /^(.*cp.*|ppc|retargeting|paid.*|pmax|performance_max)$/;\n/**\n * Meta's `{{placement}}` values, lower-cased, which an ad URL template of `utm_medium={{placement}}`\n * puts in the medium. Only an ad carries one — an organic post arrives as a referrer, with no utm\n * at all — so a medium in this list is a paid Meta click whatever the source says (a template with\n * `utm_source=meta-websitekeyinfo` was seen). Listed one by one rather than matched by prefix, so\n * that a hand-tagged `instagram_stories` post cannot be mistaken for an ad only because of how it\n * is spelled … which means a placement Meta adds later shows up as organic social (or unassigned,\n * under a source that is no social site) until it is added here. Meta's documented values plus the\n * ones seen in the wild (`facebook_mobile_reels`, `facebook_notification`, `an`), and the literal\n * macro for a template that did not expand it.\n *\n * Accepted for the ads already running with that template; new ads should rather send\n * `utm_medium=cpc` and the placement in a `placement` parameter of its own (collected as the\n * `placement` tag): GA4 matches its paid rule on the medium alone, and files a placement medium\n * under Organic Social.\n */\nexport const META_PLACEMENTS = [\n 'facebook_feed',\n 'facebook_instream',\n 'facebook_desktop_feed',\n 'facebook_mobile_feed',\n 'facebook_mobile_reels',\n 'facebook_reels',\n 'facebook_reels_overlay',\n 'facebook_right_column',\n 'facebook_marketplace',\n 'facebook_video_feeds',\n 'facebook_stories',\n 'facebook_search',\n 'facebook_instream_video',\n 'facebook_instant_article',\n 'facebook_groups_feed',\n 'facebook_profile_feed',\n 'facebook_notification',\n 'facebook_notifications',\n 'facebook_business_explore',\n 'instagram_feed',\n 'instagram_stories',\n 'instagram_reels',\n 'instagram_reels_overlay',\n 'instagram_explore',\n 'instagram_explore_home',\n 'instagram_search',\n 'instagram_shop',\n 'instagram_profile_feed',\n 'instagram_profile_reels',\n 'instagram_igtv',\n 'messenger_inbox',\n 'messenger_stories',\n 'messenger_sponsored_messages',\n 'threads_feed',\n 'threads_stream',\n 'whatsapp_status',\n 'audience_network_classic',\n 'audience_network_rewarded_video',\n 'audience_network_native_banner_and_interstitial',\n 'an',\n 'others',\n /** the macro itself, when Meta did not expand it (written in a field it does not substitute, or a\n * preview click): still only ever produced by a Meta ad URL, so still a paid Meta click */\n '{{placement}}',\n] as const;\nexport const DISPLAY_MEDIUMS = ['display', 'banner', 'expandable', 'interstitial'] as const;\n/** GA4's email spellings, as a source or a medium. */\nexport const EMAIL_MEDIUMS = ['email', 'e-mail', 'e_mail', 'e mail'] as const;\n/** GA4's Mobile Push Notifications medium: ending in `push`, or containing `mobile` or `notification`. */\nexport const PUSH_MEDIUM = /(push$|mobile|notification)/;\n/** A medium that says email or newsletter anywhere in it: `outbound email`, `cold_email`, `newsletter`, and GA4's four. */\nexport const EMAIL_MEDIUM = /(^|[^a-z])(e[-_ ]?mail|newsletter)([^a-z]|$)/;\n\n/** Where a touch came from: a tracked session, or a report by the user or staff. */\nexport const TOUCH_SOURCES = ['tracked', 'reported'] as const;\nexport type TouchSource = (typeof TOUCH_SOURCES)[number];\n\n/**\n * How a reported touch was learned:\n * - survey: the user answered \"how did you hear about us\" (onboarding, checkout)\n * - promo_code: the user redeemed a code that belongs to a channel\n * - phone: a call came in on a channel's dedicated number, marked by staff or the phone system\n * - manual: staff recorded what a customer told them\n */\nexport const REPORTED_TOUCH_KINDS = ['survey', 'promo_code', 'phone', 'manual'] as const;\nexport type ReportedTouchKind = (typeof REPORTED_TOUCH_KINDS)[number];\n\n/**\n * How much a touch is worth against another when attribution has to choose between sessions;\n * lower wins, and the latest wins among equals. Stored on the session when it is classified, so\n * renumbering means reclassifying.\n *\n * This ranks touches against each other. Within one session the rules that name its channel have\n * their own order — `utm_source`, then a click id, then the ad landing page, then the referrer —\n * and the first that says something wins; the session's priority is then the tier of that rule\n * (a campaign rule whose touch is organic by its own tag ranking as a referrer, see `classifyTouch`).\n * So two sessions tagged with utm and gclid respectively are equal here (both `campaign`) and the\n * later one is credited, while one session carrying both is named by its utm.\n */\nexport const TOUCH_PRIORITY = {\n /**\n * utm, click id, ad landing page: someone tagged that link (in that order within a session) —\n * unless the tag itself calls the touch organic, a referral or email, which then ranks as `referrer`\n */\n campaign: 1,\n /** a reported touch staff stand behind: a call on a channel's number, a recorded conversation */\n verified: 1,\n /** the browser said where the visitor came from; nobody chose it */\n referrer: 2,\n /** a reported touch that is the user's claim: a survey answer, a code that may have been shared */\n claimed: 3,\n} as const satisfies Record<string, number>;\n\n/** Which reported kinds are staff facts and which are the user's claims. */\nexport const REPORTED_TOUCH_PRIORITY = {\n phone: TOUCH_PRIORITY.verified,\n manual: TOUCH_PRIORITY.verified,\n survey: TOUCH_PRIORITY.claimed,\n promo_code: TOUCH_PRIORITY.claimed,\n} as const satisfies Record<ReportedTouchKind, number>;\n"],"mappings":"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"}
|