@bluenath/engage 2.0.2 → 2.0.4

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,25 +1,35 @@
1
1
  # @bluenath/engage
2
2
 
3
- Production-ready analytics SDK for EngagePro attribution and campaign intelligence.
3
+ The official analytics and attribution SDK for [EngagePro](https://engagepro.bluenath.com). Production-ready event capture for campaign ROI measurement and creator attribution.
4
4
 
5
- This SDK captures web events, enriches intent signals, and sends batched payloads to EngagePro ingest for campaign-level ROI measurement.
5
+ Developed by the [Bluenath](https://bluenath.com) team.
6
6
 
7
- ## Highlights
7
+ ---
8
8
 
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
9
+ ## Overview
10
+
11
+ `@bluenath/engage` is a high-performance analytics SDK designed to bridge the gap between user interactions and campaign attribution. It captures critical web events, enriches intent signals, and securely transmits batched payloads to EngagePro's ingestion servers for real-time ROI analysis.
12
+
13
+ ## Key Features
14
+
15
+ - **Automated Event Capture**: Seamlessly tracks high-value interactions including page views, commerce events, form submissions, and friction signals (e.g., rage-clicks).
16
+ - **Manual Business Events**: Flexible API for tracking custom conversions with support for value, currency, and campaign metadata.
17
+ - **Resilient Delivery**: Robust offline queueing mechanism with exponential backoff and retry logic.
18
+ - **Framework Integration**: Includes a lightweight React Provider and custom hooks for modern web architectures.
19
+ - **Data Security**: Built-in protection for sensitive input fields and secure handling of attribution identifiers.
14
20
 
15
21
  ## Installation
16
22
 
23
+ Install the package via your preferred package manager:
24
+
17
25
  ```bash
18
26
  npm install @bluenath/engage
19
27
  ```
20
28
 
21
29
  ## Quick Start (React)
22
30
 
31
+ Initialize the SDK at the root of your application using the `EngageProProvider`.
32
+
23
33
  ```tsx
24
34
  import React from "react";
25
35
  import ReactDOM from "react-dom/client";
@@ -40,60 +50,62 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
40
50
  );
41
51
  ```
42
52
 
43
- ## Manual Tracking
53
+ ## Manual Event Tracking
54
+
55
+ Use the `useAnalytics` hook to capture specific business outcomes.
44
56
 
45
57
  ```tsx
46
58
  import { useAnalytics } from "@bluenath/engage";
47
59
 
48
- export function CheckoutComplete() {
60
+ export function PurchaseConfirmation() {
49
61
  const analytics = useAnalytics();
50
62
 
51
- const onPurchase = () => {
63
+ const handlePurchase = () => {
52
64
  analytics.track("PURCHASE", {
53
- orderId: "ORD-7281",
54
- amount: 2499,
65
+ orderId: "ORD-1029",
66
+ amount: 4999,
55
67
  currency: "INR",
56
68
  ref: "camp_<campaignId>_cr_<creatorId>",
57
69
  });
58
70
  };
59
71
 
60
- return <button onClick={onPurchase}>Confirm</button>;
72
+ return <button onClick={handlePurchase}>Confirm Payout</button>;
61
73
  }
62
74
  ```
63
75
 
64
- ## Attribution Reference Format
76
+ ### Attribution Reference Format
65
77
 
66
- For campaign-level attribution, include this exact `ref` format in tracked events:
67
-
68
- ```text
69
- camp_<campaignId>_cr_<creatorId>
70
- ```
78
+ For accurate campaign attribution, ensure the `ref` field follows the standard EngagePro format:
79
+ `camp_<campaignId>_cr_<creatorId>`
71
80
 
72
- This allows EngagePro to map conversions to creator partnerships and payout logic.
81
+ ## API Reference
73
82
 
74
- ## Endpoint Policy
83
+ ### Provider Configuration
75
84
 
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
+ | Property | Type | Required | Description |
86
+ | :--- | :--- | :--- | :--- |
87
+ | `writeKey` | `string` | **Yes** | Your public brand write key from the [EngagePro Dashboard](https://engagepro.bluenath.com/app/integration). |
88
+ | `apiHost` | `string` | No | Local development override (restricted to `localhost` or `127.0.0.1`). |
89
+ | `tracking.autoTrack` | `boolean` | **Yes** | Enables automated DOM interaction capture. |
90
+ | `tracking.useCookies`| `boolean` | **Yes** | Persists anonymous identifiers across sessions. |
91
+ | `tracking.fingerprint`| `boolean` | **Yes** | Includes device context for fraud prevention and unique visit tracking. |
92
+ | `debug` | `boolean` | No | Enables console logging for integration testing. |
79
93
 
80
- ## Configuration
94
+ ## Network & Security Policies
81
95
 
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 |
96
+ - **Endpoint Isolation**: Production data is exclusively routed to EngagePro's managed infrastructure.
97
+ - **Local Development**: The `apiHost` parameter is strictly limited to local environments for testing. External host overrides are rejected for security compliance.
98
+ - **Security Best Practices**:
99
+ - Never expose internal service keys in client-side code.
100
+ - Rotate write keys immediately if leakage is suspected.
101
+ - Use hashed or anonymous identifiers; do not transmit raw PII (Personally Identifiable Information).
90
102
 
91
- ## Security Notes
103
+ ## Resources
92
104
 
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.
105
+ - [Official Documentation](https://engagepro.bluenath.com/docs)
106
+ - [EngagePro Platform](https://engagepro.bluenath.com)
107
+ - [Bluenath Creator OS](https://bluenath.com)
96
108
 
97
109
  ## License
98
110
 
99
- MIT
111
+ This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details.
@@ -1,15 +1,27 @@
1
- import { EngageConfig, AnalyticsEvent } from '../types';
1
+ import { EngageConfig, AnalyticsEvent, SentimentValue } from '../types';
2
2
 
3
3
  export declare class EngagePro {
4
4
  private config;
5
5
  private context;
6
6
  private queue;
7
7
  private transport;
8
+ private sessionId;
9
+ private countryCode;
10
+ private currentSentiment;
8
11
  constructor(config: EngageConfig);
9
12
  identify: (userId: string, traits?: Record<string, unknown>) => void;
10
13
  page: (name?: string, properties?: Record<string, unknown>) => void;
11
14
  track: (eventName: string, properties?: Record<string, unknown>, intelligence?: Partial<AnalyticsEvent>) => void;
12
15
  trackRevenue: (amount: number | string, properties?: Record<string, unknown>, intelligence?: Partial<AnalyticsEvent>) => void;
16
+ /**
17
+ * Spec §3.3: Set sentiment for the current session.
18
+ * Attached to the next emitted event.
19
+ */
20
+ setSentiment: (sentiment: SentimentValue) => void;
21
+ /**
22
+ * Emit a new-format EngageEvent to the ingest pipeline (Spec §3.1).
23
+ */
24
+ private emitEngageEvent;
13
25
  private parseRef;
14
26
  private processEvent;
15
27
  }
@@ -1,18 +1,43 @@
1
- import { AnalyticsEvent } from '../types';
1
+ import { AnalyticsEvent, EngageEvent } from '../types';
2
2
  import { Transport } from './Transport';
3
3
 
4
4
  export declare class EventQueue {
5
5
  private queue;
6
+ private engageQueue;
6
7
  private storage;
7
8
  private transport;
8
9
  private readonly STORAGE_KEY;
10
+ private readonly ENGAGE_STORAGE_KEY;
9
11
  private isFlushing;
10
12
  private flushInterval;
11
13
  private retryCount;
14
+ private readonly MAX_RETRIES;
12
15
  constructor(transport: Transport);
13
16
  private startTimer;
17
+ /**
18
+ * Enqueue a legacy AnalyticsEvent (backward compatibility)
19
+ */
14
20
  enqueue(event: AnalyticsEvent): void;
21
+ /**
22
+ * Enqueue a new EngageEvent (Spec §3.1)
23
+ */
24
+ enqueueEngageEvent(event: EngageEvent): void;
15
25
  private save;
26
+ private saveEngage;
16
27
  private load;
28
+ /**
29
+ * Flush legacy events (existing behavior)
30
+ */
17
31
  flush(): Promise<void>;
32
+ /**
33
+ * Spec §3.4: Flush EngageEvent[] with exponential backoff retry.
34
+ * Max 3 retries with 1s/2s/4s backoff. Drop batch after 3 failures.
35
+ * Do not retry on 401/422 (permanent failures).
36
+ */
37
+ flushEngageEvents(): Promise<void>;
38
+ /**
39
+ * Spec §3.4: Use navigator.sendBeacon() to flush remaining events on page unload.
40
+ * This is a best-effort fire-and-forget — no retry possible.
41
+ */
42
+ private flushBeacon;
18
43
  }
@@ -1,7 +1,30 @@
1
- import { AnalyticsEvent } from '../types';
1
+ import { AnalyticsEvent, EngageEvent } from '../types';
2
2
 
3
+ export interface TransportResult {
4
+ success: boolean;
5
+ permanent: boolean;
6
+ status?: number;
7
+ }
3
8
  export declare class Transport {
4
9
  private apiEndpoint;
10
+ private apiKey;
5
11
  constructor(apiEndpoint: string);
12
+ setApiKey(key: string): void;
13
+ /**
14
+ * Sends legacy batched events (existing format).
15
+ * Used by the old EventQueue for backward compatibility.
16
+ */
6
17
  send(events: AnalyticsEvent[]): Promise<boolean>;
18
+ /**
19
+ * Sends new EngageEvent[] to the ingest endpoint (Spec §4.1).
20
+ * Returns detailed result for retry logic.
21
+ */
22
+ sendEngageEvents(events: EngageEvent[]): Promise<TransportResult>;
23
+ private sendRaw;
24
+ /**
25
+ * Uses sendBeacon for last-resort page unload flush.
26
+ * Cannot send auth headers — the ingest endpoint must also accept
27
+ * the apiKey in the body payload as fallback.
28
+ */
29
+ beaconFlush(payload: string): boolean;
7
30
  }
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 S=0;S<256;++S)a.push((S+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 u{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 l{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 u(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 u("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"]),v={$:"USD",US$:"USD",USD:"USD","₹":"INR",RS:"INR","RS.":"INR",INR:"INR","€":"EUR",EUR:"EUR","£":"GBP",GBP:"GBP","¥":"JPY",JPY:"JPY"},m=t=>{if("string"!=typeof t)return;const e=t.trim().toUpperCase();return e?v[e]?v[e]:e.includes("₹")||e.startsWith("RS")?"INR":e.includes("$")&&!e.includes("CAD")?"USD":e.includes("€")?"EUR":e.includes("£")?"GBP":e.includes("¥")?"JPY":/^[A-Z]{3}$/.test(e)?e:void 0:void 0};class g{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)=>{const i=Math.max(0,(t=>{if("number"==typeof t)return Number.isFinite(t)?t:0;const e=t.trim().replace(/[^0-9.+-]/g,""),n=Number(e);return Number.isFinite(n)?n:0})(t)),s=m(null==e?void 0:e.currency)||m(null==e?void 0:e.currencyCode)||m(null==e?void 0:e.currencySymbol)||("string"==typeof t?m(t):void 0)||"USD";this.track("PURCHASE",{...e||{},value:i,amount:i,valuePaise:Math.round(100*i),amountPaise:Math.round(100*i),currency:s,currencyCode:s},{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 l({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.value??t.properties.amount??t.properties.valuePaise??t.properties.amountPaise,o=Number(r),a=t.properties.campaignId,c=t.properties.creatorId,h=t.properties.currency,u={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(100*o):void 0,currency:("string"==typeof h?h:void 0)||"USD",userId:e.user.id||void 0,anonymousId:e.user.anonymousId,context:e};this.config.debug,this.queue.enqueue(u)}}class b{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 E=i.createContext(null);let R=null;exports.EngageProProvider=({children:t,...e})=>{const s=i.useRef(null),r=i.useRef(null);s.current||(s.current=new g(e),"undefined"!=typeof window&&new b(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(E.Provider,{value:o,children:t})},exports.default=g,exports.init=t=>(R||(R=new g(t)),R),exports.useAnalytics=()=>{const t=i.useContext(E);if(!t)throw new Error("useAnalytics must be used within EngageProProvider");return t};
1
+ "use strict";var e=Object.defineProperty,i=(i,t,a)=>((i,t,a)=>t in i?e(i,t,{enumerable:!0,configurable:!0,writable:!0,value:a}):i[t]=a)(i,"symbol"!=typeof t?t+"":t,a);Object.defineProperties(exports,{i:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const t=require("react/jsx-runtime"),a=require("react");let n;const r=new Uint8Array(16);function s(){if(!n&&(n="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto),!n))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return n(r)}const o=[];for(let _=0;_<256;++_)o.push((_+256).toString(16).slice(1));const c={randomUUID:"undefined"!=typeof crypto&&crypto.randomUUID&&crypto.randomUUID.bind(crypto)};function u(e,i,t){if(c.randomUUID&&!e)return c.randomUUID();const a=(e=e||{}).random||(e.rng||s)();return a[6]=15&a[6]|64,a[8]=63&a[8]|128,function(e,i=0){return o[e[i+0]]+o[e[i+1]]+o[e[i+2]]+o[e[i+3]]+"-"+o[e[i+4]]+o[e[i+5]]+"-"+o[e[i+6]]+o[e[i+7]]+"-"+o[e[i+8]]+o[e[i+9]]+"-"+o[e[i+10]]+o[e[i+11]]+o[e[i+12]]+o[e[i+13]]+o[e[i+14]]+o[e[i+15]]}(a)}const d=e=>{let i=2166136261;const t=e.length;for(let a=0;a<t;a++)i^=e.charCodeAt(a),i+=(i<<1)+(i<<4)+(i<<7)+(i<<8)+(i<<24);return("0000000"+(i>>>0).toString(16)).substr(-8)};class h{constructor(e="local"){i(this,"memoryStore",{}),i(this,"keyPrefix","engage_"),i(this,"domain"),this.type=e,this.domain=this.getCookieDomain()}getItem(e){const i=this.keyPrefix+e;if(this.memoryStore.hasOwnProperty(i))return this.memoryStore[i];try{if("local"===this.type&&this.isBrowser())return window.localStorage.getItem(i);if("cookie"===this.type&&this.isBrowser())return this.getCookie(i)}catch(t){return null}return null}setItem(e,i){const t=this.keyPrefix+e;this.memoryStore[t]=i;try{"local"===this.type&&this.isBrowser()?window.localStorage.setItem(t,i):"cookie"===this.type&&this.isBrowser()&&this.setCookie(t,i,365)}catch(a){this.isQuotaError(a)&&(this.type="memory")}}removeItem(e){const i=this.keyPrefix+e;delete this.memoryStore[i];try{"local"===this.type&&this.isBrowser()?window.localStorage.removeItem(i):"cookie"===this.type&&this.isBrowser()&&this.setCookie(i,"",-1)}catch(t){}}getCookie(e){const i=e+"=",t=document.cookie.split(";");for(let a=0;a<t.length;a++){let e=t[a];for(;" "===e.charAt(0);)e=e.substring(1,e.length);if(0===e.indexOf(i))return decodeURIComponent(e.substring(i.length,e.length))}return null}setCookie(e,i,t){let a="";if(t){const e=new Date;e.setTime(e.getTime()+24*t*60*60*1e3),a="; expires="+e.toUTCString()}document.cookie=`${e}=${encodeURIComponent(i)}${a}; path=/; domain=${this.domain}; SameSite=Lax; Secure`}getCookieDomain(){if(!this.isBrowser())return"";const e=window.location.hostname,i=e.split(".");return 1===i.length||"localhost"===e?"":i.length>2?"."+i.slice(-2).join("."):"."+e}isBrowser(){try{return"undefined"!=typeof window&&void 0!==window.document}catch(e){return!1}}isQuotaError(e){return e instanceof DOMException&&(22===e.code||1014===e.code||"QuotaExceededError"===e.name||"NS_ERROR_DOM_QUOTA_REACHED"===e.name)}}class l{constructor(e){i(this,"storage"),i(this,"SESSION_TIMEOUT",18e5),i(this,"deviceId"),i(this,"sessionId"),i(this,"userId",null),i(this,"currentUrl"),i(this,"referrer"),this.storage=new h(e.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 e=this.storage.getItem("device_id");if(e)return e;const i=(()=>{if("undefined"==typeof window)return"server-side-id";const e=navigator,i=window.screen,t={userAgent:e.userAgent||"",screenRes:`${i.width}x${i.height}`,colorDepth:i.colorDepth||0,timezone:(new Date).getTimezoneOffset(),language:e.language||"en-US",platform:e.platform||"unknown",hardwareConcurrency:e.hardwareConcurrency||1,deviceMemory:e.deviceMemory||0},a=[t.platform,t.language,t.screenRes,t.colorDepth,t.timezone,t.hardwareConcurrency,t.deviceMemory].join("|");return`${d(a)}-${d(t.userAgent)}`})();return this.storage.setItem("device_id",i),i}manageSession(){const e=Date.now(),i=this.storage.getItem("session_id"),t=parseInt(this.storage.getItem("last_activity")||"0");if(!i||e-t>this.SESSION_TIMEOUT?(this.sessionId=`sess_${e}_${Math.random().toString(36).substr(2,9)}`,this.storage.setItem("session_id",this.sessionId)):this.sessionId=i,this.storage.setItem("last_activity",e.toString()),"undefined"!=typeof window){const e=()=>this.storage.setItem("last_activity",Date.now().toString());window.addEventListener("click",e),window.addEventListener("scroll",e)}}listenToHistory(){const e=history.pushState;history.pushState=(...i)=>{e.apply(history,i),this.handleUrlChange()},window.addEventListener("popstate",()=>this.handleUrlChange())}handleUrlChange(){const e=window.location.href;e!==this.currentUrl&&(this.referrer=this.currentUrl,this.currentUrl=e)}getPayload(){var e;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==(e=navigator.connection)?void 0:e.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 e=navigator.userAgent;return/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(e)?"tablet":/Mobile|Android|iP(hone|od)|IEMobile|BlackBerry|Kindle|Silk-Accelerated/.test(e)?"mobile":"desktop"}}class A{constructor(e){i(this,"queue",[]),i(this,"engageQueue",[]),i(this,"storage"),i(this,"transport"),i(this,"STORAGE_KEY","engage_queue_v1"),i(this,"ENGAGE_STORAGE_KEY","engage_events_v2"),i(this,"isFlushing",!1),i(this,"flushInterval"),i(this,"retryCount",0),i(this,"MAX_RETRIES",3),this.transport=e,this.storage=new h("local"),this.load(),"undefined"!=typeof window&&(window.addEventListener("online",()=>{this.retryCount=0,this.flush()}),window.addEventListener("visibilitychange",()=>{"hidden"===document.visibilityState&&this.flushBeacon()}),window.addEventListener("beforeunload",()=>{this.flushBeacon()})),this.startTimer()}startTimer(){this.flushInterval&&clearInterval(this.flushInterval),this.flushInterval=setInterval(()=>{this.flush()},5e3)}enqueue(e){this.queue.push(e),this.save(),(this.queue.length>=10||"PURCHASE"===e.standardEvent)&&this.flush()}enqueueEngageEvent(e){this.engageQueue.push(e),this.saveEngage(),this.engageQueue.length>=10&&this.flushEngageEvents()}save(){this.storage.setItem(this.STORAGE_KEY,JSON.stringify(this.queue))}saveEngage(){this.storage.setItem(this.ENGAGE_STORAGE_KEY,JSON.stringify(this.engageQueue))}load(){const e=this.storage.getItem(this.STORAGE_KEY);if(e)try{this.queue=JSON.parse(e)}catch(t){this.queue=[]}const i=this.storage.getItem(this.ENGAGE_STORAGE_KEY);if(i)try{this.engageQueue=JSON.parse(i)}catch(t){this.engageQueue=[]}}async flush(){if(0===this.queue.length||this.isFlushing)return void(this.engageQueue.length>0&&!this.isFlushing&&await this.flushEngageEvents());if("undefined"!=typeof navigator&&!navigator.onLine)return;this.isFlushing=!0;const e=[...this.queue];this.queue=[],this.save();try{if(!(await this.transport.send(e)))throw new Error("Server rejected batch");this.retryCount=0}catch(i){this.queue=[...e,...this.queue],this.save(),this.retryCount++}finally{this.isFlushing=!1}this.engageQueue.length>0&&await this.flushEngageEvents()}async flushEngageEvents(){if(0===this.engageQueue.length||this.isFlushing)return;if("undefined"!=typeof navigator&&!navigator.onLine)return;this.isFlushing=!0;const e=[...this.engageQueue];this.engageQueue=[],this.saveEngage();let i=0,t=!1;for(;i<this.MAX_RETRIES&&!t;){const a=await this.transport.sendEngageEvents(e);if(a.success){t=!0;break}if(a.permanent)return void(this.isFlushing=!1);if(i++,i<this.MAX_RETRIES){const e=1e3*Math.pow(2,i-1);await new Promise(i=>setTimeout(i,e))}}this.isFlushing=!1}flushBeacon(){if(this.queue.length>0){const e=JSON.stringify({batch:this.queue,sentAt:(new Date).toISOString()});this.transport.beaconFlush(e)&&(this.queue=[],this.save())}if(this.engageQueue.length>0){const e=JSON.stringify({events:this.engageQueue});this.transport.beaconFlush(e)&&(this.engageQueue=[],this.saveEngage())}}}class f{constructor(e){i(this,"apiKey",""),this.apiEndpoint=e}setApiKey(e){this.apiKey=e}async send(e){return(await this.sendRaw(JSON.stringify({batch:e,sentAt:(new Date).toISOString()}))).success}async sendEngageEvents(e){const i=JSON.stringify({events:e});return this.sendRaw(i)}async sendRaw(e){const i={"Content-Type":"application/json"};if(this.apiKey&&(i.Authorization=`Bearer ${this.apiKey}`),!this.apiKey&&"undefined"!=typeof navigator&&navigator.sendBeacon&&e.length<6e4){const i=new Blob([e],{type:"application/json"});if(navigator.sendBeacon(this.apiEndpoint,i))return{success:!0,permanent:!1}}try{const t=await fetch(this.apiEndpoint,{method:"POST",headers:i,body:e,keepalive:!0});return 401===t.status||422===t.status?{success:!1,permanent:!0,status:t.status}:{success:t.ok||202===t.status,permanent:!1,status:t.status}}catch(t){return{success:!1,permanent:!1}}}beaconFlush(e){if("undefined"==typeof navigator||!navigator.sendBeacon)return!1;const i=new Blob([e],{type:"application/json"});return navigator.sendBeacon(this.apiEndpoint,i)}}const p={"Asia/Kolkata":"IN","Asia/Calcutta":"IN","Asia/Mumbai":"IN","Asia/Dhaka":"BD","Asia/Kathmandu":"NP","Asia/Colombo":"LK","Asia/Karachi":"PK","Asia/Kabul":"AF","Asia/Tehran":"IR","Asia/Dubai":"AE","Asia/Muscat":"OM","Asia/Bahrain":"BH","Asia/Qatar":"QA","Asia/Kuwait":"KW","Asia/Riyadh":"SA","Asia/Aden":"YE","Asia/Baghdad":"IQ","Asia/Amman":"JO","Asia/Beirut":"LB","Asia/Damascus":"SY","Asia/Jerusalem":"IL","Asia/Tel_Aviv":"IL","Asia/Nicosia":"CY","Asia/Tokyo":"JP","Asia/Seoul":"KR","Asia/Pyongyang":"KP","Asia/Shanghai":"CN","Asia/Chongqing":"CN","Asia/Harbin":"CN","Asia/Urumqi":"CN","Asia/Hong_Kong":"HK","Asia/Macau":"MO","Asia/Taipei":"TW","Asia/Singapore":"SG","Asia/Kuala_Lumpur":"MY","Asia/Brunei":"BN","Asia/Jakarta":"ID","Asia/Makassar":"ID","Asia/Jayapura":"ID","Asia/Bangkok":"TH","Asia/Ho_Chi_Minh":"VN","Asia/Saigon":"VN","Asia/Phnom_Penh":"KH","Asia/Vientiane":"LA","Asia/Yangon":"MM","Asia/Rangoon":"MM","Asia/Manila":"PH","Asia/Ulaanbaatar":"MN","Asia/Hovd":"MN","Asia/Tbilisi":"GE","Asia/Baku":"AZ","Asia/Yerevan":"AM","Asia/Almaty":"KZ","Asia/Bishkek":"KG","Asia/Tashkent":"UZ","Asia/Ashgabat":"TM","Asia/Dushanbe":"TJ","Asia/Thimphu":"BT","Asia/Dili":"TL","Europe/London":"GB","Europe/Dublin":"IE","Europe/Lisbon":"PT","Europe/Madrid":"ES","Europe/Paris":"FR","Europe/Brussels":"BE","Europe/Amsterdam":"NL","Europe/Luxembourg":"LU","Europe/Berlin":"DE","Europe/Zurich":"CH","Europe/Vienna":"AT","Europe/Rome":"IT","Europe/Monaco":"MC","Europe/Vatican":"VA","Europe/Malta":"MT","Europe/Prague":"CZ","Europe/Budapest":"HU","Europe/Warsaw":"PL","Europe/Bratislava":"SK","Europe/Ljubljana":"SI","Europe/Zagreb":"HR","Europe/Belgrade":"RS","Europe/Sarajevo":"BA","Europe/Podgorica":"ME","Europe/Skopje":"MK","Europe/Tirane":"AL","Europe/Sofia":"BG","Europe/Bucharest":"RO","Europe/Chisinau":"MD","Europe/Athens":"GR","Europe/Istanbul":"TR","Europe/Helsinki":"FI","Europe/Stockholm":"SE","Europe/Oslo":"NO","Europe/Copenhagen":"DK","Europe/Tallinn":"EE","Europe/Riga":"LV","Europe/Vilnius":"LT","Europe/Minsk":"BY","Europe/Moscow":"RU","Europe/Kaliningrad":"RU","Europe/Samara":"RU","Europe/Kiev":"UA","Europe/Kyiv":"UA","Europe/Reykjavik":"IS","Europe/Andorra":"AD","Europe/Gibraltar":"GI","Europe/San_Marino":"SM","America/New_York":"US","America/Chicago":"US","America/Denver":"US","America/Los_Angeles":"US","America/Phoenix":"US","America/Anchorage":"US","Pacific/Honolulu":"US","America/Detroit":"US","America/Indianapolis":"US","America/Boise":"US","America/Juneau":"US","America/Adak":"US","America/Toronto":"CA","America/Vancouver":"CA","America/Montreal":"CA","America/Winnipeg":"CA","America/Edmonton":"CA","America/Halifax":"CA","America/St_Johns":"CA","America/Regina":"CA","America/Mexico_City":"MX","America/Cancun":"MX","America/Tijuana":"MX","America/Monterrey":"MX","America/Hermosillo":"MX","America/Guatemala":"GT","America/Belize":"BZ","America/El_Salvador":"SV","America/Tegucigalpa":"HN","America/Managua":"NI","America/Costa_Rica":"CR","America/Panama":"PA","America/Bogota":"CO","America/Lima":"PE","America/Guayaquil":"EC","America/Caracas":"VE","America/La_Paz":"BO","America/Asuncion":"PY","America/Montevideo":"UY","America/Buenos_Aires":"AR","America/Argentina/Buenos_Aires":"AR","America/Santiago":"CL","America/Sao_Paulo":"BR","America/Recife":"BR","America/Manaus":"BR","America/Fortaleza":"BR","America/Bahia":"BR","America/Havana":"CU","America/Jamaica":"JM","America/Port-au-Prince":"HT","America/Santo_Domingo":"DO","America/Puerto_Rico":"PR","America/Port_of_Spain":"TT","America/Barbados":"BB","America/Martinique":"MQ","America/Guyana":"GY","America/Paramaribo":"SR","America/Cayenne":"GF","America/Curacao":"CW","Africa/Cairo":"EG","Africa/Casablanca":"MA","Africa/Tunis":"TN","Africa/Algiers":"DZ","Africa/Tripoli":"LY","Africa/Khartoum":"SD","Africa/Addis_Ababa":"ET","Africa/Nairobi":"KE","Africa/Dar_es_Salaam":"TZ","Africa/Kampala":"UG","Africa/Mogadishu":"SO","Africa/Lagos":"NG","Africa/Accra":"GH","Africa/Abidjan":"CI","Africa/Dakar":"SN","Africa/Bamako":"ML","Africa/Ouagadougou":"BF","Africa/Conakry":"GN","Africa/Freetown":"SL","Africa/Monrovia":"LR","Africa/Lome":"TG","Africa/Porto-Novo":"BJ","Africa/Niamey":"NE","Africa/Douala":"CM","Africa/Libreville":"GA","Africa/Bangui":"CF","Africa/Brazzaville":"CG","Africa/Kinshasa":"CD","Africa/Lubumbashi":"CD","Africa/Luanda":"AO","Africa/Maputo":"MZ","Africa/Harare":"ZW","Africa/Lusaka":"ZM","Africa/Lilongwe":"MW","Africa/Johannesburg":"ZA","Africa/Windhoek":"NA","Africa/Gaborone":"BW","Africa/Maseru":"LS","Africa/Mbabane":"SZ","Indian/Antananarivo":"MG","Indian/Mauritius":"MU","Indian/Reunion":"RE","Indian/Comoro":"KM","Indian/Mayotte":"YT","Africa/Djibouti":"DJ","Africa/Asmara":"ER","Australia/Sydney":"AU","Australia/Melbourne":"AU","Australia/Brisbane":"AU","Australia/Perth":"AU","Australia/Adelaide":"AU","Australia/Hobart":"AU","Australia/Darwin":"AU","Australia/Lord_Howe":"AU","Pacific/Auckland":"NZ","Pacific/Chatham":"NZ","Pacific/Fiji":"FJ","Pacific/Tongatapu":"TO","Pacific/Apia":"WS","Pacific/Port_Moresby":"PG","Pacific/Noumea":"NC","Pacific/Guam":"GU","Pacific/Pago_Pago":"AS","Pacific/Tahiti":"PF","Atlantic/Reykjavik":"IS","Atlantic/Azores":"PT","Atlantic/Canary":"ES","Atlantic/Madeira":"PT","Atlantic/Cape_Verde":"CV","Atlantic/Bermuda":"BM","Indian/Maldives":"MV","Indian/Chagos":"IO","Indian/Christmas":"CX","Indian/Cocos":"CC"},m=new Set(["localhost","127.0.0.1"]),g="engage_session_id",y={$:"USD",US$:"USD",USD:"USD","₹":"INR",RS:"INR","RS.":"INR",INR:"INR","€":"EUR",EUR:"EUR","£":"GBP",GBP:"GBP","¥":"JPY",JPY:"JPY"},v=e=>{if("string"!=typeof e)return;const i=e.trim().toUpperCase();return i?y[i]?y[i]:i.includes("₹")||i.startsWith("RS")?"INR":i.includes("$")&&!i.includes("CAD")?"USD":i.includes("€")?"EUR":i.includes("£")?"GBP":i.includes("¥")?"JPY":/^[A-Z]{3}$/.test(i)?i:void 0:void 0};function w(e){if(!e||"object"!=typeof e)return;const i={},t=Object.keys(e).slice(0,20);for(const a of t){const t=e[a];"boolean"==typeof t||"number"==typeof t?i[a]=t:"string"==typeof t&&(i[a]=t.slice(0,100))}return Object.keys(i).length>0?i:void 0}class E{constructor(e){i(this,"config"),i(this,"context"),i(this,"queue"),i(this,"transport"),i(this,"sessionId"),i(this,"countryCode"),i(this,"currentSentiment"),i(this,"identify",(e,i)=>{this.context.userId=e,this.processEvent({event:"Identify",properties:{traits:i},standardEvent:"LOGIN",intent:"identity",confidence:1})}),i(this,"page",(e,i)=>{const t=this.context.getPayload().page;this.processEvent({event:e||t.title||"Unknown Page",properties:{path:t.path,referrer:t.referrer,title:t.title,...i},standardEvent:"PAGE_VIEW",intent:"navigation",confidence:1}),this.emitEngageEvent("pageview",i)}),i(this,"track",(e,i,t)=>{this.processEvent({event:e,properties:i||{},...t});const a={purchase:"purchase",PURCHASE:"purchase",signup:"signup",SIGNUP:"signup",register:"signup",REGISTER:"signup",add_to_cart:"add_to_cart",ADD_TO_CART:"add_to_cart",product_view:"product_view",VIEW_CONTENT:"product_view",session_start:"session_start",session_end:"session_end"},n=a[e]||a[(null==t?void 0:t.standardEvent)||""]||"custom";this.emitEngageEvent(n,i)}),i(this,"trackRevenue",(e,i,t)=>{const a=Math.max(0,(e=>{if("number"==typeof e)return Number.isFinite(e)?e:0;const i=e.trim().replace(/[^0-9.+-]/g,""),t=Number(i);return Number.isFinite(t)?t:0})(e)),n=v(null==i?void 0:i.currency)||v(null==i?void 0:i.currencyCode)||v(null==i?void 0:i.currencySymbol)||("string"==typeof e?v(e):void 0)||"USD";this.track("PURCHASE",{...i||{},value:a,amount:a,valuePaise:Math.round(100*a),amountPaise:Math.round(100*a),currency:n,currencyCode:n},{standardEvent:"PURCHASE",intent:"commerce",confidence:1,...t})}),i(this,"setSentiment",e=>{this.currentSentiment=e}),this.config=e;const t=e.apiKey||e.writeKey,a=((e,i,t)=>{if(!e)return t;try{const i=new URL(e);return i.hostname.endsWith("bluenath.com")||m.has(i.hostname)?(i.pathname="/api/v1/tracking/ingest",i.search="",i.hash="",i.toString()):t}catch{return t}})(e.apiHost,0,"https://engage-api.bluenath.com/api/v1/tracking/ingest");this.transport=new f(a),this.transport.setApiKey(t),this.context=new l({persistence:e.tracking.useCookies?"cookie":"local"}),this.queue=new A(this.transport),this.sessionId=function(){if("undefined"==typeof sessionStorage)return u();let e=sessionStorage.getItem(g);return e||(e=u(),sessionStorage.setItem(g,e)),e}(),this.countryCode=function(){try{const e=Intl.DateTimeFormat().resolvedOptions().timeZone;if(!e)return;return p[e]}catch{return}}(),e.tracking.autoTrack&&"undefined"!=typeof window&&this.page()}emitEngageEvent(e,i){const t=this.context.getPayload().page,a={type:e,timestamp:(new Date).toISOString(),sessionId:this.sessionId,userId:this.context.getPayload().user.id||void 0,countryCode:this.countryCode,sentiment:this.currentSentiment,referrer:(t.referrer||"").slice(0,500),path:t.path||("undefined"!=typeof window?window.location.pathname:"/"),currency:v(null==i?void 0:i.currency)||v(null==i?void 0:i.currencyCode)||void 0,amount:"number"==typeof(null==i?void 0:i.amountPaise)?i.amountPaise:"number"==typeof(null==i?void 0:i.amount)?Math.round(100*i.amount):void 0,productId:"string"==typeof(null==i?void 0:i.productId)?i.productId:void 0,productName:"string"==typeof(null==i?void 0:i.productName)?i.productName:void 0,productCategory:"string"==typeof(null==i?void 0:i.productCategory)?i.productCategory:void 0,metadata:w(null==i?void 0:i.metadata)};this.queue.enqueueEngageEvent(a),this.currentSentiment&&(this.currentSentiment=void 0)}parseRef(e){if(!e)return{};const i=e.match(/^camp_([^_]+)_cr_([^_]+)$/);return i?{campaignId:i[1],creatorId:i[2]}:{}}processEvent(e){const i=this.context.getPayload(),t=e.properties.ref,a=("string"==typeof t?t:void 0)||new URLSearchParams(i.page.search||"").get("ref")||void 0,n=this.parseRef(a),r=e.properties.value??e.properties.amount??e.properties.valuePaise??e.properties.amountPaise,s=Number(r),o=e.properties.campaignId,c=e.properties.creatorId,d=e.properties.currency,h={event:e.event,properties:e.properties,standardEvent:e.standardEvent,intent:e.intent,confidence:e.confidence,rawLabel:e.rawLabel,timestamp:(new Date).toISOString(),messageId:u(),writeKey:this.config.writeKey||this.config.apiKey||"",ref:a,campaignId:("string"==typeof o?o:void 0)||n.campaignId,creatorId:("string"==typeof c?c:void 0)||n.creatorId,value:Number.isFinite(s)?Math.round(s):void 0,valuePaise:Number.isFinite(s)?Math.round(100*s):void 0,currency:("string"==typeof d?d:void 0)||"USD",userId:i.user.id||void 0,anonymousId:i.user.anonymousId,context:i};this.config.debug,this.queue.enqueue(h)}}class S{constructor(e){i(this,"analytics"),i(this,"metrics",{}),this.analytics=e,"undefined"!=typeof window&&"PerformanceObserver"in window&&this.observe()}observe(){try{new PerformanceObserver(e=>{for(const i of e.getEntries()){const e=i;e.hadRecentInput||(this.metrics.cls=(this.metrics.cls||0)+e.value)}}).observe({type:"layout-shift",buffered:!0}),new PerformanceObserver(e=>{const i=e.getEntries(),t=i[i.length-1];this.metrics.lcp=t.renderTime||t.loadTime,this.logMetric("LCP",this.metrics.lcp)}).observe({type:"largest-contentful-paint",buffered:!0}),new PerformanceObserver(e=>{const i=e.getEntries()[0];i&&(this.metrics.fid=i.processingStart-i.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(e){}}logMetric(e,i){i<0||this.analytics.track(`Core Web Vital: ${e}`,{metric:e,value:Math.round(i)},{standardEvent:"PERFORMANCE",intent:"performance",confidence:1,rawLabel:`${e}: ${Math.round(i)}ms`})}}const b=a.createContext(null);let C=null;const R=e=>{if(!C){const i={...e,writeKey:e.writeKey||e.apiKey||"",apiKey:e.apiKey||e.writeKey||""};C=new E(i)}return C},M={init:e=>R({writeKey:e.apiKey,apiKey:e.apiKey,apiHost:e.apiHost,tracking:e.tracking||{useCookies:!0,fingerprint:!0,autoTrack:!0},debug:e.debug}),track(e,i){C&&C.track(e,i)},page(e,i){C&&C.page(e,i)},identify(e,i){C&&C.identify(e,i)},trackRevenue(e,i){C&&C.trackRevenue(e,i)},setSentiment(e){C&&C.setSentiment(e)}};exports.EngageProProvider=({children:e,...i})=>{const n=a.useRef(null),r=a.useRef(null);n.current||(n.current=new E(i),"undefined"!=typeof window&&new S(n.current));const s=n.current;return a.useEffect(()=>{if(!i.tracking.autoTrack)return;const e=e=>{const i=e.target.closest('button, a, input[type="submit"], [data-track], .clickable');if(i){const e=Date.now(),t=r.current;t&&t.el===i&&e-t.ts<500?(t.count++,t.ts=e,3===t.count&&(s.track("Rage Click detected",{element:i.tagName},{standardEvent:"RAGE_CLICK",intent:"frustration",confidence:1}),r.current=null)):r.current={el:i,count:1,ts:e};const a=(e=>{let i=(e.innerText||e.value||e.getAttribute("aria-label")||"").trim();if(!i){const t=e.querySelector("img");t&&t.alt&&(i=t.alt);const a=e.querySelector("title");a&&(i=a.textContent||"")}const t=i.slice(0,100),a=t.toLowerCase(),n=(e.id||"").toLowerCase(),r=e.href||"";return/add to (cart|bag)|buy now/.test(a)||n.includes("add-to-cart")?{standard:"ADD_TO_CART",intent:"commerce",label:t,confidence:.9}:/checkout|proceed/.test(a)||r.includes("/checkout")?{standard:"INITIATE_CHECKOUT",intent:"commerce",label:t,confidence:.9}:/place order|pay now/.test(a)||n.includes("place-order")?{standard:"PURCHASE",intent:"commerce",label:t,confidence:.95}:/cancel order/.test(a)||n.includes("cancel")?{standard:"ORDER_CANCEL",intent:"lifecycle",label:t,confidence:.85}:/refund|return/.test(a)||n.includes("refund")?{standard:"ORDER_REFUND",intent:"lifecycle",label:t,confidence:.85}:/track package|shipping/.test(a)?{standard:"TRACK_PACKAGE",intent:"lifecycle",label:t,confidence:.8}:/write review/.test(a)||a.includes("star")&&/^[1-5]/.test(a)?{standard:"RATE_PRODUCT",intent:"engagement",label:t,confidence:.8}:a.includes("search")||n.includes("search")?{standard:"SEARCH",intent:"search",label:t,confidence:.7}:{standard:"GENERIC",intent:"interaction",label:t,confidence:.5}})(i),n="A"===i.tagName;s.track("Interaction",{element:i.tagName.toLowerCase(),id:i.id,destination:n?i.href:void 0},{standardEvent:a.standard,intent:a.intent,rawLabel:a.label,confidence:a.confidence})}},t=e=>{const i=e.target;(e=>"password"===e.getAttribute("type")||"hidden"===e.getAttribute("type")||/password|cvc|card|cc-num|ssn|credit|hidden/i.test(e.getAttribute("name")||e.id||""))(i)||"focusin"!==e.type||i.dataset.tracked||(i.dataset.tracked="true",s.track("Form Start",{field:i.name||i.id},{standardEvent:"FORM_START",intent:"identity"}))},a=()=>{const e=new URLSearchParams(window.location.search);e.has("q")&&s.track("Search Query",{query:e.get("q")},{standardEvent:"SEARCH",intent:"search"}),requestAnimationFrame(()=>s.page())},n=history.pushState;return history.pushState=(...e)=>{n.apply(history,e),a()},window.addEventListener("popstate",a),window.addEventListener("click",e,!0),window.addEventListener("focusin",t,!0),a(),()=>{history.pushState=n,window.removeEventListener("popstate",a),window.removeEventListener("click",e,!0),window.removeEventListener("focusin",t,!0)}},[i.tracking.autoTrack]),t.jsx(b.Provider,{value:s,children:e})},exports.default=E,exports.engage=M,exports.init=R,exports.useAnalytics=()=>{const e=a.useContext(b);if(!e)throw new Error("useAnalytics must be used within EngageProProvider");return e};
package/dist/index.d.ts CHANGED
@@ -5,3 +5,20 @@ export default EngagePro;
5
5
  export { EngageProProvider, useAnalytics } from './react/EngageProProvider';
6
6
  export * from './types';
7
7
  export declare const init: (config: EngageConfig) => EngagePro;
8
+ /**
9
+ * Spec §3.5 — The `engage` singleton object for non-React apps.
10
+ * Brands integrate like:
11
+ * import { engage } from '@bluenath/engage';
12
+ * engage.init({ apiKey: 'ep_live_xxxxx' });
13
+ * engage.track('purchase', { amount: 49900, currency: 'INR' });
14
+ */
15
+ export declare const engage: {
16
+ init(config: Partial<EngageConfig> & {
17
+ apiKey: string;
18
+ }): EngagePro;
19
+ track(eventName: string, properties?: Record<string, unknown>): void;
20
+ page(name?: string, properties?: Record<string, unknown>): void;
21
+ identify(userId: string, traits?: Record<string, unknown>): void;
22
+ trackRevenue(amount: number | string, properties?: Record<string, unknown>): void;
23
+ setSentiment(sentiment: "positive" | "neutral" | "negative"): void;
24
+ };
package/dist/index.js CHANGED
@@ -299,14 +299,18 @@ class Context {
299
299
  }
300
300
  }
301
301
  class EventQueue {
302
+ // Spec §3.4
302
303
  constructor(transport) {
303
304
  __publicField(this, "queue", []);
305
+ __publicField(this, "engageQueue", []);
304
306
  __publicField(this, "storage");
305
307
  __publicField(this, "transport");
306
308
  __publicField(this, "STORAGE_KEY", "engage_queue_v1");
309
+ __publicField(this, "ENGAGE_STORAGE_KEY", "engage_events_v2");
307
310
  __publicField(this, "isFlushing", false);
308
311
  __publicField(this, "flushInterval");
309
312
  __publicField(this, "retryCount", 0);
313
+ __publicField(this, "MAX_RETRIES", 3);
310
314
  this.transport = transport;
311
315
  this.storage = new StorageEngine("local");
312
316
  this.load();
@@ -316,21 +320,23 @@ class EventQueue {
316
320
  this.flush();
317
321
  });
318
322
  window.addEventListener("visibilitychange", () => {
319
- if (document.visibilityState === "hidden") this.flush();
323
+ if (document.visibilityState === "hidden") this.flushBeacon();
324
+ });
325
+ window.addEventListener("beforeunload", () => {
326
+ this.flushBeacon();
320
327
  });
321
328
  }
322
329
  this.startTimer();
323
330
  }
324
331
  startTimer() {
325
332
  if (this.flushInterval) clearInterval(this.flushInterval);
326
- const delay = 3e3 * Math.pow(2, this.retryCount);
327
- this.flushInterval = setInterval(
328
- () => {
329
- this.flush();
330
- },
331
- Math.min(delay, 3e4)
332
- );
333
+ this.flushInterval = setInterval(() => {
334
+ this.flush();
335
+ }, 5e3);
333
336
  }
337
+ /**
338
+ * Enqueue a legacy AnalyticsEvent (backward compatibility)
339
+ */
334
340
  enqueue(event) {
335
341
  this.queue.push(event);
336
342
  this.save();
@@ -338,9 +344,25 @@ class EventQueue {
338
344
  this.flush();
339
345
  }
340
346
  }
347
+ /**
348
+ * Enqueue a new EngageEvent (Spec §3.1)
349
+ */
350
+ enqueueEngageEvent(event) {
351
+ this.engageQueue.push(event);
352
+ this.saveEngage();
353
+ if (this.engageQueue.length >= 10) {
354
+ this.flushEngageEvents();
355
+ }
356
+ }
341
357
  save() {
342
358
  this.storage.setItem(this.STORAGE_KEY, JSON.stringify(this.queue));
343
359
  }
360
+ saveEngage() {
361
+ this.storage.setItem(
362
+ this.ENGAGE_STORAGE_KEY,
363
+ JSON.stringify(this.engageQueue)
364
+ );
365
+ }
344
366
  load() {
345
367
  const data = this.storage.getItem(this.STORAGE_KEY);
346
368
  if (data) {
@@ -350,9 +372,25 @@ class EventQueue {
350
372
  this.queue = [];
351
373
  }
352
374
  }
375
+ const engageData = this.storage.getItem(this.ENGAGE_STORAGE_KEY);
376
+ if (engageData) {
377
+ try {
378
+ this.engageQueue = JSON.parse(engageData);
379
+ } catch (e) {
380
+ this.engageQueue = [];
381
+ }
382
+ }
353
383
  }
384
+ /**
385
+ * Flush legacy events (existing behavior)
386
+ */
354
387
  async flush() {
355
- if (this.queue.length === 0 || this.isFlushing) return;
388
+ if (this.queue.length === 0 || this.isFlushing) {
389
+ if (this.engageQueue.length > 0 && !this.isFlushing) {
390
+ await this.flushEngageEvents();
391
+ }
392
+ return;
393
+ }
356
394
  if (typeof navigator !== "undefined" && !navigator.onLine) return;
357
395
  this.isFlushing = true;
358
396
  const batch = [...this.queue];
@@ -362,7 +400,6 @@ class EventQueue {
362
400
  const success = await this.transport.send(batch);
363
401
  if (success) {
364
402
  this.retryCount = 0;
365
- this.startTimer();
366
403
  } else {
367
404
  throw new Error("Server rejected batch");
368
405
  }
@@ -370,45 +407,410 @@ class EventQueue {
370
407
  this.queue = [...batch, ...this.queue];
371
408
  this.save();
372
409
  this.retryCount++;
373
- this.startTimer();
374
410
  } finally {
375
411
  this.isFlushing = false;
376
412
  }
413
+ if (this.engageQueue.length > 0) {
414
+ await this.flushEngageEvents();
415
+ }
416
+ }
417
+ /**
418
+ * Spec §3.4: Flush EngageEvent[] with exponential backoff retry.
419
+ * Max 3 retries with 1s/2s/4s backoff. Drop batch after 3 failures.
420
+ * Do not retry on 401/422 (permanent failures).
421
+ */
422
+ async flushEngageEvents() {
423
+ if (this.engageQueue.length === 0 || this.isFlushing) return;
424
+ if (typeof navigator !== "undefined" && !navigator.onLine) return;
425
+ this.isFlushing = true;
426
+ const batch = [...this.engageQueue];
427
+ this.engageQueue = [];
428
+ this.saveEngage();
429
+ let attempt = 0;
430
+ let success = false;
431
+ while (attempt < this.MAX_RETRIES && !success) {
432
+ const result = await this.transport.sendEngageEvents(batch);
433
+ if (result.success) {
434
+ success = true;
435
+ break;
436
+ }
437
+ if (result.permanent) {
438
+ console.warn(
439
+ `[EngagePro] Permanent failure (${result.status}). Dropping ${batch.length} events.`
440
+ );
441
+ this.isFlushing = false;
442
+ return;
443
+ }
444
+ attempt++;
445
+ if (attempt < this.MAX_RETRIES) {
446
+ const delay = Math.pow(2, attempt - 1) * 1e3;
447
+ await new Promise((resolve) => setTimeout(resolve, delay));
448
+ }
449
+ }
450
+ if (!success) {
451
+ console.warn(
452
+ `[EngagePro] Dropped ${batch.length} events after ${this.MAX_RETRIES} failed retries.`
453
+ );
454
+ }
455
+ this.isFlushing = false;
456
+ }
457
+ /**
458
+ * Spec §3.4: Use navigator.sendBeacon() to flush remaining events on page unload.
459
+ * This is a best-effort fire-and-forget — no retry possible.
460
+ */
461
+ flushBeacon() {
462
+ if (this.queue.length > 0) {
463
+ const payload = JSON.stringify({
464
+ batch: this.queue,
465
+ sentAt: (/* @__PURE__ */ new Date()).toISOString()
466
+ });
467
+ const sent = this.transport.beaconFlush(payload);
468
+ if (sent) {
469
+ this.queue = [];
470
+ this.save();
471
+ }
472
+ }
473
+ if (this.engageQueue.length > 0) {
474
+ const payload = JSON.stringify({ events: this.engageQueue });
475
+ const sent = this.transport.beaconFlush(payload);
476
+ if (sent) {
477
+ this.engageQueue = [];
478
+ this.saveEngage();
479
+ }
480
+ }
377
481
  }
378
482
  }
379
483
  class Transport {
380
484
  constructor(apiEndpoint) {
485
+ __publicField(this, "apiKey", "");
381
486
  this.apiEndpoint = apiEndpoint;
382
487
  }
488
+ setApiKey(key) {
489
+ this.apiKey = key;
490
+ }
491
+ /**
492
+ * Sends legacy batched events (existing format).
493
+ * Used by the old EventQueue for backward compatibility.
494
+ */
383
495
  async send(events) {
384
- const payload = JSON.stringify({
385
- batch: events,
386
- sentAt: (/* @__PURE__ */ new Date()).toISOString()
387
- });
388
- if (typeof navigator !== "undefined" && navigator.sendBeacon && payload.length < 6e4) {
496
+ const result = await this.sendRaw(
497
+ JSON.stringify({ batch: events, sentAt: (/* @__PURE__ */ new Date()).toISOString() })
498
+ );
499
+ return result.success;
500
+ }
501
+ /**
502
+ * Sends new EngageEvent[] to the ingest endpoint (Spec §4.1).
503
+ * Returns detailed result for retry logic.
504
+ */
505
+ async sendEngageEvents(events) {
506
+ const payload = JSON.stringify({ events });
507
+ return this.sendRaw(payload);
508
+ }
509
+ async sendRaw(payload) {
510
+ const headers = {
511
+ "Content-Type": "application/json"
512
+ };
513
+ if (this.apiKey) {
514
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
515
+ }
516
+ if (!this.apiKey && typeof navigator !== "undefined" && navigator.sendBeacon && payload.length < 6e4) {
389
517
  const blob = new Blob([payload], { type: "application/json" });
390
518
  const success = navigator.sendBeacon(this.apiEndpoint, blob);
391
- if (success) return true;
519
+ if (success) return { success: true, permanent: false };
392
520
  }
393
521
  try {
394
522
  const response = await fetch(this.apiEndpoint, {
395
523
  method: "POST",
396
- headers: { "Content-Type": "application/json" },
524
+ headers,
397
525
  body: payload,
398
526
  keepalive: true
399
- // Tries to keep request alive during navigation
400
527
  });
401
- return response.ok;
528
+ if (response.status === 401 || response.status === 422) {
529
+ return { success: false, permanent: true, status: response.status };
530
+ }
531
+ return {
532
+ success: response.ok || response.status === 202,
533
+ permanent: false,
534
+ status: response.status
535
+ };
402
536
  } catch (e) {
403
537
  console.error("[EngagePro] Transport Failed:", e);
404
- return false;
538
+ return { success: false, permanent: false };
405
539
  }
406
540
  }
541
+ /**
542
+ * Uses sendBeacon for last-resort page unload flush.
543
+ * Cannot send auth headers — the ingest endpoint must also accept
544
+ * the apiKey in the body payload as fallback.
545
+ */
546
+ beaconFlush(payload) {
547
+ if (typeof navigator === "undefined" || !navigator.sendBeacon) return false;
548
+ const blob = new Blob([payload], { type: "application/json" });
549
+ return navigator.sendBeacon(this.apiEndpoint, blob);
550
+ }
551
+ }
552
+ const TIMEZONE_TO_COUNTRY = {
553
+ // Asia
554
+ "Asia/Kolkata": "IN",
555
+ "Asia/Calcutta": "IN",
556
+ "Asia/Mumbai": "IN",
557
+ "Asia/Dhaka": "BD",
558
+ "Asia/Kathmandu": "NP",
559
+ "Asia/Colombo": "LK",
560
+ "Asia/Karachi": "PK",
561
+ "Asia/Kabul": "AF",
562
+ "Asia/Tehran": "IR",
563
+ "Asia/Dubai": "AE",
564
+ "Asia/Muscat": "OM",
565
+ "Asia/Bahrain": "BH",
566
+ "Asia/Qatar": "QA",
567
+ "Asia/Kuwait": "KW",
568
+ "Asia/Riyadh": "SA",
569
+ "Asia/Aden": "YE",
570
+ "Asia/Baghdad": "IQ",
571
+ "Asia/Amman": "JO",
572
+ "Asia/Beirut": "LB",
573
+ "Asia/Damascus": "SY",
574
+ "Asia/Jerusalem": "IL",
575
+ "Asia/Tel_Aviv": "IL",
576
+ "Asia/Nicosia": "CY",
577
+ "Asia/Tokyo": "JP",
578
+ "Asia/Seoul": "KR",
579
+ "Asia/Pyongyang": "KP",
580
+ "Asia/Shanghai": "CN",
581
+ "Asia/Chongqing": "CN",
582
+ "Asia/Harbin": "CN",
583
+ "Asia/Urumqi": "CN",
584
+ "Asia/Hong_Kong": "HK",
585
+ "Asia/Macau": "MO",
586
+ "Asia/Taipei": "TW",
587
+ "Asia/Singapore": "SG",
588
+ "Asia/Kuala_Lumpur": "MY",
589
+ "Asia/Brunei": "BN",
590
+ "Asia/Jakarta": "ID",
591
+ "Asia/Makassar": "ID",
592
+ "Asia/Jayapura": "ID",
593
+ "Asia/Bangkok": "TH",
594
+ "Asia/Ho_Chi_Minh": "VN",
595
+ "Asia/Saigon": "VN",
596
+ "Asia/Phnom_Penh": "KH",
597
+ "Asia/Vientiane": "LA",
598
+ "Asia/Yangon": "MM",
599
+ "Asia/Rangoon": "MM",
600
+ "Asia/Manila": "PH",
601
+ "Asia/Ulaanbaatar": "MN",
602
+ "Asia/Hovd": "MN",
603
+ "Asia/Tbilisi": "GE",
604
+ "Asia/Baku": "AZ",
605
+ "Asia/Yerevan": "AM",
606
+ "Asia/Almaty": "KZ",
607
+ "Asia/Bishkek": "KG",
608
+ "Asia/Tashkent": "UZ",
609
+ "Asia/Ashgabat": "TM",
610
+ "Asia/Dushanbe": "TJ",
611
+ "Asia/Thimphu": "BT",
612
+ "Asia/Dili": "TL",
613
+ // Europe
614
+ "Europe/London": "GB",
615
+ "Europe/Dublin": "IE",
616
+ "Europe/Lisbon": "PT",
617
+ "Europe/Madrid": "ES",
618
+ "Europe/Paris": "FR",
619
+ "Europe/Brussels": "BE",
620
+ "Europe/Amsterdam": "NL",
621
+ "Europe/Luxembourg": "LU",
622
+ "Europe/Berlin": "DE",
623
+ "Europe/Zurich": "CH",
624
+ "Europe/Vienna": "AT",
625
+ "Europe/Rome": "IT",
626
+ "Europe/Monaco": "MC",
627
+ "Europe/Vatican": "VA",
628
+ "Europe/Malta": "MT",
629
+ "Europe/Prague": "CZ",
630
+ "Europe/Budapest": "HU",
631
+ "Europe/Warsaw": "PL",
632
+ "Europe/Bratislava": "SK",
633
+ "Europe/Ljubljana": "SI",
634
+ "Europe/Zagreb": "HR",
635
+ "Europe/Belgrade": "RS",
636
+ "Europe/Sarajevo": "BA",
637
+ "Europe/Podgorica": "ME",
638
+ "Europe/Skopje": "MK",
639
+ "Europe/Tirane": "AL",
640
+ "Europe/Sofia": "BG",
641
+ "Europe/Bucharest": "RO",
642
+ "Europe/Chisinau": "MD",
643
+ "Europe/Athens": "GR",
644
+ "Europe/Istanbul": "TR",
645
+ "Europe/Helsinki": "FI",
646
+ "Europe/Stockholm": "SE",
647
+ "Europe/Oslo": "NO",
648
+ "Europe/Copenhagen": "DK",
649
+ "Europe/Tallinn": "EE",
650
+ "Europe/Riga": "LV",
651
+ "Europe/Vilnius": "LT",
652
+ "Europe/Minsk": "BY",
653
+ "Europe/Moscow": "RU",
654
+ "Europe/Kaliningrad": "RU",
655
+ "Europe/Samara": "RU",
656
+ "Europe/Kiev": "UA",
657
+ "Europe/Kyiv": "UA",
658
+ "Europe/Reykjavik": "IS",
659
+ "Europe/Andorra": "AD",
660
+ "Europe/Gibraltar": "GI",
661
+ "Europe/San_Marino": "SM",
662
+ // Americas
663
+ "America/New_York": "US",
664
+ "America/Chicago": "US",
665
+ "America/Denver": "US",
666
+ "America/Los_Angeles": "US",
667
+ "America/Phoenix": "US",
668
+ "America/Anchorage": "US",
669
+ "Pacific/Honolulu": "US",
670
+ "America/Detroit": "US",
671
+ "America/Indianapolis": "US",
672
+ "America/Boise": "US",
673
+ "America/Juneau": "US",
674
+ "America/Adak": "US",
675
+ "America/Toronto": "CA",
676
+ "America/Vancouver": "CA",
677
+ "America/Montreal": "CA",
678
+ "America/Winnipeg": "CA",
679
+ "America/Edmonton": "CA",
680
+ "America/Halifax": "CA",
681
+ "America/St_Johns": "CA",
682
+ "America/Regina": "CA",
683
+ "America/Mexico_City": "MX",
684
+ "America/Cancun": "MX",
685
+ "America/Tijuana": "MX",
686
+ "America/Monterrey": "MX",
687
+ "America/Hermosillo": "MX",
688
+ "America/Guatemala": "GT",
689
+ "America/Belize": "BZ",
690
+ "America/El_Salvador": "SV",
691
+ "America/Tegucigalpa": "HN",
692
+ "America/Managua": "NI",
693
+ "America/Costa_Rica": "CR",
694
+ "America/Panama": "PA",
695
+ "America/Bogota": "CO",
696
+ "America/Lima": "PE",
697
+ "America/Guayaquil": "EC",
698
+ "America/Caracas": "VE",
699
+ "America/La_Paz": "BO",
700
+ "America/Asuncion": "PY",
701
+ "America/Montevideo": "UY",
702
+ "America/Buenos_Aires": "AR",
703
+ "America/Argentina/Buenos_Aires": "AR",
704
+ "America/Santiago": "CL",
705
+ "America/Sao_Paulo": "BR",
706
+ "America/Recife": "BR",
707
+ "America/Manaus": "BR",
708
+ "America/Fortaleza": "BR",
709
+ "America/Bahia": "BR",
710
+ "America/Havana": "CU",
711
+ "America/Jamaica": "JM",
712
+ "America/Port-au-Prince": "HT",
713
+ "America/Santo_Domingo": "DO",
714
+ "America/Puerto_Rico": "PR",
715
+ "America/Port_of_Spain": "TT",
716
+ "America/Barbados": "BB",
717
+ "America/Martinique": "MQ",
718
+ "America/Guyana": "GY",
719
+ "America/Paramaribo": "SR",
720
+ "America/Cayenne": "GF",
721
+ "America/Curacao": "CW",
722
+ // Africa
723
+ "Africa/Cairo": "EG",
724
+ "Africa/Casablanca": "MA",
725
+ "Africa/Tunis": "TN",
726
+ "Africa/Algiers": "DZ",
727
+ "Africa/Tripoli": "LY",
728
+ "Africa/Khartoum": "SD",
729
+ "Africa/Addis_Ababa": "ET",
730
+ "Africa/Nairobi": "KE",
731
+ "Africa/Dar_es_Salaam": "TZ",
732
+ "Africa/Kampala": "UG",
733
+ "Africa/Mogadishu": "SO",
734
+ "Africa/Lagos": "NG",
735
+ "Africa/Accra": "GH",
736
+ "Africa/Abidjan": "CI",
737
+ "Africa/Dakar": "SN",
738
+ "Africa/Bamako": "ML",
739
+ "Africa/Ouagadougou": "BF",
740
+ "Africa/Conakry": "GN",
741
+ "Africa/Freetown": "SL",
742
+ "Africa/Monrovia": "LR",
743
+ "Africa/Lome": "TG",
744
+ "Africa/Porto-Novo": "BJ",
745
+ "Africa/Niamey": "NE",
746
+ "Africa/Douala": "CM",
747
+ "Africa/Libreville": "GA",
748
+ "Africa/Bangui": "CF",
749
+ "Africa/Brazzaville": "CG",
750
+ "Africa/Kinshasa": "CD",
751
+ "Africa/Lubumbashi": "CD",
752
+ "Africa/Luanda": "AO",
753
+ "Africa/Maputo": "MZ",
754
+ "Africa/Harare": "ZW",
755
+ "Africa/Lusaka": "ZM",
756
+ "Africa/Lilongwe": "MW",
757
+ "Africa/Johannesburg": "ZA",
758
+ "Africa/Windhoek": "NA",
759
+ "Africa/Gaborone": "BW",
760
+ "Africa/Maseru": "LS",
761
+ "Africa/Mbabane": "SZ",
762
+ "Indian/Antananarivo": "MG",
763
+ "Indian/Mauritius": "MU",
764
+ "Indian/Reunion": "RE",
765
+ "Indian/Comoro": "KM",
766
+ "Indian/Mayotte": "YT",
767
+ "Africa/Djibouti": "DJ",
768
+ "Africa/Asmara": "ER",
769
+ // Oceania
770
+ "Australia/Sydney": "AU",
771
+ "Australia/Melbourne": "AU",
772
+ "Australia/Brisbane": "AU",
773
+ "Australia/Perth": "AU",
774
+ "Australia/Adelaide": "AU",
775
+ "Australia/Hobart": "AU",
776
+ "Australia/Darwin": "AU",
777
+ "Australia/Lord_Howe": "AU",
778
+ "Pacific/Auckland": "NZ",
779
+ "Pacific/Chatham": "NZ",
780
+ "Pacific/Fiji": "FJ",
781
+ "Pacific/Tongatapu": "TO",
782
+ "Pacific/Apia": "WS",
783
+ "Pacific/Port_Moresby": "PG",
784
+ "Pacific/Noumea": "NC",
785
+ "Pacific/Guam": "GU",
786
+ "Pacific/Pago_Pago": "AS",
787
+ "Pacific/Tahiti": "PF",
788
+ // Atlantic / Indian Ocean
789
+ "Atlantic/Reykjavik": "IS",
790
+ "Atlantic/Azores": "PT",
791
+ "Atlantic/Canary": "ES",
792
+ "Atlantic/Madeira": "PT",
793
+ "Atlantic/Cape_Verde": "CV",
794
+ "Atlantic/Bermuda": "BM",
795
+ "Indian/Maldives": "MV",
796
+ "Indian/Chagos": "IO",
797
+ "Indian/Christmas": "CX",
798
+ "Indian/Cocos": "CC"
799
+ };
800
+ function detectCountryFromTimezone() {
801
+ try {
802
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
803
+ if (!tz) return void 0;
804
+ return TIMEZONE_TO_COUNTRY[tz];
805
+ } catch {
806
+ return void 0;
807
+ }
407
808
  }
408
809
  const DEFAULT_INGEST_ENDPOINT = "https://engage-api.bluenath.com/api/v1/tracking/ingest";
409
810
  const FIXED_INGEST_PATH = "/api/v1/tracking/ingest";
410
811
  const ALLOWED_DEV_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1"]);
411
812
  const DEFAULT_CURRENCY = "USD";
813
+ const SESSION_KEY = "engage_session_id";
412
814
  const SYMBOL_TO_CURRENCY = {
413
815
  $: "USD",
414
816
  US$: "USD",
@@ -424,8 +826,8 @@ const SYMBOL_TO_CURRENCY = {
424
826
  "¥": "JPY",
425
827
  JPY: "JPY"
426
828
  };
427
- const resolveIngestEndpoint = (apiHost) => {
428
- if (!apiHost) return DEFAULT_INGEST_ENDPOINT;
829
+ const resolveEndpoint = (apiHost, path, defaultUrl) => {
830
+ if (!apiHost) return defaultUrl;
429
831
  try {
430
832
  const parsed = new URL(apiHost);
431
833
  const isAllowedHost = parsed.hostname.endsWith("bluenath.com") || ALLOWED_DEV_HOSTS.has(parsed.hostname);
@@ -433,14 +835,14 @@ const resolveIngestEndpoint = (apiHost) => {
433
835
  console.warn(
434
836
  `[EngagePro] Ignoring unsupported apiHost "${parsed.hostname}". Falling back to managed endpoint.`
435
837
  );
436
- return DEFAULT_INGEST_ENDPOINT;
838
+ return defaultUrl;
437
839
  }
438
- parsed.pathname = FIXED_INGEST_PATH;
840
+ parsed.pathname = path;
439
841
  parsed.search = "";
440
842
  parsed.hash = "";
441
843
  return parsed.toString();
442
844
  } catch {
443
- return DEFAULT_INGEST_ENDPOINT;
845
+ return defaultUrl;
444
846
  }
445
847
  };
446
848
  const parseCurrencyToken = (value) => {
@@ -462,6 +864,29 @@ const parseRevenueAmount = (value) => {
462
864
  const parsed = Number(numeric);
463
865
  return Number.isFinite(parsed) ? parsed : 0;
464
866
  };
867
+ function getOrCreateSessionId() {
868
+ if (typeof sessionStorage === "undefined") return v4();
869
+ let sessionId = sessionStorage.getItem(SESSION_KEY);
870
+ if (!sessionId) {
871
+ sessionId = v4();
872
+ sessionStorage.setItem(SESSION_KEY, sessionId);
873
+ }
874
+ return sessionId;
875
+ }
876
+ function validateMetadata(meta) {
877
+ if (!meta || typeof meta !== "object") return void 0;
878
+ const result = {};
879
+ const keys = Object.keys(meta).slice(0, 20);
880
+ for (const key of keys) {
881
+ const val = meta[key];
882
+ if (typeof val === "boolean" || typeof val === "number") {
883
+ result[key] = val;
884
+ } else if (typeof val === "string") {
885
+ result[key] = val.slice(0, 100);
886
+ }
887
+ }
888
+ return Object.keys(result).length > 0 ? result : void 0;
889
+ }
465
890
  class EngagePro {
466
891
  constructor(config) {
467
892
  __publicField(this, "config");
@@ -469,6 +894,11 @@ class EngagePro {
469
894
  __publicField(this, "context");
470
895
  __publicField(this, "queue");
471
896
  __publicField(this, "transport");
897
+ // Spec §3.2: Auto-detected fields
898
+ __publicField(this, "sessionId");
899
+ __publicField(this, "countryCode");
900
+ // Spec §3.3: Current sentiment (set by brand, attached to next event)
901
+ __publicField(this, "currentSentiment");
472
902
  // ✅ ARROW FUNCTION (Auto-bound 'this')
473
903
  __publicField(this, "identify", (userId, traits) => {
474
904
  this.context.userId = userId;
@@ -495,6 +925,7 @@ class EngagePro {
495
925
  intent: "navigation",
496
926
  confidence: 1
497
927
  });
928
+ this.emitEngageEvent("pageview", properties);
498
929
  });
499
930
  // ✅ ARROW FUNCTION
500
931
  __publicField(this, "track", (eventName, properties, intelligence) => {
@@ -503,6 +934,22 @@ class EngagePro {
503
934
  properties: properties || {},
504
935
  ...intelligence
505
936
  });
937
+ const typeMap = {
938
+ purchase: "purchase",
939
+ PURCHASE: "purchase",
940
+ signup: "signup",
941
+ SIGNUP: "signup",
942
+ register: "signup",
943
+ REGISTER: "signup",
944
+ add_to_cart: "add_to_cart",
945
+ ADD_TO_CART: "add_to_cart",
946
+ product_view: "product_view",
947
+ VIEW_CONTENT: "product_view",
948
+ session_start: "session_start",
949
+ session_end: "session_end"
950
+ };
951
+ const engageType = typeMap[eventName] || typeMap[(intelligence == null ? void 0 : intelligence.standardEvent) || ""] || "custom";
952
+ this.emitEngageEvent(engageType, properties);
506
953
  });
507
954
  __publicField(this, "trackRevenue", (amount, properties, intelligence) => {
508
955
  const parsedAmount = Math.max(0, parseRevenueAmount(amount));
@@ -526,12 +973,53 @@ class EngagePro {
526
973
  }
527
974
  );
528
975
  });
976
+ /**
977
+ * Spec §3.3: Set sentiment for the current session.
978
+ * Attached to the next emitted event.
979
+ */
980
+ __publicField(this, "setSentiment", (sentiment) => {
981
+ this.currentSentiment = sentiment;
982
+ });
529
983
  this.config = config;
530
- this.transport = new Transport(resolveIngestEndpoint(config.apiHost));
984
+ const apiKey = config.apiKey || config.writeKey;
985
+ const legacyEndpoint = resolveEndpoint(config.apiHost, FIXED_INGEST_PATH, DEFAULT_INGEST_ENDPOINT);
986
+ this.transport = new Transport(legacyEndpoint);
987
+ this.transport.setApiKey(apiKey);
531
988
  this.context = new Context({
532
989
  persistence: config.tracking.useCookies ? "cookie" : "local"
533
990
  });
534
991
  this.queue = new EventQueue(this.transport);
992
+ this.sessionId = getOrCreateSessionId();
993
+ this.countryCode = detectCountryFromTimezone();
994
+ if (config.tracking.autoTrack && typeof window !== "undefined") {
995
+ this.page();
996
+ }
997
+ }
998
+ /**
999
+ * Emit a new-format EngageEvent to the ingest pipeline (Spec §3.1).
1000
+ */
1001
+ emitEngageEvent(type, properties) {
1002
+ const pageCtx = this.context.getPayload().page;
1003
+ const event = {
1004
+ type,
1005
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1006
+ sessionId: this.sessionId,
1007
+ userId: this.context.getPayload().user.id || void 0,
1008
+ countryCode: this.countryCode,
1009
+ sentiment: this.currentSentiment,
1010
+ referrer: (pageCtx.referrer || "").slice(0, 500),
1011
+ path: pageCtx.path || (typeof window !== "undefined" ? window.location.pathname : "/"),
1012
+ currency: parseCurrencyToken(properties == null ? void 0 : properties.currency) || parseCurrencyToken(properties == null ? void 0 : properties.currencyCode) || void 0,
1013
+ amount: typeof (properties == null ? void 0 : properties.amountPaise) === "number" ? properties.amountPaise : typeof (properties == null ? void 0 : properties.amount) === "number" ? Math.round(properties.amount * 100) : void 0,
1014
+ productId: typeof (properties == null ? void 0 : properties.productId) === "string" ? properties.productId : void 0,
1015
+ productName: typeof (properties == null ? void 0 : properties.productName) === "string" ? properties.productName : void 0,
1016
+ productCategory: typeof (properties == null ? void 0 : properties.productCategory) === "string" ? properties.productCategory : void 0,
1017
+ metadata: validateMetadata(properties == null ? void 0 : properties.metadata)
1018
+ };
1019
+ this.queue.enqueueEngageEvent(event);
1020
+ if (this.currentSentiment) {
1021
+ this.currentSentiment = void 0;
1022
+ }
535
1023
  }
536
1024
  parseRef(ref) {
537
1025
  if (!ref) return {};
@@ -539,7 +1027,7 @@ class EngagePro {
539
1027
  if (!m) return {};
540
1028
  return { campaignId: m[1], creatorId: m[2] };
541
1029
  }
542
- // Internal Helper
1030
+ // Internal Helper (legacy event processing)
543
1031
  processEvent(data) {
544
1032
  const baseContext = this.context.getPayload();
545
1033
  const refValue = data.properties.ref;
@@ -561,7 +1049,7 @@ class EngagePro {
561
1049
  // Metadata
562
1050
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
563
1051
  messageId: v4(),
564
- writeKey: this.config.writeKey,
1052
+ writeKey: this.config.writeKey || this.config.apiKey || "",
565
1053
  ref,
566
1054
  campaignId: (typeof campaignIdValue === "string" ? campaignIdValue : void 0) || refParts.campaignId,
567
1055
  creatorId: (typeof creatorIdValue === "string" ? creatorIdValue : void 0) || refParts.creatorId,
@@ -823,13 +1311,69 @@ const EngageProProvider = ({
823
1311
  let instance = null;
824
1312
  const init = (config) => {
825
1313
  if (!instance) {
826
- instance = new EngagePro(config);
1314
+ const normalizedConfig = {
1315
+ ...config,
1316
+ writeKey: config.writeKey || config.apiKey || "",
1317
+ apiKey: config.apiKey || config.writeKey || ""
1318
+ };
1319
+ instance = new EngagePro(normalizedConfig);
827
1320
  }
828
1321
  return instance;
829
1322
  };
1323
+ const engage = {
1324
+ init(config) {
1325
+ return init({
1326
+ writeKey: config.apiKey,
1327
+ apiKey: config.apiKey,
1328
+ apiHost: config.apiHost,
1329
+ tracking: config.tracking || {
1330
+ useCookies: true,
1331
+ fingerprint: true,
1332
+ autoTrack: true
1333
+ },
1334
+ debug: config.debug
1335
+ });
1336
+ },
1337
+ track(eventName, properties) {
1338
+ if (!instance) {
1339
+ console.warn("[EngagePro] Not initialized. Call engage.init() first.");
1340
+ return;
1341
+ }
1342
+ instance.track(eventName, properties);
1343
+ },
1344
+ page(name, properties) {
1345
+ if (!instance) {
1346
+ console.warn("[EngagePro] Not initialized. Call engage.init() first.");
1347
+ return;
1348
+ }
1349
+ instance.page(name, properties);
1350
+ },
1351
+ identify(userId, traits) {
1352
+ if (!instance) {
1353
+ console.warn("[EngagePro] Not initialized. Call engage.init() first.");
1354
+ return;
1355
+ }
1356
+ instance.identify(userId, traits);
1357
+ },
1358
+ trackRevenue(amount, properties) {
1359
+ if (!instance) {
1360
+ console.warn("[EngagePro] Not initialized. Call engage.init() first.");
1361
+ return;
1362
+ }
1363
+ instance.trackRevenue(amount, properties);
1364
+ },
1365
+ setSentiment(sentiment) {
1366
+ if (!instance) {
1367
+ console.warn("[EngagePro] Not initialized. Call engage.init() first.");
1368
+ return;
1369
+ }
1370
+ instance.setSentiment(sentiment);
1371
+ }
1372
+ };
830
1373
  export {
831
1374
  EngageProProvider,
832
1375
  EngagePro as default,
1376
+ engage,
833
1377
  init,
834
1378
  useAnalytics
835
1379
  };
@@ -0,0 +1,6 @@
1
+ export declare const TIMEZONE_TO_COUNTRY: Record<string, string>;
2
+ /**
3
+ * Detects country code from browser timezone.
4
+ * Returns ISO 3166-1 alpha-2 code or undefined.
5
+ */
6
+ export declare function detectCountryFromTimezone(): string | undefined;
@@ -64,6 +64,7 @@ export interface AnalyticsEvent {
64
64
  }
65
65
  export interface EngageConfig {
66
66
  writeKey: string;
67
+ apiKey?: string;
67
68
  apiHost?: string;
68
69
  tracking: {
69
70
  useCookies: boolean;
@@ -72,3 +73,25 @@ export interface EngageConfig {
72
73
  };
73
74
  debug?: boolean;
74
75
  }
76
+ export type EngageEventType = "pageview" | "product_view" | "add_to_cart" | "purchase" | "signup" | "session_start" | "session_end" | "custom";
77
+ export type SentimentValue = "positive" | "neutral" | "negative";
78
+ /**
79
+ * Spec §3.1 — The event schema emitted by the engage module
80
+ * to POST /api/v1/analytics/ingest. Backend validates against this.
81
+ */
82
+ export interface EngageEvent {
83
+ type: EngageEventType;
84
+ timestamp: string;
85
+ sessionId: string;
86
+ userId?: string;
87
+ currency?: string;
88
+ amount?: number;
89
+ productId?: string;
90
+ productName?: string;
91
+ productCategory?: string;
92
+ countryCode?: string;
93
+ sentiment?: SentimentValue;
94
+ referrer?: string;
95
+ path: string;
96
+ metadata?: Record<string, string | number | boolean>;
97
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bluenath/engage",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",