@shware/analytics 8.8.0 → 8.10.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/README.md +34 -0
- package/dist/attribution/index.cjs +274 -0
- package/dist/attribution/index.cjs.map +1 -0
- package/dist/attribution/index.d.cts +103 -0
- package/dist/attribution/index.d.cts.map +1 -0
- package/dist/attribution/index.d.mts +103 -0
- package/dist/attribution/index.d.mts.map +1 -0
- package/dist/attribution/index.mjs +257 -0
- package/dist/attribution/index.mjs.map +1 -0
- package/dist/schema/index.cjs +0 -1
- package/dist/schema/index.cjs.map +1 -1
- package/dist/schema/index.d.cts +0 -1
- package/dist/schema/index.d.cts.map +1 -1
- package/dist/schema/index.d.mts +0 -1
- package/dist/schema/index.d.mts.map +1 -1
- package/dist/schema/index.mjs +0 -1
- package/dist/schema/index.mjs.map +1 -1
- package/dist/third-parties/posthog.cjs +7 -1
- package/dist/third-parties/posthog.cjs.map +1 -1
- package/dist/third-parties/posthog.d.cts +8 -2
- package/dist/third-parties/posthog.d.cts.map +1 -1
- package/dist/third-parties/posthog.d.mts +8 -2
- package/dist/third-parties/posthog.d.mts.map +1 -1
- package/dist/third-parties/posthog.mjs +7 -1
- package/dist/third-parties/posthog.mjs.map +1 -1
- package/dist/visitor/index.cjs +5 -1
- package/dist/visitor/index.cjs.map +1 -1
- package/dist/visitor/index.mjs +5 -1
- package/dist/visitor/index.mjs.map +1 -1
- package/dist/visitor/types.d.cts +14 -2
- package/dist/visitor/types.d.cts.map +1 -1
- package/dist/visitor/types.d.mts +14 -2
- package/dist/visitor/types.d.mts.map +1 -1
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -60,6 +60,40 @@ changes. The framework `Analytics` components wire this up.
|
|
|
60
60
|
- /analytics/tracks: track events
|
|
61
61
|
- /analytics/visitor: app visitors
|
|
62
62
|
|
|
63
|
+
## Sessions and attribution
|
|
64
|
+
|
|
65
|
+
How the events this SDK sends are meant to be read on the server side, for attribution. Three
|
|
66
|
+
layers, each derived from the one below it, none needing a table of its own:
|
|
67
|
+
|
|
68
|
+
- **Session** — `event where name = 'session_start'`. The SDK opens a session after 30 minutes
|
|
69
|
+
without an event and sends exactly one `session_start` at the head of the batch that opened it,
|
|
70
|
+
carrying the tags of the event that opened it: the landing page's URL, utm parameters and click
|
|
71
|
+
ids, captured when that event happened rather than when the batch went out. A partial unique
|
|
72
|
+
index on `event (session_id) where name = 'session_start'` makes that row the session's one
|
|
73
|
+
record on the server; there is no session table.
|
|
74
|
+
- **Touchpoint** — a view over those rows that reads each session's tags as `channel`, `medium`
|
|
75
|
+
and `campaign`. An explicit `utm_source` wins; then a click id (`fbclid`, `gclid`, … — one a
|
|
76
|
+
first-party cookie may carry over from an earlier click); then a landing page reserved for one
|
|
77
|
+
channel's ads. The rules for what counts as which channel live here and nowhere else.
|
|
78
|
+
- **Attribution** — a view over touchpoints that credits a session with no touch of its own to
|
|
79
|
+
the same person's most recent paid touch, across devices (visitors sharing a `user_id`), within a
|
|
80
|
+
window. Last paid touch, first touch and multi-touch are alternative models at this layer over
|
|
81
|
+
the same touchpoints.
|
|
82
|
+
|
|
83
|
+
What the SDK guarantees for this to hold:
|
|
84
|
+
|
|
85
|
+
- `session_start` is sent once per session, first in its batch, with the opening event's tags and
|
|
86
|
+
timestamp. If the server rejected that batch, it goes out again with the session's next batch
|
|
87
|
+
under the same session id; the server's unique index drops a duplicate should both land.
|
|
88
|
+
- On native, the install referrer's `utm_*` are attached only on the launch that first resolves
|
|
89
|
+
the referrer, so a later session is a touch only when it actually arrived through something.
|
|
90
|
+
- `session_id` is a client-generated uuidv7 persisted in `config.storage`, shared across tabs and
|
|
91
|
+
reloads. It is a grouping key, not an identity: event and visitor ids are the server's.
|
|
92
|
+
- The person a visitor belongs to is the server's too: `visitor.distinct_id`, the visitor's own id
|
|
93
|
+
until someone signs in on it and the user's id from then on. The client never sends it;
|
|
94
|
+
`setVisitor` hands the server's value to the third-party user setters, and PostHog is identified
|
|
95
|
+
by it once a user is known.
|
|
96
|
+
|
|
63
97
|
## UTM params
|
|
64
98
|
|
|
65
99
|
Typed as `UTMParams` (exported from `@shware/analytics`). Value unions follow the
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/attribution/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* The vocabulary attribution is built from, shared by every product's `touchpoint` view and by the
|
|
5
|
+
* dashboards that read it. Data only, no SQL: a product turns these into its view. What stays with
|
|
6
|
+
* the product is its own hostnames (referrer exclusions) and its landing-page convention.
|
|
7
|
+
*/
|
|
8
|
+
/** A session that arrived through nothing we can name. GA's spelling, so dashboards read as GA. */
|
|
9
|
+
const DIRECT_CHANNEL = "(direct)";
|
|
10
|
+
/** The medium of a direct session. */
|
|
11
|
+
const NO_MEDIUM = "(none)";
|
|
12
|
+
/** The medium of a tagged link that named a source but no medium. */
|
|
13
|
+
const MEDIUM_NOT_SET = "(not set)";
|
|
14
|
+
/** The channels the rules below can name. A `utm_source` outside this list is its own channel. */
|
|
15
|
+
const CHANNELS = [
|
|
16
|
+
"meta",
|
|
17
|
+
"google",
|
|
18
|
+
"dv360",
|
|
19
|
+
"microsoft",
|
|
20
|
+
"tiktok",
|
|
21
|
+
"reddit",
|
|
22
|
+
"linkedin",
|
|
23
|
+
"snapchat",
|
|
24
|
+
"x",
|
|
25
|
+
"kakao",
|
|
26
|
+
"yandex",
|
|
27
|
+
"yahoo",
|
|
28
|
+
"duckduckgo",
|
|
29
|
+
"baidu",
|
|
30
|
+
"ecosia",
|
|
31
|
+
"naver",
|
|
32
|
+
"brave",
|
|
33
|
+
"pinterest",
|
|
34
|
+
"youtube"
|
|
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
|
+
exports.AD_LANDING_PAGE = AD_LANDING_PAGE;
|
|
257
|
+
exports.CHANNELS = CHANNELS;
|
|
258
|
+
exports.CHANNEL_GROUPS = CHANNEL_GROUPS;
|
|
259
|
+
exports.CLICK_ID_CHANNELS = CLICK_ID_CHANNELS;
|
|
260
|
+
exports.DIRECT_CHANNEL = DIRECT_CHANNEL;
|
|
261
|
+
exports.DISPLAY_MEDIUMS = DISPLAY_MEDIUMS;
|
|
262
|
+
exports.EMAIL_MEDIUMS = EMAIL_MEDIUMS;
|
|
263
|
+
exports.MEDIUM_NOT_SET = MEDIUM_NOT_SET;
|
|
264
|
+
exports.NO_MEDIUM = NO_MEDIUM;
|
|
265
|
+
exports.PAID_MEDIUM = PAID_MEDIUM;
|
|
266
|
+
exports.REFERRERS_NOT_A_TOUCH = REFERRERS_NOT_A_TOUCH;
|
|
267
|
+
exports.REFERRER_SITES = REFERRER_SITES;
|
|
268
|
+
exports.REPORTED_TOUCH_KINDS = REPORTED_TOUCH_KINDS;
|
|
269
|
+
exports.REPORTED_TOUCH_PRIORITY = REPORTED_TOUCH_PRIORITY;
|
|
270
|
+
exports.SOURCE_ALIASES = SOURCE_ALIASES;
|
|
271
|
+
exports.TOUCH_PRIORITY = TOUCH_PRIORITY;
|
|
272
|
+
exports.TOUCH_SOURCES = TOUCH_SOURCES;
|
|
273
|
+
|
|
274
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../../src/attribution/index.ts"],"sourcesContent":["import type { AdvertisingInfo } from '../track/types';\n\n/**\n * The vocabulary attribution is built from, shared by every product's `touchpoint` view and by the\n * dashboards that read it. Data only, no SQL: a product turns these into its view. What stays with\n * the product is its own hostnames (referrer exclusions) and its landing-page convention.\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] 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 'referral',\n 'email',\n 'affiliate',\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 facebook: 'meta',\n instagram: 'meta',\n bing: 'microsoft',\n twitter: 'x',\n} as const satisfies Record<string, Channel>;\n\n/**\n * Which click id belongs to which channel, in priority order. Every key is a field this SDK\n * collects (`AdvertisingInfo`); `fbp` and `rdt_uuid` are deliberately absent — they are pixel\n * browser ids, present with or without a click.\n */\nexport const CLICK_ID_CHANNELS = [\n ['fbclid', 'meta'],\n ['fbc', 'meta'],\n ['gclid', 'google'],\n ['gbraid', 'google'],\n ['wbraid', 'google'],\n ['gad_source', 'google'],\n ['gad_campaignid', 'google'],\n ['dclid', 'dv360'],\n ['msclkid', 'microsoft'],\n ['ttclid', 'tiktok'],\n ['rdt_cid', 'reddit'],\n ['li_fat_id', 'linkedin'],\n ['sccid', 'snapchat'],\n ['twclid', 'x'],\n ['ko_click_id', 'kakao'],\n ['yclid', 'yandex'],\n] as const satisfies readonly (readonly [keyof AdvertisingInfo, Channel])[];\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';\n\n/**\n * Referrer hosts that name a channel, as POSIX 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.\n */\nexport const REFERRER_SITES = [\n ['google', 'organic', String.raw`(^|\\.)google\\.[a-z]{2,}(\\.[a-z]{2,3})?$`],\n ['microsoft', 'organic', String.raw`(^|\\.)bing\\.com$`],\n ['yahoo', 'organic', String.raw`(^|\\.)yahoo\\.[a-z]{2,}(\\.[a-z]{2,3})?$`],\n ['duckduckgo', 'organic', String.raw`(^|\\.)duckduckgo\\.com$`],\n ['baidu', 'organic', String.raw`(^|\\.)baidu\\.com$`],\n ['yandex', 'organic', String.raw`(^|\\.)yandex\\.[a-z]{2,}(\\.[a-z]{2,3})?$`],\n ['ecosia', 'organic', String.raw`(^|\\.)ecosia\\.org$`],\n ['naver', 'organic', String.raw`(^|\\.)naver\\.com$`],\n ['brave', 'organic', String.raw`^search\\.brave\\.com$`],\n ['meta', 'social', String.raw`(^|\\.)(facebook\\.com|instagram\\.com|fb\\.com|threads\\.net)$`],\n ['x', 'social', String.raw`(^|\\.)(twitter\\.com|x\\.com|t\\.co)$`],\n ['linkedin', 'social', String.raw`(^|\\.)(linkedin\\.com|lnkd\\.in)$`],\n ['reddit', 'social', String.raw`(^|\\.)reddit\\.com$`],\n ['tiktok', 'social', String.raw`(^|\\.)tiktok\\.com$`],\n ['pinterest', 'social', String.raw`(^|\\.)pinterest\\.[a-z]{2,}(\\.[a-z]{2,3})?$`],\n ['snapchat', 'social', String.raw`(^|\\.)snapchat\\.com$`],\n ['youtube', 'video', String.raw`(^|\\.)(youtube\\.com|youtu\\.be)$`],\n] as const satisfies readonly (readonly [Channel, ReferrerMedium, string])[];\n\n/**\n * Referrer hosts that are navigation, not acquisition: the payment and sign-in providers a\n * visitor is bounced through and back from, and the local dev host. A product appends its own\n * hostnames — a session that starts from an internal link after the session timeout refers to\n * them. POSIX regular expressions over the lower-cased host.\n */\nexport const REFERRERS_NOT_A_TOUCH = [\n String.raw`^localhost(:|$)`,\n String.raw`(^|\\.)stripe\\.com$`,\n String.raw`^(pay|accounts)\\.google\\.com$`,\n String.raw`^(appleid|apps)\\.apple\\.com$`,\n String.raw`^play\\.google\\.com$`,\n String.raw`^login\\.microsoftonline\\.com$`,\n] as const;\n\n/** GA4's paid mediums: cpc, cpm, ppc, retargeting, paid_social, … */\nexport const PAID_MEDIUM = '^(.*cp.*|ppc|retargeting|paid.*)$';\nexport const DISPLAY_MEDIUMS = ['display', 'banner', 'expandable', 'interstitial'] as const;\nexport const EMAIL_MEDIUMS = ['email', 'e-mail', 'e_mail', 'e mail'] as const;\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. Derived in the views, never stored, so\n * renumbering is a view regeneration.\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 * 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 /** utm, click id, ad landing page: someone tagged that link (in that order within a session) */\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":";;;;;;;;AASA,MAAa,iBAAiB;;AAE9B,MAAa,YAAY;;AAEzB,MAAa,iBAAiB;;AAG9B,MAAa,WAAW;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAKA,MAAa,iBAAiB;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAKA,MAAa,iBAAiB;CAC5B,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,KAAK;CACL,UAAU;CACV,WAAW;CACX,MAAM;CACN,SAAS;AACX;;;;;;AAOA,MAAa,oBAAoB;CAC/B,CAAC,UAAU,MAAM;CACjB,CAAC,OAAO,MAAM;CACd,CAAC,SAAS,QAAQ;CAClB,CAAC,UAAU,QAAQ;CACnB,CAAC,UAAU,QAAQ;CACnB,CAAC,cAAc,QAAQ;CACvB,CAAC,kBAAkB,QAAQ;CAC3B,CAAC,SAAS,OAAO;CACjB,CAAC,WAAW,WAAW;CACvB,CAAC,UAAU,QAAQ;CACnB,CAAC,WAAW,QAAQ;CACpB,CAAC,aAAa,UAAU;CACxB,CAAC,SAAS,UAAU;CACpB,CAAC,UAAU,GAAG;CACd,CAAC,eAAe,OAAO;CACvB,CAAC,SAAS,QAAQ;AACpB;;;;;;AAOA,MAAa,kBAAkB;;;;;;;AAU/B,MAAa,iBAAiB;CAC5B;EAAC;EAAU;EAAW,OAAO,GAAG;CAAyC;CACzE;EAAC;EAAa;EAAW,OAAO,GAAG;CAAkB;CACrD;EAAC;EAAS;EAAW,OAAO,GAAG;CAAwC;CACvE;EAAC;EAAc;EAAW,OAAO,GAAG;CAAwB;CAC5D;EAAC;EAAS;EAAW,OAAO,GAAG;CAAmB;CAClD;EAAC;EAAU;EAAW,OAAO,GAAG;CAAyC;CACzE;EAAC;EAAU;EAAW,OAAO,GAAG;CAAoB;CACpD;EAAC;EAAS;EAAW,OAAO,GAAG;CAAmB;CAClD;EAAC;EAAS;EAAW,OAAO,GAAG;CAAsB;CACrD;EAAC;EAAQ;EAAU,OAAO,GAAG;CAA4D;CACzF;EAAC;EAAK;EAAU,OAAO,GAAG;CAAoC;CAC9D;EAAC;EAAY;EAAU,OAAO,GAAG;CAAiC;CAClE;EAAC;EAAU;EAAU,OAAO,GAAG;CAAoB;CACnD;EAAC;EAAU;EAAU,OAAO,GAAG;CAAoB;CACnD;EAAC;EAAa;EAAU,OAAO,GAAG;CAA4C;CAC9E;EAAC;EAAY;EAAU,OAAO,GAAG;CAAsB;CACvD;EAAC;EAAW;EAAS,OAAO,GAAG;CAAiC;AAClE;;;;;;;AAQA,MAAa,wBAAwB;CACnC,OAAO,GAAG;CACV,OAAO,GAAG;CACV,OAAO,GAAG;CACV,OAAO,GAAG;CACV,OAAO,GAAG;CACV,OAAO,GAAG;AACZ;;AAGA,MAAa,cAAc;AAC3B,MAAa,kBAAkB;CAAC;CAAW;CAAU;CAAc;AAAc;AACjF,MAAa,gBAAgB;CAAC;CAAS;CAAU;CAAU;AAAQ;;AAGnE,MAAa,gBAAgB,CAAC,WAAW,UAAU;;;;;;;;AAUnD,MAAa,uBAAuB;CAAC;CAAU;CAAc;CAAS;AAAQ;;;;;;;;;;;;AAc9E,MAAa,iBAAiB;;CAE5B,UAAU;;CAEV,UAAU;;CAEV,UAAU;;CAEV,SAAS;AACX;;AAGA,MAAa,0BAA0B;CACrC,OAAO,eAAe;CACtB,QAAQ,eAAe;CACvB,QAAQ,eAAe;CACvB,YAAY,eAAe;AAC7B"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
//#region src/attribution/index.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The vocabulary attribution is built from, shared by every product's `touchpoint` view and by the
|
|
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
|
+
declare const DIRECT_CHANNEL = "(direct)";
|
|
9
|
+
/** The medium of a direct session. */
|
|
10
|
+
declare const NO_MEDIUM = "(none)";
|
|
11
|
+
/** The medium of a tagged link that named a source but no medium. */
|
|
12
|
+
declare 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
|
+
declare const CHANNELS: readonly ['meta', 'google', 'dv360', 'microsoft', 'tiktok', 'reddit', 'linkedin', 'snapchat', 'x', 'kakao', 'yandex', 'yahoo', 'duckduckgo', 'baidu', 'ecosia', 'naver', 'brave', 'pinterest', 'youtube'];
|
|
15
|
+
type Channel = (typeof CHANNELS)[number];
|
|
16
|
+
/** GA4's default channel groups, as identifiers; a dashboard maps them to display names. */
|
|
17
|
+
declare const CHANNEL_GROUPS: readonly ['direct', 'paid_search', 'paid_social', 'paid_other', 'display', 'organic_search', 'organic_social', 'organic_video', 'referral', 'email', 'affiliate', 'unassigned'];
|
|
18
|
+
type ChannelGroup = (typeof CHANNEL_GROUPS)[number];
|
|
19
|
+
/** `utm_source` spellings that mean the same channel: Meta's `{{site_source_name}}` values and the usual synonyms. */
|
|
20
|
+
declare const SOURCE_ALIASES: {
|
|
21
|
+
readonly fb: 'meta';
|
|
22
|
+
readonly ig: 'meta';
|
|
23
|
+
readonly an: 'meta';
|
|
24
|
+
readonly msg: 'meta';
|
|
25
|
+
readonly facebook: 'meta';
|
|
26
|
+
readonly instagram: 'meta';
|
|
27
|
+
readonly bing: 'microsoft';
|
|
28
|
+
readonly twitter: 'x';
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Which click id belongs to which channel, in priority order. Every key is a field this SDK
|
|
32
|
+
* collects (`AdvertisingInfo`); `fbp` and `rdt_uuid` are deliberately absent — they are pixel
|
|
33
|
+
* browser ids, present with or without a click.
|
|
34
|
+
*/
|
|
35
|
+
declare const CLICK_ID_CHANNELS: readonly [readonly ["fbclid", "meta"], readonly ["fbc", "meta"], readonly ["gclid", "google"], readonly ["gbraid", "google"], readonly ["wbraid", "google"], readonly ["gad_source", "google"], readonly ["gad_campaignid", "google"], readonly ["dclid", "dv360"], readonly ["msclkid", "microsoft"], readonly ["ttclid", "tiktok"], readonly ["rdt_cid", "reddit"], readonly ["li_fat_id", "linkedin"], readonly ["sccid", "snapchat"], readonly ["twclid", "x"], readonly ["ko_click_id", "kakao"], readonly ["yclid", "yandex"]];
|
|
36
|
+
/**
|
|
37
|
+
* A landing page reserved for one channel's ads, for ads that lost their parameters:
|
|
38
|
+
* `/lp/<channel>`, with or without host, query and hash; the capture is the channel. A product
|
|
39
|
+
* that names its landing pages differently replaces this.
|
|
40
|
+
*/
|
|
41
|
+
declare const AD_LANDING_PAGE = "^(?:https?://[^/]+)?/lp/([a-z]+)(?:[/?#]|$)";
|
|
42
|
+
type ReferrerMedium = 'organic' | 'social' | 'video';
|
|
43
|
+
/**
|
|
44
|
+
* Referrer hosts that name a channel, as POSIX regular expressions over the lower-cased host,
|
|
45
|
+
* with the medium GA4 gives that kind of site. Anything else that refers is `referral`, kept as
|
|
46
|
+
* its host. A country TLD is one or two labels (`google.com`, `google.co.uk`, `google.com.hk`),
|
|
47
|
+
* never an open tail, so `google.com.evil.io` is not Google.
|
|
48
|
+
*/
|
|
49
|
+
declare const REFERRER_SITES: readonly [readonly ["google", "organic", string], readonly ["microsoft", "organic", string], readonly ["yahoo", "organic", string], readonly ["duckduckgo", "organic", string], readonly ["baidu", "organic", string], readonly ["yandex", "organic", string], readonly ["ecosia", "organic", string], readonly ["naver", "organic", string], readonly ["brave", "organic", string], readonly ["meta", "social", string], readonly ["x", "social", string], readonly ["linkedin", "social", string], readonly ["reddit", "social", string], readonly ["tiktok", "social", string], readonly ["pinterest", "social", string], readonly ["snapchat", "social", string], readonly ["youtube", "video", string]];
|
|
50
|
+
/**
|
|
51
|
+
* Referrer hosts that are navigation, not acquisition: the payment and sign-in providers a
|
|
52
|
+
* visitor is bounced through and back from, and the local dev host. A product appends its own
|
|
53
|
+
* hostnames — a session that starts from an internal link after the session timeout refers to
|
|
54
|
+
* them. POSIX regular expressions over the lower-cased host.
|
|
55
|
+
*/
|
|
56
|
+
declare const REFERRERS_NOT_A_TOUCH: readonly [string, string, string, string, string, string];
|
|
57
|
+
/** GA4's paid mediums: cpc, cpm, ppc, retargeting, paid_social, … */
|
|
58
|
+
declare const PAID_MEDIUM = "^(.*cp.*|ppc|retargeting|paid.*)$";
|
|
59
|
+
declare const DISPLAY_MEDIUMS: readonly ['display', 'banner', 'expandable', 'interstitial'];
|
|
60
|
+
declare const EMAIL_MEDIUMS: readonly ['email', 'e-mail', 'e_mail', 'e mail'];
|
|
61
|
+
/** Where a touch came from: a tracked session, or a report by the user or staff. */
|
|
62
|
+
declare const TOUCH_SOURCES: readonly ['tracked', 'reported'];
|
|
63
|
+
type TouchSource = (typeof TOUCH_SOURCES)[number];
|
|
64
|
+
/**
|
|
65
|
+
* How a reported touch was learned:
|
|
66
|
+
* - survey: the user answered "how did you hear about us" (onboarding, checkout)
|
|
67
|
+
* - promo_code: the user redeemed a code that belongs to a channel
|
|
68
|
+
* - phone: a call came in on a channel's dedicated number, marked by staff or the phone system
|
|
69
|
+
* - manual: staff recorded what a customer told them
|
|
70
|
+
*/
|
|
71
|
+
declare const REPORTED_TOUCH_KINDS: readonly ['survey', 'promo_code', 'phone', 'manual'];
|
|
72
|
+
type ReportedTouchKind = (typeof REPORTED_TOUCH_KINDS)[number];
|
|
73
|
+
/**
|
|
74
|
+
* How much a touch is worth against another when attribution has to choose between sessions;
|
|
75
|
+
* lower wins, and the latest wins among equals. Derived in the views, never stored, so
|
|
76
|
+
* renumbering is a view regeneration.
|
|
77
|
+
*
|
|
78
|
+
* This ranks touches against each other. Within one session the rules that name its channel have
|
|
79
|
+
* their own order — `utm_source`, then a click id, then the ad landing page, then the referrer —
|
|
80
|
+
* and the first that says something wins; the session's priority is then the tier of that rule.
|
|
81
|
+
* So two sessions tagged with utm and gclid respectively are equal here (both `campaign`) and the
|
|
82
|
+
* later one is credited, while one session carrying both is named by its utm.
|
|
83
|
+
*/
|
|
84
|
+
declare const TOUCH_PRIORITY: {
|
|
85
|
+
/** utm, click id, ad landing page: someone tagged that link (in that order within a session) */
|
|
86
|
+
readonly campaign: 1;
|
|
87
|
+
/** a reported touch staff stand behind: a call on a channel's number, a recorded conversation */
|
|
88
|
+
readonly verified: 1;
|
|
89
|
+
/** the browser said where the visitor came from; nobody chose it */
|
|
90
|
+
readonly referrer: 2;
|
|
91
|
+
/** a reported touch that is the user's claim: a survey answer, a code that may have been shared */
|
|
92
|
+
readonly claimed: 3;
|
|
93
|
+
};
|
|
94
|
+
/** Which reported kinds are staff facts and which are the user's claims. */
|
|
95
|
+
declare const REPORTED_TOUCH_PRIORITY: {
|
|
96
|
+
readonly phone: 1;
|
|
97
|
+
readonly manual: 1;
|
|
98
|
+
readonly survey: 3;
|
|
99
|
+
readonly promo_code: 3;
|
|
100
|
+
};
|
|
101
|
+
//#endregion
|
|
102
|
+
export { AD_LANDING_PAGE, CHANNELS, CHANNEL_GROUPS, CLICK_ID_CHANNELS, Channel, ChannelGroup, 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, ReferrerMedium, ReportedTouchKind, SOURCE_ALIASES, TOUCH_PRIORITY, TOUCH_SOURCES, TouchSource };
|
|
103
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/attribution/index.ts"],"mappings":";;;;;;;cASa;;cAEA;;cAEA;;cAGA;KAsBD,kBAAkB;;cAGjB;KAeD,uBAAuB;;cAGtB;WACP;WACA;WACA;WACC;WACK;WACC;WACL;WACG;;;;;;;cAQE;;;;;;cAwBA;KAED;;;;;;;cAQC;;;;;;;cA0BA;;cAUA;cACA;cACA;;cAGA;KACD,sBAAsB;;;;;;;;cASrB;KACD,4BAA4B;;;;;;;;;;;;cAa3B;;WAED;;WAEA;;WAEA;;WAED;;;cAIE"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
//#region src/attribution/index.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The vocabulary attribution is built from, shared by every product's `touchpoint` view and by the
|
|
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
|
+
declare const DIRECT_CHANNEL = "(direct)";
|
|
9
|
+
/** The medium of a direct session. */
|
|
10
|
+
declare const NO_MEDIUM = "(none)";
|
|
11
|
+
/** The medium of a tagged link that named a source but no medium. */
|
|
12
|
+
declare 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
|
+
declare const CHANNELS: readonly ['meta', 'google', 'dv360', 'microsoft', 'tiktok', 'reddit', 'linkedin', 'snapchat', 'x', 'kakao', 'yandex', 'yahoo', 'duckduckgo', 'baidu', 'ecosia', 'naver', 'brave', 'pinterest', 'youtube'];
|
|
15
|
+
type Channel = (typeof CHANNELS)[number];
|
|
16
|
+
/** GA4's default channel groups, as identifiers; a dashboard maps them to display names. */
|
|
17
|
+
declare const CHANNEL_GROUPS: readonly ['direct', 'paid_search', 'paid_social', 'paid_other', 'display', 'organic_search', 'organic_social', 'organic_video', 'referral', 'email', 'affiliate', 'unassigned'];
|
|
18
|
+
type ChannelGroup = (typeof CHANNEL_GROUPS)[number];
|
|
19
|
+
/** `utm_source` spellings that mean the same channel: Meta's `{{site_source_name}}` values and the usual synonyms. */
|
|
20
|
+
declare const SOURCE_ALIASES: {
|
|
21
|
+
readonly fb: 'meta';
|
|
22
|
+
readonly ig: 'meta';
|
|
23
|
+
readonly an: 'meta';
|
|
24
|
+
readonly msg: 'meta';
|
|
25
|
+
readonly facebook: 'meta';
|
|
26
|
+
readonly instagram: 'meta';
|
|
27
|
+
readonly bing: 'microsoft';
|
|
28
|
+
readonly twitter: 'x';
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Which click id belongs to which channel, in priority order. Every key is a field this SDK
|
|
32
|
+
* collects (`AdvertisingInfo`); `fbp` and `rdt_uuid` are deliberately absent — they are pixel
|
|
33
|
+
* browser ids, present with or without a click.
|
|
34
|
+
*/
|
|
35
|
+
declare const CLICK_ID_CHANNELS: readonly [readonly ["fbclid", "meta"], readonly ["fbc", "meta"], readonly ["gclid", "google"], readonly ["gbraid", "google"], readonly ["wbraid", "google"], readonly ["gad_source", "google"], readonly ["gad_campaignid", "google"], readonly ["dclid", "dv360"], readonly ["msclkid", "microsoft"], readonly ["ttclid", "tiktok"], readonly ["rdt_cid", "reddit"], readonly ["li_fat_id", "linkedin"], readonly ["sccid", "snapchat"], readonly ["twclid", "x"], readonly ["ko_click_id", "kakao"], readonly ["yclid", "yandex"]];
|
|
36
|
+
/**
|
|
37
|
+
* A landing page reserved for one channel's ads, for ads that lost their parameters:
|
|
38
|
+
* `/lp/<channel>`, with or without host, query and hash; the capture is the channel. A product
|
|
39
|
+
* that names its landing pages differently replaces this.
|
|
40
|
+
*/
|
|
41
|
+
declare const AD_LANDING_PAGE = "^(?:https?://[^/]+)?/lp/([a-z]+)(?:[/?#]|$)";
|
|
42
|
+
type ReferrerMedium = 'organic' | 'social' | 'video';
|
|
43
|
+
/**
|
|
44
|
+
* Referrer hosts that name a channel, as POSIX regular expressions over the lower-cased host,
|
|
45
|
+
* with the medium GA4 gives that kind of site. Anything else that refers is `referral`, kept as
|
|
46
|
+
* its host. A country TLD is one or two labels (`google.com`, `google.co.uk`, `google.com.hk`),
|
|
47
|
+
* never an open tail, so `google.com.evil.io` is not Google.
|
|
48
|
+
*/
|
|
49
|
+
declare const REFERRER_SITES: readonly [readonly ["google", "organic", string], readonly ["microsoft", "organic", string], readonly ["yahoo", "organic", string], readonly ["duckduckgo", "organic", string], readonly ["baidu", "organic", string], readonly ["yandex", "organic", string], readonly ["ecosia", "organic", string], readonly ["naver", "organic", string], readonly ["brave", "organic", string], readonly ["meta", "social", string], readonly ["x", "social", string], readonly ["linkedin", "social", string], readonly ["reddit", "social", string], readonly ["tiktok", "social", string], readonly ["pinterest", "social", string], readonly ["snapchat", "social", string], readonly ["youtube", "video", string]];
|
|
50
|
+
/**
|
|
51
|
+
* Referrer hosts that are navigation, not acquisition: the payment and sign-in providers a
|
|
52
|
+
* visitor is bounced through and back from, and the local dev host. A product appends its own
|
|
53
|
+
* hostnames — a session that starts from an internal link after the session timeout refers to
|
|
54
|
+
* them. POSIX regular expressions over the lower-cased host.
|
|
55
|
+
*/
|
|
56
|
+
declare const REFERRERS_NOT_A_TOUCH: readonly [string, string, string, string, string, string];
|
|
57
|
+
/** GA4's paid mediums: cpc, cpm, ppc, retargeting, paid_social, … */
|
|
58
|
+
declare const PAID_MEDIUM = "^(.*cp.*|ppc|retargeting|paid.*)$";
|
|
59
|
+
declare const DISPLAY_MEDIUMS: readonly ['display', 'banner', 'expandable', 'interstitial'];
|
|
60
|
+
declare const EMAIL_MEDIUMS: readonly ['email', 'e-mail', 'e_mail', 'e mail'];
|
|
61
|
+
/** Where a touch came from: a tracked session, or a report by the user or staff. */
|
|
62
|
+
declare const TOUCH_SOURCES: readonly ['tracked', 'reported'];
|
|
63
|
+
type TouchSource = (typeof TOUCH_SOURCES)[number];
|
|
64
|
+
/**
|
|
65
|
+
* How a reported touch was learned:
|
|
66
|
+
* - survey: the user answered "how did you hear about us" (onboarding, checkout)
|
|
67
|
+
* - promo_code: the user redeemed a code that belongs to a channel
|
|
68
|
+
* - phone: a call came in on a channel's dedicated number, marked by staff or the phone system
|
|
69
|
+
* - manual: staff recorded what a customer told them
|
|
70
|
+
*/
|
|
71
|
+
declare const REPORTED_TOUCH_KINDS: readonly ['survey', 'promo_code', 'phone', 'manual'];
|
|
72
|
+
type ReportedTouchKind = (typeof REPORTED_TOUCH_KINDS)[number];
|
|
73
|
+
/**
|
|
74
|
+
* How much a touch is worth against another when attribution has to choose between sessions;
|
|
75
|
+
* lower wins, and the latest wins among equals. Derived in the views, never stored, so
|
|
76
|
+
* renumbering is a view regeneration.
|
|
77
|
+
*
|
|
78
|
+
* This ranks touches against each other. Within one session the rules that name its channel have
|
|
79
|
+
* their own order — `utm_source`, then a click id, then the ad landing page, then the referrer —
|
|
80
|
+
* and the first that says something wins; the session's priority is then the tier of that rule.
|
|
81
|
+
* So two sessions tagged with utm and gclid respectively are equal here (both `campaign`) and the
|
|
82
|
+
* later one is credited, while one session carrying both is named by its utm.
|
|
83
|
+
*/
|
|
84
|
+
declare const TOUCH_PRIORITY: {
|
|
85
|
+
/** utm, click id, ad landing page: someone tagged that link (in that order within a session) */
|
|
86
|
+
readonly campaign: 1;
|
|
87
|
+
/** a reported touch staff stand behind: a call on a channel's number, a recorded conversation */
|
|
88
|
+
readonly verified: 1;
|
|
89
|
+
/** the browser said where the visitor came from; nobody chose it */
|
|
90
|
+
readonly referrer: 2;
|
|
91
|
+
/** a reported touch that is the user's claim: a survey answer, a code that may have been shared */
|
|
92
|
+
readonly claimed: 3;
|
|
93
|
+
};
|
|
94
|
+
/** Which reported kinds are staff facts and which are the user's claims. */
|
|
95
|
+
declare const REPORTED_TOUCH_PRIORITY: {
|
|
96
|
+
readonly phone: 1;
|
|
97
|
+
readonly manual: 1;
|
|
98
|
+
readonly survey: 3;
|
|
99
|
+
readonly promo_code: 3;
|
|
100
|
+
};
|
|
101
|
+
//#endregion
|
|
102
|
+
export { AD_LANDING_PAGE, CHANNELS, CHANNEL_GROUPS, CLICK_ID_CHANNELS, Channel, ChannelGroup, 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, ReferrerMedium, ReportedTouchKind, SOURCE_ALIASES, TOUCH_PRIORITY, TOUCH_SOURCES, TouchSource };
|
|
103
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/attribution/index.ts"],"mappings":";;;;;;;cASa;;cAEA;;cAEA;;cAGA;KAsBD,kBAAkB;;cAGjB;KAeD,uBAAuB;;cAGtB;WACP;WACA;WACA;WACC;WACK;WACC;WACL;WACG;;;;;;;cAQE;;;;;;cAwBA;KAED;;;;;;;cAQC;;;;;;;cA0BA;;cAUA;cACA;cACA;;cAGA;KACD,sBAAsB;;;;;;;;cASrB;KACD,4BAA4B;;;;;;;;;;;;cAa3B;;WAED;;WAEA;;WAEA;;WAED;;;cAIE"}
|