@grainql/analytics-web 2.2.0 → 2.2.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/dist/index.global.dev.js +1 -1
- package/dist/index.global.js +1 -1
- package/dist/react/index.d.ts +44 -519
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +54 -1517
- package/dist/react/index.mjs +42 -1514
- package/package.json +1 -1
- package/dist/react/activity.d.ts +0 -59
- package/dist/react/activity.d.ts.map +0 -1
- package/dist/react/activity.js +0 -130
- package/dist/react/activity.mjs +0 -126
- package/dist/react/consent.d.ts +0 -72
- package/dist/react/consent.d.ts.map +0 -1
- package/dist/react/consent.js +0 -195
- package/dist/react/consent.mjs +0 -191
- package/dist/react/cookies.d.ts +0 -28
- package/dist/react/cookies.d.ts.map +0 -1
- package/dist/react/cookies.js +0 -94
- package/dist/react/cookies.mjs +0 -88
- package/dist/react/heartbeat.d.ts +0 -47
- package/dist/react/heartbeat.d.ts.map +0 -1
- package/dist/react/heartbeat.js +0 -119
- package/dist/react/heartbeat.mjs +0 -115
- package/dist/react/page-tracking.d.ts +0 -60
- package/dist/react/page-tracking.d.ts.map +0 -1
- package/dist/react/page-tracking.js +0 -179
- package/dist/react/page-tracking.mjs +0 -175
- package/dist/react/react/index.d.ts +0 -47
- package/dist/react/react/index.d.ts.map +0 -1
- package/dist/react/react/index.js +0 -58
- package/dist/react/react/index.mjs +0 -44
- /package/dist/react/{react/GrainProvider.d.ts → GrainProvider.d.ts} +0 -0
- /package/dist/react/{react/GrainProvider.d.ts.map → GrainProvider.d.ts.map} +0 -0
- /package/dist/react/{react/GrainProvider.js → GrainProvider.js} +0 -0
- /package/dist/react/{react/GrainProvider.mjs → GrainProvider.mjs} +0 -0
- /package/dist/react/{react/components → components}/ConsentBanner.d.ts +0 -0
- /package/dist/react/{react/components → components}/ConsentBanner.d.ts.map +0 -0
- /package/dist/react/{react/components → components}/ConsentBanner.js +0 -0
- /package/dist/react/{react/components → components}/ConsentBanner.mjs +0 -0
- /package/dist/react/{react/components → components}/CookieNotice.d.ts +0 -0
- /package/dist/react/{react/components → components}/CookieNotice.d.ts.map +0 -0
- /package/dist/react/{react/components → components}/CookieNotice.js +0 -0
- /package/dist/react/{react/components → components}/CookieNotice.mjs +0 -0
- /package/dist/react/{react/components → components}/PrivacyPreferenceCenter.d.ts +0 -0
- /package/dist/react/{react/components → components}/PrivacyPreferenceCenter.d.ts.map +0 -0
- /package/dist/react/{react/components → components}/PrivacyPreferenceCenter.js +0 -0
- /package/dist/react/{react/components → components}/PrivacyPreferenceCenter.mjs +0 -0
- /package/dist/react/{react/context.d.ts → context.d.ts} +0 -0
- /package/dist/react/{react/context.d.ts.map → context.d.ts.map} +0 -0
- /package/dist/react/{react/context.js → context.js} +0 -0
- /package/dist/react/{react/context.mjs → context.mjs} +0 -0
- /package/dist/react/{react/hooks → hooks}/useAllConfigs.d.ts +0 -0
- /package/dist/react/{react/hooks → hooks}/useAllConfigs.d.ts.map +0 -0
- /package/dist/react/{react/hooks → hooks}/useAllConfigs.js +0 -0
- /package/dist/react/{react/hooks → hooks}/useAllConfigs.mjs +0 -0
- /package/dist/react/{react/hooks → hooks}/useConfig.d.ts +0 -0
- /package/dist/react/{react/hooks → hooks}/useConfig.d.ts.map +0 -0
- /package/dist/react/{react/hooks → hooks}/useConfig.js +0 -0
- /package/dist/react/{react/hooks → hooks}/useConfig.mjs +0 -0
- /package/dist/react/{react/hooks → hooks}/useConsent.d.ts +0 -0
- /package/dist/react/{react/hooks → hooks}/useConsent.d.ts.map +0 -0
- /package/dist/react/{react/hooks → hooks}/useConsent.js +0 -0
- /package/dist/react/{react/hooks → hooks}/useConsent.mjs +0 -0
- /package/dist/react/{react/hooks → hooks}/useDataDeletion.d.ts +0 -0
- /package/dist/react/{react/hooks → hooks}/useDataDeletion.d.ts.map +0 -0
- /package/dist/react/{react/hooks → hooks}/useDataDeletion.js +0 -0
- /package/dist/react/{react/hooks → hooks}/useDataDeletion.mjs +0 -0
- /package/dist/react/{react/hooks → hooks}/useGrainAnalytics.d.ts +0 -0
- /package/dist/react/{react/hooks → hooks}/useGrainAnalytics.d.ts.map +0 -0
- /package/dist/react/{react/hooks → hooks}/useGrainAnalytics.js +0 -0
- /package/dist/react/{react/hooks → hooks}/useGrainAnalytics.mjs +0 -0
- /package/dist/react/{react/hooks → hooks}/usePrivacyPreferences.d.ts +0 -0
- /package/dist/react/{react/hooks → hooks}/usePrivacyPreferences.d.ts.map +0 -0
- /package/dist/react/{react/hooks → hooks}/usePrivacyPreferences.js +0 -0
- /package/dist/react/{react/hooks → hooks}/usePrivacyPreferences.mjs +0 -0
- /package/dist/react/{react/hooks → hooks}/useTrack.d.ts +0 -0
- /package/dist/react/{react/hooks → hooks}/useTrack.d.ts.map +0 -0
- /package/dist/react/{react/hooks → hooks}/useTrack.js +0 -0
- /package/dist/react/{react/hooks → hooks}/useTrack.mjs +0 -0
- /package/dist/react/{react/types.d.ts → types.d.ts} +0 -0
- /package/dist/react/{react/types.d.ts.map → types.d.ts.map} +0 -0
- /package/dist/react/{react/types.js → types.js} +0 -0
- /package/dist/react/{react/types.mjs → types.mjs} +0 -0
package/dist/index.global.dev.js
CHANGED
package/dist/index.global.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
/* Grain Analytics Web SDK v2.2.
|
|
1
|
+
/* Grain Analytics Web SDK v2.2.1 | MIT License */
|
|
2
2
|
"use strict";var Grain=(()=>{var C=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var T=Object.prototype.hasOwnProperty;var x=(c,e)=>{for(var t in e)C(c,t,{get:e[t],enumerable:!0})},U=(c,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of A(e))!T.call(c,n)&&n!==t&&C(c,n,{get:()=>e[n],enumerable:!(r=R(e,n))||r.enumerable});return c};var D=c=>U(C({},"__esModule",{value:!0}),c);var $={};x($,{GrainAnalytics:()=>u,createGrainAnalytics:()=>P,default:()=>O});var I=["necessary","analytics","functional"],f="1.0.0",p=class{constructor(e,t="opt-out"){this.consentState=null;this.listeners=[];this.consentMode=t,this.storageKey=`grain_consent_${e}`,this.loadConsentState()}loadConsentState(){if(!(typeof window>"u"))try{let e=localStorage.getItem(this.storageKey);if(e){let t=JSON.parse(e);this.consentState={...t,timestamp:new Date(t.timestamp)}}else(this.consentMode==="opt-out"||this.consentMode==="disabled")&&(this.consentState={granted:!0,categories:I,timestamp:new Date,version:f},this.saveConsentState())}catch(e){console.error("[Grain Consent] Failed to load consent state:",e)}}saveConsentState(){if(!(typeof window>"u"||!this.consentState))try{localStorage.setItem(this.storageKey,JSON.stringify(this.consentState))}catch(e){console.error("[Grain Consent] Failed to save consent state:",e)}}grantConsent(e){let t=e||I;this.consentState={granted:!0,categories:t,timestamp:new Date,version:f},this.saveConsentState(),this.notifyListeners()}revokeConsent(e){this.consentState?e?this.consentState={...this.consentState,categories:this.consentState.categories.filter(t=>!e.includes(t)),granted:this.consentState.categories.length>0,timestamp:new Date}:this.consentState={granted:!1,categories:[],timestamp:new Date,version:f}:this.consentState={granted:!1,categories:[],timestamp:new Date,version:f},this.saveConsentState(),this.notifyListeners()}getConsentState(){return this.consentState?{...this.consentState}:null}hasConsent(e){return this.consentMode==="disabled"?!0:this.consentMode==="opt-in"&&!this.consentState||!this.consentState?.granted?!1:e?this.consentState.categories.includes(e):!0}shouldWaitForConsent(){return this.consentMode==="opt-in"&&!this.consentState?.granted}addListener(e){this.listeners.push(e)}removeListener(e){let t=this.listeners.indexOf(e);t>-1&&this.listeners.splice(t,1)}notifyListeners(){this.consentState&&this.listeners.forEach(e=>{try{e(this.consentState)}catch(t){console.error("[Grain Consent] Listener error:",t)}})}clearConsent(){if(!(typeof window>"u"))try{localStorage.removeItem(this.storageKey),this.consentState=null}catch(e){console.error("[Grain Consent] Failed to clear consent:",e)}}};function b(c,e,t){if(typeof document>"u")return;let r=[`${encodeURIComponent(c)}=${encodeURIComponent(e)}`];t?.maxAge!==void 0&&r.push(`max-age=${t.maxAge}`),t?.domain&&r.push(`domain=${t.domain}`),t?.path?r.push(`path=${t.path}`):r.push("path=/"),t?.sameSite&&r.push(`samesite=${t.sameSite}`),t?.secure&&r.push("secure"),document.cookie=r.join("; ")}function w(c){if(typeof document>"u")return null;let e=encodeURIComponent(c)+"=",t=document.cookie.split(";");for(let r=0;r<t.length;r++){let n=t[r];for(;n.charAt(0)===" ";)n=n.substring(1);if(n.indexOf(e)===0)return decodeURIComponent(n.substring(e.length))}return null}function L(c,e){if(typeof document>"u")return;let t=[`${encodeURIComponent(c)}=`,"max-age=0"];e?.domain&&t.push(`domain=${e.domain}`),e?.path?t.push(`path=${e.path}`):t.push("path=/"),document.cookie=t.join("; ")}function k(){if(typeof document>"u")return!1;try{let c="_grain_cookie_test";b(c,"test",{maxAge:1});let e=w(c)==="test";return L(c),e}catch{return!1}}var m=class{constructor(){this.activityThreshold=3e4;this.listeners=[];this.isDestroyed=!1;this.activityEvents=["mousemove","mousedown","keydown","scroll","touchstart","click"];this.lastActivityTime=Date.now(),this.boundActivityHandler=this.debounce(this.handleActivity.bind(this),500),this.setupListeners()}setupListeners(){if(!(typeof window>"u"))for(let e of this.activityEvents)window.addEventListener(e,this.boundActivityHandler,{passive:!0})}handleActivity(){this.isDestroyed||(this.lastActivityTime=Date.now(),this.notifyListeners())}debounce(e,t){let r=null;return()=>{r!==null&&clearTimeout(r),r=window.setTimeout(()=>{e(),r=null},t)}}isActive(e){let t=e??this.activityThreshold;return Date.now()-this.lastActivityTime<t}getTimeSinceLastActivity(){return Date.now()-this.lastActivityTime}getLastActivityTime(){return this.lastActivityTime}setActivityThreshold(e){this.activityThreshold=e}addListener(e){this.listeners.push(e)}removeListener(e){let t=this.listeners.indexOf(e);t>-1&&this.listeners.splice(t,1)}notifyListeners(){for(let e of this.listeners)try{e()}catch(t){console.error("[Activity Detector] Listener error:",t)}}destroy(){if(!this.isDestroyed){if(typeof window<"u")for(let e of this.activityEvents)window.removeEventListener(e,this.boundActivityHandler);this.listeners=[],this.isDestroyed=!0}}};var v=class{constructor(e,t,r){this.heartbeatTimer=null;this.isDestroyed=!1;this.hasSentPageLoadHeartbeat=!1;this.tracker=e,this.activityDetector=t,this.config=r,this.lastHeartbeatTime=Date.now(),this.currentInterval=r.activeInterval,this.sendPageLoadHeartbeat(),this.scheduleNextHeartbeat()}sendPageLoadHeartbeat(){if(!(this.isDestroyed||this.hasSentPageLoadHeartbeat))if(typeof window<"u"&&document.readyState!=="complete"){let e=()=>{this.sendHeartbeat("page_load"),this.hasSentPageLoadHeartbeat=!0,window.removeEventListener("load",e)};window.addEventListener("load",e)}else this.sendHeartbeat("page_load"),this.hasSentPageLoadHeartbeat=!0}scheduleNextHeartbeat(){if(this.isDestroyed)return;this.heartbeatTimer!==null&&clearTimeout(this.heartbeatTimer);let e=this.activityDetector.isActive(6e4);this.currentInterval=e?this.config.activeInterval:this.config.inactiveInterval,this.heartbeatTimer=window.setTimeout(()=>{this.sendHeartbeat("periodic"),this.scheduleNextHeartbeat()},this.currentInterval),this.config.debug&&console.log(`[Heartbeat] Scheduled next heartbeat in ${this.currentInterval/1e3}s (${e?"active":"inactive"})`)}sendHeartbeat(e="periodic"){if(this.isDestroyed)return;let t=Date.now(),r=this.activityDetector.isActive(6e4),n=this.tracker.hasConsent("analytics"),s={type:"heartbeat",heartbeat_type:e,status:r?"active":"inactive",timestamp:t};if(n){let i=this.tracker.getCurrentPage();i&&(s.page=i),e==="periodic"&&(s.duration=t-this.lastHeartbeatTime,s.event_count=this.tracker.getEventCountSinceLastHeartbeat(),this.tracker.resetEventCountSinceLastHeartbeat())}this.tracker.trackSystemEvent("_grain_heartbeat",s),this.lastHeartbeatTime=t,this.config.debug&&console.log("[Heartbeat] Sent heartbeat:",s)}destroy(){this.isDestroyed||(this.heartbeatTimer!==null&&(clearTimeout(this.heartbeatTimer),this.heartbeatTimer=null),this.isDestroyed=!0,this.config.debug&&console.log("[Heartbeat] Destroyed"))}};var y=class{constructor(e,t){this.isDestroyed=!1;this.currentPath=null;this.originalPushState=null;this.originalReplaceState=null;this.handlePopState=()=>{this.isDestroyed||this.trackCurrentPage()};this.handleHashChange=()=>{this.isDestroyed||this.trackCurrentPage()};this.tracker=e,this.config=t,this.trackCurrentPage(),this.setupHistoryListeners(),this.setupHashChangeListener()}setupHistoryListeners(){typeof window>"u"||typeof history>"u"||(this.originalPushState=history.pushState,history.pushState=(e,t,r)=>{this.originalPushState?.call(history,e,t,r),this.trackCurrentPage()},this.originalReplaceState=history.replaceState,history.replaceState=(e,t,r)=>{this.originalReplaceState?.call(history,e,t,r),this.trackCurrentPage()},window.addEventListener("popstate",this.handlePopState))}setupHashChangeListener(){typeof window>"u"||window.addEventListener("hashchange",this.handleHashChange)}trackCurrentPage(){if(this.isDestroyed||typeof window>"u")return;let e=this.extractPath(window.location.href);if(e===this.currentPath)return;this.currentPath=e;let t=this.tracker.hasConsent("analytics"),r={page:e,timestamp:Date.now()};t&&(r.referrer=document.referrer||"",r.title=document.title||"",r.full_url=window.location.href),this.tracker.trackSystemEvent("page_view",r),this.config.debug&&console.log("[Page Tracking] Tracked page view:",r)}extractPath(e){try{let t=new URL(e),r=t.pathname+t.hash;return!this.config.stripQueryParams&&t.search&&(r+=t.search),r}catch(t){return this.config.debug&&console.warn("[Page Tracking] Failed to parse URL:",e,t),e}}getCurrentPage(){return this.currentPath}trackPage(e,t){if(this.isDestroyed)return;let r=this.tracker.hasConsent("analytics"),n={page:e,timestamp:Date.now(),...t};r&&typeof document<"u"&&(n.referrer||(n.referrer=document.referrer||""),n.title||(n.title=document.title||""),!n.full_url&&typeof window<"u"&&(n.full_url=window.location.href)),this.tracker.trackSystemEvent("page_view",n),this.config.debug&&console.log("[Page Tracking] Manually tracked page:",n)}destroy(){this.isDestroyed||(typeof history<"u"&&(this.originalPushState&&(history.pushState=this.originalPushState),this.originalReplaceState&&(history.replaceState=this.originalReplaceState)),typeof window<"u"&&(window.removeEventListener("popstate",this.handlePopState),window.removeEventListener("hashchange",this.handleHashChange)),this.isDestroyed=!0,this.config.debug&&console.log("[Page Tracking] Destroyed"))}};var u=class{constructor(e){this.eventQueue=[];this.waitingForConsentQueue=[];this.flushTimer=null;this.isDestroyed=!1;this.globalUserId=null;this.persistentAnonymousUserId=null;this.configCache=null;this.configRefreshTimer=null;this.configChangeListeners=[];this.configFetchPromise=null;this.cookiesEnabled=!1;this.activityDetector=null;this.heartbeatManager=null;this.pageTrackingManager=null;this.ephemeralSessionId=null;this.eventCountSinceLastHeartbeat=0;this.config={apiUrl:"https://api.grainql.com",authStrategy:"NONE",batchSize:50,flushInterval:5e3,retryAttempts:3,retryDelay:1e3,maxEventsPerRequest:160,debug:!1,defaultConfigurations:{},configCacheKey:"grain_config",configRefreshInterval:3e5,enableConfigCache:!0,consentMode:"opt-out",waitForConsent:!1,enableCookies:!1,anonymizeIP:!1,disableAutoProperties:!1,enableHeartbeat:!0,heartbeatActiveInterval:12e4,heartbeatInactiveInterval:3e5,enableAutoPageView:!0,stripQueryParams:!0,...e,tenantId:e.tenantId},this.consentManager=new p(this.config.tenantId,this.config.consentMode),this.config.enableCookies&&(this.cookiesEnabled=k(),!this.cookiesEnabled&&this.config.debug&&console.warn("[Grain Analytics] Cookies are not available, falling back to localStorage")),e.userId&&(this.globalUserId=e.userId),this.validateConfig(),this.initializePersistentAnonymousUserId(),this.setupBeforeUnload(),this.startFlushTimer(),this.initializeConfigCache(),this.ephemeralSessionId=this.generateUUID(),typeof window<"u"&&this.initializeAutomaticTracking(),this.consentManager.addListener(t=>{t.granted&&this.handleConsentGranted()})}validateConfig(){if(!this.config.tenantId)throw new Error("Grain Analytics: tenantId is required");if(this.config.authStrategy==="SERVER_SIDE"&&!this.config.secretKey)throw new Error("Grain Analytics: secretKey is required for SERVER_SIDE auth strategy");if(this.config.authStrategy==="JWT"&&!this.config.authProvider)throw new Error("Grain Analytics: authProvider is required for JWT auth strategy")}generateUUID(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){let t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}shouldAllowPersistentStorage(){let e=this.consentManager.hasConsent("analytics"),t=this.config.consentMode==="opt-in",r=!!this.globalUserId,n=this.config.authStrategy==="JWT";return e||!t||r||n}generateAnonymousUserId(){return this.generateUUID()}initializePersistentAnonymousUserId(){if(typeof window>"u")return;if(!this.shouldAllowPersistentStorage()){this.log("Opt-in mode without consent: skipping persistent ID initialization (GDPR compliance)");return}let e=`grain_anonymous_user_id_${this.config.tenantId}`,t="_grain_uid";try{if(this.cookiesEnabled){let n=w(t);if(n){this.persistentAnonymousUserId=n,this.log("Loaded persistent anonymous user ID from cookie:",this.persistentAnonymousUserId);return}}let r=localStorage.getItem(e);r?(this.persistentAnonymousUserId=r,this.log("Loaded persistent anonymous user ID from localStorage:",this.persistentAnonymousUserId),this.cookiesEnabled&&this.savePersistentAnonymousUserId(r)):(this.persistentAnonymousUserId=this.generateAnonymousUserId(),this.savePersistentAnonymousUserId(this.persistentAnonymousUserId),this.log("Generated new persistent anonymous user ID:",this.persistentAnonymousUserId))}catch(r){this.log("Failed to initialize persistent anonymous user ID:",r),this.persistentAnonymousUserId=this.generateAnonymousUserId()}}savePersistentAnonymousUserId(e){if(typeof window>"u")return;if(!this.shouldAllowPersistentStorage()){this.log("Opt-in mode without consent: skipping persistent ID save (GDPR compliance)");return}let t=`grain_anonymous_user_id_${this.config.tenantId}`,r="_grain_uid";try{if(this.cookiesEnabled){let n={maxAge:31536e3,sameSite:"lax",secure:typeof window<"u"&&window.location.protocol==="https:",...this.config.cookieOptions};b(r,e,n)}localStorage.setItem(t,e)}catch(n){this.log("Failed to save persistent anonymous user ID:",n)}}getEffectiveUserIdInternal(){return this.globalUserId?this.globalUserId:this.persistentAnonymousUserId?this.persistentAnonymousUserId:(this.persistentAnonymousUserId=this.generateAnonymousUserId(),this.savePersistentAnonymousUserId(this.persistentAnonymousUserId),this.persistentAnonymousUserId)}log(...e){this.config.debug&&console.log("[Grain Analytics]",...e)}createErrorDigest(e){let t=[...new Set(e.map(i=>i.eventName))],r=[...new Set(e.map(i=>i.userId))],n=0,s=0;return e.forEach(i=>{let o=i.properties||{};n+=Object.keys(o).length,s+=JSON.stringify(i).length}),{eventCount:e.length,totalProperties:n,totalSize:s,eventNames:t,userIds:r}}formatError(e,t,r){let n=r?this.createErrorDigest(r):{eventCount:0,totalProperties:0,totalSize:0,eventNames:[],userIds:[]},s="UNKNOWN_ERROR",i="An unknown error occurred";if(e instanceof Error)i=e.message,i.includes("fetch failed")||i.includes("network error")?s="NETWORK_ERROR":i.includes("timeout")?s="TIMEOUT_ERROR":i.includes("HTTP 4")?s="CLIENT_ERROR":i.includes("HTTP 5")?s="SERVER_ERROR":i.includes("JSON")?s="PARSE_ERROR":i.includes("auth")||i.includes("unauthorized")?s="AUTH_ERROR":i.includes("rate limit")||i.includes("429")?s="RATE_LIMIT_ERROR":s="GENERAL_ERROR";else if(typeof e=="string")i=e,s="STRING_ERROR";else if(e&&typeof e=="object"&&"status"in e){let o=e.status;s=`HTTP_${o}`,i=`HTTP ${o} error`}return{code:s,message:i,digest:n,timestamp:new Date().toISOString(),context:t,originalError:e}}logError(e){let{code:t,message:r,digest:n,timestamp:s,context:i}=e,o={"\u{1F6A8} Grain Analytics Error":{"Error Code":t,Message:r,Context:i,Timestamp:s,"Event Digest":{Events:n.eventCount,Properties:n.totalProperties,"Size (bytes)":n.totalSize,"Event Names":n.eventNames.length>0?n.eventNames.join(", "):"None","User IDs":n.userIds.length>0?n.userIds.slice(0,3).join(", ")+(n.userIds.length>3?"...":""):"None"}}};console.error("\u{1F6A8} Grain Analytics Error:",o),this.config.debug&&console.error(`[Grain Analytics] ${t}: ${r} (${i}) - Events: ${n.eventCount}, Props: ${n.totalProperties}, Size: ${n.totalSize}B`)}async safeExecute(e,t,r){try{return await e()}catch(n){let s=this.formatError(n,t,r);return this.logError(s),null}}formatEvent(e){return{eventName:e.eventName,userId:e.userId||this.getEffectiveUserIdInternal(),properties:e.properties||{}}}async getAuthHeaders(){let e={"Content-Type":"application/json"};switch(this.config.authStrategy){case"NONE":break;case"SERVER_SIDE":e.Authorization=`Chase ${this.config.secretKey}`;break;case"JWT":if(this.config.authProvider){let t=await this.config.authProvider.getToken();e.Authorization=`Bearer ${t}`}break}return e}async delay(e){return new Promise(t=>setTimeout(t,e))}isRetriableError(e){if(e instanceof Error){let t=e.message.toLowerCase();if(t.includes("fetch failed")||t==="network error"||t.includes("timeout")||t.includes("connection"))return!0}if(typeof e=="object"&&e!==null&&"status"in e){let t=e.status;return t>=500||t===429}return!1}async sendEvents(e){if(e.length===0)return;let t;for(let r=0;r<=this.config.retryAttempts;r++)try{let n=await this.getAuthHeaders(),s=`${this.config.apiUrl}/v1/events/${encodeURIComponent(this.config.tenantId)}/multi`;this.log(`Sending ${e.length} events to ${s} (attempt ${r+1})`);let i=await fetch(s,{method:"POST",headers:n,body:JSON.stringify(e)});if(!i.ok){let o=`HTTP ${i.status}`;try{let h=await i.json();h?.message&&(o=h.message)}catch{let h=await i.text();h&&(o=h)}let a=new Error(`Failed to send events: ${o}`);throw a.status=i.status,a}this.log(`Successfully sent ${e.length} events`);return}catch(n){if(t=n,r===this.config.retryAttempts){let i=this.formatError(n,`sendEvents (attempt ${r+1}/${this.config.retryAttempts+1})`,e);this.logError(i);return}if(!this.isRetriableError(n)){let i=this.formatError(n,"sendEvents (non-retriable error)",e);this.logError(i);return}let s=this.config.retryDelay*Math.pow(2,r);this.log(`Retrying in ${s}ms after error:`,n),await this.delay(s)}}async sendEventsWithBeacon(e){if(e.length!==0)try{let t=await this.getAuthHeaders(),r=`${this.config.apiUrl}/v1/events/${encodeURIComponent(this.config.tenantId)}/multi`,n=JSON.stringify({events:e});if(typeof navigator<"u"&&"sendBeacon"in navigator){let s=new Blob([n],{type:"application/json"});if(navigator.sendBeacon(r,s)){this.log(`Successfully sent ${e.length} events via beacon`);return}}await fetch(r,{method:"POST",headers:t,body:n,keepalive:!0}),this.log(`Successfully sent ${e.length} events via fetch (keepalive)`)}catch(t){let r=this.formatError(t,"sendEventsWithBeacon",e);this.logError(r)}}startFlushTimer(){typeof window>"u"||(this.flushTimer&&clearInterval(this.flushTimer),this.flushTimer=window.setInterval(()=>{this.eventQueue.length>0&&this.flush().catch(e=>{let t=this.formatError(e,"auto-flush");this.logError(t)})},this.config.flushInterval))}setupBeforeUnload(){if(typeof window>"u")return;let e=()=>{if(this.eventQueue.length>0){let t=[...this.eventQueue];this.eventQueue=[];let r=this.chunkEvents(t,this.config.maxEventsPerRequest);r.length>0&&this.sendEventsWithBeacon(r[0]).catch(()=>{})}};window.addEventListener("beforeunload",e),window.addEventListener("pagehide",e),document.addEventListener("visibilitychange",()=>{if(document.visibilityState==="hidden"&&this.eventQueue.length>0){let t=[...this.eventQueue];this.eventQueue=[];let r=this.chunkEvents(t,this.config.maxEventsPerRequest);r.length>0&&this.sendEventsWithBeacon(r[0]).catch(()=>{})}})}initializeAutomaticTracking(){if(this.config.enableHeartbeat)try{this.activityDetector=new m,this.heartbeatManager=new v(this,this.activityDetector,{activeInterval:this.config.heartbeatActiveInterval,inactiveInterval:this.config.heartbeatInactiveInterval,debug:this.config.debug}),this.log("Heartbeat tracking initialized")}catch(e){this.log("Failed to initialize heartbeat tracking:",e)}if(this.config.enableAutoPageView)try{this.pageTrackingManager=new y(this,{stripQueryParams:this.config.stripQueryParams,debug:this.config.debug}),this.log("Auto page view tracking initialized")}catch(e){this.log("Failed to initialize page view tracking:",e)}}handleConsentGranted(){this.flushWaitingForConsentQueue(),this.persistentAnonymousUserId||(this.initializePersistentAnonymousUserId(),this.log("Initialized persistent ID after consent grant")),this.ephemeralSessionId&&this.trackSystemEvent("_grain_consent_granted",{previous_session_id:this.ephemeralSessionId,new_user_id:this.getEffectiveUserId(),timestamp:Date.now()})}trackSystemEvent(e,t){if(this.isDestroyed)return;let r=this.consentManager.hasConsent("analytics"),n={eventName:e,userId:r?this.getEffectiveUserId():this.getEphemeralSessionId(),properties:{...t,_minimal:!r,_consent_status:r?"granted":"pending"}};this.eventQueue.push(n),this.eventCountSinceLastHeartbeat++,this.log(`Queued system event: ${e}`,t),this.eventQueue.length>=this.config.batchSize&&this.flush().catch(s=>{let i=this.formatError(s,"flush system event");this.logError(i)})}getEphemeralSessionId(){return this.ephemeralSessionId||(this.ephemeralSessionId=this.generateUUID()),this.ephemeralSessionId}getCurrentPage(){return this.pageTrackingManager?.getCurrentPage()||null}getEventCountSinceLastHeartbeat(){return this.eventCountSinceLastHeartbeat}resetEventCountSinceLastHeartbeat(){this.eventCountSinceLastHeartbeat=0}getEffectiveUserId(){return this.getEffectiveUserIdInternal()}getSessionId(){return this.consentManager.hasConsent("analytics")?this.getEffectiveUserId():this.getEphemeralSessionId()}async track(e,t,r){try{if(this.isDestroyed){let o=new Error("Grain Analytics: Client has been destroyed"),a=this.formatError(o,"track (client destroyed)");this.logError(a);return}let n,s={};if(typeof e=="string"?(n={eventName:e,properties:t},s=r||{}):(n=e,s=t||{}),this.config.allowedProperties&&n.properties){let o={};for(let a of this.config.allowedProperties)a in n.properties&&(o[a]=n.properties[a]);n.properties=o}let i=this.formatEvent(n);if(this.consentManager.shouldWaitForConsent()&&this.config.waitForConsent){this.waitingForConsentQueue.push(i),this.log(`Event waiting for consent: ${n.eventName}`,n.properties);return}if(!this.consentManager.hasConsent("analytics")){this.log(`Event blocked by consent: ${n.eventName}`);return}this.eventQueue.push(i),this.eventCountSinceLastHeartbeat++,this.log(`Queued event: ${n.eventName}`,n.properties),(s.flush||this.eventQueue.length>=this.config.batchSize)&&await this.flush()}catch(n){let s=this.formatError(n,"track");this.logError(s)}}flushWaitingForConsentQueue(){this.waitingForConsentQueue.length!==0&&(this.log(`Flushing ${this.waitingForConsentQueue.length} events waiting for consent`),this.eventQueue.push(...this.waitingForConsentQueue),this.waitingForConsentQueue=[],this.flush().catch(e=>{let t=this.formatError(e,"flush waiting for consent queue");this.logError(t)}))}identify(e){this.log(`Identified user: ${e}`),this.globalUserId=e,this.persistentAnonymousUserId=null}setUserId(e){this.log(`Set global user ID: ${e}`),this.globalUserId=e,e?this.persistentAnonymousUserId=null:this.persistentAnonymousUserId||(this.persistentAnonymousUserId=this.generateAnonymousUserId(),this.savePersistentAnonymousUserId(this.persistentAnonymousUserId))}getUserId(){return this.globalUserId}getEffectiveUserIdPublic(){return this.getEffectiveUserIdInternal()}login(e){try{if(this.isDestroyed){let t=new Error("Grain Analytics: Client has been destroyed"),r=this.formatError(t,"login (client destroyed)");this.logError(r);return}e.userId&&(this.log(`Login: Setting user ID to ${e.userId}`),this.globalUserId=e.userId,this.persistentAnonymousUserId=null),e.authToken&&(this.log("Login: Setting auth token"),this.config.authStrategy==="NONE"&&(this.config.authStrategy="JWT"),this.config.authProvider={getToken:()=>e.authToken}),e.authStrategy&&(this.log(`Login: Setting auth strategy to ${e.authStrategy}`),this.config.authStrategy=e.authStrategy),this.log(`Login successful. Effective user ID: ${this.getEffectiveUserIdInternal()}`)}catch(t){let r=this.formatError(t,"login");this.logError(r)}}logout(){try{if(this.isDestroyed){let e=new Error("Grain Analytics: Client has been destroyed"),t=this.formatError(e,"logout (client destroyed)");this.logError(t);return}if(this.log("Logout: Clearing user session"),this.globalUserId=null,this.config.authStrategy="NONE",this.config.authProvider=void 0,!this.persistentAnonymousUserId&&(this.persistentAnonymousUserId=this.generateAnonymousUserId(),typeof window<"u"))try{let e=`grain_anonymous_user_id_${this.config.tenantId}`;localStorage.setItem(e,this.persistentAnonymousUserId)}catch(e){this.log("Failed to persist new anonymous user ID after logout:",e)}this.log(`Logout successful. Effective user ID: ${this.getEffectiveUserIdInternal()}`)}catch(e){let t=this.formatError(e,"logout");this.logError(t)}}async setProperty(e,t){try{if(this.isDestroyed){let o=new Error("Grain Analytics: Client has been destroyed"),a=this.formatError(o,"setProperty (client destroyed)");this.logError(a);return}let r=t?.userId||this.getEffectiveUserIdInternal(),n=Object.keys(e);if(n.length>4){let o=new Error("Grain Analytics: Maximum 4 properties allowed per request"),a=this.formatError(o,"setProperty (validation)");this.logError(a);return}if(n.length===0){let o=new Error("Grain Analytics: At least one property is required"),a=this.formatError(o,"setProperty (validation)");this.logError(a);return}let s={};for(let[o,a]of Object.entries(e))a==null?s[o]="":typeof a=="string"?s[o]=a:s[o]=JSON.stringify(a);let i={userId:r,...s};await this.sendProperties(i)}catch(r){let n=this.formatError(r,"setProperty");this.logError(n)}}async sendProperties(e){let t;for(let r=0;r<=this.config.retryAttempts;r++)try{let n=await this.getAuthHeaders(),s=`${this.config.apiUrl}/v1/events/${encodeURIComponent(this.config.tenantId)}/properties`;this.log(`Setting properties for user ${e.userId} (attempt ${r+1})`);let i=await fetch(s,{method:"POST",headers:n,body:JSON.stringify(e)});if(!i.ok){let o=`HTTP ${i.status}`;try{let h=await i.json();h?.message&&(o=h.message)}catch{let h=await i.text();h&&(o=h)}let a=new Error(`Failed to set properties: ${o}`);throw a.status=i.status,a}this.log(`Successfully set properties for user ${e.userId}`);return}catch(n){if(t=n,r===this.config.retryAttempts){let i=this.formatError(n,`sendProperties (attempt ${r+1}/${this.config.retryAttempts+1})`);this.logError(i);return}if(!this.isRetriableError(n)){let i=this.formatError(n,"sendProperties (non-retriable error)");this.logError(i);return}let s=this.config.retryDelay*Math.pow(2,r);this.log(`Retrying in ${s}ms after error:`,n),await this.delay(s)}}async trackLogin(e,t){try{return await this.track("login",e,t)}catch(r){let n=this.formatError(r,"trackLogin");this.logError(n)}}async trackSignup(e,t){try{return await this.track("signup",e,t)}catch(r){let n=this.formatError(r,"trackSignup");this.logError(n)}}async trackCheckout(e,t){try{return await this.track("checkout",e,t)}catch(r){let n=this.formatError(r,"trackCheckout");this.logError(n)}}async trackPageView(e,t){try{return await this.track("page_view",e,t)}catch(r){let n=this.formatError(r,"trackPageView");this.logError(n)}}async trackPurchase(e,t){try{return await this.track("purchase",e,t)}catch(r){let n=this.formatError(r,"trackPurchase");this.logError(n)}}async trackSearch(e,t){try{return await this.track("search",e,t)}catch(r){let n=this.formatError(r,"trackSearch");this.logError(n)}}async trackAddToCart(e,t){try{return await this.track("add_to_cart",e,t)}catch(r){let n=this.formatError(r,"trackAddToCart");this.logError(n)}}async trackRemoveFromCart(e,t){try{return await this.track("remove_from_cart",e,t)}catch(r){let n=this.formatError(r,"trackRemoveFromCart");this.logError(n)}}async flush(){try{if(this.eventQueue.length===0)return;let e=[...this.eventQueue];this.eventQueue=[];let t=this.chunkEvents(e,this.config.maxEventsPerRequest);for(let r of t)await this.sendEvents(r)}catch(e){let t=this.formatError(e,"flush");this.logError(t)}}initializeConfigCache(){if(!(!this.config.enableConfigCache||typeof window>"u"))try{let e=localStorage.getItem(this.config.configCacheKey);e&&(this.configCache=JSON.parse(e),this.log("Loaded configuration from cache:",this.configCache))}catch(e){this.log("Failed to load configuration cache:",e)}}saveConfigCache(e){if(!(!this.config.enableConfigCache||typeof window>"u"))try{localStorage.setItem(this.config.configCacheKey,JSON.stringify(e)),this.log("Saved configuration to cache:",e)}catch(t){this.log("Failed to save configuration cache:",t)}}getConfig(e){if(this.configCache?.configurations?.[e])return this.configCache.configurations[e];if(this.config.defaultConfigurations?.[e])return this.config.defaultConfigurations[e]}getAllConfigs(){let e={...this.config.defaultConfigurations};return this.configCache?.configurations&&Object.assign(e,this.configCache.configurations),e}async fetchConfig(e={}){try{if(this.isDestroyed){let o=new Error("Grain Analytics: Client has been destroyed"),a=this.formatError(o,"fetchConfig (client destroyed)");return this.logError(a),null}let t=e.userId||this.getEffectiveUserIdInternal(),r=e.immediateKeys||[],n=e.properties||{},s={userId:t,immediateKeys:r,properties:n},i;for(let o=0;o<=this.config.retryAttempts;o++)try{let a=await this.getAuthHeaders(),h=`${this.config.apiUrl}/v1/client/${encodeURIComponent(this.config.tenantId)}/config/configurations`;this.log(`Fetching configurations for user ${t} (attempt ${o+1})`);let g=await fetch(h,{method:"POST",headers:a,body:JSON.stringify(s)});if(!g.ok){let E=`HTTP ${g.status}`;try{let d=await g.json();d?.message&&(E=d.message)}catch{let d=await g.text();d&&(E=d)}let S=new Error(`Failed to fetch configurations: ${E}`);throw S.status=g.status,S}let l=await g.json();return l.configurations&&this.updateConfigCache(l,t),this.log(`Successfully fetched configurations for user ${t}:`,l),l}catch(a){if(i=a,o===this.config.retryAttempts){let g=this.formatError(a,`fetchConfig (attempt ${o+1}/${this.config.retryAttempts+1})`);return this.logError(g),null}if(!this.isRetriableError(a)){let g=this.formatError(a,"fetchConfig (non-retriable error)");return this.logError(g),null}let h=this.config.retryDelay*Math.pow(2,o);this.log(`Retrying config fetch in ${h}ms after error:`,a),await this.delay(h)}return null}catch(t){let r=this.formatError(t,"fetchConfig");return this.logError(r),null}}async getConfigAsync(e,t={}){try{if(!t.forceRefresh&&this.configCache?.configurations?.[e])return this.configCache.configurations[e];if(!t.forceRefresh&&this.config.defaultConfigurations?.[e])return this.config.defaultConfigurations[e];let r=await this.fetchConfig(t);return r?r.configurations[e]:this.config.defaultConfigurations?.[e]}catch(r){let n=this.formatError(r,"getConfigAsync");return this.logError(n),this.config.defaultConfigurations?.[e]}}async getAllConfigsAsync(e={}){try{if(!e.forceRefresh&&this.configCache?.configurations)return{...this.config.defaultConfigurations,...this.configCache.configurations};let t=await this.fetchConfig(e);return t?{...this.config.defaultConfigurations,...t.configurations}:{...this.config.defaultConfigurations}}catch(t){let r=this.formatError(t,"getAllConfigsAsync");return this.logError(r),{...this.config.defaultConfigurations}}}updateConfigCache(e,t){let r={configurations:e.configurations,snapshotId:e.snapshotId,timestamp:e.timestamp,userId:t},n=this.configCache?.configurations||{};this.configCache=r,this.saveConfigCache(r),JSON.stringify(n)!==JSON.stringify(e.configurations)&&this.notifyConfigChangeListeners(e.configurations)}addConfigChangeListener(e){this.configChangeListeners.push(e)}removeConfigChangeListener(e){let t=this.configChangeListeners.indexOf(e);t>-1&&this.configChangeListeners.splice(t,1)}notifyConfigChangeListeners(e){this.configChangeListeners.forEach(t=>{try{t(e)}catch(r){console.error("[Grain Analytics] Config change listener error:",r)}})}startConfigRefreshTimer(){typeof window>"u"||(this.configRefreshTimer&&clearInterval(this.configRefreshTimer),this.configRefreshTimer=window.setInterval(()=>{this.isDestroyed||this.fetchConfig().catch(e=>{let t=this.formatError(e,"auto-config refresh");this.logError(t)})},this.config.configRefreshInterval))}stopConfigRefreshTimer(){this.configRefreshTimer&&(clearInterval(this.configRefreshTimer),this.configRefreshTimer=null)}async preloadConfig(e=[],t){try{let r=this.getEffectiveUserIdInternal();this.log(`Preloading config for user: ${r}`),await this.fetchConfig({immediateKeys:e,properties:t})&&this.startConfigRefreshTimer()}catch(r){let n=this.formatError(r,"preloadConfig");this.logError(n)}}chunkEvents(e,t){let r=[];for(let n=0;n<e.length;n+=t)r.push(e.slice(n,n+t));return r}grantConsent(e){try{this.consentManager.grantConsent(e),this.log("Consent granted",e)}catch(t){let r=this.formatError(t,"grantConsent");this.logError(r)}}revokeConsent(e){try{this.consentManager.revokeConsent(e),this.log("Consent revoked",e),this.eventQueue=[],this.waitingForConsentQueue=[]}catch(t){let r=this.formatError(t,"revokeConsent");this.logError(r)}}getConsentState(){return this.consentManager.getConsentState()}hasConsent(e){return this.consentManager.hasConsent(e)}onConsentChange(e){this.consentManager.addListener(e)}offConsentChange(e){this.consentManager.removeListener(e)}destroy(){if(this.isDestroyed=!0,this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=null),this.stopConfigRefreshTimer(),this.configChangeListeners=[],this.heartbeatManager&&(this.heartbeatManager.destroy(),this.heartbeatManager=null),this.pageTrackingManager&&(this.pageTrackingManager.destroy(),this.pageTrackingManager=null),this.activityDetector&&(this.activityDetector.destroy(),this.activityDetector=null),this.eventQueue.length>0){let e=[...this.eventQueue];this.eventQueue=[];let t=this.chunkEvents(e,this.config.maxEventsPerRequest);if(t.length>0){this.sendEventsWithBeacon(t[0]).catch(()=>{});for(let r=1;r<t.length;r++)this.sendEventsWithBeacon(t[r]).catch(()=>{})}}}};function P(c){return new u(c)}var O=u;typeof window<"u"&&(window.Grain={GrainAnalytics:u,createGrainAnalytics:P});return D($);})();
|
|
3
3
|
//# sourceMappingURL=index.global.js.map
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,522 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Grain Analytics
|
|
3
|
-
*
|
|
2
|
+
* Grain Analytics React Hooks
|
|
3
|
+
*
|
|
4
|
+
* React integration for @grainql/analytics-web
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* import { GrainProvider, useConfig, useTrack } from '@grainql/analytics-web/react';
|
|
9
|
+
*
|
|
10
|
+
* // Pattern 1: Provider-managed client
|
|
11
|
+
* <GrainProvider config={{ tenantId: 'xxx' }}>
|
|
12
|
+
* <App />
|
|
13
|
+
* </GrainProvider>
|
|
14
|
+
*
|
|
15
|
+
* // Pattern 2: External client
|
|
16
|
+
* const grain = new GrainAnalytics({ tenantId: 'xxx' });
|
|
17
|
+
* <GrainProvider client={grain}>
|
|
18
|
+
* <App />
|
|
19
|
+
* </GrainProvider>
|
|
20
|
+
*
|
|
21
|
+
* // Use hooks in components
|
|
22
|
+
* function MyComponent() {
|
|
23
|
+
* const { value } = useConfig('hero_variant');
|
|
24
|
+
* const track = useTrack();
|
|
25
|
+
*
|
|
26
|
+
* return <button onClick={() => track('clicked')}>Click</button>;
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
4
29
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
export type
|
|
21
|
-
export
|
|
22
|
-
getToken(): Promise<string> | string;
|
|
23
|
-
}
|
|
24
|
-
export type { ConsentState, ConsentMode, CookieConfig };
|
|
25
|
-
export interface GrainConfig {
|
|
26
|
-
tenantId: string;
|
|
27
|
-
apiUrl?: string;
|
|
28
|
-
authStrategy?: AuthStrategy;
|
|
29
|
-
secretKey?: string;
|
|
30
|
-
authProvider?: AuthProvider;
|
|
31
|
-
userId?: string;
|
|
32
|
-
batchSize?: number;
|
|
33
|
-
flushInterval?: number;
|
|
34
|
-
retryAttempts?: number;
|
|
35
|
-
retryDelay?: number;
|
|
36
|
-
maxEventsPerRequest?: number;
|
|
37
|
-
debug?: boolean;
|
|
38
|
-
defaultConfigurations?: Record<string, string>;
|
|
39
|
-
configCacheKey?: string;
|
|
40
|
-
configRefreshInterval?: number;
|
|
41
|
-
enableConfigCache?: boolean;
|
|
42
|
-
consentMode?: ConsentMode;
|
|
43
|
-
waitForConsent?: boolean;
|
|
44
|
-
enableCookies?: boolean;
|
|
45
|
-
cookieOptions?: CookieConfig;
|
|
46
|
-
anonymizeIP?: boolean;
|
|
47
|
-
disableAutoProperties?: boolean;
|
|
48
|
-
allowedProperties?: string[];
|
|
49
|
-
enableHeartbeat?: boolean;
|
|
50
|
-
heartbeatActiveInterval?: number;
|
|
51
|
-
heartbeatInactiveInterval?: number;
|
|
52
|
-
enableAutoPageView?: boolean;
|
|
53
|
-
stripQueryParams?: boolean;
|
|
54
|
-
}
|
|
55
|
-
export interface SendEventOptions {
|
|
56
|
-
flush?: boolean;
|
|
57
|
-
}
|
|
58
|
-
export interface SetPropertyOptions {
|
|
59
|
-
userId?: string;
|
|
60
|
-
}
|
|
61
|
-
export interface LoginOptions {
|
|
62
|
-
userId?: string;
|
|
63
|
-
authToken?: string;
|
|
64
|
-
authStrategy?: AuthStrategy;
|
|
65
|
-
}
|
|
66
|
-
export interface PropertyPayload {
|
|
67
|
-
userId: string;
|
|
68
|
-
[key: string]: string;
|
|
69
|
-
}
|
|
70
|
-
export interface RemoteConfigRequest {
|
|
71
|
-
userId: string;
|
|
72
|
-
immediateKeys: string[];
|
|
73
|
-
properties?: Record<string, string>;
|
|
74
|
-
}
|
|
75
|
-
export interface RemoteConfigResponse {
|
|
76
|
-
userId: string;
|
|
77
|
-
snapshotId: string;
|
|
78
|
-
configurations: Record<string, string>;
|
|
79
|
-
isFinal: boolean;
|
|
80
|
-
qualifiedSegments: string[];
|
|
81
|
-
qualifiedRuleSets: string[];
|
|
82
|
-
timestamp: string;
|
|
83
|
-
isFromCache: boolean;
|
|
84
|
-
}
|
|
85
|
-
export interface RemoteConfigOptions {
|
|
86
|
-
immediateKeys?: string[];
|
|
87
|
-
properties?: Record<string, string>;
|
|
88
|
-
userId?: string;
|
|
89
|
-
forceRefresh?: boolean;
|
|
90
|
-
}
|
|
91
|
-
export interface RemoteConfigCache {
|
|
92
|
-
configurations: Record<string, string>;
|
|
93
|
-
snapshotId: string;
|
|
94
|
-
timestamp: string;
|
|
95
|
-
userId: string;
|
|
96
|
-
}
|
|
97
|
-
export type ConfigChangeListener = (configurations: Record<string, string>) => void;
|
|
98
|
-
export interface LoginEventProperties extends Record<string, unknown> {
|
|
99
|
-
method?: string;
|
|
100
|
-
success?: boolean;
|
|
101
|
-
errorMessage?: string;
|
|
102
|
-
loginAttempt?: number;
|
|
103
|
-
rememberMe?: boolean;
|
|
104
|
-
twoFactorEnabled?: boolean;
|
|
105
|
-
}
|
|
106
|
-
export interface SignupEventProperties extends Record<string, unknown> {
|
|
107
|
-
method?: string;
|
|
108
|
-
source?: string;
|
|
109
|
-
plan?: string;
|
|
110
|
-
success?: boolean;
|
|
111
|
-
errorMessage?: string;
|
|
112
|
-
}
|
|
113
|
-
export interface CheckoutEventProperties extends Record<string, unknown> {
|
|
114
|
-
orderId?: string;
|
|
115
|
-
total?: number;
|
|
116
|
-
currency?: string;
|
|
117
|
-
items?: Array<{
|
|
118
|
-
id: string;
|
|
119
|
-
name: string;
|
|
120
|
-
price: number;
|
|
121
|
-
quantity: number;
|
|
122
|
-
}>;
|
|
123
|
-
paymentMethod?: string;
|
|
124
|
-
success?: boolean;
|
|
125
|
-
errorMessage?: string;
|
|
126
|
-
couponCode?: string;
|
|
127
|
-
discount?: number;
|
|
128
|
-
}
|
|
129
|
-
export interface PageViewEventProperties extends Record<string, unknown> {
|
|
130
|
-
page?: string;
|
|
131
|
-
title?: string;
|
|
132
|
-
referrer?: string;
|
|
133
|
-
url?: string;
|
|
134
|
-
userAgent?: string;
|
|
135
|
-
screenResolution?: string;
|
|
136
|
-
viewportSize?: string;
|
|
137
|
-
}
|
|
138
|
-
export interface PurchaseEventProperties extends Record<string, unknown> {
|
|
139
|
-
orderId?: string;
|
|
140
|
-
total?: number;
|
|
141
|
-
currency?: string;
|
|
142
|
-
items?: Array<{
|
|
143
|
-
id: string;
|
|
144
|
-
name: string;
|
|
145
|
-
price: number;
|
|
146
|
-
quantity: number;
|
|
147
|
-
category?: string;
|
|
148
|
-
}>;
|
|
149
|
-
paymentMethod?: string;
|
|
150
|
-
shippingMethod?: string;
|
|
151
|
-
tax?: number;
|
|
152
|
-
shipping?: number;
|
|
153
|
-
discount?: number;
|
|
154
|
-
couponCode?: string;
|
|
155
|
-
}
|
|
156
|
-
export interface SearchEventProperties extends Record<string, unknown> {
|
|
157
|
-
query?: string;
|
|
158
|
-
results?: number;
|
|
159
|
-
filters?: Record<string, unknown>;
|
|
160
|
-
sortBy?: string;
|
|
161
|
-
category?: string;
|
|
162
|
-
success?: boolean;
|
|
163
|
-
}
|
|
164
|
-
export interface AddToCartEventProperties extends Record<string, unknown> {
|
|
165
|
-
itemId?: string;
|
|
166
|
-
itemName?: string;
|
|
167
|
-
price?: number;
|
|
168
|
-
quantity?: number;
|
|
169
|
-
currency?: string;
|
|
170
|
-
category?: string;
|
|
171
|
-
variant?: string;
|
|
172
|
-
}
|
|
173
|
-
export interface RemoveFromCartEventProperties extends Record<string, unknown> {
|
|
174
|
-
itemId?: string;
|
|
175
|
-
itemName?: string;
|
|
176
|
-
price?: number;
|
|
177
|
-
quantity?: number;
|
|
178
|
-
currency?: string;
|
|
179
|
-
category?: string;
|
|
180
|
-
variant?: string;
|
|
181
|
-
}
|
|
182
|
-
export interface ErrorDigest {
|
|
183
|
-
eventCount: number;
|
|
184
|
-
totalProperties: number;
|
|
185
|
-
totalSize: number;
|
|
186
|
-
eventNames: string[];
|
|
187
|
-
userIds: string[];
|
|
188
|
-
}
|
|
189
|
-
export interface FormattedError {
|
|
190
|
-
code: string;
|
|
191
|
-
message: string;
|
|
192
|
-
digest: ErrorDigest;
|
|
193
|
-
timestamp: string;
|
|
194
|
-
context: string;
|
|
195
|
-
originalError?: unknown;
|
|
196
|
-
}
|
|
197
|
-
export declare class GrainAnalytics implements HeartbeatTracker, PageTracker {
|
|
198
|
-
private config;
|
|
199
|
-
private eventQueue;
|
|
200
|
-
private waitingForConsentQueue;
|
|
201
|
-
private flushTimer;
|
|
202
|
-
private isDestroyed;
|
|
203
|
-
private globalUserId;
|
|
204
|
-
private persistentAnonymousUserId;
|
|
205
|
-
private configCache;
|
|
206
|
-
private configRefreshTimer;
|
|
207
|
-
private configChangeListeners;
|
|
208
|
-
private configFetchPromise;
|
|
209
|
-
private consentManager;
|
|
210
|
-
private cookiesEnabled;
|
|
211
|
-
private activityDetector;
|
|
212
|
-
private heartbeatManager;
|
|
213
|
-
private pageTrackingManager;
|
|
214
|
-
private ephemeralSessionId;
|
|
215
|
-
private eventCountSinceLastHeartbeat;
|
|
216
|
-
constructor(config: GrainConfig);
|
|
217
|
-
private validateConfig;
|
|
218
|
-
/**
|
|
219
|
-
* Generate a UUID v4 string
|
|
220
|
-
*/
|
|
221
|
-
private generateUUID;
|
|
222
|
-
/**
|
|
223
|
-
* Check if we should allow persistent storage (GDPR compliance)
|
|
224
|
-
*
|
|
225
|
-
* Returns true if:
|
|
226
|
-
* - Consent has been granted, OR
|
|
227
|
-
* - Not in opt-in mode, OR
|
|
228
|
-
* - User has been explicitly identified by the site (login/identify), OR
|
|
229
|
-
* - Using JWT auth strategy (functional/essential purpose)
|
|
230
|
-
*/
|
|
231
|
-
private shouldAllowPersistentStorage;
|
|
232
|
-
/**
|
|
233
|
-
* Generate a proper UUIDv4 identifier for anonymous user ID
|
|
234
|
-
*/
|
|
235
|
-
private generateAnonymousUserId;
|
|
236
|
-
/**
|
|
237
|
-
* Initialize persistent anonymous user ID from cookies or localStorage
|
|
238
|
-
* Priority: Cookie → localStorage → generate new
|
|
239
|
-
*
|
|
240
|
-
* GDPR Compliance: In opt-in mode without consent, we skip loading/saving
|
|
241
|
-
* persistent IDs unless the user has been explicitly identified by the site
|
|
242
|
-
* or when using JWT auth (functional/essential purpose)
|
|
243
|
-
*/
|
|
244
|
-
private initializePersistentAnonymousUserId;
|
|
245
|
-
/**
|
|
246
|
-
* Save persistent anonymous user ID to cookie and/or localStorage
|
|
247
|
-
*
|
|
248
|
-
* GDPR Compliance: In opt-in mode without consent, we don't persist IDs
|
|
249
|
-
* unless the user has been explicitly identified or using JWT auth
|
|
250
|
-
*/
|
|
251
|
-
private savePersistentAnonymousUserId;
|
|
252
|
-
/**
|
|
253
|
-
* Get the effective user ID (global userId or persistent anonymous ID)
|
|
254
|
-
*
|
|
255
|
-
* GDPR Compliance: In opt-in mode without consent and no explicit user identification,
|
|
256
|
-
* this should not be called. Use getEphemeralSessionId() instead.
|
|
257
|
-
*/
|
|
258
|
-
private getEffectiveUserIdInternal;
|
|
259
|
-
private log;
|
|
260
|
-
/**
|
|
261
|
-
* Create error digest from events
|
|
262
|
-
*/
|
|
263
|
-
private createErrorDigest;
|
|
264
|
-
/**
|
|
265
|
-
* Format error with beautiful structure
|
|
266
|
-
*/
|
|
267
|
-
private formatError;
|
|
268
|
-
/**
|
|
269
|
-
* Log formatted error gracefully
|
|
270
|
-
*/
|
|
271
|
-
private logError;
|
|
272
|
-
/**
|
|
273
|
-
* Safely execute a function with error handling
|
|
274
|
-
*/
|
|
275
|
-
private safeExecute;
|
|
276
|
-
private formatEvent;
|
|
277
|
-
private getAuthHeaders;
|
|
278
|
-
private delay;
|
|
279
|
-
private isRetriableError;
|
|
280
|
-
private sendEvents;
|
|
281
|
-
private sendEventsWithBeacon;
|
|
282
|
-
private startFlushTimer;
|
|
283
|
-
private setupBeforeUnload;
|
|
284
|
-
/**
|
|
285
|
-
* Initialize automatic tracking (heartbeat and page views)
|
|
286
|
-
*/
|
|
287
|
-
private initializeAutomaticTracking;
|
|
288
|
-
/**
|
|
289
|
-
* Handle consent granted - upgrade ephemeral session to persistent user
|
|
290
|
-
*/
|
|
291
|
-
private handleConsentGranted;
|
|
292
|
-
/**
|
|
293
|
-
* Track system events that bypass consent checks (for necessary/functional tracking)
|
|
294
|
-
*/
|
|
295
|
-
trackSystemEvent(eventName: string, properties: Record<string, unknown>): void;
|
|
296
|
-
/**
|
|
297
|
-
* Get ephemeral session ID (memory-only, not persisted)
|
|
298
|
-
*/
|
|
299
|
-
getEphemeralSessionId(): string;
|
|
300
|
-
/**
|
|
301
|
-
* Get the current page path from page tracker
|
|
302
|
-
*/
|
|
303
|
-
getCurrentPage(): string | null;
|
|
304
|
-
/**
|
|
305
|
-
* Get event count since last heartbeat
|
|
306
|
-
*/
|
|
307
|
-
getEventCountSinceLastHeartbeat(): number;
|
|
308
|
-
/**
|
|
309
|
-
* Reset event count since last heartbeat
|
|
310
|
-
*/
|
|
311
|
-
resetEventCountSinceLastHeartbeat(): void;
|
|
312
|
-
/**
|
|
313
|
-
* Get the effective user ID (public method)
|
|
314
|
-
*/
|
|
315
|
-
getEffectiveUserId(): string;
|
|
316
|
-
/**
|
|
317
|
-
* Get the session ID (ephemeral or persistent based on consent)
|
|
318
|
-
*/
|
|
319
|
-
getSessionId(): string;
|
|
320
|
-
/**
|
|
321
|
-
* Track an analytics event
|
|
322
|
-
*/
|
|
323
|
-
track(eventName: string, properties?: Record<string, unknown>, options?: SendEventOptions): Promise<void>;
|
|
324
|
-
track(event: GrainEvent, options?: SendEventOptions): Promise<void>;
|
|
325
|
-
/**
|
|
326
|
-
* Flush events that were waiting for consent
|
|
327
|
-
*/
|
|
328
|
-
private flushWaitingForConsentQueue;
|
|
329
|
-
/**
|
|
330
|
-
* Identify a user (sets userId for subsequent events)
|
|
331
|
-
*/
|
|
332
|
-
identify(userId: string): void;
|
|
333
|
-
/**
|
|
334
|
-
* Set global user ID for all subsequent events
|
|
335
|
-
*/
|
|
336
|
-
setUserId(userId: string | null): void;
|
|
337
|
-
/**
|
|
338
|
-
* Get current global user ID
|
|
339
|
-
*/
|
|
340
|
-
getUserId(): string | null;
|
|
341
|
-
/**
|
|
342
|
-
* Get current effective user ID (global userId or persistent anonymous ID)
|
|
343
|
-
*/
|
|
344
|
-
getEffectiveUserIdPublic(): string;
|
|
345
|
-
/**
|
|
346
|
-
* Login with auth token or userId on the fly
|
|
347
|
-
*
|
|
348
|
-
* @example
|
|
349
|
-
* // Login with userId only
|
|
350
|
-
* client.login({ userId: 'user123' });
|
|
351
|
-
*
|
|
352
|
-
* // Login with auth token (automatically sets authStrategy to JWT)
|
|
353
|
-
* client.login({ authToken: 'jwt-token-here' });
|
|
354
|
-
*
|
|
355
|
-
* // Login with both userId and auth token
|
|
356
|
-
* client.login({ userId: 'user123', authToken: 'jwt-token-here' });
|
|
357
|
-
*
|
|
358
|
-
* // Override auth strategy
|
|
359
|
-
* client.login({ userId: 'user123', authStrategy: 'SERVER_SIDE' });
|
|
360
|
-
*/
|
|
361
|
-
login(options: LoginOptions): void;
|
|
362
|
-
/**
|
|
363
|
-
* Logout and clear user session, fall back to UUIDv4 identifier
|
|
364
|
-
*
|
|
365
|
-
* @example
|
|
366
|
-
* // Logout user and return to anonymous mode
|
|
367
|
-
* client.logout();
|
|
368
|
-
*
|
|
369
|
-
* // After logout, events will use the persistent UUIDv4 identifier
|
|
370
|
-
* client.track('page_view', { page: 'home' });
|
|
371
|
-
*/
|
|
372
|
-
logout(): void;
|
|
373
|
-
/**
|
|
374
|
-
* Set user properties
|
|
375
|
-
*/
|
|
376
|
-
setProperty(properties: Record<string, unknown>, options?: SetPropertyOptions): Promise<void>;
|
|
377
|
-
/**
|
|
378
|
-
* Send properties to the API
|
|
379
|
-
*/
|
|
380
|
-
private sendProperties;
|
|
381
|
-
/**
|
|
382
|
-
* Track user login event
|
|
383
|
-
*/
|
|
384
|
-
trackLogin(properties?: LoginEventProperties, options?: SendEventOptions): Promise<void>;
|
|
385
|
-
/**
|
|
386
|
-
* Track user signup event
|
|
387
|
-
*/
|
|
388
|
-
trackSignup(properties?: SignupEventProperties, options?: SendEventOptions): Promise<void>;
|
|
389
|
-
/**
|
|
390
|
-
* Track checkout event
|
|
391
|
-
*/
|
|
392
|
-
trackCheckout(properties?: CheckoutEventProperties, options?: SendEventOptions): Promise<void>;
|
|
393
|
-
/**
|
|
394
|
-
* Track page view event
|
|
395
|
-
*/
|
|
396
|
-
trackPageView(properties?: PageViewEventProperties, options?: SendEventOptions): Promise<void>;
|
|
397
|
-
/**
|
|
398
|
-
* Track purchase event
|
|
399
|
-
*/
|
|
400
|
-
trackPurchase(properties?: PurchaseEventProperties, options?: SendEventOptions): Promise<void>;
|
|
401
|
-
/**
|
|
402
|
-
* Track search event
|
|
403
|
-
*/
|
|
404
|
-
trackSearch(properties?: SearchEventProperties, options?: SendEventOptions): Promise<void>;
|
|
405
|
-
/**
|
|
406
|
-
* Track add to cart event
|
|
407
|
-
*/
|
|
408
|
-
trackAddToCart(properties?: AddToCartEventProperties, options?: SendEventOptions): Promise<void>;
|
|
409
|
-
/**
|
|
410
|
-
* Track remove from cart event
|
|
411
|
-
*/
|
|
412
|
-
trackRemoveFromCart(properties?: RemoveFromCartEventProperties, options?: SendEventOptions): Promise<void>;
|
|
413
|
-
/**
|
|
414
|
-
* Manually flush all queued events
|
|
415
|
-
*/
|
|
416
|
-
flush(): Promise<void>;
|
|
417
|
-
/**
|
|
418
|
-
* Initialize configuration cache from localStorage
|
|
419
|
-
*/
|
|
420
|
-
private initializeConfigCache;
|
|
421
|
-
/**
|
|
422
|
-
* Save configuration cache to localStorage
|
|
423
|
-
*/
|
|
424
|
-
private saveConfigCache;
|
|
425
|
-
/**
|
|
426
|
-
* Get configuration value with fallback to defaults
|
|
427
|
-
*/
|
|
428
|
-
getConfig(key: string): string | undefined;
|
|
429
|
-
/**
|
|
430
|
-
* Get all configurations with fallback to defaults
|
|
431
|
-
*/
|
|
432
|
-
getAllConfigs(): Record<string, string>;
|
|
433
|
-
/**
|
|
434
|
-
* Fetch configurations from API
|
|
435
|
-
*/
|
|
436
|
-
fetchConfig(options?: RemoteConfigOptions): Promise<RemoteConfigResponse | null>;
|
|
437
|
-
/**
|
|
438
|
-
* Get configuration asynchronously (cache-first with fallback to API)
|
|
439
|
-
*/
|
|
440
|
-
getConfigAsync(key: string, options?: RemoteConfigOptions): Promise<string | undefined>;
|
|
441
|
-
/**
|
|
442
|
-
* Get all configurations asynchronously (cache-first with fallback to API)
|
|
443
|
-
*/
|
|
444
|
-
getAllConfigsAsync(options?: RemoteConfigOptions): Promise<Record<string, string>>;
|
|
445
|
-
/**
|
|
446
|
-
* Update configuration cache and notify listeners
|
|
447
|
-
*/
|
|
448
|
-
private updateConfigCache;
|
|
449
|
-
/**
|
|
450
|
-
* Add configuration change listener
|
|
451
|
-
*/
|
|
452
|
-
addConfigChangeListener(listener: ConfigChangeListener): void;
|
|
453
|
-
/**
|
|
454
|
-
* Remove configuration change listener
|
|
455
|
-
*/
|
|
456
|
-
removeConfigChangeListener(listener: ConfigChangeListener): void;
|
|
457
|
-
/**
|
|
458
|
-
* Notify all configuration change listeners
|
|
459
|
-
*/
|
|
460
|
-
private notifyConfigChangeListeners;
|
|
461
|
-
/**
|
|
462
|
-
* Start automatic configuration refresh timer
|
|
463
|
-
*/
|
|
464
|
-
private startConfigRefreshTimer;
|
|
465
|
-
/**
|
|
466
|
-
* Stop automatic configuration refresh timer
|
|
467
|
-
*/
|
|
468
|
-
private stopConfigRefreshTimer;
|
|
469
|
-
/**
|
|
470
|
-
* Preload configurations for immediate access
|
|
471
|
-
*/
|
|
472
|
-
preloadConfig(immediateKeys?: string[], properties?: Record<string, string>): Promise<void>;
|
|
473
|
-
/**
|
|
474
|
-
* Split events array into chunks of specified size
|
|
475
|
-
*/
|
|
476
|
-
private chunkEvents;
|
|
477
|
-
/**
|
|
478
|
-
* Grant consent for tracking
|
|
479
|
-
* @param categories - Optional array of consent categories (e.g., ['analytics', 'functional'])
|
|
480
|
-
*/
|
|
481
|
-
grantConsent(categories?: string[]): void;
|
|
482
|
-
/**
|
|
483
|
-
* Revoke consent for tracking (opt-out)
|
|
484
|
-
* @param categories - Optional array of categories to revoke (if not provided, revokes all)
|
|
485
|
-
*/
|
|
486
|
-
revokeConsent(categories?: string[]): void;
|
|
487
|
-
/**
|
|
488
|
-
* Get current consent state
|
|
489
|
-
*/
|
|
490
|
-
getConsentState(): ConsentState | null;
|
|
491
|
-
/**
|
|
492
|
-
* Check if user has granted consent
|
|
493
|
-
* @param category - Optional category to check (if not provided, checks general consent)
|
|
494
|
-
*/
|
|
495
|
-
hasConsent(category?: string): boolean;
|
|
496
|
-
/**
|
|
497
|
-
* Add listener for consent state changes
|
|
498
|
-
*/
|
|
499
|
-
onConsentChange(listener: (state: ConsentState) => void): void;
|
|
500
|
-
/**
|
|
501
|
-
* Remove consent change listener
|
|
502
|
-
*/
|
|
503
|
-
offConsentChange(listener: (state: ConsentState) => void): void;
|
|
504
|
-
/**
|
|
505
|
-
* Destroy the client and clean up resources
|
|
506
|
-
*/
|
|
507
|
-
destroy(): void;
|
|
508
|
-
}
|
|
509
|
-
/**
|
|
510
|
-
* Create a new Grain Analytics client
|
|
511
|
-
*/
|
|
512
|
-
export declare function createGrainAnalytics(config: GrainConfig): GrainAnalytics;
|
|
513
|
-
export default GrainAnalytics;
|
|
514
|
-
declare global {
|
|
515
|
-
interface Window {
|
|
516
|
-
Grain?: {
|
|
517
|
-
GrainAnalytics: typeof GrainAnalytics;
|
|
518
|
-
createGrainAnalytics: typeof createGrainAnalytics;
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
}
|
|
30
|
+
export { GrainProvider } from './GrainProvider';
|
|
31
|
+
export { useGrainAnalytics } from './hooks/useGrainAnalytics';
|
|
32
|
+
export { useConfig } from './hooks/useConfig';
|
|
33
|
+
export { useAllConfigs } from './hooks/useAllConfigs';
|
|
34
|
+
export { useTrack } from './hooks/useTrack';
|
|
35
|
+
export { useConsent } from './hooks/useConsent';
|
|
36
|
+
export { usePrivacyPreferences } from './hooks/usePrivacyPreferences';
|
|
37
|
+
export { useDataDeletion } from './hooks/useDataDeletion';
|
|
38
|
+
export { ConsentBanner } from './components/ConsentBanner';
|
|
39
|
+
export { PrivacyPreferenceCenter } from './components/PrivacyPreferenceCenter';
|
|
40
|
+
export { CookieNotice } from './components/CookieNotice';
|
|
41
|
+
export type { GrainProviderProps, UseConfigOptions, UseConfigResult, UseAllConfigsOptions, UseAllConfigsResult, TrackFunction, } from './types';
|
|
42
|
+
export type { ConsentBannerProps } from './components/ConsentBanner';
|
|
43
|
+
export type { PrivacyPreferenceCenterProps } from './components/PrivacyPreferenceCenter';
|
|
44
|
+
export type { CookieNoticeProps } from './components/CookieNotice';
|
|
45
|
+
export type { PrivacyPreferences } from './hooks/usePrivacyPreferences';
|
|
46
|
+
export type { DataDeletionOptions } from './hooks/useDataDeletion';
|
|
522
47
|
//# sourceMappingURL=index.d.ts.map
|