@analytics-debugger/ads-click-tracker
Advanced tools
@@ -13,7 +13,10 @@ class AdsClickTracker { | ||
| }; | ||
| options.clickIdConfigs.forEach(({ name, expirationMs = 2592e6, maxClicks = 100 }) => { | ||
| this.configs.set(name, { expires: expirationMs, max: maxClicks }); | ||
| options.clickIdConfigs.forEach(({ name, ttl = 2592e6, maxClicks = 100 }) => { | ||
| this.configs.set(name, { expires: ttl, max: maxClicks }); | ||
| this.clicks[name] = this.clicks[name] || []; | ||
| }); | ||
| this.load(); | ||
| if (typeof this.options.callbacks?.onLoaded === "function") { | ||
| this.options.callbacks.onLoaded(this.get(true)); | ||
| } | ||
| this.checkUrl(); | ||
@@ -82,22 +85,17 @@ } | ||
| const referrer = typeof document !== "undefined" ? document.referrer : ""; | ||
| if (!Array.isArray(this.clicks[source])) { | ||
| this.clicks[source] = []; | ||
| } | ||
| const clickData = { | ||
| value, | ||
| timestamp: now, | ||
| expiresAt: now + expiresMs, | ||
| landing, | ||
| referrer | ||
| }; | ||
| this.clicks[source] = this.clicks[source] || []; | ||
| const existingIndex = this.clicks[source].findIndex((c) => c.value === value); | ||
| if (existingIndex >= 0) { | ||
| this.clicks[source][existingIndex] = { | ||
| value, | ||
| timestamp: now, | ||
| expiresAt: now + expiresMs, | ||
| landing, | ||
| referrer | ||
| }; | ||
| this.clicks[source][existingIndex] = clickData; | ||
| typeof this.options.callbacks?.onUpdatedClickId === "function" && this.options.callbacks.onUpdatedClickId(clickData); | ||
| } else { | ||
| this.clicks[source].push({ | ||
| value, | ||
| timestamp: now, | ||
| expiresAt: now + expiresMs, | ||
| landing, | ||
| referrer | ||
| }); | ||
| this.clicks[source].push(clickData); | ||
| typeof this.options.callbacks?.onNewClickId === "function" && this.options.callbacks.onNewClickId(clickData); | ||
| } | ||
@@ -111,8 +109,18 @@ this.save(); | ||
| } | ||
| get(source) { | ||
| const now = Date.now(); | ||
| if (source) { | ||
| return Array.isArray(this.clicks[source]) ? this.clicks[source].filter((c) => c && c.expiresAt > now) : []; | ||
| get(latest) { | ||
| if (!latest || latest !== true) { | ||
| return this.clicks; | ||
| } | ||
| return Object.values(this.clicks).flat().filter((c) => c && c.expiresAt > now); | ||
| const getLatestClick = (clicks = []) => { | ||
| if (!clicks.length) | ||
| return []; | ||
| const latest2 = clicks.reduce((newest, current) => current.timestamp > newest.timestamp ? current : newest, clicks[0]); | ||
| return [latest2]; | ||
| }; | ||
| const result = {}; | ||
| const allSources = Array.from(this.configs.keys()); | ||
| allSources.forEach((sourceName) => { | ||
| result[sourceName] = getLatestClick(this.clicks[sourceName]); | ||
| }); | ||
| return result; | ||
| } | ||
@@ -119,0 +127,0 @@ clear(source) { |
@@ -1,1 +0,1 @@ | ||
| class n{clicks={};configs=new Map;options;constructor(s){this.options={storageKey:"_act_",debug:!1,checkHash:!0,decodeValues:!0,...s},s.clickIdConfigs.forEach(({name:t,expirationMs:i=2592e6,maxClicks:e=100})=>{this.configs.set(t,{expires:i,max:e}),this.clicks[t]=this.clicks[t]||[]}),this.load(),this.checkUrl()}log(s){this.options.debug&&console.info(`[Tracker] ${s}`)}load(){try{const s=localStorage.getItem(this.options.storageKey);if(s){const t=JSON.parse(s);this.configs.forEach((i,e)=>{this.clicks[e]=Array.isArray(t[e])?t[e]:[]})}this.cleanup()}catch(s){this.log(`Load error: ${s}`)}}save(){try{localStorage.setItem(this.options.storageKey,JSON.stringify(this.clicks))}catch(s){this.log(`Save error: ${s}`)}}cleanup(){const s=Date.now();Object.keys(this.clicks).forEach(t=>{Array.isArray(this.clicks[t])||(this.clicks[t]=[]);const i=this.configs.get(t);this.clicks[t]=this.clicks[t].filter(e=>e&&e.expiresAt>s).slice(-(i?.max||100))})}checkUrl(){if(!(typeof window>"u"))try{const s=new URL(window.location.href);this.processParams(s.searchParams),this.options.checkHash&&s.hash&&this.processParams(new URLSearchParams(s.hash.substring(1)))}catch(s){this.log(`URL parse error: ${s}`)}}processParams(s){this.configs.forEach((t,i)=>{const e=s.get(i);e&&this.recordClick(i,this.options.decodeValues?decodeURIComponent(e):e,t.expires)})}recordClick(s,t,i){const e=Date.now(),r=typeof window<"u"?window.location.href:"",o=typeof document<"u"?document.referrer:"";Array.isArray(this.clicks[s])||(this.clicks[s]=[]);const a=this.clicks[s].findIndex(l=>l.value===t);a>=0?this.clicks[s][a]={value:t,timestamp:e,expiresAt:e+i,landing:r,referrer:o}:this.clicks[s].push({value:t,timestamp:e,expiresAt:e+i,landing:r,referrer:o}),this.save()}track(s,t,i){const e=this.configs.get(s)||{expires:i||2592e6};this.recordClick(s,t,e.expires)}get(s){const t=Date.now();return s?Array.isArray(this.clicks[s])?this.clicks[s].filter(i=>i&&i.expiresAt>t):[]:Object.values(this.clicks).flat().filter(i=>i&&i.expiresAt>t)}clear(s){s?this.clicks[s]=[]:Object.keys(this.clicks).forEach(t=>{this.clicks[t]=[]}),this.save()}}let h;function k(c){return h||(h=new n(c))}export{k as default}; | ||
| class k{clicks={};configs=new Map;options;constructor(s){this.options={storageKey:"_act_",debug:!1,checkHash:!0,decodeValues:!0,...s},s.clickIdConfigs.forEach(({name:c,ttl:i=2592e6,maxClicks:t=100})=>{this.configs.set(c,{expires:i,max:t}),this.clicks[c]=this.clicks[c]||[]}),this.load(),typeof this.options.callbacks?.onLoaded=="function"&&this.options.callbacks.onLoaded(this.get(!0)),this.checkUrl()}log(s){this.options.debug&&console.info(`[Tracker] ${s}`)}load(){try{const s=localStorage.getItem(this.options.storageKey);if(s){const c=JSON.parse(s);this.configs.forEach((i,t)=>{this.clicks[t]=Array.isArray(c[t])?c[t]:[]})}this.cleanup()}catch(s){this.log(`Load error: ${s}`)}}save(){try{localStorage.setItem(this.options.storageKey,JSON.stringify(this.clicks))}catch(s){this.log(`Save error: ${s}`)}}cleanup(){const s=Date.now();Object.keys(this.clicks).forEach(c=>{Array.isArray(this.clicks[c])||(this.clicks[c]=[]);const i=this.configs.get(c);this.clicks[c]=this.clicks[c].filter(t=>t&&t.expiresAt>s).slice(-(i?.max||100))})}checkUrl(){if(!(typeof window>"u"))try{const s=new URL(window.location.href);this.processParams(s.searchParams),this.options.checkHash&&s.hash&&this.processParams(new URLSearchParams(s.hash.substring(1)))}catch(s){this.log(`URL parse error: ${s}`)}}processParams(s){this.configs.forEach((c,i)=>{const t=s.get(i);t&&this.recordClick(i,this.options.decodeValues?decodeURIComponent(t):t,c.expires)})}recordClick(s,c,i){const t=Date.now(),e=typeof window<"u"?window.location.href:"",o=typeof document<"u"?document.referrer:"",r={value:c,timestamp:t,expiresAt:t+i,landing:e,referrer:o};this.clicks[s]=this.clicks[s]||[];const n=this.clicks[s].findIndex(l=>l.value===c);n>=0?(this.clicks[s][n]=r,typeof this.options.callbacks?.onUpdatedClickId=="function"&&this.options.callbacks.onUpdatedClickId(r)):(this.clicks[s].push(r),typeof this.options.callbacks?.onNewClickId=="function"&&this.options.callbacks.onNewClickId(r)),this.save()}track(s,c,i){const t=this.configs.get(s)||{expires:i||2592e6};this.recordClick(s,c,t.expires)}get(s){if(!s||s!==!0)return this.clicks;const c=(t=[])=>t.length?[t.reduce((e,o)=>o.timestamp>e.timestamp?o:e,t[0])]:[],i={};return Array.from(this.configs.keys()).forEach(t=>{i[t]=c(this.clicks[t])}),i}clear(s){s?this.clicks[s]=[]:Object.keys(this.clicks).forEach(c=>{this.clicks[c]=[]}),this.save()}}let h;function p(a){return h||(h=new k(a))}export{p as default}; |
@@ -16,7 +16,10 @@ var AdsClickTracker = (function () { | ||
| }; | ||
| options.clickIdConfigs.forEach(({ name, expirationMs = 2592e6, maxClicks = 100 }) => { | ||
| this.configs.set(name, { expires: expirationMs, max: maxClicks }); | ||
| options.clickIdConfigs.forEach(({ name, ttl = 2592e6, maxClicks = 100 }) => { | ||
| this.configs.set(name, { expires: ttl, max: maxClicks }); | ||
| this.clicks[name] = this.clicks[name] || []; | ||
| }); | ||
| this.load(); | ||
| if (typeof this.options.callbacks?.onLoaded === "function") { | ||
| this.options.callbacks.onLoaded(this.get(true)); | ||
| } | ||
| this.checkUrl(); | ||
@@ -85,22 +88,17 @@ } | ||
| const referrer = typeof document !== "undefined" ? document.referrer : ""; | ||
| if (!Array.isArray(this.clicks[source])) { | ||
| this.clicks[source] = []; | ||
| } | ||
| const clickData = { | ||
| value, | ||
| timestamp: now, | ||
| expiresAt: now + expiresMs, | ||
| landing, | ||
| referrer | ||
| }; | ||
| this.clicks[source] = this.clicks[source] || []; | ||
| const existingIndex = this.clicks[source].findIndex((c) => c.value === value); | ||
| if (existingIndex >= 0) { | ||
| this.clicks[source][existingIndex] = { | ||
| value, | ||
| timestamp: now, | ||
| expiresAt: now + expiresMs, | ||
| landing, | ||
| referrer | ||
| }; | ||
| this.clicks[source][existingIndex] = clickData; | ||
| typeof this.options.callbacks?.onUpdatedClickId === "function" && this.options.callbacks.onUpdatedClickId(clickData); | ||
| } else { | ||
| this.clicks[source].push({ | ||
| value, | ||
| timestamp: now, | ||
| expiresAt: now + expiresMs, | ||
| landing, | ||
| referrer | ||
| }); | ||
| this.clicks[source].push(clickData); | ||
| typeof this.options.callbacks?.onNewClickId === "function" && this.options.callbacks.onNewClickId(clickData); | ||
| } | ||
@@ -114,8 +112,18 @@ this.save(); | ||
| } | ||
| get(source) { | ||
| const now = Date.now(); | ||
| if (source) { | ||
| return Array.isArray(this.clicks[source]) ? this.clicks[source].filter((c) => c && c.expiresAt > now) : []; | ||
| get(latest) { | ||
| if (!latest || latest !== true) { | ||
| return this.clicks; | ||
| } | ||
| return Object.values(this.clicks).flat().filter((c) => c && c.expiresAt > now); | ||
| const getLatestClick = (clicks = []) => { | ||
| if (!clicks.length) | ||
| return []; | ||
| const latest2 = clicks.reduce((newest, current) => current.timestamp > newest.timestamp ? current : newest, clicks[0]); | ||
| return [latest2]; | ||
| }; | ||
| const result = {}; | ||
| const allSources = Array.from(this.configs.keys()); | ||
| allSources.forEach((sourceName) => { | ||
| result[sourceName] = getLatestClick(this.clicks[sourceName]); | ||
| }); | ||
| return result; | ||
| } | ||
@@ -122,0 +130,0 @@ clear(source) { |
@@ -1,1 +0,1 @@ | ||
| var AdsClickTracker=function(){"use strict";class n{clicks={};configs=new Map;options;constructor(s){this.options={storageKey:"_act_",debug:!1,checkHash:!0,decodeValues:!0,...s},s.clickIdConfigs.forEach(({name:t,expirationMs:i=2592e6,maxClicks:c=100})=>{this.configs.set(t,{expires:i,max:c}),this.clicks[t]=this.clicks[t]||[]}),this.load(),this.checkUrl()}log(s){this.options.debug&&console.info(`[Tracker] ${s}`)}load(){try{const s=localStorage.getItem(this.options.storageKey);if(s){const t=JSON.parse(s);this.configs.forEach((i,c)=>{this.clicks[c]=Array.isArray(t[c])?t[c]:[]})}this.cleanup()}catch(s){this.log(`Load error: ${s}`)}}save(){try{localStorage.setItem(this.options.storageKey,JSON.stringify(this.clicks))}catch(s){this.log(`Save error: ${s}`)}}cleanup(){const s=Date.now();Object.keys(this.clicks).forEach(t=>{Array.isArray(this.clicks[t])||(this.clicks[t]=[]);const i=this.configs.get(t);this.clicks[t]=this.clicks[t].filter(c=>c&&c.expiresAt>s).slice(-(i?.max||100))})}checkUrl(){if(!(typeof window>"u"))try{const s=new URL(window.location.href);this.processParams(s.searchParams),this.options.checkHash&&s.hash&&this.processParams(new URLSearchParams(s.hash.substring(1)))}catch(s){this.log(`URL parse error: ${s}`)}}processParams(s){this.configs.forEach((t,i)=>{const c=s.get(i);c&&this.recordClick(i,this.options.decodeValues?decodeURIComponent(c):c,t.expires)})}recordClick(s,t,i){const c=Date.now(),o=typeof window<"u"?window.location.href:"",a=typeof document<"u"?document.referrer:"";Array.isArray(this.clicks[s])||(this.clicks[s]=[]);const h=this.clicks[s].findIndex(k=>k.value===t);h>=0?this.clicks[s][h]={value:t,timestamp:c,expiresAt:c+i,landing:o,referrer:a}:this.clicks[s].push({value:t,timestamp:c,expiresAt:c+i,landing:o,referrer:a}),this.save()}track(s,t,i){const c=this.configs.get(s)||{expires:i||2592e6};this.recordClick(s,t,c.expires)}get(s){const t=Date.now();return s?Array.isArray(this.clicks[s])?this.clicks[s].filter(i=>i&&i.expiresAt>t):[]:Object.values(this.clicks).flat().filter(i=>i&&i.expiresAt>t)}clear(s){s?this.clicks[s]=[]:Object.keys(this.clicks).forEach(t=>{this.clicks[t]=[]}),this.save()}}let e;function l(r){return e||(e=new n(r))}return l}(); | ||
| var AdsClickTracker=function(){"use strict";class l{clicks={};configs=new Map;options;constructor(s){this.options={storageKey:"_act_",debug:!1,checkHash:!0,decodeValues:!0,...s},s.clickIdConfigs.forEach(({name:c,ttl:i=2592e6,maxClicks:t=100})=>{this.configs.set(c,{expires:i,max:t}),this.clicks[c]=this.clicks[c]||[]}),this.load(),typeof this.options.callbacks?.onLoaded=="function"&&this.options.callbacks.onLoaded(this.get(!0)),this.checkUrl()}log(s){this.options.debug&&console.info(`[Tracker] ${s}`)}load(){try{const s=localStorage.getItem(this.options.storageKey);if(s){const c=JSON.parse(s);this.configs.forEach((i,t)=>{this.clicks[t]=Array.isArray(c[t])?c[t]:[]})}this.cleanup()}catch(s){this.log(`Load error: ${s}`)}}save(){try{localStorage.setItem(this.options.storageKey,JSON.stringify(this.clicks))}catch(s){this.log(`Save error: ${s}`)}}cleanup(){const s=Date.now();Object.keys(this.clicks).forEach(c=>{Array.isArray(this.clicks[c])||(this.clicks[c]=[]);const i=this.configs.get(c);this.clicks[c]=this.clicks[c].filter(t=>t&&t.expiresAt>s).slice(-(i?.max||100))})}checkUrl(){if(!(typeof window>"u"))try{const s=new URL(window.location.href);this.processParams(s.searchParams),this.options.checkHash&&s.hash&&this.processParams(new URLSearchParams(s.hash.substring(1)))}catch(s){this.log(`URL parse error: ${s}`)}}processParams(s){this.configs.forEach((c,i)=>{const t=s.get(i);t&&this.recordClick(i,this.options.decodeValues?decodeURIComponent(t):t,c.expires)})}recordClick(s,c,i){const t=Date.now(),e=typeof window<"u"?window.location.href:"",o=typeof document<"u"?document.referrer:"",r={value:c,timestamp:t,expiresAt:t+i,landing:e,referrer:o};this.clicks[s]=this.clicks[s]||[];const h=this.clicks[s].findIndex(p=>p.value===c);h>=0?(this.clicks[s][h]=r,typeof this.options.callbacks?.onUpdatedClickId=="function"&&this.options.callbacks.onUpdatedClickId(r)):(this.clicks[s].push(r),typeof this.options.callbacks?.onNewClickId=="function"&&this.options.callbacks.onNewClickId(r)),this.save()}track(s,c,i){const t=this.configs.get(s)||{expires:i||2592e6};this.recordClick(s,c,t.expires)}get(s){if(!s||s!==!0)return this.clicks;const c=(t=[])=>t.length?[t.reduce((e,o)=>o.timestamp>e.timestamp?o:e,t[0])]:[],i={};return Array.from(this.configs.keys()).forEach(t=>{i[t]=c(this.clicks[t])}),i}clear(s){s?this.clicks[s]=[]:Object.keys(this.clicks).forEach(c=>{this.clicks[c]=[]}),this.save()}}let a;function k(n){return a||(a=new l(n))}return k}(); |
@@ -19,7 +19,10 @@ (function (global, factory) { | ||
| }; | ||
| options.clickIdConfigs.forEach(({ name, expirationMs = 2592e6, maxClicks = 100 }) => { | ||
| this.configs.set(name, { expires: expirationMs, max: maxClicks }); | ||
| options.clickIdConfigs.forEach(({ name, ttl = 2592e6, maxClicks = 100 }) => { | ||
| this.configs.set(name, { expires: ttl, max: maxClicks }); | ||
| this.clicks[name] = this.clicks[name] || []; | ||
| }); | ||
| this.load(); | ||
| if (typeof this.options.callbacks?.onLoaded === "function") { | ||
| this.options.callbacks.onLoaded(this.get(true)); | ||
| } | ||
| this.checkUrl(); | ||
@@ -88,22 +91,17 @@ } | ||
| const referrer = typeof document !== "undefined" ? document.referrer : ""; | ||
| if (!Array.isArray(this.clicks[source])) { | ||
| this.clicks[source] = []; | ||
| } | ||
| const clickData = { | ||
| value, | ||
| timestamp: now, | ||
| expiresAt: now + expiresMs, | ||
| landing, | ||
| referrer | ||
| }; | ||
| this.clicks[source] = this.clicks[source] || []; | ||
| const existingIndex = this.clicks[source].findIndex((c) => c.value === value); | ||
| if (existingIndex >= 0) { | ||
| this.clicks[source][existingIndex] = { | ||
| value, | ||
| timestamp: now, | ||
| expiresAt: now + expiresMs, | ||
| landing, | ||
| referrer | ||
| }; | ||
| this.clicks[source][existingIndex] = clickData; | ||
| typeof this.options.callbacks?.onUpdatedClickId === "function" && this.options.callbacks.onUpdatedClickId(clickData); | ||
| } else { | ||
| this.clicks[source].push({ | ||
| value, | ||
| timestamp: now, | ||
| expiresAt: now + expiresMs, | ||
| landing, | ||
| referrer | ||
| }); | ||
| this.clicks[source].push(clickData); | ||
| typeof this.options.callbacks?.onNewClickId === "function" && this.options.callbacks.onNewClickId(clickData); | ||
| } | ||
@@ -117,8 +115,18 @@ this.save(); | ||
| } | ||
| get(source) { | ||
| const now = Date.now(); | ||
| if (source) { | ||
| return Array.isArray(this.clicks[source]) ? this.clicks[source].filter((c) => c && c.expiresAt > now) : []; | ||
| get(latest) { | ||
| if (!latest || latest !== true) { | ||
| return this.clicks; | ||
| } | ||
| return Object.values(this.clicks).flat().filter((c) => c && c.expiresAt > now); | ||
| const getLatestClick = (clicks = []) => { | ||
| if (!clicks.length) | ||
| return []; | ||
| const latest2 = clicks.reduce((newest, current) => current.timestamp > newest.timestamp ? current : newest, clicks[0]); | ||
| return [latest2]; | ||
| }; | ||
| const result = {}; | ||
| const allSources = Array.from(this.configs.keys()); | ||
| allSources.forEach((sourceName) => { | ||
| result[sourceName] = getLatestClick(this.clicks[sourceName]); | ||
| }); | ||
| return result; | ||
| } | ||
@@ -125,0 +133,0 @@ clear(source) { |
@@ -1,1 +0,1 @@ | ||
| (function(r,c){typeof exports=="object"&&typeof module<"u"?module.exports=c():typeof define=="function"&&define.amd?define(c):(r=typeof globalThis<"u"?globalThis:r||self,r.AdsClickTracker=c())})(void 0,function(){"use strict";class r{clicks={};configs=new Map;options;constructor(s){this.options={storageKey:"_act_",debug:!1,checkHash:!0,decodeValues:!0,...s},s.clickIdConfigs.forEach(({name:e,expirationMs:t=2592e6,maxClicks:i=100})=>{this.configs.set(e,{expires:t,max:i}),this.clicks[e]=this.clicks[e]||[]}),this.load(),this.checkUrl()}log(s){this.options.debug&&console.info(`[Tracker] ${s}`)}load(){try{const s=localStorage.getItem(this.options.storageKey);if(s){const e=JSON.parse(s);this.configs.forEach((t,i)=>{this.clicks[i]=Array.isArray(e[i])?e[i]:[]})}this.cleanup()}catch(s){this.log(`Load error: ${s}`)}}save(){try{localStorage.setItem(this.options.storageKey,JSON.stringify(this.clicks))}catch(s){this.log(`Save error: ${s}`)}}cleanup(){const s=Date.now();Object.keys(this.clicks).forEach(e=>{Array.isArray(this.clicks[e])||(this.clicks[e]=[]);const t=this.configs.get(e);this.clicks[e]=this.clicks[e].filter(i=>i&&i.expiresAt>s).slice(-(t?.max||100))})}checkUrl(){if(!(typeof window>"u"))try{const s=new URL(window.location.href);this.processParams(s.searchParams),this.options.checkHash&&s.hash&&this.processParams(new URLSearchParams(s.hash.substring(1)))}catch(s){this.log(`URL parse error: ${s}`)}}processParams(s){this.configs.forEach((e,t)=>{const i=s.get(t);i&&this.recordClick(t,this.options.decodeValues?decodeURIComponent(i):i,e.expires)})}recordClick(s,e,t){const i=Date.now(),a=typeof window<"u"?window.location.href:"",n=typeof document<"u"?document.referrer:"";Array.isArray(this.clicks[s])||(this.clicks[s]=[]);const h=this.clicks[s].findIndex(f=>f.value===e);h>=0?this.clicks[s][h]={value:e,timestamp:i,expiresAt:i+t,landing:a,referrer:n}:this.clicks[s].push({value:e,timestamp:i,expiresAt:i+t,landing:a,referrer:n}),this.save()}track(s,e,t){const i=this.configs.get(s)||{expires:t||2592e6};this.recordClick(s,e,i.expires)}get(s){const e=Date.now();return s?Array.isArray(this.clicks[s])?this.clicks[s].filter(t=>t&&t.expiresAt>e):[]:Object.values(this.clicks).flat().filter(t=>t&&t.expiresAt>e)}clear(s){s?this.clicks[s]=[]:Object.keys(this.clicks).forEach(e=>{this.clicks[e]=[]}),this.save()}}let c;function l(o){return c||(c=new r(o))}return l}); | ||
| (function(o,c){typeof exports=="object"&&typeof module<"u"?module.exports=c():typeof define=="function"&&define.amd?define(c):(o=typeof globalThis<"u"?globalThis:o||self,o.AdsClickTracker=c())})(void 0,function(){"use strict";class o{clicks={};configs=new Map;options;constructor(s){this.options={storageKey:"_act_",debug:!1,checkHash:!0,decodeValues:!0,...s},s.clickIdConfigs.forEach(({name:t,ttl:i=2592e6,maxClicks:e=100})=>{this.configs.set(t,{expires:i,max:e}),this.clicks[t]=this.clicks[t]||[]}),this.load(),typeof this.options.callbacks?.onLoaded=="function"&&this.options.callbacks.onLoaded(this.get(!0)),this.checkUrl()}log(s){this.options.debug&&console.info(`[Tracker] ${s}`)}load(){try{const s=localStorage.getItem(this.options.storageKey);if(s){const t=JSON.parse(s);this.configs.forEach((i,e)=>{this.clicks[e]=Array.isArray(t[e])?t[e]:[]})}this.cleanup()}catch(s){this.log(`Load error: ${s}`)}}save(){try{localStorage.setItem(this.options.storageKey,JSON.stringify(this.clicks))}catch(s){this.log(`Save error: ${s}`)}}cleanup(){const s=Date.now();Object.keys(this.clicks).forEach(t=>{Array.isArray(this.clicks[t])||(this.clicks[t]=[]);const i=this.configs.get(t);this.clicks[t]=this.clicks[t].filter(e=>e&&e.expiresAt>s).slice(-(i?.max||100))})}checkUrl(){if(!(typeof window>"u"))try{const s=new URL(window.location.href);this.processParams(s.searchParams),this.options.checkHash&&s.hash&&this.processParams(new URLSearchParams(s.hash.substring(1)))}catch(s){this.log(`URL parse error: ${s}`)}}processParams(s){this.configs.forEach((t,i)=>{const e=s.get(i);e&&this.recordClick(i,this.options.decodeValues?decodeURIComponent(e):e,t.expires)})}recordClick(s,t,i){const e=Date.now(),r=typeof window<"u"?window.location.href:"",n=typeof document<"u"?document.referrer:"",a={value:t,timestamp:e,expiresAt:e+i,landing:r,referrer:n};this.clicks[s]=this.clicks[s]||[];const l=this.clicks[s].findIndex(f=>f.value===t);l>=0?(this.clicks[s][l]=a,typeof this.options.callbacks?.onUpdatedClickId=="function"&&this.options.callbacks.onUpdatedClickId(a)):(this.clicks[s].push(a),typeof this.options.callbacks?.onNewClickId=="function"&&this.options.callbacks.onNewClickId(a)),this.save()}track(s,t,i){const e=this.configs.get(s)||{expires:i||2592e6};this.recordClick(s,t,e.expires)}get(s){if(!s||s!==!0)return this.clicks;const t=(e=[])=>e.length?[e.reduce((r,n)=>n.timestamp>r.timestamp?n:r,e[0])]:[],i={};return Array.from(this.configs.keys()).forEach(e=>{i[e]=t(this.clicks[e])}),i}clear(s){s?this.clicks[s]=[]:Object.keys(this.clicks).forEach(t=>{this.clicks[t]=[]}),this.save()}}let c;function d(h){return c||(c=new o(h))}return d}); |
+7
-2
| interface ClickConfig { | ||
| name: string; | ||
| expirationMs?: number; | ||
| ttl?: number; | ||
| maxClicks?: number; | ||
@@ -12,2 +12,7 @@ } | ||
| decodeValues?: boolean; | ||
| callbacks?: { | ||
| onLoaded?: (clicks: Record<string, ClickData | ClickData[]>) => void; | ||
| onNewClickId?: (click: ClickData) => void; | ||
| onUpdatedClickId?: (click: ClickData) => void; | ||
| }; | ||
| } | ||
@@ -34,3 +39,3 @@ interface ClickData { | ||
| track(source: string, value: string, expiresMs?: number): void; | ||
| get(source?: string): ClickData[]; | ||
| get(latest?: boolean): Record<string, ClickData[]>; | ||
| clear(source?: string): void; | ||
@@ -37,0 +42,0 @@ } |
+1
-1
| { | ||
| "name": "@analytics-debugger/ads-click-tracker", | ||
| "type": "module", | ||
| "version": "0.0.1-beta.1", | ||
| "version": "0.0.1-beta.2", | ||
| "description": "Library to keep track of clickIds from different advertising platforms", | ||
@@ -6,0 +6,0 @@ "author": "David Vallejo <thyngster@gmail.com>", |
+129
-108
| # 🎯 Ads Click Tracker | ||
| [](https://www.npmjs.com/package/ads-click-tracker) | ||
| [](https://bundlephobia.com/package/ads-click-tracker) | ||
| [](LICENSE) | ||
| [](https://www.npmjs.com/package/ads-click-tracker) | ||
| [](https://www.npmjs.com/package/@analytics-debugger/ads-click-tracker) | ||
| [](https://bundlephobia.com/package/@analytics-debugger/ads-click-tracker@latest) | ||
| [](LICENSE) | ||
| [](https://www.npmjs.com/package/@analytics-debugger/ads-click-tracker) | ||
| ## 📖 Overview | ||
| # @analytics-debugger/ads-click-tracker | ||
| Ads Click Tracker is a powerful, lightweight solution for marketing attribution tracking that simplifies the complexity of capturing and managing click data across multiple advertising platforms. | ||
| A lightweight, client-side library for tracking and managing ad click IDs across user sessions. | ||
| ## 🌟 Key Features | ||
| ## Features | ||
@@ -33,2 +33,5 @@ - **💡 Comprehensive Attribution Tracking** | ||
| - **🪝 Event callbacks** | ||
| - For onLoad, New Click Id found, Updated ClickID | ||
| - **🚀 Developer-Friendly** | ||
@@ -40,157 +43,175 @@ - Zero dependencies | ||
| ## 🔧 Installation | ||
| ## Installation | ||
| ```bash | ||
| # npm | ||
| npm install ads-click-tracker | ||
| # Yarn | ||
| yarn add ads-click-tracker | ||
| # pnpm | ||
| pnpm add ads-click-tracker | ||
| npm install @analytics-debugger/ads-click-tracker | ||
| # or | ||
| yarn add @analytics-debugger/ads-click-tracker | ||
| ``` | ||
| ## 💻 Quick Start | ||
| ## Quick Start | ||
| ### Basic Usage | ||
| ```javascript | ||
| import { initTracker } from 'ads-click-tracker' | ||
| import AdsClickTracker from '@analytics-debugger/ads-click-tracker' | ||
| // Initialize with multiple click ID configurations | ||
| const tracker = initTracker({ | ||
| // Initialize the tracker | ||
| const tracker = AdsClickTracker({ | ||
| debug: true, | ||
| clickIdConfigs: [ | ||
| { name: 'gclid', expirationMs: 30 * 24 * 60 * 60 * 1000 }, // 30 days for Google Ads | ||
| { name: 'fbclid', maxClicks: 50 } // 50 clicks limit for Facebook | ||
| { name: 'gclid', ttl: 30 * 24 * 60 * 60 * 1000, maxClicks: 100 }, // Google Ads (30 days) | ||
| { name: 'fbclid', ttl: 7 * 24 * 60 * 60 * 1000, maxClicks: 50 }, // Facebook (7 days) | ||
| { name: 'ttclid', ttl: 14 * 24 * 60 * 60 * 1000, maxClicks: 50 }, // TikTok (14 days) | ||
| { name: 'msclkid', ttl: 30 * 24 * 60 * 60 * 1000, maxClicks: 100 } // Microsoft (30 days) | ||
| ], | ||
| debug: true // Enable console logging | ||
| callbacks: { | ||
| onLoaded: (clicks) => { | ||
| console.log('Tracker initialized with clicks:', clicks) | ||
| }, | ||
| onNewClickId: (click) => { | ||
| console.log('New click ID detected:', click) | ||
| }, | ||
| onUpdatedClickId: (click) => { | ||
| console.log('Click ID updated:', click) | ||
| } | ||
| } | ||
| }) | ||
| // Get all tracked clicks | ||
| const allClicks = tracker.get() | ||
| // Get only the latest click for each source | ||
| const latestClicks = tracker.get(true) | ||
| // Manually track a click | ||
| tracker.trackClick('affiliate', 'ref123', 24 * 60 * 60 * 1000) // 1 day expiration | ||
| tracker.track('gclid', 'EAIaIQobChMIh76p9Y3', 2592000000) // value, expiration in ms | ||
| // Retrieve tracked clicks | ||
| const googleClicks = tracker.getClicks('gclid') | ||
| // Clear clicks for a specific source | ||
| tracker.clear('fbclid') | ||
| // Clear all clicks | ||
| tracker.clear() | ||
| ``` | ||
| ## 📊 Click Data Structure | ||
| ## API Reference | ||
| Each tracked click includes: | ||
| ### Initialization | ||
| ```typescript | ||
| interface TrackedClick { | ||
| value: string // Click ID (e.g., "abc123") | ||
| timestamp: number // Recording timestamp (milliseconds) | ||
| expiresAt: number // Expiration timestamp (milliseconds) | ||
| landing: string // Landing page URL | ||
| referrer: string // Referring URL | ||
| class AdsClickTracker { | ||
| constructor(options: TrackerOptions) | ||
| // ... other methods | ||
| } | ||
| ``` | ||
| ## ⚙️ Configuration Options | ||
| #### TrackerOptions | ||
| ### `initTracker(options)` | ||
| | Option | Type | Default | Description | | ||
| |--------|------|---------|-------------| | ||
| | `storageKey` | string | `'_act_'` | Key used for localStorage | | ||
| | `clickIdConfigs` | ClickConfig[] | *required* | Array of click ID configurations | | ||
| | `debug` | boolean | `false` | Enable debug logging | | ||
| | `checkHash` | boolean | `true` | Check hash fragment for click IDs | | ||
| | `decodeValues` | boolean | `true` | URL decode click ID values | | ||
| | `callbacks` | object | `{}` | Callback functions | | ||
| | Option | Type | Description | Default | | ||
| |--------|------|-------------|---------| | ||
| | `storageKey` | `string` | Custom storage key in localStorage | `'_ads_clicks'` | | ||
| | `clickIdConfigs` | `ClickConfig[]` | Click ID tracking configurations | `[]` | | ||
| | `debug` | `boolean` | Enable debug logging | `false` | | ||
| #### ClickConfig | ||
| ### `ClickConfig` | ||
| | Property | Type | Default | Description | | ||
| |----------|------|---------|-------------| | ||
| | `name` | string | *required* | Parameter name to track (e.g., 'gclid') | | ||
| | `ttl` | number | `2592000000` (30 days) | Time in milliseconds before click expires | | ||
| | `maxClicks` | number | `100` | Maximum number of clicks to store per source | | ||
| | Property | Type | Description | | ||
| |----------|------|-------------| | ||
| | `name` | `string` | Parameter name (e.g., 'gclid') | | ||
| | `expirationMs` | `number?` | Expiration time in milliseconds | | ||
| | `maxClicks` | `number?` | Maximum number of clicks to store | | ||
| ### Methods | ||
| ## 📚 API Reference | ||
| #### get(latest?: boolean): Record<string, ClickData[]> | ||
| ### `trackClick(source, value, expirationMs?)` | ||
| Get all tracked clicks. If `latest` is `true`, returns only the most recent click for each source. | ||
| Manually track a click: | ||
| #### track(source: string, value: string, ttl?: number): void | ||
| ```javascript | ||
| tracker.trackClick( | ||
| 'campaign_id', // Source name | ||
| 'summer_sale', // Click ID value | ||
| 7 * 24 * 60 * 60 * 1000 // Optional 7-day expiration | ||
| ) | ||
| ``` | ||
| Manually track a click ID. | ||
| ### `getClicks(source?)` | ||
| #### clear(source?: string): void | ||
| Retrieve tracked clicks: | ||
| Clear clicks for a specific source or all sources if no source is provided. | ||
| ```javascript | ||
| // Get all active clicks | ||
| const allClicks = tracker.getClicks() | ||
| ### ClickData Structure | ||
| // Get clicks for a specific source | ||
| const fbClicks = tracker.getClicks('fbclid') | ||
| ```typescript | ||
| interface ClickData { | ||
| value: string // Click ID value | ||
| timestamp: number // When the click was recorded | ||
| expiresAt: number // When the click data expires | ||
| landing: string // Landing page URL | ||
| referrer: string // Referrer URL | ||
| } | ||
| ``` | ||
| ### `clear(source?)` | ||
| ## Use Cases | ||
| Clear stored clicks: | ||
| ### Attribution Tracking | ||
| Track which ad campaigns are driving conversions by capturing click IDs when users arrive on your site. | ||
| ```javascript | ||
| // Clear Google clicks | ||
| tracker.clear('gclid') | ||
| // On your conversion/thank you page | ||
| import AdsClickTracker from '@analytics-debugger/ads-click-tracker' | ||
| // Clear all clicks | ||
| tracker.clear() | ||
| ``` | ||
| ## 🚀 Advanced Examples | ||
| ### Marketing Attribution Setup | ||
| ```javascript | ||
| const marketingTracker = initTracker({ | ||
| storageKey: 'marketing_clicks', | ||
| const tracker = AdsClickTracker({ | ||
| clickIdConfigs: [ | ||
| { name: 'gclid', expirationMs: 30 * 24 * 60 * 60 * 1000 }, // Google (30 days) | ||
| { name: 'fbclid', expirationMs: 7 * 24 * 60 * 60 * 1000 }, // Facebook (7 days) | ||
| { name: 'utm_campaign', maxClicks: 200 } // Custom campaign tracking | ||
| { name: 'gclid' }, | ||
| { name: 'fbclid' } | ||
| ] | ||
| }) | ||
| // Get the latest clicks to include in your conversion tracking | ||
| const latestClicks = tracker.get(true) | ||
| // Send this data to your analytics or CRM system | ||
| sendToAnalytics({ | ||
| conversion: true, | ||
| value: 100, | ||
| adClicks: latestClicks | ||
| }) | ||
| ``` | ||
| ### React Integration | ||
| ### Integration with Forms | ||
| Automatically include ad click data in your form submissions for attribution. | ||
| ```javascript | ||
| import { initTracker } from 'ads-click-tracker' | ||
| import { useEffect } from 'react' | ||
| document.querySelector('#leadForm').addEventListener('submit', (event) => { | ||
| const tracker = initTracker({ | ||
| clickIdConfigs: [ | ||
| { name: 'gclid' }, | ||
| { name: 'fbclid' }, | ||
| { name: 'ttclid' }, | ||
| { name: 'msclkid' } | ||
| ] | ||
| }) | ||
| function useClickTracker() { | ||
| useEffect(() => { | ||
| const tracker = initTracker({ | ||
| clickIdConfigs: [{ name: 'campaign_id' }] | ||
| }) | ||
| const latestClicks = tracker.get(true) | ||
| return () => tracker.clear() | ||
| }, []) | ||
| } | ||
| // Add hidden fields to your form with click data | ||
| Object.entries(latestClicks).forEach(([source, clicks]) => { | ||
| if (clicks.length > 0) { | ||
| const hiddenField = document.createElement('input') | ||
| hiddenField.type = 'hidden' | ||
| hiddenField.name = source | ||
| hiddenField.value = clicks[0].value | ||
| event.target.appendChild(hiddenField) | ||
| } | ||
| }) | ||
| }) | ||
| ``` | ||
| ## 🌐 Browser Support | ||
| ## Browser Support | ||
| - Chrome | ||
| - Firefox | ||
| - Safari | ||
| - Edge (latest versions) | ||
| AdsClickTracker works in all modern browsers that support localStorage and ES6 features. For older browsers, consider using a transpiler like Babel. | ||
| **Requirements:** | ||
| - localStorage support | ||
| - Modern browsers | ||
| ## License | ||
| **Note:** Internet Explorer 11 is not supported | ||
| MIT | ||
| ## 📜 License | ||
| MIT License | ||
| ## 🤝 Contributing | ||
@@ -197,0 +218,0 @@ |
31961
14.31%457
6.28%223
10.4%