@bluenath/engage 1.0.4 → 2.0.1

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 CHANGED
@@ -1,50 +1,38 @@
1
1
  # @bluenath/engage
2
2
 
3
- The intelligent, zero-configuration analytics SDK for modern React applications. @bluenath/engage automatically captures clicks, form submissions, and page views, using heuristic intelligence to classify user intent (Commerce, Frustration, Engagement) without manual tagging.
3
+ Production-ready analytics SDK for EngagePro attribution and campaign intelligence.
4
4
 
5
- ## Features
5
+ This SDK captures web events, enriches intent signals, and sends batched payloads to EngagePro ingest for campaign-level ROI measurement.
6
6
 
7
- - 🧠 **Auto-Capture Intelligence**: Automatically detects `ADD_TO_CART`, `CHECKOUT`, `REFUND`, and more by analyzing button text and DOM attributes.
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
- ## 🚀 Installation
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
- ```bash
17
- npm install @bluenath/engage
18
- ```
19
-
20
- or
15
+ ## Installation
21
16
 
22
17
  ```bash
23
- yarn add @bluenath/engage
18
+ npm install @bluenath/engage
24
19
  ```
25
20
 
26
- ## 🛠️ Usage
27
-
28
- ### 1. Wrap your App
21
+ ## Quick Start (React)
29
22
 
