@bluenath/engage 1.0.4 → 2.0.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 +58 -69
- package/dist/core/Analytics.d.ts +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +40 -3
- package/dist/types/index.d.ts +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,50 +1,38 @@
|
|
|
1
1
|
# @bluenath/engage
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Production-ready analytics SDK for EngagePro attribution and campaign intelligence.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This SDK captures web events, enriches intent signals, and sends batched payloads to EngagePro ingest for campaign-level ROI measurement.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- 😡 **Rage Click Detection**: Detects user frustration (rapid clicking) automatically.
|
|
9
|
-
- ⚡️ **Performance Monitoring**: Tracks Core Web Vitals (LCP, TTFB, Load Time) out of the box.
|
|
10
|
-
- 🔌 **Offline Support**: Queues events in LocalStorage when offline and syncs when connectivity returns.
|
|
11
|
-
- 🛡️ **PII Protection**: Automatically ignores password and credit card fields.
|
|
12
|
-
- ⚛️ **First-Class React Support**: Drop-in Provider component.
|
|
7
|
+
## Highlights
|
|
13
8
|
|
|
14
|
-
|
|
9
|
+
- Auto-captures high-value interaction events (page, commerce, form, rage-click)
|
|
10
|
+
- Supports manual business events with value and currency fields
|
|
11
|
+
- Handles offline queueing and retry with backoff
|
|
12
|
+
- Includes lightweight React Provider and hook API
|
|
13
|
+
- Protects sensitive input fields during capture
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
npm install @bluenath/engage
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
or
|
|
15
|
+
## Installation
|
|
21
16
|
|
|
22
17
|
```bash
|
|
23
|
-
|
|
18
|
+
npm install @bluenath/engage
|
|
24
19
|
```
|
|
25
20
|
|
|
26
|
-
##
|
|
27
|
-
|
|
28
|
-
### 1. Wrap your App
|
|
21
|
+
## Quick Start (React)
|
|
29
22
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
```jsx
|
|
23
|
+
```tsx
|
|
33
24
|
import React from "react";
|
|
34
25
|
import ReactDOM from "react-dom/client";
|
|
35
26
|
import { EngageProProvider } from "@bluenath/engage";
|
|
36
27
|
import App from "./App";
|
|
37
28
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
root.render(
|
|
29
|
+
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
41
30
|
<EngageProProvider
|
|
42
|
-
writeKey="
|
|
43
|
-
apiHost="https://your-api.com/analyticsdk/ingest"
|
|
31
|
+
writeKey="YOUR_PUBLIC_WRITE_KEY"
|
|
44
32
|
tracking={{
|
|
45
|
-
autoTrack: true,
|
|
46
|
-
useCookies: true,
|
|
47
|
-
fingerprint: true,
|
|
33
|
+
autoTrack: true,
|
|
34
|
+
useCookies: true,
|
|
35
|
+
fingerprint: true,
|
|
48
36
|
}}
|
|
49
37
|
>
|
|
50
38
|
<App />
|
|
@@ -52,59 +40,60 @@ root.render(
|
|
|
52
40
|
);
|
|
53
41
|
```
|
|
54
42
|
|
|
55
|
-
|
|
43
|
+
## Manual Tracking
|
|
56
44
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
```jsx
|
|
45
|
+
```tsx
|
|
60
46
|
import { useAnalytics } from "@bluenath/engage";
|
|
61
47
|
|
|
62
|
-
|
|
63
|
-
const
|
|
48
|
+
export function CheckoutComplete() {
|
|
49
|
+
const analytics = useAnalytics();
|
|
64
50
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
51
|
+
const onPurchase = () => {
|
|
52
|
+
analytics.track("PURCHASE", {
|
|
53
|
+
orderId: "ORD-7281",
|
|
54
|
+
amount: 2499,
|
|
55
|
+
currency: "INR",
|
|
56
|
+
ref: "camp_<campaignId>_cr_<creatorId>",
|
|
70
57
|
});
|
|
71
58
|
};
|
|
72
59
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
category: "Tutorial",
|
|
77
|
-
});
|
|
78
|
-
};
|
|
60
|
+
return <button onClick={onPurchase}>Confirm</button>;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
79
63
|
|
|
80
|
-
|
|
81
|
-
|
|
64
|
+
## Attribution Reference Format
|
|
65
|
+
|
|
66
|
+
For campaign-level attribution, include this exact `ref` format in tracked events:
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
camp_<campaignId>_cr_<creatorId>
|
|
82
70
|
```
|
|
83
71
|
|
|
84
|
-
|
|
72
|
+
This allows EngagePro to map conversions to creator partnerships and payout logic.
|
|
73
|
+
|
|
74
|
+
## Endpoint Policy
|
|
75
|
+
|
|
76
|
+
- Production ingest endpoint is fixed to EngagePro managed backend.
|
|
77
|
+
- Optional `apiHost` override is accepted only for local development (`localhost` or `127.0.0.1`).
|
|
78
|
+
- Unsupported hosts are rejected and automatically fall back to the managed endpoint.
|
|
85
79
|
|
|
86
|
-
|
|
80
|
+
## Configuration
|
|
87
81
|
|
|
88
|
-
|
|
|
89
|
-
|
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `PERFORMANCE` | Page Load metrics (TTFB, Load Time) |
|
|
82
|
+
| Field | Type | Required | Description |
|
|
83
|
+
| ---------------------- | --------- | -------- | ----------------------------------------------- |
|
|
84
|
+
| `writeKey` | `string` | Yes | Brand public write key from EngagePro dashboard |
|
|
85
|
+
| `apiHost` | `string` | No | Local override only for local development |
|
|
86
|
+
| `tracking.autoTrack` | `boolean` | Yes | Enable DOM interaction capture |
|
|
87
|
+
| `tracking.useCookies` | `boolean` | Yes | Persist anonymous identity across sessions |
|
|
88
|
+
| `tracking.fingerprint` | `boolean` | Yes | Include device fingerprint context |
|
|
89
|
+
| `debug` | `boolean` | No | Log payloads in console for integration testing |
|
|
97
90
|
|
|
98
|
-
##
|
|
91
|
+
## Security Notes
|
|
99
92
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
| `apiHost` | `string` | `...` | The endpoint where events are sent. |
|
|
104
|
-
| `tracking.autoTrack` | `boolean` | `true` | Enables/Disables DOM listening. |
|
|
105
|
-
| `tracking.useCookies` | `boolean` | `false` | Use cookies for cross-subdomain tracking. |
|
|
106
|
-
| `debug` | `boolean` | `false` | Log events to console for debugging. |
|
|
93
|
+
- Do not use internal service keys in frontend apps.
|
|
94
|
+
- Rotate leaked write keys immediately from EngagePro dashboard.
|
|
95
|
+
- Avoid sending raw PII inside custom properties.
|
|
107
96
|
|
|
108
|
-
##
|
|
97
|
+
## License
|
|
109
98
|
|
|
110
|
-
MIT
|
|
99
|
+
MIT
|
package/dist/core/Analytics.d.ts
CHANGED
|
@@ -9,5 +9,6 @@ export declare class EngagePro {
|
|
|
9
9
|
identify: (userId: string, traits?: any) => void;
|
|
10
10
|
page: (name?: string, properties?: any) => void;
|
|
11
11
|
track: (eventName: string, properties?: any, intelligence?: Partial<AnalyticsEvent>) => void;
|
|
12
|
+
private parseRef;
|
|
12
13
|
private processEvent;
|
|
13
14
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var t=Object.defineProperty,e=(e,n,i)=>((e,n,i)=>n in e?t(e,n,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[n]=i)(e,"symbol"!=typeof n?n+"":n,i);Object.defineProperties(exports,{t:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const n=require("react/jsx-runtime"),i=require("react");let s;const r=new Uint8Array(16);function o(){if(!s&&(s="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto),!s))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return s(r)}const a=[];for(let g=0;g<256;++g)a.push((g+256).toString(16).slice(1));const c={randomUUID:"undefined"!=typeof crypto&&crypto.randomUUID&&crypto.randomUUID.bind(crypto)};function d(t,e,n){if(c.randomUUID&&!t)return c.randomUUID();const i=(t=t||{}).random||(t.rng||o)();return i[6]=15&i[6]|64,i[8]=63&i[8]|128,function(t,e=0){return a[t[e+0]]+a[t[e+1]]+a[t[e+2]]+a[t[e+3]]+"-"+a[t[e+4]]+a[t[e+5]]+"-"+a[t[e+6]]+a[t[e+7]]+"-"+a[t[e+8]]+a[t[e+9]]+"-"+a[t[e+10]]+a[t[e+11]]+a[t[e+12]]+a[t[e+13]]+a[t[e+14]]+a[t[e+15]]}(i)}const h=t=>{let e=2166136261;const n=t.length;for(let i=0;i<n;i++)e^=t.charCodeAt(i),e+=(e<<1)+(e<<4)+(e<<7)+(e<<8)+(e<<24);return("0000000"+(e>>>0).toString(16)).substr(-8)};class l{constructor(t="local"){e(this,"memoryStore",{}),e(this,"keyPrefix","engage_"),e(this,"domain"),this.type=t,this.domain=this.getCookieDomain()}getItem(t){const e=this.keyPrefix+t;if(this.memoryStore.hasOwnProperty(e))return this.memoryStore[e];try{if("local"===this.type&&this.isBrowser())return window.localStorage.getItem(e);if("cookie"===this.type&&this.isBrowser())return this.getCookie(e)}catch(n){return null}return null}setItem(t,e){const n=this.keyPrefix+t;this.memoryStore[n]=e;try{"local"===this.type&&this.isBrowser()?window.localStorage.setItem(n,e):"cookie"===this.type&&this.isBrowser()&&this.setCookie(n,e,365)}catch(i){this.isQuotaError(i)&&(this.type="memory")}}removeItem(t){const e=this.keyPrefix+t;delete this.memoryStore[e];try{"local"===this.type&&this.isBrowser()?window.localStorage.removeItem(e):"cookie"===this.type&&this.isBrowser()&&this.setCookie(e,"",-1)}catch(n){}}getCookie(t){const e=t+"=",n=document.cookie.split(";");for(let i=0;i<n.length;i++){let t=n[i];for(;" "===t.charAt(0);)t=t.substring(1,t.length);if(0===t.indexOf(e))return decodeURIComponent(t.substring(e.length,t.length))}return null}setCookie(t,e,n){let i="";if(n){const t=new Date;t.setTime(t.getTime()+24*n*60*60*1e3),i="; expires="+t.toUTCString()}document.cookie=`${t}=${encodeURIComponent(e)}${i}; path=/; domain=${this.domain}; SameSite=Lax; Secure`}getCookieDomain(){if(!this.isBrowser())return"";const t=window.location.hostname,e=t.split(".");return 1===e.length||"localhost"===t?"":e.length>2?"."+e.slice(-2).join("."):"."+t}isBrowser(){try{return"undefined"!=typeof window&&void 0!==window.document}catch(t){return!1}}isQuotaError(t){return t instanceof DOMException&&(22===t.code||1014===t.code||"QuotaExceededError"===t.name||"NS_ERROR_DOM_QUOTA_REACHED"===t.name)}}class u{constructor(t){e(this,"storage"),e(this,"SESSION_TIMEOUT",18e5),e(this,"deviceId"),e(this,"sessionId"),e(this,"userId",null),e(this,"currentUrl"),e(this,"referrer"),this.storage=new l(t.persistence),this.deviceId=this.getOrSetDeviceId(),this.sessionId="",this.manageSession(),"undefined"!=typeof window?(this.currentUrl=window.location.href,this.referrer=document.referrer,this.listenToHistory()):(this.currentUrl="",this.referrer="")}getOrSetDeviceId(){const t=this.storage.getItem("device_id");if(t)return t;const e=(()=>{if("undefined"==typeof window)return"server-side-id";const t=navigator,e=window.screen,n={userAgent:t.userAgent||"",screenRes:`${e.width}x${e.height}`,colorDepth:e.colorDepth||0,timezone:(new Date).getTimezoneOffset(),language:t.language||"en-US",platform:t.platform||"unknown",hardwareConcurrency:t.hardwareConcurrency||1,deviceMemory:t.deviceMemory||0},i=[n.platform,n.language,n.screenRes,n.colorDepth,n.timezone,n.hardwareConcurrency,n.deviceMemory].join("|");return`${h(i)}-${h(n.userAgent)}`})();return this.storage.setItem("device_id",e),e}manageSession(){const t=Date.now(),e=this.storage.getItem("session_id"),n=parseInt(this.storage.getItem("last_activity")||"0");if(!e||t-n>this.SESSION_TIMEOUT?(this.sessionId=`sess_${t}_${Math.random().toString(36).substr(2,9)}`,this.storage.setItem("session_id",this.sessionId)):this.sessionId=e,this.storage.setItem("last_activity",t.toString()),"undefined"!=typeof window){const t=()=>this.storage.setItem("last_activity",Date.now().toString());window.addEventListener("click",t),window.addEventListener("scroll",t)}}listenToHistory(){const t=history.pushState;history.pushState=(...e)=>{t.apply(history,e),this.handleUrlChange()},window.addEventListener("popstate",()=>this.handleUrlChange())}handleUrlChange(){const t=window.location.href;t!==this.currentUrl&&(this.referrer=this.currentUrl,this.currentUrl=t)}getPayload(){var t;return{library:{name:"@engagepro/analytics",version:"2.0.0"},user:{anonymousId:this.deviceId,id:this.userId},session:{id:this.sessionId,startTime:parseInt(this.sessionId.split("_")[1]||Date.now().toString())},page:{path:"undefined"!=typeof window?window.location.pathname:"",referrer:this.referrer,title:"undefined"!=typeof document?document.title:"",search:"undefined"!=typeof window?window.location.search:"",url:this.currentUrl},network:{online:"undefined"==typeof navigator||navigator.onLine,downlink:null==(t=navigator.connection)?void 0:t.downlink},screen:{width:"undefined"!=typeof screen?screen.width:0,height:"undefined"!=typeof screen?screen.height:0,density:"undefined"!=typeof window?window.devicePixelRatio:1},device:{fingerprint:this.deviceId,type:this.getDeviceType(),userAgent:"undefined"!=typeof navigator?navigator.userAgent:"server"},locale:"undefined"!=typeof navigator?navigator.language:"en-US",timezone:Intl.DateTimeFormat().resolvedOptions().timeZone}}getDeviceType(){if("undefined"==typeof navigator)return"desktop";const t=navigator.userAgent;return/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(t)?"tablet":/Mobile|Android|iP(hone|od)|IEMobile|BlackBerry|Kindle|Silk-Accelerated/.test(t)?"mobile":"desktop"}}class f{constructor(t){e(this,"queue",[]),e(this,"storage"),e(this,"transport"),e(this,"STORAGE_KEY","engage_queue_v1"),e(this,"isFlushing",!1),e(this,"flushInterval"),e(this,"retryCount",0),this.transport=t,this.storage=new l("local"),this.load(),"undefined"!=typeof window&&(window.addEventListener("online",()=>{this.retryCount=0,this.flush()}),window.addEventListener("visibilitychange",()=>{"hidden"===document.visibilityState&&this.flush()})),this.startTimer()}startTimer(){this.flushInterval&&clearInterval(this.flushInterval);const t=3e3*Math.pow(2,this.retryCount);this.flushInterval=setInterval(()=>{this.flush()},Math.min(t,3e4))}enqueue(t){this.queue.push(t),this.save(),(this.queue.length>=10||"PURCHASE"===t.standardEvent)&&this.flush()}save(){this.storage.setItem(this.STORAGE_KEY,JSON.stringify(this.queue))}load(){const t=this.storage.getItem(this.STORAGE_KEY);if(t)try{this.queue=JSON.parse(t)}catch(e){this.queue=[]}}async flush(){if(0===this.queue.length||this.isFlushing)return;if("undefined"!=typeof navigator&&!navigator.onLine)return;this.isFlushing=!0;const t=[...this.queue];this.queue=[],this.save();try{if(!(await this.transport.send(t)))throw new Error("Server rejected batch");this.retryCount=0,this.startTimer()}catch(e){this.queue=[...t,...this.queue],this.save(),this.retryCount++,this.startTimer()}finally{this.isFlushing=!1}}}class w{constructor(t){this.apiEndpoint=t}async send(t){const e=JSON.stringify({batch:t,sentAt:(new Date).toISOString()});if("undefined"!=typeof navigator&&navigator.sendBeacon&&e.length<6e4){const t=new Blob([e],{type:"application/json"});if(navigator.sendBeacon(this.apiEndpoint,t))return!0}try{return(await fetch(this.apiEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:e,keepalive:!0})).ok}catch(n){return!1}}}class p{constructor(t){e(this,"config"),e(this,"context"),e(this,"queue"),e(this,"transport"),e(this,"identify",(t,e)=>{this.context.userId=t,this.processEvent({event:"Identify",properties:{traits:e},standardEvent:"LOGIN",intent:"identity",confidence:1})}),e(this,"page",(t,e)=>{const n=this.context.getPayload().page;this.processEvent({event:t||n.title||"Unknown Page",properties:{path:n.path,referrer:n.referrer,title:n.title,...e},standardEvent:"PAGE_VIEW",intent:"navigation",confidence:1})}),e(this,"track",(t,e,n)=>{this.processEvent({event:t,properties:e||{},...n})}),this.config=t,this.transport=new w(t.apiHost||"http://127.0.0.1:3000/api/v1/tracking/ingest"),this.context=new u({persistence:t.tracking.useCookies?"cookie":"local"}),this.queue=new f(this.transport)}processEvent(t){const e=this.context.getPayload(),n={event:t.event,properties:t.properties,standardEvent:t.standardEvent,intent:t.intent,confidence:t.confidence,rawLabel:t.rawLabel,timestamp:(new Date).toISOString(),messageId:d(),writeKey:this.config.writeKey,userId:e.user.id||void 0,anonymousId:e.user.anonymousId,context:e};this.config.debug,this.queue.enqueue(n)}}class y{constructor(t){e(this,"analytics"),e(this,"metrics",{}),this.analytics=t,"undefined"!=typeof window&&"PerformanceObserver"in window&&this.observe()}observe(){try{new PerformanceObserver(t=>{for(const e of t.getEntries()){const t=e;t.hadRecentInput||(this.metrics.cls=(this.metrics.cls||0)+t.value)}}).observe({type:"layout-shift",buffered:!0}),new PerformanceObserver(t=>{const e=t.getEntries(),n=e[e.length-1];this.metrics.lcp=n.renderTime||n.loadTime,this.logMetric("LCP",this.metrics.lcp)}).observe({type:"largest-contentful-paint",buffered:!0}),new PerformanceObserver(t=>{const e=t.getEntries()[0];e&&(this.metrics.fid=e.processingStart-e.startTime,this.logMetric("FID",this.metrics.fid))}).observe({type:"first-input",buffered:!0}),window.addEventListener("visibilitychange",()=>{"hidden"===document.visibilityState&&this.metrics.cls&&this.logMetric("CLS",this.metrics.cls)})}catch(t){}}logMetric(t,e){e<0||this.analytics.track(`Core Web Vital: ${t}`,{metric:t,value:Math.round(e)},{standardEvent:"PERFORMANCE",intent:"performance",confidence:1,rawLabel:`${t}: ${Math.round(e)}ms`})}}const v=i.createContext(null);let m=null;exports.EngageProProvider=({children:t,...e})=>{const s=i.useRef(null),r=i.useRef(null);s.current||(s.current=new p(e),"undefined"!=typeof window&&new y(s.current));const o=s.current;return i.useEffect(()=>{if(!e.tracking.autoTrack)return;const t=t=>{const e=t.target.closest('button, a, input[type="submit"], [data-track], .clickable');if(e){const t=Date.now(),n=r.current;n&&n.el===e&&t-n.ts<500?(n.count++,n.ts=t,3===n.count&&(o.track("Rage Click detected",{element:e.tagName},{standardEvent:"RAGE_CLICK",intent:"frustration",confidence:1}),r.current=null)):r.current={el:e,count:1,ts:t};const i=(t=>{let e=(t.innerText||t.value||t.getAttribute("aria-label")||"").trim();if(!e){const n=t.querySelector("img");n&&n.alt&&(e=n.alt);const i=t.querySelector("title");i&&(e=i.textContent||"")}const n=e.slice(0,100),i=n.toLowerCase(),s=(t.id||"").toLowerCase(),r=t.href||"";return/add to (cart|bag)|buy now/.test(i)||s.includes("add-to-cart")?{standard:"ADD_TO_CART",intent:"commerce",label:n,confidence:.9}:/checkout|proceed/.test(i)||r.includes("/checkout")?{standard:"INITIATE_CHECKOUT",intent:"commerce",label:n,confidence:.9}:/place order|pay now/.test(i)||s.includes("place-order")?{standard:"PURCHASE",intent:"commerce",label:n,confidence:.95}:/cancel order/.test(i)||s.includes("cancel")?{standard:"ORDER_CANCEL",intent:"lifecycle",label:n,confidence:.85}:/refund|return/.test(i)||s.includes("refund")?{standard:"ORDER_REFUND",intent:"lifecycle",label:n,confidence:.85}:/track package|shipping/.test(i)?{standard:"TRACK_PACKAGE",intent:"lifecycle",label:n,confidence:.8}:/write review/.test(i)||i.includes("star")&&/^[1-5]/.test(i)?{standard:"RATE_PRODUCT",intent:"engagement",label:n,confidence:.8}:i.includes("search")||s.includes("search")?{standard:"SEARCH",intent:"search",label:n,confidence:.7}:{standard:"GENERIC",intent:"interaction",label:n,confidence:.5}})(e),s="A"===e.tagName;o.track("Interaction",{element:e.tagName.toLowerCase(),id:e.id,destination:s?e.href:void 0},{standardEvent:i.standard,intent:i.intent,rawLabel:i.label,confidence:i.confidence})}},n=t=>{const e=t.target;(t=>"password"===t.getAttribute("type")||"hidden"===t.getAttribute("type")||/password|cvc|card|cc-num|ssn|credit|hidden/i.test(t.getAttribute("name")||t.id||""))(e)||"focusin"!==t.type||e.dataset.tracked||(e.dataset.tracked="true",o.track("Form Start",{field:e.name||e.id},{standardEvent:"FORM_START",intent:"identity"}))},i=()=>{const t=new URLSearchParams(window.location.search);t.has("q")&&o.track("Search Query",{query:t.get("q")},{standardEvent:"SEARCH",intent:"search"}),requestAnimationFrame(()=>o.page())},s=history.pushState;return history.pushState=(...t)=>{s.apply(history,t),i()},window.addEventListener("popstate",i),window.addEventListener("click",t,!0),window.addEventListener("focusin",n,!0),i(),()=>{window.removeEventListener("popstate",i),window.removeEventListener("click",t,!0),window.removeEventListener("focusin",n,!0)}},[e.tracking.autoTrack]),n.jsx(v.Provider,{value:o,children:t})},exports.default=p,exports.init=t=>(m||(m=new p(t)),m),exports.useAnalytics=()=>{const t=i.useContext(v);if(!t)throw new Error("useAnalytics must be used within EngageProProvider");return t};
|
|
1
|
+
"use strict";var t=Object.defineProperty,e=(e,n,i)=>((e,n,i)=>n in e?t(e,n,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[n]=i)(e,"symbol"!=typeof n?n+"":n,i);Object.defineProperties(exports,{t:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const n=require("react/jsx-runtime"),i=require("react");let s;const r=new Uint8Array(16);function o(){if(!s&&(s="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto),!s))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return s(r)}const a=[];for(let E=0;E<256;++E)a.push((E+256).toString(16).slice(1));const c={randomUUID:"undefined"!=typeof crypto&&crypto.randomUUID&&crypto.randomUUID.bind(crypto)};function d(t,e,n){if(c.randomUUID&&!t)return c.randomUUID();const i=(t=t||{}).random||(t.rng||o)();return i[6]=15&i[6]|64,i[8]=63&i[8]|128,function(t,e=0){return a[t[e+0]]+a[t[e+1]]+a[t[e+2]]+a[t[e+3]]+"-"+a[t[e+4]]+a[t[e+5]]+"-"+a[t[e+6]]+a[t[e+7]]+"-"+a[t[e+8]]+a[t[e+9]]+"-"+a[t[e+10]]+a[t[e+11]]+a[t[e+12]]+a[t[e+13]]+a[t[e+14]]+a[t[e+15]]}(i)}const h=t=>{let e=2166136261;const n=t.length;for(let i=0;i<n;i++)e^=t.charCodeAt(i),e+=(e<<1)+(e<<4)+(e<<7)+(e<<8)+(e<<24);return("0000000"+(e>>>0).toString(16)).substr(-8)};class l{constructor(t="local"){e(this,"memoryStore",{}),e(this,"keyPrefix","engage_"),e(this,"domain"),this.type=t,this.domain=this.getCookieDomain()}getItem(t){const e=this.keyPrefix+t;if(this.memoryStore.hasOwnProperty(e))return this.memoryStore[e];try{if("local"===this.type&&this.isBrowser())return window.localStorage.getItem(e);if("cookie"===this.type&&this.isBrowser())return this.getCookie(e)}catch(n){return null}return null}setItem(t,e){const n=this.keyPrefix+t;this.memoryStore[n]=e;try{"local"===this.type&&this.isBrowser()?window.localStorage.setItem(n,e):"cookie"===this.type&&this.isBrowser()&&this.setCookie(n,e,365)}catch(i){this.isQuotaError(i)&&(this.type="memory")}}removeItem(t){const e=this.keyPrefix+t;delete this.memoryStore[e];try{"local"===this.type&&this.isBrowser()?window.localStorage.removeItem(e):"cookie"===this.type&&this.isBrowser()&&this.setCookie(e,"",-1)}catch(n){}}getCookie(t){const e=t+"=",n=document.cookie.split(";");for(let i=0;i<n.length;i++){let t=n[i];for(;" "===t.charAt(0);)t=t.substring(1,t.length);if(0===t.indexOf(e))return decodeURIComponent(t.substring(e.length,t.length))}return null}setCookie(t,e,n){let i="";if(n){const t=new Date;t.setTime(t.getTime()+24*n*60*60*1e3),i="; expires="+t.toUTCString()}document.cookie=`${t}=${encodeURIComponent(e)}${i}; path=/; domain=${this.domain}; SameSite=Lax; Secure`}getCookieDomain(){if(!this.isBrowser())return"";const t=window.location.hostname,e=t.split(".");return 1===e.length||"localhost"===t?"":e.length>2?"."+e.slice(-2).join("."):"."+t}isBrowser(){try{return"undefined"!=typeof window&&void 0!==window.document}catch(t){return!1}}isQuotaError(t){return t instanceof DOMException&&(22===t.code||1014===t.code||"QuotaExceededError"===t.name||"NS_ERROR_DOM_QUOTA_REACHED"===t.name)}}class u{constructor(t){e(this,"storage"),e(this,"SESSION_TIMEOUT",18e5),e(this,"deviceId"),e(this,"sessionId"),e(this,"userId",null),e(this,"currentUrl"),e(this,"referrer"),this.storage=new l(t.persistence),this.deviceId=this.getOrSetDeviceId(),this.sessionId="",this.manageSession(),"undefined"!=typeof window?(this.currentUrl=window.location.href,this.referrer=document.referrer,this.listenToHistory()):(this.currentUrl="",this.referrer="")}getOrSetDeviceId(){const t=this.storage.getItem("device_id");if(t)return t;const e=(()=>{if("undefined"==typeof window)return"server-side-id";const t=navigator,e=window.screen,n={userAgent:t.userAgent||"",screenRes:`${e.width}x${e.height}`,colorDepth:e.colorDepth||0,timezone:(new Date).getTimezoneOffset(),language:t.language||"en-US",platform:t.platform||"unknown",hardwareConcurrency:t.hardwareConcurrency||1,deviceMemory:t.deviceMemory||0},i=[n.platform,n.language,n.screenRes,n.colorDepth,n.timezone,n.hardwareConcurrency,n.deviceMemory].join("|");return`${h(i)}-${h(n.userAgent)}`})();return this.storage.setItem("device_id",e),e}manageSession(){const t=Date.now(),e=this.storage.getItem("session_id"),n=parseInt(this.storage.getItem("last_activity")||"0");if(!e||t-n>this.SESSION_TIMEOUT?(this.sessionId=`sess_${t}_${Math.random().toString(36).substr(2,9)}`,this.storage.setItem("session_id",this.sessionId)):this.sessionId=e,this.storage.setItem("last_activity",t.toString()),"undefined"!=typeof window){const t=()=>this.storage.setItem("last_activity",Date.now().toString());window.addEventListener("click",t),window.addEventListener("scroll",t)}}listenToHistory(){const t=history.pushState;history.pushState=(...e)=>{t.apply(history,e),this.handleUrlChange()},window.addEventListener("popstate",()=>this.handleUrlChange())}handleUrlChange(){const t=window.location.href;t!==this.currentUrl&&(this.referrer=this.currentUrl,this.currentUrl=t)}getPayload(){var t;return{library:{name:"@engagepro/analytics",version:"2.0.0"},user:{anonymousId:this.deviceId,id:this.userId},session:{id:this.sessionId,startTime:parseInt(this.sessionId.split("_")[1]||Date.now().toString())},page:{path:"undefined"!=typeof window?window.location.pathname:"",referrer:this.referrer,title:"undefined"!=typeof document?document.title:"",search:"undefined"!=typeof window?window.location.search:"",url:this.currentUrl},network:{online:"undefined"==typeof navigator||navigator.onLine,downlink:null==(t=navigator.connection)?void 0:t.downlink},screen:{width:"undefined"!=typeof screen?screen.width:0,height:"undefined"!=typeof screen?screen.height:0,density:"undefined"!=typeof window?window.devicePixelRatio:1},device:{fingerprint:this.deviceId,type:this.getDeviceType(),userAgent:"undefined"!=typeof navigator?navigator.userAgent:"server"},locale:"undefined"!=typeof navigator?navigator.language:"en-US",timezone:Intl.DateTimeFormat().resolvedOptions().timeZone}}getDeviceType(){if("undefined"==typeof navigator)return"desktop";const t=navigator.userAgent;return/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(t)?"tablet":/Mobile|Android|iP(hone|od)|IEMobile|BlackBerry|Kindle|Silk-Accelerated/.test(t)?"mobile":"desktop"}}class f{constructor(t){e(this,"queue",[]),e(this,"storage"),e(this,"transport"),e(this,"STORAGE_KEY","engage_queue_v1"),e(this,"isFlushing",!1),e(this,"flushInterval"),e(this,"retryCount",0),this.transport=t,this.storage=new l("local"),this.load(),"undefined"!=typeof window&&(window.addEventListener("online",()=>{this.retryCount=0,this.flush()}),window.addEventListener("visibilitychange",()=>{"hidden"===document.visibilityState&&this.flush()})),this.startTimer()}startTimer(){this.flushInterval&&clearInterval(this.flushInterval);const t=3e3*Math.pow(2,this.retryCount);this.flushInterval=setInterval(()=>{this.flush()},Math.min(t,3e4))}enqueue(t){this.queue.push(t),this.save(),(this.queue.length>=10||"PURCHASE"===t.standardEvent)&&this.flush()}save(){this.storage.setItem(this.STORAGE_KEY,JSON.stringify(this.queue))}load(){const t=this.storage.getItem(this.STORAGE_KEY);if(t)try{this.queue=JSON.parse(t)}catch(e){this.queue=[]}}async flush(){if(0===this.queue.length||this.isFlushing)return;if("undefined"!=typeof navigator&&!navigator.onLine)return;this.isFlushing=!0;const t=[...this.queue];this.queue=[],this.save();try{if(!(await this.transport.send(t)))throw new Error("Server rejected batch");this.retryCount=0,this.startTimer()}catch(e){this.queue=[...t,...this.queue],this.save(),this.retryCount++,this.startTimer()}finally{this.isFlushing=!1}}}class w{constructor(t){this.apiEndpoint=t}async send(t){const e=JSON.stringify({batch:t,sentAt:(new Date).toISOString()});if("undefined"!=typeof navigator&&navigator.sendBeacon&&e.length<6e4){const t=new Blob([e],{type:"application/json"});if(navigator.sendBeacon(this.apiEndpoint,t))return!0}try{return(await fetch(this.apiEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:e,keepalive:!0})).ok}catch(n){return!1}}}const p="https://engage-api.bluenath.com/api/v1/tracking/ingest",y=new Set(["localhost","127.0.0.1"]);class v{constructor(t){e(this,"config"),e(this,"context"),e(this,"queue"),e(this,"transport"),e(this,"identify",(t,e)=>{this.context.userId=t,this.processEvent({event:"Identify",properties:{traits:e},standardEvent:"LOGIN",intent:"identity",confidence:1})}),e(this,"page",(t,e)=>{const n=this.context.getPayload().page;this.processEvent({event:t||n.title||"Unknown Page",properties:{path:n.path,referrer:n.referrer,title:n.title,...e},standardEvent:"PAGE_VIEW",intent:"navigation",confidence:1})}),e(this,"track",(t,e,n)=>{this.processEvent({event:t,properties:e||{},...n})}),this.config=t,this.transport=new w((t=>{if(!t)return p;try{const e=new URL(t);return e.hostname.endsWith("bluenath.com")||y.has(e.hostname)?(e.pathname="/api/v1/tracking/ingest",e.search="",e.hash="",e.toString()):p}catch{return p}})(t.apiHost)),this.context=new u({persistence:t.tracking.useCookies?"cookie":"local"}),this.queue=new f(this.transport)}parseRef(t){if(!t)return{};const e=t.match(/^camp_([^_]+)_cr_([^_]+)$/);return e?{campaignId:e[1],creatorId:e[2]}:{}}processEvent(t){var e,n,i,s,r,o;const a=this.context.getPayload(),c=(null==(e=t.properties)?void 0:e.ref)||new URLSearchParams(a.page.search||"").get("ref")||void 0,h=this.parseRef(c),l=(null==(n=t.properties)?void 0:n.value)??(null==(i=t.properties)?void 0:i.amount),u=Number(l),f={event:t.event,properties:t.properties,standardEvent:t.standardEvent,intent:t.intent,confidence:t.confidence,rawLabel:t.rawLabel,timestamp:(new Date).toISOString(),messageId:d(),writeKey:this.config.writeKey,ref:c,campaignId:(null==(s=t.properties)?void 0:s.campaignId)||h.campaignId,creatorId:(null==(r=t.properties)?void 0:r.creatorId)||h.creatorId,value:Number.isFinite(u)?u:void 0,currency:(null==(o=t.properties)?void 0:o.currency)||"INR",userId:a.user.id||void 0,anonymousId:a.user.anonymousId,context:a};this.config.debug,this.queue.enqueue(f)}}class m{constructor(t){e(this,"analytics"),e(this,"metrics",{}),this.analytics=t,"undefined"!=typeof window&&"PerformanceObserver"in window&&this.observe()}observe(){try{new PerformanceObserver(t=>{for(const e of t.getEntries()){const t=e;t.hadRecentInput||(this.metrics.cls=(this.metrics.cls||0)+t.value)}}).observe({type:"layout-shift",buffered:!0}),new PerformanceObserver(t=>{const e=t.getEntries(),n=e[e.length-1];this.metrics.lcp=n.renderTime||n.loadTime,this.logMetric("LCP",this.metrics.lcp)}).observe({type:"largest-contentful-paint",buffered:!0}),new PerformanceObserver(t=>{const e=t.getEntries()[0];e&&(this.metrics.fid=e.processingStart-e.startTime,this.logMetric("FID",this.metrics.fid))}).observe({type:"first-input",buffered:!0}),window.addEventListener("visibilitychange",()=>{"hidden"===document.visibilityState&&this.metrics.cls&&this.logMetric("CLS",this.metrics.cls)})}catch(t){}}logMetric(t,e){e<0||this.analytics.track(`Core Web Vital: ${t}`,{metric:t,value:Math.round(e)},{standardEvent:"PERFORMANCE",intent:"performance",confidence:1,rawLabel:`${t}: ${Math.round(e)}ms`})}}const g=i.createContext(null);let b=null;exports.EngageProProvider=({children:t,...e})=>{const s=i.useRef(null),r=i.useRef(null);s.current||(s.current=new v(e),"undefined"!=typeof window&&new m(s.current));const o=s.current;return i.useEffect(()=>{if(!e.tracking.autoTrack)return;const t=t=>{const e=t.target.closest('button, a, input[type="submit"], [data-track], .clickable');if(e){const t=Date.now(),n=r.current;n&&n.el===e&&t-n.ts<500?(n.count++,n.ts=t,3===n.count&&(o.track("Rage Click detected",{element:e.tagName},{standardEvent:"RAGE_CLICK",intent:"frustration",confidence:1}),r.current=null)):r.current={el:e,count:1,ts:t};const i=(t=>{let e=(t.innerText||t.value||t.getAttribute("aria-label")||"").trim();if(!e){const n=t.querySelector("img");n&&n.alt&&(e=n.alt);const i=t.querySelector("title");i&&(e=i.textContent||"")}const n=e.slice(0,100),i=n.toLowerCase(),s=(t.id||"").toLowerCase(),r=t.href||"";return/add to (cart|bag)|buy now/.test(i)||s.includes("add-to-cart")?{standard:"ADD_TO_CART",intent:"commerce",label:n,confidence:.9}:/checkout|proceed/.test(i)||r.includes("/checkout")?{standard:"INITIATE_CHECKOUT",intent:"commerce",label:n,confidence:.9}:/place order|pay now/.test(i)||s.includes("place-order")?{standard:"PURCHASE",intent:"commerce",label:n,confidence:.95}:/cancel order/.test(i)||s.includes("cancel")?{standard:"ORDER_CANCEL",intent:"lifecycle",label:n,confidence:.85}:/refund|return/.test(i)||s.includes("refund")?{standard:"ORDER_REFUND",intent:"lifecycle",label:n,confidence:.85}:/track package|shipping/.test(i)?{standard:"TRACK_PACKAGE",intent:"lifecycle",label:n,confidence:.8}:/write review/.test(i)||i.includes("star")&&/^[1-5]/.test(i)?{standard:"RATE_PRODUCT",intent:"engagement",label:n,confidence:.8}:i.includes("search")||s.includes("search")?{standard:"SEARCH",intent:"search",label:n,confidence:.7}:{standard:"GENERIC",intent:"interaction",label:n,confidence:.5}})(e),s="A"===e.tagName;o.track("Interaction",{element:e.tagName.toLowerCase(),id:e.id,destination:s?e.href:void 0},{standardEvent:i.standard,intent:i.intent,rawLabel:i.label,confidence:i.confidence})}},n=t=>{const e=t.target;(t=>"password"===t.getAttribute("type")||"hidden"===t.getAttribute("type")||/password|cvc|card|cc-num|ssn|credit|hidden/i.test(t.getAttribute("name")||t.id||""))(e)||"focusin"!==t.type||e.dataset.tracked||(e.dataset.tracked="true",o.track("Form Start",{field:e.name||e.id},{standardEvent:"FORM_START",intent:"identity"}))},i=()=>{const t=new URLSearchParams(window.location.search);t.has("q")&&o.track("Search Query",{query:t.get("q")},{standardEvent:"SEARCH",intent:"search"}),requestAnimationFrame(()=>o.page())},s=history.pushState;return history.pushState=(...t)=>{s.apply(history,t),i()},window.addEventListener("popstate",i),window.addEventListener("click",t,!0),window.addEventListener("focusin",n,!0),i(),()=>{history.pushState=s,window.removeEventListener("popstate",i),window.removeEventListener("click",t,!0),window.removeEventListener("focusin",n,!0)}},[e.tracking.autoTrack]),n.jsx(g.Provider,{value:o,children:t})},exports.default=v,exports.init=t=>(b||(b=new v(t)),b),exports.useAnalytics=()=>{const t=i.useContext(g);if(!t)throw new Error("useAnalytics must be used within EngageProProvider");return t};
|
package/dist/index.js
CHANGED
|
@@ -405,6 +405,28 @@ class Transport {
|
|
|
405
405
|
}
|
|
406
406
|
}
|
|
407
407
|
}
|
|
408
|
+
const DEFAULT_INGEST_ENDPOINT = "https://engage-api.bluenath.com/api/v1/tracking/ingest";
|
|
409
|
+
const FIXED_INGEST_PATH = "/api/v1/tracking/ingest";
|
|
410
|
+
const ALLOWED_DEV_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1"]);
|
|
411
|
+
const resolveIngestEndpoint = (apiHost) => {
|
|
412
|
+
if (!apiHost) return DEFAULT_INGEST_ENDPOINT;
|
|
413
|
+
try {
|
|
414
|
+
const parsed = new URL(apiHost);
|
|
415
|
+
const isAllowedHost = parsed.hostname.endsWith("bluenath.com") || ALLOWED_DEV_HOSTS.has(parsed.hostname);
|
|
416
|
+
if (!isAllowedHost) {
|
|
417
|
+
console.warn(
|
|
418
|
+
`[EngagePro] Ignoring unsupported apiHost "${parsed.hostname}". Falling back to managed endpoint.`
|
|
419
|
+
);
|
|
420
|
+
return DEFAULT_INGEST_ENDPOINT;
|
|
421
|
+
}
|
|
422
|
+
parsed.pathname = FIXED_INGEST_PATH;
|
|
423
|
+
parsed.search = "";
|
|
424
|
+
parsed.hash = "";
|
|
425
|
+
return parsed.toString();
|
|
426
|
+
} catch {
|
|
427
|
+
return DEFAULT_INGEST_ENDPOINT;
|
|
428
|
+
}
|
|
429
|
+
};
|
|
408
430
|
class EngagePro {
|
|
409
431
|
constructor(config) {
|
|
410
432
|
__publicField(this, "config");
|
|
@@ -448,17 +470,26 @@ class EngagePro {
|
|
|
448
470
|
});
|
|
449
471
|
});
|
|
450
472
|
this.config = config;
|
|
451
|
-
this.transport = new Transport(
|
|
452
|
-
config.apiHost || "http://127.0.0.1:3000/api/v1/tracking/ingest"
|
|
453
|
-
);
|
|
473
|
+
this.transport = new Transport(resolveIngestEndpoint(config.apiHost));
|
|
454
474
|
this.context = new Context({
|
|
455
475
|
persistence: config.tracking.useCookies ? "cookie" : "local"
|
|
456
476
|
});
|
|
457
477
|
this.queue = new EventQueue(this.transport);
|
|
458
478
|
}
|
|
479
|
+
parseRef(ref) {
|
|
480
|
+
if (!ref) return {};
|
|
481
|
+
const m = ref.match(/^camp_([^_]+)_cr_([^_]+)$/);
|
|
482
|
+
if (!m) return {};
|
|
483
|
+
return { campaignId: m[1], creatorId: m[2] };
|
|
484
|
+
}
|
|
459
485
|
// Internal Helper
|
|
460
486
|
processEvent(data) {
|
|
487
|
+
var _a, _b, _c, _d, _e, _f;
|
|
461
488
|
const baseContext = this.context.getPayload();
|
|
489
|
+
const ref = ((_a = data.properties) == null ? void 0 : _a.ref) || new URLSearchParams(baseContext.page.search || "").get("ref") || void 0;
|
|
490
|
+
const refParts = this.parseRef(ref);
|
|
491
|
+
const valueCandidate = ((_b = data.properties) == null ? void 0 : _b.value) ?? ((_c = data.properties) == null ? void 0 : _c.amount);
|
|
492
|
+
const value = Number(valueCandidate);
|
|
462
493
|
const payload = {
|
|
463
494
|
event: data.event,
|
|
464
495
|
properties: data.properties,
|
|
@@ -471,6 +502,11 @@ class EngagePro {
|
|
|
471
502
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
472
503
|
messageId: v4(),
|
|
473
504
|
writeKey: this.config.writeKey,
|
|
505
|
+
ref,
|
|
506
|
+
campaignId: ((_d = data.properties) == null ? void 0 : _d.campaignId) || refParts.campaignId,
|
|
507
|
+
creatorId: ((_e = data.properties) == null ? void 0 : _e.creatorId) || refParts.creatorId,
|
|
508
|
+
value: Number.isFinite(value) ? value : void 0,
|
|
509
|
+
currency: ((_f = data.properties) == null ? void 0 : _f.currency) || "INR",
|
|
474
510
|
// Identity
|
|
475
511
|
userId: baseContext.user.id || void 0,
|
|
476
512
|
anonymousId: baseContext.user.anonymousId,
|
|
@@ -715,6 +751,7 @@ const EngageProProvider = ({
|
|
|
715
751
|
window.addEventListener("focusin", handleInput, true);
|
|
716
752
|
trackPage();
|
|
717
753
|
return () => {
|
|
754
|
+
history.pushState = originalPush;
|
|
718
755
|
window.removeEventListener("popstate", trackPage);
|
|
719
756
|
window.removeEventListener("click", handleInteraction, true);
|
|
720
757
|
window.removeEventListener("focusin", handleInput, true);
|
package/dist/types/index.d.ts
CHANGED
|
@@ -49,6 +49,11 @@ export interface AnalyticsEvent {
|
|
|
49
49
|
timestamp?: string;
|
|
50
50
|
messageId?: string;
|
|
51
51
|
writeKey?: string;
|
|
52
|
+
campaignId?: string;
|
|
53
|
+
creatorId?: string;
|
|
54
|
+
ref?: string;
|
|
55
|
+
value?: number;
|
|
56
|
+
currency?: string;
|
|
52
57
|
context?: Partial<AnalyticsContext>;
|
|
53
58
|
}
|
|
54
59
|
export interface EngageConfig {
|