@shware/analytics 8.11.0 → 8.13.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/classify.cjs +128 -0
- package/dist/attribution/classify.cjs.map +1 -0
- package/dist/attribution/classify.d.cts +62 -0
- package/dist/attribution/classify.d.cts.map +1 -0
- package/dist/attribution/classify.d.mts +62 -0
- package/dist/attribution/classify.d.mts.map +1 -0
- package/dist/attribution/classify.mjs +126 -0
- package/dist/attribution/classify.mjs.map +1 -0
- package/dist/attribution/index.cjs +23 -274
- package/dist/attribution/index.d.cts +3 -108
- package/dist/attribution/index.d.mts +3 -108
- package/dist/attribution/index.mjs +3 -258
- package/dist/attribution/vocabulary.cjs +449 -0
- package/dist/attribution/vocabulary.cjs.map +1 -0
- package/dist/attribution/vocabulary.d.cts +154 -0
- package/dist/attribution/vocabulary.d.cts.map +1 -0
- package/dist/attribution/vocabulary.d.mts +154 -0
- package/dist/attribution/vocabulary.d.mts.map +1 -0
- package/dist/attribution/vocabulary.mjs +430 -0
- package/dist/attribution/vocabulary.mjs.map +1 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/third-parties/google-analytics.d.cts +1 -1
- package/dist/third-parties/google-analytics.d.mts +1 -1
- package/dist/third-parties/linkedin-insight-tag.d.cts +1 -1
- package/dist/third-parties/linkedin-insight-tag.d.mts +1 -1
- package/dist/third-parties/meta-pixel.d.cts +1 -1
- package/dist/third-parties/meta-pixel.d.mts +1 -1
- package/dist/third-parties/microsoft-uet.d.cts +1 -1
- package/dist/third-parties/microsoft-uet.d.mts +1 -1
- package/dist/third-parties/openai-pixel.d.cts +1 -1
- package/dist/third-parties/openai-pixel.d.mts +1 -1
- package/dist/third-parties/reddit-pixel.d.cts +1 -1
- package/dist/third-parties/reddit-pixel.d.mts +1 -1
- package/dist/visitor/types.d.cts +1 -1
- package/dist/visitor/types.d.mts +1 -1
- package/package.json +1 -1
- package/dist/attribution/index.cjs.map +0 -1
- package/dist/attribution/index.d.cts.map +0 -1
- package/dist/attribution/index.d.mts.map +0 -1
- package/dist/attribution/index.mjs.map +0 -1
|
@@ -1,258 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* dashboards that read it. Data only, no SQL: a product turns these into its view. What stays with
|
|
5
|
-
* the product is its own hostnames (referrer exclusions) and its landing-page convention.
|
|
6
|
-
*/
|
|
7
|
-
/** A session that arrived through nothing we can name. GA's spelling, so dashboards read as GA. */
|
|
8
|
-
const DIRECT_CHANNEL = "(direct)";
|
|
9
|
-
/** The medium of a direct session. */
|
|
10
|
-
const NO_MEDIUM = "(none)";
|
|
11
|
-
/** The medium of a tagged link that named a source but no medium. */
|
|
12
|
-
const MEDIUM_NOT_SET = "(not set)";
|
|
13
|
-
/** The channels the rules below can name. A `utm_source` outside this list is its own channel. */
|
|
14
|
-
const CHANNELS = [
|
|
15
|
-
"meta",
|
|
16
|
-
"google",
|
|
17
|
-
"dv360",
|
|
18
|
-
"microsoft",
|
|
19
|
-
"tiktok",
|
|
20
|
-
"reddit",
|
|
21
|
-
"linkedin",
|
|
22
|
-
"snapchat",
|
|
23
|
-
"x",
|
|
24
|
-
"kakao",
|
|
25
|
-
"yandex",
|
|
26
|
-
"yahoo",
|
|
27
|
-
"duckduckgo",
|
|
28
|
-
"baidu",
|
|
29
|
-
"ecosia",
|
|
30
|
-
"naver",
|
|
31
|
-
"brave",
|
|
32
|
-
"pinterest",
|
|
33
|
-
"youtube",
|
|
34
|
-
"referral_program"
|
|
35
|
-
];
|
|
36
|
-
/** GA4's default channel groups, as identifiers; a dashboard maps them to display names. */
|
|
37
|
-
const CHANNEL_GROUPS = [
|
|
38
|
-
"direct",
|
|
39
|
-
"paid_search",
|
|
40
|
-
"paid_social",
|
|
41
|
-
"paid_other",
|
|
42
|
-
"display",
|
|
43
|
-
"organic_search",
|
|
44
|
-
"organic_social",
|
|
45
|
-
"organic_video",
|
|
46
|
-
"referral",
|
|
47
|
-
"email",
|
|
48
|
-
"affiliate",
|
|
49
|
-
"unassigned"
|
|
50
|
-
];
|
|
51
|
-
/** `utm_source` spellings that mean the same channel: Meta's `{{site_source_name}}` values and the usual synonyms. */
|
|
52
|
-
const SOURCE_ALIASES = {
|
|
53
|
-
fb: "meta",
|
|
54
|
-
ig: "meta",
|
|
55
|
-
an: "meta",
|
|
56
|
-
msg: "meta",
|
|
57
|
-
facebook: "meta",
|
|
58
|
-
instagram: "meta",
|
|
59
|
-
bing: "microsoft",
|
|
60
|
-
twitter: "x"
|
|
61
|
-
};
|
|
62
|
-
/**
|
|
63
|
-
* Which click id belongs to which channel, in priority order. Every key is a field this SDK
|
|
64
|
-
* collects (`AdvertisingInfo`); `fbp` and `rdt_uuid` are deliberately absent — they are pixel
|
|
65
|
-
* browser ids, present with or without a click.
|
|
66
|
-
*/
|
|
67
|
-
const CLICK_ID_CHANNELS = [
|
|
68
|
-
["fbclid", "meta"],
|
|
69
|
-
["fbc", "meta"],
|
|
70
|
-
["gclid", "google"],
|
|
71
|
-
["gbraid", "google"],
|
|
72
|
-
["wbraid", "google"],
|
|
73
|
-
["gad_source", "google"],
|
|
74
|
-
["gad_campaignid", "google"],
|
|
75
|
-
["dclid", "dv360"],
|
|
76
|
-
["msclkid", "microsoft"],
|
|
77
|
-
["ttclid", "tiktok"],
|
|
78
|
-
["rdt_cid", "reddit"],
|
|
79
|
-
["li_fat_id", "linkedin"],
|
|
80
|
-
["sccid", "snapchat"],
|
|
81
|
-
["twclid", "x"],
|
|
82
|
-
["ko_click_id", "kakao"],
|
|
83
|
-
["yclid", "yandex"]
|
|
84
|
-
];
|
|
85
|
-
/**
|
|
86
|
-
* A landing page reserved for one channel's ads, for ads that lost their parameters:
|
|
87
|
-
* `/lp/<channel>`, with or without host, query and hash; the capture is the channel. A product
|
|
88
|
-
* that names its landing pages differently replaces this.
|
|
89
|
-
*/
|
|
90
|
-
const AD_LANDING_PAGE = "^(?:https?://[^/]+)?/lp/([a-z]+)(?:[/?#]|$)";
|
|
91
|
-
/**
|
|
92
|
-
* Referrer hosts that name a channel, as POSIX regular expressions over the lower-cased host,
|
|
93
|
-
* with the medium GA4 gives that kind of site. Anything else that refers is `referral`, kept as
|
|
94
|
-
* its host. A country TLD is one or two labels (`google.com`, `google.co.uk`, `google.com.hk`),
|
|
95
|
-
* never an open tail, so `google.com.evil.io` is not Google.
|
|
96
|
-
*/
|
|
97
|
-
const REFERRER_SITES = [
|
|
98
|
-
[
|
|
99
|
-
"google",
|
|
100
|
-
"organic",
|
|
101
|
-
String.raw`(^|\.)google\.[a-z]{2,}(\.[a-z]{2,3})?$`
|
|
102
|
-
],
|
|
103
|
-
[
|
|
104
|
-
"microsoft",
|
|
105
|
-
"organic",
|
|
106
|
-
String.raw`(^|\.)bing\.com$`
|
|
107
|
-
],
|
|
108
|
-
[
|
|
109
|
-
"yahoo",
|
|
110
|
-
"organic",
|
|
111
|
-
String.raw`(^|\.)yahoo\.[a-z]{2,}(\.[a-z]{2,3})?$`
|
|
112
|
-
],
|
|
113
|
-
[
|
|
114
|
-
"duckduckgo",
|
|
115
|
-
"organic",
|
|
116
|
-
String.raw`(^|\.)duckduckgo\.com$`
|
|
117
|
-
],
|
|
118
|
-
[
|
|
119
|
-
"baidu",
|
|
120
|
-
"organic",
|
|
121
|
-
String.raw`(^|\.)baidu\.com$`
|
|
122
|
-
],
|
|
123
|
-
[
|
|
124
|
-
"yandex",
|
|
125
|
-
"organic",
|
|
126
|
-
String.raw`(^|\.)yandex\.[a-z]{2,}(\.[a-z]{2,3})?$`
|
|
127
|
-
],
|
|
128
|
-
[
|
|
129
|
-
"ecosia",
|
|
130
|
-
"organic",
|
|
131
|
-
String.raw`(^|\.)ecosia\.org$`
|
|
132
|
-
],
|
|
133
|
-
[
|
|
134
|
-
"naver",
|
|
135
|
-
"organic",
|
|
136
|
-
String.raw`(^|\.)naver\.com$`
|
|
137
|
-
],
|
|
138
|
-
[
|
|
139
|
-
"brave",
|
|
140
|
-
"organic",
|
|
141
|
-
String.raw`^search\.brave\.com$`
|
|
142
|
-
],
|
|
143
|
-
[
|
|
144
|
-
"meta",
|
|
145
|
-
"social",
|
|
146
|
-
String.raw`(^|\.)(facebook\.com|instagram\.com|fb\.com|threads\.net)$`
|
|
147
|
-
],
|
|
148
|
-
[
|
|
149
|
-
"x",
|
|
150
|
-
"social",
|
|
151
|
-
String.raw`(^|\.)(twitter\.com|x\.com|t\.co)$`
|
|
152
|
-
],
|
|
153
|
-
[
|
|
154
|
-
"linkedin",
|
|
155
|
-
"social",
|
|
156
|
-
String.raw`(^|\.)(linkedin\.com|lnkd\.in)$`
|
|
157
|
-
],
|
|
158
|
-
[
|
|
159
|
-
"reddit",
|
|
160
|
-
"social",
|
|
161
|
-
String.raw`(^|\.)reddit\.com$`
|
|
162
|
-
],
|
|
163
|
-
[
|
|
164
|
-
"tiktok",
|
|
165
|
-
"social",
|
|
166
|
-
String.raw`(^|\.)tiktok\.com$`
|
|
167
|
-
],
|
|
168
|
-
[
|
|
169
|
-
"pinterest",
|
|
170
|
-
"social",
|
|
171
|
-
String.raw`(^|\.)pinterest\.[a-z]{2,}(\.[a-z]{2,3})?$`
|
|
172
|
-
],
|
|
173
|
-
[
|
|
174
|
-
"snapchat",
|
|
175
|
-
"social",
|
|
176
|
-
String.raw`(^|\.)snapchat\.com$`
|
|
177
|
-
],
|
|
178
|
-
[
|
|
179
|
-
"youtube",
|
|
180
|
-
"video",
|
|
181
|
-
String.raw`(^|\.)(youtube\.com|youtu\.be)$`
|
|
182
|
-
]
|
|
183
|
-
];
|
|
184
|
-
/**
|
|
185
|
-
* Referrer hosts that are navigation, not acquisition: the payment and sign-in providers a
|
|
186
|
-
* visitor is bounced through and back from, and the local dev host. A product appends its own
|
|
187
|
-
* hostnames — a session that starts from an internal link after the session timeout refers to
|
|
188
|
-
* them. POSIX regular expressions over the lower-cased host.
|
|
189
|
-
*/
|
|
190
|
-
const REFERRERS_NOT_A_TOUCH = [
|
|
191
|
-
String.raw`^localhost(:|$)`,
|
|
192
|
-
String.raw`(^|\.)stripe\.com$`,
|
|
193
|
-
String.raw`^(pay|accounts)\.google\.com$`,
|
|
194
|
-
String.raw`^(appleid|apps)\.apple\.com$`,
|
|
195
|
-
String.raw`^play\.google\.com$`,
|
|
196
|
-
String.raw`^login\.microsoftonline\.com$`
|
|
197
|
-
];
|
|
198
|
-
/** GA4's paid mediums: cpc, cpm, ppc, retargeting, paid_social, … */
|
|
199
|
-
const PAID_MEDIUM = "^(.*cp.*|ppc|retargeting|paid.*)$";
|
|
200
|
-
const DISPLAY_MEDIUMS = [
|
|
201
|
-
"display",
|
|
202
|
-
"banner",
|
|
203
|
-
"expandable",
|
|
204
|
-
"interstitial"
|
|
205
|
-
];
|
|
206
|
-
const EMAIL_MEDIUMS = [
|
|
207
|
-
"email",
|
|
208
|
-
"e-mail",
|
|
209
|
-
"e_mail",
|
|
210
|
-
"e mail"
|
|
211
|
-
];
|
|
212
|
-
/** Where a touch came from: a tracked session, or a report by the user or staff. */
|
|
213
|
-
const TOUCH_SOURCES = ["tracked", "reported"];
|
|
214
|
-
/**
|
|
215
|
-
* How a reported touch was learned:
|
|
216
|
-
* - survey: the user answered "how did you hear about us" (onboarding, checkout)
|
|
217
|
-
* - promo_code: the user redeemed a code that belongs to a channel
|
|
218
|
-
* - phone: a call came in on a channel's dedicated number, marked by staff or the phone system
|
|
219
|
-
* - manual: staff recorded what a customer told them
|
|
220
|
-
*/
|
|
221
|
-
const REPORTED_TOUCH_KINDS = [
|
|
222
|
-
"survey",
|
|
223
|
-
"promo_code",
|
|
224
|
-
"phone",
|
|
225
|
-
"manual"
|
|
226
|
-
];
|
|
227
|
-
/**
|
|
228
|
-
* How much a touch is worth against another when attribution has to choose between sessions;
|
|
229
|
-
* lower wins, and the latest wins among equals. Derived in the views, never stored, so
|
|
230
|
-
* renumbering is a view regeneration.
|
|
231
|
-
*
|
|
232
|
-
* This ranks touches against each other. Within one session the rules that name its channel have
|
|
233
|
-
* their own order — `utm_source`, then a click id, then the ad landing page, then the referrer —
|
|
234
|
-
* and the first that says something wins; the session's priority is then the tier of that rule.
|
|
235
|
-
* So two sessions tagged with utm and gclid respectively are equal here (both `campaign`) and the
|
|
236
|
-
* later one is credited, while one session carrying both is named by its utm.
|
|
237
|
-
*/
|
|
238
|
-
const TOUCH_PRIORITY = {
|
|
239
|
-
/** utm, click id, ad landing page: someone tagged that link (in that order within a session) */
|
|
240
|
-
campaign: 1,
|
|
241
|
-
/** a reported touch staff stand behind: a call on a channel's number, a recorded conversation */
|
|
242
|
-
verified: 1,
|
|
243
|
-
/** the browser said where the visitor came from; nobody chose it */
|
|
244
|
-
referrer: 2,
|
|
245
|
-
/** a reported touch that is the user's claim: a survey answer, a code that may have been shared */
|
|
246
|
-
claimed: 3
|
|
247
|
-
};
|
|
248
|
-
/** Which reported kinds are staff facts and which are the user's claims. */
|
|
249
|
-
const REPORTED_TOUCH_PRIORITY = {
|
|
250
|
-
phone: TOUCH_PRIORITY.verified,
|
|
251
|
-
manual: TOUCH_PRIORITY.verified,
|
|
252
|
-
survey: TOUCH_PRIORITY.claimed,
|
|
253
|
-
promo_code: TOUCH_PRIORITY.claimed
|
|
254
|
-
};
|
|
255
|
-
//#endregion
|
|
256
|
-
export { AD_LANDING_PAGE, CHANNELS, CHANNEL_GROUPS, CLICK_ID_CHANNELS, DIRECT_CHANNEL, DISPLAY_MEDIUMS, EMAIL_MEDIUMS, MEDIUM_NOT_SET, NO_MEDIUM, PAID_MEDIUM, REFERRERS_NOT_A_TOUCH, REFERRER_SITES, REPORTED_TOUCH_KINDS, REPORTED_TOUCH_PRIORITY, SOURCE_ALIASES, TOUCH_PRIORITY, TOUCH_SOURCES };
|
|
257
|
-
|
|
258
|
-
//# sourceMappingURL=index.mjs.map
|
|
1
|
+
import { AD_LANDING_PAGE, CHANNELS, CHANNEL_GROUPS, CLICK_ID_CHANNELS, DIRECT_CHANNEL, DISPLAY_MEDIUMS, EMAIL_MEDIUM, EMAIL_MEDIUMS, MEDIUM_NOT_SET, META_PLACEMENTS, NO_MEDIUM, PAID_MEDIUM, REFERRERS_NOT_A_TOUCH, REFERRER_SITES, REPORTED_TOUCH_KINDS, REPORTED_TOUCH_PRIORITY, SOURCE_ALIASES, TOUCH_PRIORITY, TOUCH_SOURCES } from "./vocabulary.mjs";
|
|
2
|
+
import { channelGroupOf, classifyTouch } from "./classify.mjs";
|
|
3
|
+
export { AD_LANDING_PAGE, CHANNELS, CHANNEL_GROUPS, CLICK_ID_CHANNELS, DIRECT_CHANNEL, DISPLAY_MEDIUMS, EMAIL_MEDIUM, EMAIL_MEDIUMS, MEDIUM_NOT_SET, META_PLACEMENTS, NO_MEDIUM, PAID_MEDIUM, REFERRERS_NOT_A_TOUCH, REFERRER_SITES, REPORTED_TOUCH_KINDS, REPORTED_TOUCH_PRIORITY, SOURCE_ALIASES, TOUCH_PRIORITY, TOUCH_SOURCES, channelGroupOf, classifyTouch };
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/attribution/vocabulary.ts
|
|
3
|
+
/**
|
|
4
|
+
* The vocabulary attribution is built from, shared by every product's `session` table and by the
|
|
5
|
+
* dashboards that read it. Data for `classifyTouch` (./classify), which turns it into a channel;
|
|
6
|
+
* a product's dashboards read the same names back. What stays with the product is its own hostnames
|
|
7
|
+
* (referrer exclusions) and its landing-page conventions.
|
|
8
|
+
*/
|
|
9
|
+
/** A session that arrived through nothing we can name. GA's spelling, so dashboards read as GA. */
|
|
10
|
+
const DIRECT_CHANNEL = "(direct)";
|
|
11
|
+
/** The medium of a direct session. */
|
|
12
|
+
const NO_MEDIUM = "(none)";
|
|
13
|
+
/** The medium of a tagged link that named a source but no medium. */
|
|
14
|
+
const MEDIUM_NOT_SET = "(not set)";
|
|
15
|
+
/** The channels the rules below can name. A `utm_source` outside this list is its own channel. */
|
|
16
|
+
const CHANNELS = [
|
|
17
|
+
"meta",
|
|
18
|
+
"google",
|
|
19
|
+
"dv360",
|
|
20
|
+
"microsoft",
|
|
21
|
+
"tiktok",
|
|
22
|
+
"reddit",
|
|
23
|
+
"linkedin",
|
|
24
|
+
"snapchat",
|
|
25
|
+
"x",
|
|
26
|
+
"kakao",
|
|
27
|
+
"yandex",
|
|
28
|
+
"yahoo",
|
|
29
|
+
"duckduckgo",
|
|
30
|
+
"baidu",
|
|
31
|
+
"ecosia",
|
|
32
|
+
"naver",
|
|
33
|
+
"brave",
|
|
34
|
+
"pinterest",
|
|
35
|
+
"youtube",
|
|
36
|
+
"chatgpt",
|
|
37
|
+
"perplexity",
|
|
38
|
+
"gemini",
|
|
39
|
+
"claude",
|
|
40
|
+
"copilot",
|
|
41
|
+
"grok",
|
|
42
|
+
"deepseek",
|
|
43
|
+
"doubao",
|
|
44
|
+
"kimi",
|
|
45
|
+
"qwen",
|
|
46
|
+
"yuanbao",
|
|
47
|
+
"ernie",
|
|
48
|
+
"zhipu",
|
|
49
|
+
"referral_program"
|
|
50
|
+
];
|
|
51
|
+
/** GA4's default channel groups, as identifiers; a dashboard maps them to display names. */
|
|
52
|
+
const CHANNEL_GROUPS = [
|
|
53
|
+
"direct",
|
|
54
|
+
"paid_search",
|
|
55
|
+
"paid_social",
|
|
56
|
+
"paid_other",
|
|
57
|
+
"display",
|
|
58
|
+
"organic_search",
|
|
59
|
+
"organic_social",
|
|
60
|
+
"organic_video",
|
|
61
|
+
"organic_ai",
|
|
62
|
+
"referral",
|
|
63
|
+
"email",
|
|
64
|
+
"affiliate",
|
|
65
|
+
"unassigned"
|
|
66
|
+
];
|
|
67
|
+
/** `utm_source` spellings that mean the same channel: Meta's `{{site_source_name}}` values and the usual synonyms. */
|
|
68
|
+
const SOURCE_ALIASES = {
|
|
69
|
+
fb: "meta",
|
|
70
|
+
ig: "meta",
|
|
71
|
+
an: "meta",
|
|
72
|
+
msg: "meta",
|
|
73
|
+
facebook: "meta",
|
|
74
|
+
instagram: "meta",
|
|
75
|
+
bing: "microsoft",
|
|
76
|
+
twitter: "x",
|
|
77
|
+
"google ads": "google",
|
|
78
|
+
googleads: "google",
|
|
79
|
+
adwords: "google",
|
|
80
|
+
"chatgpt.com": "chatgpt",
|
|
81
|
+
"chat.openai.com": "chatgpt",
|
|
82
|
+
openai: "chatgpt",
|
|
83
|
+
"perplexity.ai": "perplexity",
|
|
84
|
+
"claude.ai": "claude",
|
|
85
|
+
"copilot.com": "copilot",
|
|
86
|
+
"deepseek.com": "deepseek",
|
|
87
|
+
"doubao.com": "doubao",
|
|
88
|
+
"kimi.com": "kimi"
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Which click id belongs to which channel, in priority order. Every key is a field this SDK
|
|
92
|
+
* collects (`AdvertisingInfo`); `fbp` and `rdt_uuid` are deliberately absent — they are pixel
|
|
93
|
+
* browser ids, present with or without a click.
|
|
94
|
+
*/
|
|
95
|
+
const CLICK_ID_CHANNELS = [
|
|
96
|
+
["fbclid", "meta"],
|
|
97
|
+
["fbc", "meta"],
|
|
98
|
+
["gclid", "google"],
|
|
99
|
+
["gbraid", "google"],
|
|
100
|
+
["wbraid", "google"],
|
|
101
|
+
["gad_source", "google"],
|
|
102
|
+
["gad_campaignid", "google"],
|
|
103
|
+
["dclid", "dv360"],
|
|
104
|
+
["msclkid", "microsoft"],
|
|
105
|
+
["ttclid", "tiktok"],
|
|
106
|
+
["rdt_cid", "reddit"],
|
|
107
|
+
["li_fat_id", "linkedin"],
|
|
108
|
+
["sccid", "snapchat"],
|
|
109
|
+
["twclid", "x"],
|
|
110
|
+
["ko_click_id", "kakao"],
|
|
111
|
+
["yclid", "yandex"]
|
|
112
|
+
];
|
|
113
|
+
/**
|
|
114
|
+
* A landing page reserved for one channel's ads, for ads that lost their parameters:
|
|
115
|
+
* `/lp/<channel>`, with or without host, query and hash; the capture is the channel. A product
|
|
116
|
+
* that names its landing pages differently replaces this.
|
|
117
|
+
*/
|
|
118
|
+
const AD_LANDING_PAGE = /^(?:https?:\/\/[^/]+)?\/lp\/([a-z]+)(?:[/?#]|$)/;
|
|
119
|
+
/**
|
|
120
|
+
* Referrer hosts that name a channel, as regular expressions over the lower-cased host,
|
|
121
|
+
* with the medium GA4 gives that kind of site. Anything else that refers is `referral`, kept as
|
|
122
|
+
* its host. A country TLD is one or two labels (`google.com`, `google.co.uk`, `google.com.hk`),
|
|
123
|
+
* never an open tail, so `google.com.evil.io` is not Google. In match order: the AI assistants
|
|
124
|
+
* first, because `gemini.google.com`, `copilot.microsoft.com` and `yiyan.baidu.com` would
|
|
125
|
+
* otherwise be Google, Microsoft and Baidu search.
|
|
126
|
+
*/
|
|
127
|
+
const REFERRER_SITES = [
|
|
128
|
+
[
|
|
129
|
+
"chatgpt",
|
|
130
|
+
"ai",
|
|
131
|
+
/(^|\.)(chatgpt\.com|chat\.openai\.com)$/
|
|
132
|
+
],
|
|
133
|
+
[
|
|
134
|
+
"perplexity",
|
|
135
|
+
"ai",
|
|
136
|
+
/(^|\.)perplexity\.ai$/
|
|
137
|
+
],
|
|
138
|
+
[
|
|
139
|
+
"gemini",
|
|
140
|
+
"ai",
|
|
141
|
+
/^gemini\.google\.com$/
|
|
142
|
+
],
|
|
143
|
+
[
|
|
144
|
+
"claude",
|
|
145
|
+
"ai",
|
|
146
|
+
/(^|\.)claude\.ai$/
|
|
147
|
+
],
|
|
148
|
+
[
|
|
149
|
+
"copilot",
|
|
150
|
+
"ai",
|
|
151
|
+
/^copilot\.microsoft\.com$|(^|\.)copilot\.com$/
|
|
152
|
+
],
|
|
153
|
+
[
|
|
154
|
+
"grok",
|
|
155
|
+
"ai",
|
|
156
|
+
/(^|\.)(grok\.com|x\.ai)$/
|
|
157
|
+
],
|
|
158
|
+
[
|
|
159
|
+
"deepseek",
|
|
160
|
+
"ai",
|
|
161
|
+
/(^|\.)deepseek\.com$/
|
|
162
|
+
],
|
|
163
|
+
[
|
|
164
|
+
"doubao",
|
|
165
|
+
"ai",
|
|
166
|
+
/(^|\.)doubao\.com$/
|
|
167
|
+
],
|
|
168
|
+
[
|
|
169
|
+
"kimi",
|
|
170
|
+
"ai",
|
|
171
|
+
/(^|\.)(kimi\.com|kimi\.moonshot\.cn|moonshot\.cn)$/
|
|
172
|
+
],
|
|
173
|
+
[
|
|
174
|
+
"qwen",
|
|
175
|
+
"ai",
|
|
176
|
+
/^tongyi\.aliyun\.com$|(^|\.)(tongyi\.com|qianwen\.com|qwen\.ai)$/
|
|
177
|
+
],
|
|
178
|
+
[
|
|
179
|
+
"yuanbao",
|
|
180
|
+
"ai",
|
|
181
|
+
/^yuanbao\.tencent\.com$/
|
|
182
|
+
],
|
|
183
|
+
[
|
|
184
|
+
"ernie",
|
|
185
|
+
"ai",
|
|
186
|
+
/^yiyan\.baidu\.com$|(^|\.)ernie\.baidu\.com$/
|
|
187
|
+
],
|
|
188
|
+
[
|
|
189
|
+
"zhipu",
|
|
190
|
+
"ai",
|
|
191
|
+
/(^|\.)(chatglm\.cn|zhipuai\.cn|bigmodel\.cn)$/
|
|
192
|
+
],
|
|
193
|
+
[
|
|
194
|
+
"google",
|
|
195
|
+
"organic",
|
|
196
|
+
/(^|\.)google\.[a-z]{2,}(\.[a-z]{2,3})?$/
|
|
197
|
+
],
|
|
198
|
+
[
|
|
199
|
+
"microsoft",
|
|
200
|
+
"organic",
|
|
201
|
+
/(^|\.)bing\.com$/
|
|
202
|
+
],
|
|
203
|
+
[
|
|
204
|
+
"yahoo",
|
|
205
|
+
"organic",
|
|
206
|
+
/(^|\.)yahoo\.[a-z]{2,}(\.[a-z]{2,3})?$/
|
|
207
|
+
],
|
|
208
|
+
[
|
|
209
|
+
"duckduckgo",
|
|
210
|
+
"organic",
|
|
211
|
+
/(^|\.)duckduckgo\.com$/
|
|
212
|
+
],
|
|
213
|
+
[
|
|
214
|
+
"baidu",
|
|
215
|
+
"organic",
|
|
216
|
+
/(^|\.)baidu\.com$/
|
|
217
|
+
],
|
|
218
|
+
[
|
|
219
|
+
"yandex",
|
|
220
|
+
"organic",
|
|
221
|
+
/(^|\.)yandex\.[a-z]{2,}(\.[a-z]{2,3})?$/
|
|
222
|
+
],
|
|
223
|
+
[
|
|
224
|
+
"ecosia",
|
|
225
|
+
"organic",
|
|
226
|
+
/(^|\.)ecosia\.org$/
|
|
227
|
+
],
|
|
228
|
+
[
|
|
229
|
+
"naver",
|
|
230
|
+
"organic",
|
|
231
|
+
/(^|\.)naver\.com$/
|
|
232
|
+
],
|
|
233
|
+
[
|
|
234
|
+
"brave",
|
|
235
|
+
"organic",
|
|
236
|
+
/^search\.brave\.com$/
|
|
237
|
+
],
|
|
238
|
+
[
|
|
239
|
+
"meta",
|
|
240
|
+
"social",
|
|
241
|
+
/(^|\.)(facebook\.com|instagram\.com|fb\.com|threads\.net)$/
|
|
242
|
+
],
|
|
243
|
+
[
|
|
244
|
+
"x",
|
|
245
|
+
"social",
|
|
246
|
+
/(^|\.)(twitter\.com|x\.com|t\.co)$/
|
|
247
|
+
],
|
|
248
|
+
[
|
|
249
|
+
"linkedin",
|
|
250
|
+
"social",
|
|
251
|
+
/(^|\.)(linkedin\.com|lnkd\.in)$/
|
|
252
|
+
],
|
|
253
|
+
[
|
|
254
|
+
"reddit",
|
|
255
|
+
"social",
|
|
256
|
+
/(^|\.)reddit\.com$/
|
|
257
|
+
],
|
|
258
|
+
[
|
|
259
|
+
"tiktok",
|
|
260
|
+
"social",
|
|
261
|
+
/(^|\.)tiktok\.com$/
|
|
262
|
+
],
|
|
263
|
+
[
|
|
264
|
+
"pinterest",
|
|
265
|
+
"social",
|
|
266
|
+
/(^|\.)pinterest\.[a-z]{2,}(\.[a-z]{2,3})?$/
|
|
267
|
+
],
|
|
268
|
+
[
|
|
269
|
+
"snapchat",
|
|
270
|
+
"social",
|
|
271
|
+
/(^|\.)snapchat\.com$/
|
|
272
|
+
],
|
|
273
|
+
[
|
|
274
|
+
"youtube",
|
|
275
|
+
"video",
|
|
276
|
+
/(^|\.)(youtube\.com|youtu\.be)$/
|
|
277
|
+
]
|
|
278
|
+
];
|
|
279
|
+
/**
|
|
280
|
+
* Referrer hosts that are navigation, not acquisition, when a session happens to start on the way
|
|
281
|
+
* back from them: the local dev host; the payment providers a visitor is bounced through; the
|
|
282
|
+
* sign-in providers whose login lives on a host of its own (Google, Apple, Microsoft, WeChat,
|
|
283
|
+
* Kakao, LINE, Naver, Yahoo, Twitch, X's OAuth 1.0a endpoint); and the hosted auth services a
|
|
284
|
+
* product may sit behind (Auth0, Okta, Supabase, Firebase, Clerk). A product appends its own
|
|
285
|
+
* hostnames — a session that starts from an internal link after the session timeout refers to
|
|
286
|
+
* them. Matched before `REFERRER_SITES`, so `nid.naver.com` and `login.yahoo.com` are not read as
|
|
287
|
+
* a search. Providers whose login shares the host with their content — Facebook, X's OAuth 2.0,
|
|
288
|
+
* LinkedIn, GitHub, Discord — cannot be told apart by host (referrer policies strip the path) and
|
|
289
|
+
* are left as the referrers they usually are. Regular expressions over the lower-cased host.
|
|
290
|
+
*/
|
|
291
|
+
const REFERRERS_NOT_A_TOUCH = [
|
|
292
|
+
/^localhost(:|$)/,
|
|
293
|
+
/(^|\.)stripe\.com$/,
|
|
294
|
+
/^pay\.google\.com$/,
|
|
295
|
+
/(^|\.)paypal\.com$/,
|
|
296
|
+
/(^|\.)alipay\.com$/,
|
|
297
|
+
/(^|\.)paddle\.com$/,
|
|
298
|
+
/(^|\.)lemonsqueezy\.com$/,
|
|
299
|
+
/^apps\.apple\.com$/,
|
|
300
|
+
/^play\.google\.com$/,
|
|
301
|
+
/^accounts\.google\.com$/,
|
|
302
|
+
/^appleid\.apple\.com$/,
|
|
303
|
+
/^login\.(microsoftonline|live)\.com$/,
|
|
304
|
+
/^open\.weixin\.qq\.com$/,
|
|
305
|
+
/^(kauth|accounts)\.kakao\.com$/,
|
|
306
|
+
/^access\.line\.me$/,
|
|
307
|
+
/^nid\.naver\.com$/,
|
|
308
|
+
/^login\.yahoo\.com$/,
|
|
309
|
+
/^id\.twitch\.tv$/,
|
|
310
|
+
/^api\.twitter\.com$/,
|
|
311
|
+
/(^|\.)auth0\.com$/,
|
|
312
|
+
/(^|\.)okta\.com$/,
|
|
313
|
+
/(^|\.)supabase\.co$/,
|
|
314
|
+
/(^|\.)firebaseapp\.com$/,
|
|
315
|
+
/(^|\.)clerk\.accounts\.dev$/
|
|
316
|
+
];
|
|
317
|
+
/** GA4's paid mediums — cpc, cpm, ppc, retargeting, paid_social, … — plus Performance Max. */
|
|
318
|
+
const PAID_MEDIUM = /^(.*cp.*|ppc|retargeting|paid.*|pmax|performance_max)$/;
|
|
319
|
+
/**
|
|
320
|
+
* Meta's `{{placement}}` values, lower-cased, which an ad URL template of `utm_medium={{placement}}`
|
|
321
|
+
* puts in the medium. Only an ad carries one — an organic post arrives as a referrer, with no utm
|
|
322
|
+
* at all — so a medium in this list is a paid Meta click whatever the source says (a template with
|
|
323
|
+
* `utm_source=meta-websitekeyinfo` was seen). Listed one by one rather than matched by prefix, so
|
|
324
|
+
* that a hand-tagged `instagram_stories` post cannot be mistaken for an ad only because of how it
|
|
325
|
+
* is spelled … which means a placement Meta adds later shows up as unassigned until it is added
|
|
326
|
+
* here. Meta's documented values plus the ones seen in the wild (`facebook_mobile_reels`,
|
|
327
|
+
* `facebook_notification`, `an`), and the literal macro for a template that did not expand it.
|
|
328
|
+
*/
|
|
329
|
+
const META_PLACEMENTS = [
|
|
330
|
+
"facebook_desktop_feed",
|
|
331
|
+
"facebook_mobile_feed",
|
|
332
|
+
"facebook_mobile_reels",
|
|
333
|
+
"facebook_reels",
|
|
334
|
+
"facebook_reels_overlay",
|
|
335
|
+
"facebook_right_column",
|
|
336
|
+
"facebook_marketplace",
|
|
337
|
+
"facebook_video_feeds",
|
|
338
|
+
"facebook_stories",
|
|
339
|
+
"facebook_search",
|
|
340
|
+
"facebook_instream_video",
|
|
341
|
+
"facebook_instant_article",
|
|
342
|
+
"facebook_groups_feed",
|
|
343
|
+
"facebook_profile_feed",
|
|
344
|
+
"facebook_notification",
|
|
345
|
+
"facebook_notifications",
|
|
346
|
+
"facebook_business_explore",
|
|
347
|
+
"instagram_feed",
|
|
348
|
+
"instagram_stories",
|
|
349
|
+
"instagram_reels",
|
|
350
|
+
"instagram_reels_overlay",
|
|
351
|
+
"instagram_explore",
|
|
352
|
+
"instagram_explore_home",
|
|
353
|
+
"instagram_search",
|
|
354
|
+
"instagram_shop",
|
|
355
|
+
"instagram_profile_feed",
|
|
356
|
+
"instagram_profile_reels",
|
|
357
|
+
"instagram_igtv",
|
|
358
|
+
"messenger_inbox",
|
|
359
|
+
"messenger_stories",
|
|
360
|
+
"messenger_sponsored_messages",
|
|
361
|
+
"threads_feed",
|
|
362
|
+
"whatsapp_status",
|
|
363
|
+
"audience_network_classic",
|
|
364
|
+
"audience_network_rewarded_video",
|
|
365
|
+
"audience_network_native_banner_and_interstitial",
|
|
366
|
+
"an",
|
|
367
|
+
"others",
|
|
368
|
+
"{{placement}}"
|
|
369
|
+
];
|
|
370
|
+
const DISPLAY_MEDIUMS = [
|
|
371
|
+
"display",
|
|
372
|
+
"banner",
|
|
373
|
+
"expandable",
|
|
374
|
+
"interstitial"
|
|
375
|
+
];
|
|
376
|
+
/** GA4's email spellings, as a source or a medium. */
|
|
377
|
+
const EMAIL_MEDIUMS = [
|
|
378
|
+
"email",
|
|
379
|
+
"e-mail",
|
|
380
|
+
"e_mail",
|
|
381
|
+
"e mail"
|
|
382
|
+
];
|
|
383
|
+
/** A medium that says email or newsletter anywhere in it: `outbound email`, `cold_email`, `newsletter`, and GA4's four. */
|
|
384
|
+
const EMAIL_MEDIUM = /(^|[^a-z])(e[-_ ]?mail|newsletter)([^a-z]|$)/;
|
|
385
|
+
/** Where a touch came from: a tracked session, or a report by the user or staff. */
|
|
386
|
+
const TOUCH_SOURCES = ["tracked", "reported"];
|
|
387
|
+
/**
|
|
388
|
+
* How a reported touch was learned:
|
|
389
|
+
* - survey: the user answered "how did you hear about us" (onboarding, checkout)
|
|
390
|
+
* - promo_code: the user redeemed a code that belongs to a channel
|
|
391
|
+
* - phone: a call came in on a channel's dedicated number, marked by staff or the phone system
|
|
392
|
+
* - manual: staff recorded what a customer told them
|
|
393
|
+
*/
|
|
394
|
+
const REPORTED_TOUCH_KINDS = [
|
|
395
|
+
"survey",
|
|
396
|
+
"promo_code",
|
|
397
|
+
"phone",
|
|
398
|
+
"manual"
|
|
399
|
+
];
|
|
400
|
+
/**
|
|
401
|
+
* How much a touch is worth against another when attribution has to choose between sessions;
|
|
402
|
+
* lower wins, and the latest wins among equals. Stored on the session when it is classified, so
|
|
403
|
+
* renumbering means reclassifying.
|
|
404
|
+
*
|
|
405
|
+
* This ranks touches against each other. Within one session the rules that name its channel have
|
|
406
|
+
* their own order — `utm_source`, then a click id, then the ad landing page, then the referrer —
|
|
407
|
+
* and the first that says something wins; the session's priority is then the tier of that rule.
|
|
408
|
+
* So two sessions tagged with utm and gclid respectively are equal here (both `campaign`) and the
|
|
409
|
+
* later one is credited, while one session carrying both is named by its utm.
|
|
410
|
+
*/
|
|
411
|
+
const TOUCH_PRIORITY = {
|
|
412
|
+
/** utm, click id, ad landing page: someone tagged that link (in that order within a session) */
|
|
413
|
+
campaign: 1,
|
|
414
|
+
/** a reported touch staff stand behind: a call on a channel's number, a recorded conversation */
|
|
415
|
+
verified: 1,
|
|
416
|
+
/** the browser said where the visitor came from; nobody chose it */
|
|
417
|
+
referrer: 2,
|
|
418
|
+
/** a reported touch that is the user's claim: a survey answer, a code that may have been shared */
|
|
419
|
+
claimed: 3
|
|
420
|
+
};
|
|
421
|
+
/** Which reported kinds are staff facts and which are the user's claims. */
|
|
422
|
+
const REPORTED_TOUCH_PRIORITY = {
|
|
423
|
+
phone: TOUCH_PRIORITY.verified,
|
|
424
|
+
manual: TOUCH_PRIORITY.verified,
|
|
425
|
+
survey: TOUCH_PRIORITY.claimed,
|
|
426
|
+
promo_code: TOUCH_PRIORITY.claimed
|
|
427
|
+
};
|
|
428
|
+
//#endregion
|
|
429
|
+
exports.AD_LANDING_PAGE = AD_LANDING_PAGE;
|
|
430
|
+
exports.CHANNELS = CHANNELS;
|
|
431
|
+
exports.CHANNEL_GROUPS = CHANNEL_GROUPS;
|
|
432
|
+
exports.CLICK_ID_CHANNELS = CLICK_ID_CHANNELS;
|
|
433
|
+
exports.DIRECT_CHANNEL = DIRECT_CHANNEL;
|
|
434
|
+
exports.DISPLAY_MEDIUMS = DISPLAY_MEDIUMS;
|
|
435
|
+
exports.EMAIL_MEDIUM = EMAIL_MEDIUM;
|
|
436
|
+
exports.EMAIL_MEDIUMS = EMAIL_MEDIUMS;
|
|
437
|
+
exports.MEDIUM_NOT_SET = MEDIUM_NOT_SET;
|
|
438
|
+
exports.META_PLACEMENTS = META_PLACEMENTS;
|
|
439
|
+
exports.NO_MEDIUM = NO_MEDIUM;
|
|
440
|
+
exports.PAID_MEDIUM = PAID_MEDIUM;
|
|
441
|
+
exports.REFERRERS_NOT_A_TOUCH = REFERRERS_NOT_A_TOUCH;
|
|
442
|
+
exports.REFERRER_SITES = REFERRER_SITES;
|
|
443
|
+
exports.REPORTED_TOUCH_KINDS = REPORTED_TOUCH_KINDS;
|
|
444
|
+
exports.REPORTED_TOUCH_PRIORITY = REPORTED_TOUCH_PRIORITY;
|
|
445
|
+
exports.SOURCE_ALIASES = SOURCE_ALIASES;
|
|
446
|
+
exports.TOUCH_PRIORITY = TOUCH_PRIORITY;
|
|
447
|
+
exports.TOUCH_SOURCES = TOUCH_SOURCES;
|
|
448
|
+
|
|
449
|
+
//# sourceMappingURL=vocabulary.cjs.map
|