@rebilly/lead-source-tracker 8.28.4 → 8.30.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 +47 -1
- package/dist/build.js +1 -1
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -29,6 +29,12 @@ You can provide additional cookie attribues using the second argument:
|
|
|
29
29
|
|
|
30
30
|
See https://github.com/js-cookie/js-cookie#cookie-attributes for the full list of supported attributes.
|
|
31
31
|
|
|
32
|
+
The second argument also accepts two non-cookie options, `classifyReferrer` and `visitorId`:
|
|
33
|
+
|
|
34
|
+
`RebillyLeadSource.collect(window, { domain: 'rebilly.com', secure: true, classifyReferrer: true, visitorId: true })`
|
|
35
|
+
|
|
36
|
+
See [Referrer classification](#referrer-classification-opt-in) and [Visitor id](#visitor-id-opt-in) below.
|
|
37
|
+
|
|
32
38
|
4. Call `collect` again when submitting lead source information to the Rebilly API. The function returns all saved cookie data as an object.
|
|
33
39
|
|
|
34
40
|
Example:
|
|
@@ -74,6 +80,44 @@ Some data is automatically inferred by `RebillyLeadSource` when data collection
|
|
|
74
80
|
2. `document.location`
|
|
75
81
|
- URL is parsed and saved to leadSource.path
|
|
76
82
|
|
|
83
|
+
### Referrer classification (opt-in)
|
|
84
|
+
|
|
85
|
+
Most visitors arrive without UTM parameters, so `source` and `medium` stay empty and reports show these leads as "direct / none".
|
|
86
|
+
When you pass `classifyReferrer: true` to `collect`, the tracker maps well-known referrer hostnames to `source` and `medium` values.
|
|
87
|
+
|
|
88
|
+
Rules:
|
|
89
|
+
|
|
90
|
+
- Classification fills a field only when nothing else set it. UTM parameters, `#rls-token` attributes, and previously saved cookie values always win.
|
|
91
|
+
- Classification uses the saved first-touch referrer, so a later visit does not change an existing attribution.
|
|
92
|
+
- Unknown referrers stay unclassified. The tracker never guesses.
|
|
93
|
+
- The option is off by default. Existing integrations see no behavior change.
|
|
94
|
+
|
|
95
|
+
Recognized hostnames include search engines (`google`, `bing`, `duckduckgo` → `organic-search`), developer communities (`github`, `stack-overflow`, `reddit`, `hacker-news`, `dev-to` → `community`), social networks (`linkedin`, `x-twitter`, `youtube`, `bluesky`, `facebook`, `instagram`, `tiktok`, `snapchat`, `pinterest`, `threads` → `social`), webmail (`gmail`, `outlook`, `yahoo-mail` → `email`), and AI assistants (`chatgpt`, `perplexity`, `claude`, `gemini`, `copilot` → `ai-assistant`).
|
|
96
|
+
For Google, only search hosts (`google.<tld>`, `www.google.<tld>`) classify as organic search — other Google properties such as `docs.google.com` or `accounts.google.com` stay unclassified.
|
|
97
|
+
See `src/referrerClassification.ts` for the full table.
|
|
98
|
+
|
|
99
|
+
### Visitor id (opt-in)
|
|
100
|
+
|
|
101
|
+
The tracker does not store a browser identifier unless you ask for one.
|
|
102
|
+
When you pass `visitorId: true` to `collect`, it writes a UUIDv7 ([RFC 9562](https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-version-7)) to `leadSource.clickId` and keeps that value for the cookie lifetime.
|
|
103
|
+
|
|
104
|
+
Rules:
|
|
105
|
+
|
|
106
|
+
- The id is generated only when `leadSource.clickId` is empty. A later visit, `utm_*` parameters, and ad click parameters such as `gclid` do not change it.
|
|
107
|
+
- Incoming `clickId` / `clickid` query params and `#rls-token` `data-click-id` are ignored while the option is on. The cookie holds a generated visitor id, not an ad click id.
|
|
108
|
+
- An existing non-empty `clickId` (including an old ad id) is left as-is.
|
|
109
|
+
- The option is off by default. Existing integrations that use `clickId` for ad attribution see no behavior change.
|
|
110
|
+
- Generation needs `crypto.getRandomValues`. There is no `Math.random()` fallback. If `crypto.getRandomValues` is missing, `clickId` stays unset and the rest of collect still runs.
|
|
111
|
+
- Pass `visitorId: true` on **every** `collect` call. An option-off `collect` (including the package's `DOMContentLoaded` auto-collect) treats `clickId` as updatable again and can overwrite a generated id from `?clickId=` or `#rls-token` `data-click-id`.
|
|
112
|
+
|
|
113
|
+
Read the id without parsing the cookie:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
const visitorId = RebillyLeadSource.getVisitorId();
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`getVisitorId()` only reads the cookie. It does not generate or write. Call `collect(..., { visitorId: true })` first if you need the id to exist.
|
|
120
|
+
|
|
77
121
|
### Query params
|
|
78
122
|
|
|
79
123
|
| key | example | purpose |
|
|
@@ -86,7 +130,7 @@ Some data is automatically inferred by `RebillyLeadSource` when data collection
|
|
|
86
130
|
| `content` | `?content=example` | Change the leadSource `content` field |
|
|
87
131
|
| `affiliate` | `?affiliate=example` | Change the leadSource `affiliate` field |
|
|
88
132
|
| `subAffiliate` or `subaffiliate` | `?subAffiliate=example` | Change the leadSource `subAffiliate` field |
|
|
89
|
-
| `clickId` or `clickid` | `?clickId=example` | Change the leadSource `clickId` field
|
|
133
|
+
| `clickId` or `clickid` | `?clickId=example` | Change the leadSource `clickId` field. Ignored when `visitorId: true` |
|
|
90
134
|
| `salesAgent` or `salesagent` | `?salesAgent=example` | Change the leadSource `salesAgent` field |
|
|
91
135
|
| `path` | `?path=example` | Change the leadSource `path` field. This cannot be updated once it is set the first time |
|
|
92
136
|
| `referrer` | `?referrer=example` | Change the leadSource `referrer` field. This cannot be updated once it is set the first time |
|
|
@@ -127,6 +171,8 @@ The following fields will not be updated after the first time they are saved to
|
|
|
127
171
|
- path
|
|
128
172
|
- referrer
|
|
129
173
|
|
|
174
|
+
When `visitorId: true`, `clickId` is also left unchanged after it is first saved (generated UUIDv7, or whatever value was already in the cookie).
|
|
175
|
+
|
|
130
176
|
The only exception is if the cookie expires (after 45 days) or is cleared by the user.
|
|
131
177
|
|
|
132
178
|
All other fields will update the saved cookie whenever `collect` is called.
|
package/dist/build.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(l,m){typeof exports=="object"&&typeof module<"u"?module.exports=m():typeof define=="function"&&define.amd?define(m):(l=typeof globalThis<"u"?globalThis:l||self,l.RebillyLeadSource=m())})(this,(function(){"use strict";/*! js-cookie v3.0.8 | MIT */function l(e){for(var o=1;o<arguments.length;o++){var r=arguments[o];for(var c in r)c!=="__proto__"&&(e[c]=r[c])}return e}var m={read:function(e){return e[0]==='"'&&(e=e.slice(1,-1)),e.replace(/(%[\dA-F]{2})+/gi,decodeURIComponent)},write:function(e){return encodeURIComponent(e).replace(/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g,decodeURIComponent)}};function h(e,o){function r(n,u,i){if(!(typeof document>"u")){i=l({},o,i),typeof i.expires=="number"&&(i.expires=new Date(Date.now()+i.expires*864e5)),i.expires&&(i.expires=i.expires.toUTCString()),n=encodeURIComponent(n).replace(/%(2[346B]|5E|60|7C)/g,decodeURIComponent).replace(/[()]/g,escape);var a="";for(var s in i)i[s]&&(a+="; "+s,i[s]!==!0&&(a+="="+i[s].split(";")[0]));return document.cookie=n+"="+e.write(u,n)+a}}function c(n){if(!(typeof document>"u"||arguments.length&&!n)){for(var u=document.cookie?document.cookie.split("; "):[],i={},a=0;a<u.length;a++){var s=u[a].split("="),f=s.slice(1).join("=");try{var d=decodeURIComponent(s[0]);if(d in i||(i[d]=e.read(f,d)),n===d)break}catch{}}return n?i[n]:i}}return Object.create({set:r,get:c,remove:function(n,u){r(n,"",l({},u,{expires:-1}))},withAttributes:function(n){return h(this.converter,l({},this.attributes,n))},withConverter:function(n){return h(l({},this.converter,n),this.attributes)}},{attributes:{value:Object.freeze(o)},converter:{value:Object.freeze(e)}})}var g=h(m,{path:"/"});function t(e,o,r){return{test:c=>c===e||c.endsWith(`.${e}`),source:o,medium:r}}const b=[t("chatgpt.com","chatgpt","ai-assistant"),t("chat.openai.com","chatgpt","ai-assistant"),t("perplexity.ai","perplexity","ai-assistant"),t("claude.ai","claude","ai-assistant"),t("gemini.google.com","gemini","ai-assistant"),t("copilot.microsoft.com","copilot","ai-assistant"),t("mail.google.com","gmail","email"),t("outlook.live.com","outlook","email"),t("outlook.office.com","outlook","email"),t("mail.yahoo.com","yahoo-mail","email"),{test:e=>/^(www\.)?google\.[a-z]{2,3}(\.[a-z]{2})?$/.test(e),source:"google",medium:"organic-search"},t("bing.com","bing","organic-search"),t("duckduckgo.com","duckduckgo","organic-search"),t("github.com","github","community"),t("stackoverflow.com","stack-overflow","community"),t("reddit.com","reddit","community"),t("news.ycombinator.com","hacker-news","community"),t("dev.to","dev-to","community"),t("linkedin.com","linkedin","social"),t("lnkd.in","linkedin","social"),t("twitter.com","x-twitter","social"),t("x.com","x-twitter","social"),t("t.co","x-twitter","social"),t("youtube.com","youtube","social"),t("youtu.be","youtube","social"),t("bsky.app","bluesky","social"),t("facebook.com","facebook","social"),t("fb.com","facebook","social"),t("fb.me","facebook","social"),t("instagram.com","instagram","social"),t("tiktok.com","tiktok","social"),t("snapchat.com","snapchat","social"),t("pinterest.com","pinterest","social"),t("pin.it","pinterest","social"),t("threads.net","threads","social"),t("threads.com","threads","social")];function D(e){if(!e)return null;let o;try{o=new URL(e).hostname.toLowerCase()}catch{return null}const r=b.find(c=>c.test(o));return r?{source:r.source,medium:r.medium}:null}function I(e){return[...e].map(o=>o.toString(16).padStart(2,"0")).join("")}function U(){if(typeof crypto>"u"||typeof crypto.getRandomValues!="function")return null;const e=new Uint8Array(16);crypto.getRandomValues(e);const o=Date.now();e[0]=Math.floor(o/1099511627776)&255,e[1]=Math.floor(o/4294967296)&255,e[2]=Math.floor(o/16777216)&255,e[3]=Math.floor(o/65536)&255,e[4]=Math.floor(o/256)&255,e[5]=o&255,e[6]=e[6]&15|112,e[8]=e[8]&63|128;const r=I(e);return`${r.slice(0,8)}-${r.slice(8,12)}-${r.slice(12,16)}-${r.slice(16,20)}-${r.slice(20)}`}const k="rls",y={expires:45},O=["source","medium","campaign","term","content","affiliate","subAffiliate","clickId","salesAgent"],C=["path","referrer"],S=[...C,...O];function x(e,o){const{classifyReferrer:r=!1,visitorId:c=!1,...n}=o||{},{document:u}=e;if($(e))return p({dnt:!0}),{};const i={path:u.location.host+u.location.pathname,...A(u),..._(u),...F(e)};c&&delete i.clickId;const{leadSource:a}=T({leadSource:i},n,r,c);return a||{}}function p({dnt:e=!1}){const o={dnt:e};return e?R(o):g.remove(k,y),o}function L(){var e;return(e=v().leadSource)==null?void 0:e.clickId}function T(e,o,r=!1,c=!1){const n=v();if(n.dnt||e.dnt)return p({dnt:!0});p({dnt:!1});const u=n.leadSource||{},i=C.reduce((s,f)=>{const d=u[f];return d&&(s[f]=d),s},{}),a={leadSource:{...u,...w(e.leadSource),...i}};if(r){const s=a.leadSource;if(!s.source||!s.medium){const f=D(s.referrer);f&&(s.source=s.source||f.source,s.medium=s.medium||f.medium)}}if(c&&!a.leadSource.clickId){const s=U();s&&(a.leadSource.clickId=s)}return Object.assign(y,o||{}),R(a),a}function v(){const e=g.get(k);if(!e)return{};try{const o=JSON.parse(e);return o.dnt?{dnt:!0}:o}catch{return{}}}function R(e){g.set(k,JSON.stringify(e),y)}function A(e){const{dataset:o}=e.querySelector("#rls-token")||{};return w(o)}function F(e){const o=new URLSearchParams(e.location.search),r=S.reduce((c,n)=>(c[n]=o.get(`utm_${n}`)||o.get(n)||o.get(`utm_${n.toLowerCase()}`)||o.get(n.toLowerCase())||null,c),{});return w(r)}function _(e){try{return new URL(e.referrer).hostname!==e.location.hostname?{referrer:e.referrer}:{}}catch{return{}}}function $(e){const r=new URLSearchParams(e.location.search).has("dnt"),{dnt:c}=v(),n=M(e);return r||n||c}function M(e){const{navigator:o}=e;if(e.doNotTrack||o.doNotTrack||o.msDoNotTrack)return e.doNotTrack=="1"||o.doNotTrack=="yes"||o.doNotTrack=="1"||o.msDoNotTrack=="1"}function w(e={}){return S.reduce((o,r)=>{const c=(e[r]||"").trim();return c&&(o[r]=c),o},{})}return typeof window<"u"&&document.addEventListener("DOMContentLoaded",()=>{x(window)}),{collect:x,clear:p,getVisitorId:L}}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebilly/lead-source-tracker",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.30.0",
|
|
4
4
|
"description": "Simple library which is used to collect and store lead source information into a cookie",
|
|
5
5
|
"author": "Rebilly",
|
|
6
6
|
"repository": "https://github.com/Rebilly/rebilly",
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
],
|
|
11
11
|
"scripts": {
|
|
12
12
|
"serve": "pnpm build && node ./dev/server.js",
|
|
13
|
-
"build": "vite build"
|
|
13
|
+
"build": "vite build",
|
|
14
|
+
"test:unit": "vitest run",
|
|
15
|
+
"test:unit:watch": "vitest"
|
|
14
16
|
},
|
|
15
17
|
"browserslist": [
|
|
16
18
|
"> 1%",
|
|
@@ -20,7 +22,8 @@
|
|
|
20
22
|
"@rebilly/eslint-config": "*",
|
|
21
23
|
"express": "^5.1.0",
|
|
22
24
|
"vite": "^6.4.3",
|
|
23
|
-
"vite-plugin-commonjs": "^0.10.4"
|
|
25
|
+
"vite-plugin-commonjs": "^0.10.4",
|
|
26
|
+
"vitest": "^4.1.0"
|
|
24
27
|
},
|
|
25
28
|
"dependencies": {
|
|
26
29
|
"js-cookie": "^3.0.8"
|