30
- Wrap your root component with `EngageProProvider`. This single step enables auto-capture for the entire application.
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
- const root = ReactDOM.createRoot(document.getElementById("root"));
39
-
40
- root.render(
29
+ ReactDOM.createRoot(document.getElementById("root")!).render(
41
30
  <EngageProProvider
42
- writeKey="YOUR_WRITE_KEY"
43
- apiHost="https://your-api.com/analyticsdk/ingest"
31
+ writeKey="YOUR_PUBLIC_WRITE_KEY"
44
32
  tracking={{
45
- autoTrack: true, // 🧠 Enable Smart Sentinel
46
- useCookies: true, // Persist session across subdomains
47
- fingerprint: true, // Device fingerprinting for guest users
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
- ### 2. Manual Tracking (Optional)
43
+ ## Manual Tracking
56
44
 
57
- While auto-capture handles 90% of events, you can manually track specific actions using the hook.
58
-
59
- ```jsx
45
+ ```tsx
60
46
  import { useAnalytics } from "@bluenath/engage";
61
47
 
62
- const ProductPage = () => {
63
- const { track, identify } = useAnalytics();
48
+ export function CheckoutComplete() {
49
+ const analytics = useAnalytics();
64
50
 
65
- const handleLogin = (user) => {
66
- // Stitch session to user ID
67
- identify(user.id, {
68
- email: user.email,
69
- plan: "premium",
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
- const handleCustomAction = () => {
74
- track("Video Watched", {
75
- duration: 120,
76
- category: "Tutorial",
77
- });
78
- };
60
+ return <button onClick={onPurchase}>Confirm</button>;
61
+ }
62
+ ```
79
63
 
80
- return <button onClick={handleCustomAction}>Play Video</button>;
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
- ## 🧠 Smart Events (Auto-Detected)
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
- The SDK automatically maps user interactions to these standard events based on heuristics:
80
+ ## Configuration
87
81
 
88
- | Event Name | Trigger Logic (Example) |
89
- | ------------------- | -------------------------------------------------- |
90
- | `ADD_TO_CART` | Clicks on buttons labeled "Add to Cart", "Buy Now" |
91
- | `INITIATE_CHECKOUT` | Clicks on "Checkout", "Proceed" |
92
- | `PURCHASE` | Clicks on "Place Order", "Pay Now" |
93
- | `ORDER_CANCEL` | Clicks on "Cancel Order" |
94
- | `RAGE_CLICK` | 3+ rapid clicks on the same element (< 500ms) |
95
- | `FORM_START` | User focuses on an input field |
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
- ## 🔒 Configuration Options
91
+ ## Security Notes
99
92
 
100
- | Prop | Type | Default | Description |
101
- | --------------------- | --------- | -------- | ----------------------------------------- |
102
- | `writeKey` | `string` | Required | Your project's API Key. |
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
- ## 📄 License
97
+ ## License
109
98
 
110
- MIT © Bluenath
99
+ MIT
@@ -6,8 +6,10 @@ export declare class EngagePro {
6
6
  private queue;
7
7
  private transport;
8
8
  constructor(config: EngageConfig);
9
- identify: (userId: string, traits?: any) => void;
10
- page: (name?: string, properties?: any) => void;
11
- track: (eventName: string, properties?: any, intelligence?: Partial<AnalyticsEvent>) => void;
9
+ identify: (userId: string, traits?: Record<string, unknown>) => void;
10
+ page: (name?: string, properties?: Record<string, unknown>) => void;
11
+ track: (eventName: string, properties?: Record<string, unknown>, intelligence?: Partial<AnalyticsEvent>) => void;
12
+ trackRevenue: (amountPaise: number, properties?: Record<string, unknown>, intelligence?: Partial<AnalyticsEvent>) => void;
13
+ private parseRef;
12
14
  private processEvent;
13
15
  }
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 p{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 w="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})}),e(this,"trackRevenue",(t,e,n)=>{this.track("PURCHASE",{...e||{},value:Math.round(t),valuePaise:Math.round(t)},{standardEvent:"PURCHASE",intent:"commerce",confidence:1,...n})}),this.config=t,this.transport=new p((t=>{if(!t)return w;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()):w}catch{return w}})(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){const e=this.context.getPayload(),n=t.properties.ref,i=("string"==typeof n?n:void 0)||new URLSearchParams(e.page.search||"").get("ref")||void 0,s=this.parseRef(i),r=t.properties.valuePaise??t.properties.amountPaise??t.properties.value??t.properties.amount,o=Number(r),a=t.properties.campaignId,c=t.properties.creatorId,h=t.properties.currency,l={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:i,campaignId:("string"==typeof a?a:void 0)||s.campaignId,creatorId:("string"==typeof c?c:void 0)||s.creatorId,value:Number.isFinite(o)?Math.round(o):void 0,valuePaise:Number.isFinite(o)?Math.round(o):void 0,currency:("string"==typeof h?h:void 0)||"INR",userId:e.user.id||void 0,anonymousId:e.user.anonymousId,context:e};this.config.debug,this.queue.enqueue(l)}}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");
@@ -447,18 +469,46 @@ class EngagePro {
447
469
  ...intelligence
448
470
  });
449
471
  });
472
+ __publicField(this, "trackRevenue", (amountPaise, properties, intelligence) => {
473
+ this.track(
474
+ "PURCHASE",
475
+ {
476
+ ...properties || {},
477
+ value: Math.round(amountPaise),
478
+ valuePaise: Math.round(amountPaise)
479
+ },
480
+ {
481
+ standardEvent: "PURCHASE",
482
+ intent: "commerce",
483
+ confidence: 1,
484
+ ...intelligence
485
+ }
486
+ );
487
+ });
450
488
  this.config = config;
451
- this.transport = new Transport(
452
- config.apiHost || "http://127.0.0.1:3000/api/v1/tracking/ingest"
453
- );
489
+ this.transport = new Transport(resolveIngestEndpoint(config.apiHost));
454
490
  this.context = new Context({
455
491
  persistence: config.tracking.useCookies ? "cookie" : "local"
456
492
  });
457
493
  this.queue = new EventQueue(this.transport);
458
494
  }
495
+ parseRef(ref) {
496
+ if (!ref) return {};
497
+ const m = ref.match(/^camp_([^_]+)_cr_([^_]+)$/);
498
+ if (!m) return {};
499
+ return { campaignId: m[1], creatorId: m[2] };
500
+ }
459
501
  // Internal Helper
460
502
  processEvent(data) {
461
503
  const baseContext = this.context.getPayload();
504
+ const refValue = data.properties.ref;
505
+ const ref = (typeof refValue === "string" ? refValue : void 0) || new URLSearchParams(baseContext.page.search || "").get("ref") || void 0;
506
+ const refParts = this.parseRef(ref);
507
+ const valueCandidate = data.properties.valuePaise ?? data.properties.amountPaise ?? data.properties.value ?? data.properties.amount;
508
+ const value = Number(valueCandidate);
509
+ const campaignIdValue = data.properties.campaignId;
510
+ const creatorIdValue = data.properties.creatorId;
511
+ const currencyValue = data.properties.currency;
462
512
  const payload = {
463
513
  event: data.event,
464
514
  properties: data.properties,
@@ -471,6 +521,12 @@ class EngagePro {
471
521
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
472
522
  messageId: v4(),
473
523
  writeKey: this.config.writeKey,
524
+ ref,
525
+ campaignId: (typeof campaignIdValue === "string" ? campaignIdValue : void 0) || refParts.campaignId,
526
+ creatorId: (typeof creatorIdValue === "string" ? creatorIdValue : void 0) || refParts.creatorId,
527
+ value: Number.isFinite(value) ? Math.round(value) : void 0,
528
+ valuePaise: Number.isFinite(value) ? Math.round(value) : void 0,
529
+ currency: (typeof currencyValue === "string" ? currencyValue : void 0) || "INR",
474
530
  // Identity
475
531
  userId: baseContext.user.id || void 0,
476
532
  anonymousId: baseContext.user.anonymousId,
@@ -715,6 +771,7 @@ const EngageProProvider = ({
715
771
  window.addEventListener("focusin", handleInput, true);
716
772
  trackPage();
717
773
  return () => {
774
+ history.pushState = originalPush;
718
775
  window.removeEventListener("popstate", trackPage);
719
776
  window.removeEventListener("click", handleInteraction, true);
720
777
  window.removeEventListener("focusin", handleInput, true);
@@ -39,7 +39,7 @@ export interface AnalyticsContext {
39
39
  }
40
40
  export interface AnalyticsEvent {
41
41
  event: string;
42
- properties?: Record<string, any>;
42
+ properties?: Record<string, unknown>;
43
43
  standardEvent?: StandardEvent;
44
44
  intent?: IntentType;
45
45
  confidence?: number;
@@ -49,6 +49,12 @@ 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
+ valuePaise?: number;
57
+ currency?: string;
52
58
  context?: Partial<AnalyticsContext>;
53
59
  }
54
60
  export interface EngageConfig {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bluenath/engage",
3
- "version": "1.0.4",
3
+ "version": "2.0.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",