Comparing version 0.2.27 to 0.2.28
126
analytics.js
import EventQueue from "./eventQueue"; | ||
import { sessionIdKey, userIdKey } from "./client"; | ||
const eventQueue = EventQueue.getInstance(); | ||
const eventCategories = { | ||
@@ -51,54 +50,89 @@ WIDGET_INTERACTION: "widget_interaction", | ||
}; | ||
// TODO: update sdk types to include getAnalyticsEvents function | ||
const getAnalyticsEvents = (widgetState = {}) => { | ||
const createEvent = (category, type, props) => { | ||
const event = { | ||
category, | ||
type, | ||
props, | ||
metadata: { | ||
timestamp: new Date().toISOString(), | ||
localTimestamp: new Date().toLocaleString(), | ||
timeZone: getTimeZoneData().timeZone, | ||
timeZoneOffset: getTimeZoneData().timeZoneOffset, | ||
timeOfDay: getTimeOfDay(), | ||
pageUrl: window.location.href, | ||
referrerUrl: document.referrer, | ||
device: /Mobi|Android/i.test(navigator.userAgent) | ||
? "mobile" | ||
: "desktop", | ||
screenWidth: window.screen.width, | ||
screenHeight: window.screen.height, | ||
browserLanguage: navigator.language, | ||
browser: getBrowserName(navigator.userAgent), | ||
...widgetState, | ||
}, | ||
function replaceUndefinedWithNull(obj) { | ||
for (const key in obj) { | ||
if (obj[key] === undefined) { | ||
obj[key] = null; | ||
} else if (typeof obj[key] === "object" && obj[key] !== null) { | ||
replaceUndefinedWithNull(obj[key]); | ||
} | ||
} | ||
} | ||
const analyticsEventsWrapper = (client) => { | ||
const eventQueue = EventQueue.getInstance(client); | ||
// TODO: update sdk types to include getAnalyticsEvents function | ||
const getAnalyticsEvents = (widgetState = {}) => { | ||
const createEvent = (category, name, props) => { | ||
if (typeof name !== "string") { | ||
name = String(name); | ||
} | ||
// in javascript, typeof null is object, so we need to check if props is null | ||
if (typeof props !== "object" || props === null) { | ||
props = {}; | ||
} | ||
let duration = null; | ||
if (props.startTime && typeof props.startTime === "number") { | ||
duration = Date.now() - props.startTime; | ||
delete props.startTime; | ||
} | ||
props.duration = duration; | ||
const event = { | ||
category, | ||
name, | ||
props, | ||
// these props will be validated by the server | ||
// but we still need to include them in the event in case these events | ||
// are indeed should be added to the previous session even if the session is expired | ||
analyticsUserId: localStorage.getItem(userIdKey), | ||
analyticsSessionId: localStorage.getItem(sessionIdKey), | ||
metadata: { | ||
timestamp: new Date().toISOString(), | ||
localTimestamp: new Date().toLocaleString(), | ||
timeZone: getTimeZoneData().timeZone, | ||
timeZoneOffset: getTimeZoneData().timeZoneOffset, | ||
timeOfDay: getTimeOfDay(), | ||
pageUrl: window.location.href, | ||
referrerUrl: document.referrer, | ||
device: /Mobi|Android/i.test(navigator.userAgent) | ||
? "mobile" | ||
: "desktop", | ||
screenWidth: window.screen.width, | ||
screenHeight: window.screen.height, | ||
browserLanguage: navigator.language, | ||
browser: getBrowserName(navigator.userAgent), | ||
widgetState, | ||
}, | ||
}; | ||
replaceUndefinedWithNull(event); | ||
eventQueue.enqueueEvent(event); | ||
return event; | ||
}; | ||
eventQueue.enqueueEvent(event); | ||
return event; | ||
}; | ||
const addWidgetInteractionEvent = (type, props = {}) => | ||
createEvent(eventCategories.WIDGET_INTERACTION, type, props); | ||
const addWidgetInteractionEvent = (name, props = {}) => | ||
createEvent(eventCategories.WIDGET_INTERACTION, type, props); | ||
const addHostInteractionEvent = (type, props = {}) => | ||
createEvent(eventCategories.HOST_INTERACTION, type, props); | ||
const addHostInteractionEvent = (name, props = {}) => | ||
createEvent(eventCategories.HOST_INTERACTION, type, props); | ||
const addUserUpdateEvent = (type, props = {}) => | ||
createEvent(eventCategories.USER_UPDATE, type, props); | ||
const addUserUpdateEvent = (name, props = {}) => | ||
createEvent(eventCategories.USER_UPDATE, type, props); | ||
const addConversionEvent = (type, props = {}) => | ||
createEvent(eventCategories.CONVERSION, type, props); | ||
const addConversionEvent = (name, props = {}) => | ||
createEvent(eventCategories.CONVERSION, type, props); | ||
const addCustomEvent = (type, props = {}) => | ||
createEvent(eventCategories.CUSTOM, type, props); | ||
return { | ||
addWidgetInteractionEvent, | ||
addHostInteractionEvent, | ||
addUserUpdateEvent, | ||
addConversionEvent, | ||
addCustomEvent, | ||
const addCustomEvent = (name, props = {}) => | ||
createEvent(eventCategories.CUSTOM, type, props); | ||
return { | ||
addWidgetInteractionEvent, | ||
addHostInteractionEvent, | ||
addUserUpdateEvent, | ||
addConversionEvent, | ||
addCustomEvent, | ||
}; | ||
}; | ||
return getAnalyticsEvents; | ||
}; | ||
export { getAnalyticsEvents }; | ||
export { analyticsEventsWrapper }; |
@@ -1,1 +0,1 @@ | ||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.truetoformApi=t():e.truetoformApi=t()}(this,(()=>(()=>{"use strict";var e={d:(t,r)=>{for(var n in r)e.o(r,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:r[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};e.r(t),e.d(t,{default:()=>m});const r="ttf-analytics-session-id",n="ttf-analytics-user-id",o=e=>{const t="localhost"===window.location.hostname||"https://truetoform-test-store.myshopify.com"===window.location.origin?"https://api.truetoform.online/v1":"https://api.truetoform.fit/v1",o=async(o,s,i=null,a=null,c=null)=>{const u=(()=>{const t={"Content-Type":"application/json","X-TTF-API-KEY":e},o=localStorage.getItem(r),s=localStorage.getItem(n);return o&&(t["X-TTF-SESSION-ID"]=o),s&&(t["X-TTF-USER-ID"]=s),t})();c&&(u["If-None-Match"]=c);const l={method:o,headers:u,body:i?JSON.stringify(i):null,credentials:"include",signal:a},d=await fetch(`${t}/${s}`,l);if(!d.ok){const e=await d.json();throw new Error(`Error ${d.status}: ${e?.message}`)}const g=d.headers.get("X-New-Session-ID"),f=d.headers.get("X-New-User-ID");if(g&&localStorage.setItem(r,g),f&&localStorage.setItem(n,f),204===d.status)return null;const h=d.headers.get("content-type");if(h&&h.includes("application/json")){return await d.json()}if(h&&h.includes("text/event-stream"))return d.body;if(304===d.status)return{currentETag:localStorage.getItem("ttf-sdk-eTag")};return await d.text()};return{get:(e,t,r)=>o("GET",e,null,t,r),post:(e,t,r)=>o("POST",e,t,r),put:(e,t,r)=>o("PUT",e,t,r),delete:(e,t)=>o("DELETE",e,null,t)}};class s{constructor(){if(s.instance)return s.instance;this.eventQueue=[],this.MAX_BATCH_SIZE=50,this.MAX_TIME_INTERVAL=5e3,this.sendEventsTimer=null,this.loadQueueFromLocalStorage(),window.addEventListener("beforeunload",(()=>{this.sendRemainingEvents()})),window.addEventListener("storage",(e=>{console.log("Storage event:",e),"analyticsEventQueue"===e.key&&this.loadQueueFromLocalStorage()})),s.instance=this}static getInstance(){return s.instance||(s.instance=new s),s.instance}loadQueueFromLocalStorage(){try{const e=localStorage.getItem("analyticsEventQueue");e&&(this.eventQueue=JSON.parse(e))}catch(e){console.error("Failed to load event queue from localStorage:",e)}}saveQueueToLocalStorage(){try{localStorage.setItem("analyticsEventQueue",JSON.stringify(this.eventQueue))}catch(e){console.error("Failed to save event queue to localStorage:",e)}}enqueueEvent(e){this.eventQueue.push(e),this.saveQueueToLocalStorage(),this.checkAndSendEvents()}checkAndSendEvents(){console.log("Checking and sending events"),this.eventQueue.length>=this.MAX_BATCH_SIZE?this.sendEvents():this.sendEventsTimer||(this.sendEventsTimer=setTimeout((()=>{this.sendEvents()}),this.MAX_TIME_INTERVAL))}async sendEvents(){if(this.sendEventsTimer&&(clearTimeout(this.sendEventsTimer),this.sendEventsTimer=null),0===this.eventQueue.length)return;const e=[...this.eventQueue];this.eventQueue=[],this.saveQueueToLocalStorage(),console.log("Sending events:",e)}sendRemainingEvents(){if(this.eventQueue.length>0){const e=localStorage.getItem("userIdKey"),t=localStorage.getItem("sessionIdKey");console.log("Send remaining events",e,t,this.eventQueue)}}}const i=s.getInstance(),a="widget_interaction",c="host_interaction",u="user_update",l="conversion",d="custom",g=()=>{const e=(new Date).getHours();return e>=5&&e<12?"morning":e>=12&&e<14?"noon":e>=14&&e<18?"afternoon":e>=18&&e<21?"evening":"night"},f=()=>({timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,timeZoneOffset:(new Date).getTimezoneOffset()}),h=(e={})=>{const t=(t,r,n)=>{const o={category:t,type:r,props:n,metadata:{timestamp:(new Date).toISOString(),localTimestamp:(new Date).toLocaleString(),timeZone:f().timeZone,timeZoneOffset:f().timeZoneOffset,timeOfDay:g(),pageUrl:window.location.href,referrerUrl:document.referrer,device:/Mobi|Android/i.test(navigator.userAgent)?"mobile":"desktop",screenWidth:window.screen.width,screenHeight:window.screen.height,browserLanguage:navigator.language,browser:(s=navigator.userAgent,s.includes("Firefox")?"Firefox":s.includes("Edg")?"Edge":s.includes("Chrome")&&!s.includes("Chromium")?"Chrome":s.includes("Safari")&&!s.includes("Chrome")?"Safari":s.includes("Opera")||s.includes("OPR")?"Opera":s.includes("MSIE")||s.includes("Trident")?"Internet Explorer":"Unknown"),...e}};var s;return i.enqueueEvent(o),o};return{addWidgetInteractionEvent:(e,r={})=>t(a,e,r),addHostInteractionEvent:(e,r={})=>t(c,e,r),addUserUpdateEvent:(e,r={})=>t(u,e,r),addConversionEvent:(e,r={})=>t(l,e,r),addCustomEvent:(e,r={})=>t(d,e,r)}},m=e=>{const t=o(e),s=e=>{if(!e)throw new Error("survey data is required");return t.post("surveys",e)},i=e=>{if(!e)throw new Error("session ID is required");return t.post("scans",{sessionId:e})};return{isStale:async()=>{const e=localStorage.getItem("ttf-sdk-eTag"),r=await t.get("stale-check",null,e),{currentETag:n}=r;return localStorage.setItem("ttf-sdk-eTag",n),e!==n},initAnalytics:async()=>t.post("analytics/init").then((e=>{const{userId:t,sessionId:o}=e;return localStorage.setItem(n,t),localStorage.setItem(r,o),e})),getGarment:e=>{if(!e)throw new Error("garment ID is required");return t.get(`garments/${e}`)},createSurvey:s,getSurvey:e=>{if(!e)throw new Error("survey ID is required");return t.get(`surveys/${e}`)},createScan:i,getScan:e=>{if(!e)throw new Error("scan ID is required");return t.get(`scans/${e}`)},getPrediction:e=>{if(!e)throw new Error("prediction ID is required");return t.get(`predictions/${e}`)},createSession:()=>t.post("sessions"),createScanPrediction:async(e,r)=>{if(!e)throw new Error("garment ID is required");if(!r)throw new Error("session ID is required");const n=await i(r);if(!n||!n.id)throw new Error("Failed to create scan");return t.post("predictions",{garmentId:e,creatorId:n.id})},createSurveyPrediction:async(e,r)=>{if(!e)throw new Error("garment ID is required");if(!r)throw new Error("survey data is required");const n=await s(r);if(!n||!n.id)throw new Error("Failed to create survey");return t.post("predictions",{garmentId:e,creatorId:n.id})},createPredictionFromId:(e,r)=>{if(!e)throw new Error("garment ID is required");if(!r)throw new Error("creator ID is required");return t.post("predictions",{garmentId:e,creatorId:r})},getRealtimeSession:(e,{onChange:r,onSuccess:n,onError:o})=>{let s=()=>{};return async function(){try{const i=new AbortController,a=i.signal;s=()=>i.abort();const c=await t.get(`sessions/${e}`,a),u=c?.getReader(),l=new TextDecoder;let{done:d,value:g}=await u.read();for(;!d;){const e=l.decode(g,{stream:!0});try{const t=JSON.parse(e);switch(console.log("SSE data:",t),t.type){case"SESSION_DATA":if(r&&"function"==typeof r)try{r(t.payload?.progress)}catch(e){console.error("Error calling onChange:",e)}if(n&&"function"==typeof n){if("READY"===t.payload?.progress)try{n(t.payload)}catch(e){console.error("Error calling onSuccess:",e)}}if(o&&"function"==typeof o){if("FAILED"===t.payload?.progress)try{o(t.payload)}catch(e){console.error("Error calling onError:",e)}}break;case"ERROR":if(console.error("SSE error:",t),o&&"function"==typeof o)try{o(t)}catch(e){console.error("Error calling onError:",e)}}}catch(e){if(console.error("Error parsing SSE data:",e),o&&"function"==typeof o)try{o({type:"ERROR",message:"Parsing error"})}catch(e){console.error("Error calling onError:",e)}}({done:d,value:g}=await u.read())}}catch(e){console.error("Stream error:",e),o&&"function"==typeof o&&o({type:"ERROR",message:"Stream error"})}}(),s},getSessionStatus:e=>t.get(`sessions/${e}/status`),getAnalyticsEvents:h}};return t})())); | ||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.truetoformApi=t():e.truetoformApi=t()}(this,(()=>(()=>{"use strict";var e={d:(t,r)=>{for(var n in r)e.o(r,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:r[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};e.r(t),e.d(t,{default:()=>h});const r="ttf-analytics-session-id",n="ttf-analytics-user-id",o=e=>{const t="localhost"===window.location.hostname||"https://truetoform-test-store.myshopify.com"===window.location.origin?"https://api.truetoform.online/v1":"https://api.truetoform.fit/v1",o=async(o,s,a=null,i=null,c=null)=>{const l=(()=>{const t={"Content-Type":"application/json","X-TTF-API-KEY":e},o=localStorage.getItem(r),s=localStorage.getItem(n);return o&&(t["X-TTF-SESSION-ID"]=o),s&&(t["X-TTF-USER-ID"]=s),t})();c&&(l["If-None-Match"]=c);const d={method:o,headers:l,body:a?JSON.stringify(a):null,credentials:"include",signal:i},u=await fetch(`${t}/${s}`,d);if(!u.ok){const e=await u.json();throw new Error(`Error ${u.status}: ${e?.message}`)}const g=u.headers.get("X-New-Session-ID"),m=u.headers.get("X-New-User-ID");if(g&&localStorage.setItem(r,g),m&&localStorage.setItem(n,m),204===u.status)return null;const f=u.headers.get("content-type");if(f&&f.includes("application/json")){return await u.json()}if(f&&f.includes("text/event-stream"))return u.body;if(304===u.status)return{currentETag:localStorage.getItem("ttf-sdk-eTag")};return await u.text()};return{get:(e,t,r)=>o("GET",e,null,t,r),post:(e,t,r)=>o("POST",e,t,r),put:(e,t,r)=>o("PUT",e,t,r),delete:(e,t)=>o("DELETE",e,null,t)}};class s{constructor(e){if(s.instance)return s.instance;this.eventQueue=[],this.MAX_BATCH_SIZE=50,this.MAX_TIME_INTERVAL=5e3,this.sendEventsTimer=null,this.client=e,this.loadQueueFromLocalStorage(),window.addEventListener("beforeunload",(()=>{this.sendEvents()})),window.addEventListener("storage",(e=>{console.log("Storage event:",e),"analyticsEventQueue"===e.key&&this.loadQueueFromLocalStorage()})),s.instance=this}static getInstance(e){return s.instance||(s.instance=new s(e)),s.instance}loadQueueFromLocalStorage(){try{const e=localStorage.getItem("analyticsEventQueue");e&&(this.eventQueue=JSON.parse(e))}catch(e){console.error("Failed to load event queue from localStorage:",e)}}saveQueueToLocalStorage(){try{localStorage.setItem("analyticsEventQueue",JSON.stringify(this.eventQueue))}catch(e){console.error("Failed to save event queue to localStorage:",e)}}enqueueEvent(e){const t=e.metadata.widgetState;t&&"object"==typeof t&&Object.keys(t).length&&(this.latestWidgetState=t),this.latestWidgetState&&(e.metadata.widgetState=this.latestWidgetState),this.eventQueue.push(e),this.saveQueueToLocalStorage(),this.checkAndSendEvents()}checkAndSendEvents(){console.log("Checking and sending events"),this.eventQueue.length>=this.MAX_BATCH_SIZE?this.sendEvents():this.sendEventsTimer||(this.sendEventsTimer=setTimeout((()=>{this.sendEvents()}),this.MAX_TIME_INTERVAL))}async sendEvents(){if(this.sendEventsTimer&&(clearTimeout(this.sendEventsTimer),this.sendEventsTimer=null),0===this.eventQueue.length)return;const e=[...this.eventQueue];this.eventQueue=[],this.saveQueueToLocalStorage(),console.log("Sending events:",e);try{await this.client.post("/analytics/event",{events:e})}catch(t){console.error("Failed to send events:",t),this.eventQueue=[...e,...this.eventQueue],this.saveQueueToLocalStorage()}}}const a=s,i="widget_interaction",c="host_interaction",l="user_update",d="conversion",u="custom",g=()=>{const e=(new Date).getHours();return e>=5&&e<12?"morning":e>=12&&e<14?"noon":e>=14&&e<18?"afternoon":e>=18&&e<21?"evening":"night"},m=()=>({timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,timeZoneOffset:(new Date).getTimezoneOffset()});function f(e){for(const t in e)void 0===e[t]?e[t]=null:"object"==typeof e[t]&&null!==e[t]&&f(e[t])}const y=e=>{const t=a.getInstance(e);return(e={})=>{const o=(o,s,a)=>{"string"!=typeof s&&(s=String(s)),"object"==typeof a&&null!==a||(a={});let i=null;a.startTime&&"number"==typeof a.startTime&&(i=Date.now()-a.startTime,delete a.startTime),a.duration=i;const c={category:o,name:s,props:a,analyticsUserId:localStorage.getItem(n),analyticsSessionId:localStorage.getItem(r),metadata:{timestamp:(new Date).toISOString(),localTimestamp:(new Date).toLocaleString(),timeZone:m().timeZone,timeZoneOffset:m().timeZoneOffset,timeOfDay:g(),pageUrl:window.location.href,referrerUrl:document.referrer,device:/Mobi|Android/i.test(navigator.userAgent)?"mobile":"desktop",screenWidth:window.screen.width,screenHeight:window.screen.height,browserLanguage:navigator.language,browser:(l=navigator.userAgent,l.includes("Firefox")?"Firefox":l.includes("Edg")?"Edge":l.includes("Chrome")&&!l.includes("Chromium")?"Chrome":l.includes("Safari")&&!l.includes("Chrome")?"Safari":l.includes("Opera")||l.includes("OPR")?"Opera":l.includes("MSIE")||l.includes("Trident")?"Internet Explorer":"Unknown"),widgetState:e}};var l;return f(c),t.enqueueEvent(c),c};return{addWidgetInteractionEvent:(e,t={})=>o(i,type,t),addHostInteractionEvent:(e,t={})=>o(c,type,t),addUserUpdateEvent:(e,t={})=>o(l,type,t),addConversionEvent:(e,t={})=>o(d,type,t),addCustomEvent:(e,t={})=>o(u,type,t)}}},h=e=>{const t=o(e),s=y(t),a=e=>(0,s({}).addCustomEvent)("server_request",e),i=e=>{const r=Date.now();if(!e)throw new Error("survey data is required");return t.post("surveys",e).finally((()=>{a({name:"create_survey",startTime:r})}))},c=e=>{const r=Date.now();if(!e)throw new Error("session ID is required");return t.post("scans",{sessionId:e}).finally((()=>{a({name:"create_scan",startTime:r})}))};return{isStale:async()=>{const e=localStorage.getItem("ttf-sdk-eTag"),r=await t.get("stale-check",null,e),{currentETag:n}=r;return localStorage.setItem("ttf-sdk-eTag",n),e!==n},initAnalytics:async()=>t.post("analytics/init").then((e=>{const{userId:t,sessionId:o}=e;return localStorage.setItem(n,t),localStorage.setItem(r,o),e})),getGarment:e=>{const r=Date.now();if(!e)throw new Error("garment ID is required");return t.get(`garments/${e}`).finally((()=>{a({name:"get_garment",startTime:r,garmentId:e})}))},createSurvey:i,getSurvey:e=>{const r=Date.now();if(!e)throw new Error("survey ID is required");return t.get(`surveys/${e}`).finally((()=>{a({name:"get_survey",startTime:r,surveyId:e})}))},createScan:c,getScan:e=>{const r=Date.now();if(!e)throw new Error("scan ID is required");return t.get(`scans/${e}`).finally((()=>{a({name:"get_scan",startTime:r,scanId:e})}))},getPrediction:e=>{const r=Date.now();if(!e)throw new Error("prediction ID is required");return t.get(`predictions/${e}`).finally((()=>{a({name:"get_prediction",startTime:r,predictionId:e})}))},createSession:()=>{const e=Date.now();return t.post("sessions").finally((()=>{a({name:"create_session",startTime:e})}))},createScanPrediction:async(e,r)=>{const n=Date.now();if(!e)throw new Error("garment ID is required");if(!r)throw new Error("session ID is required");const o=await c(r);if(!o||!o.id)throw new Error("Failed to create scan");return t.post("predictions",{garmentId:e,creatorId:o.id}).finally((()=>{a({name:"create_scan_prediction",startTime:n,garmentId:e,sessionId:r})}))},createSurveyPrediction:async(e,r)=>{const n=Date.now();if(!e)throw new Error("garment ID is required");if(!r)throw new Error("survey data is required");const o=await i(r);if(!o||!o.id)throw new Error("Failed to create survey");return t.post("predictions",{garmentId:e,creatorId:o.id}).finally((()=>{a({name:"create_survey_prediction",startTime:n,garmentId:e,surveyId:o.id})}))},createPredictionFromId:(e,r)=>{const n=Date.now();if(!e)throw new Error("garment ID is required");if(!r)throw new Error("creator ID is required");return t.post("predictions",{garmentId:e,creatorId:r}).finally((()=>{a({name:"create_background_prediction",startTime:n,garmentId:e,creatorId:r})}))},getRealtimeSession:(e,{onChange:r,onSuccess:n,onError:o})=>{let s=()=>{};return async function(){try{const i=Date.now(),c=new AbortController,l=c.signal;s=()=>c.abort();const d=await t.get(`sessions/${e}`,l).finally((()=>{a({name:"get_realtime_session",startTime:i,sessionId:e})})),u=d?.getReader(),g=new TextDecoder;let{done:m,value:f}=await u.read();for(;!m;){const e=g.decode(f,{stream:!0});try{const t=JSON.parse(e);switch(console.log("SSE data:",t),t.type){case"SESSION_DATA":if(r&&"function"==typeof r)try{r(t.payload?.progress)}catch(e){console.error("Error calling onChange:",e)}if(n&&"function"==typeof n){if("READY"===t.payload?.progress)try{n(t.payload)}catch(e){console.error("Error calling onSuccess:",e)}}if(o&&"function"==typeof o){if("FAILED"===t.payload?.progress)try{o(t.payload)}catch(e){console.error("Error calling onError:",e)}}break;case"ERROR":if(console.error("SSE error:",t),o&&"function"==typeof o)try{o(t)}catch(e){console.error("Error calling onError:",e)}}}catch(e){if(console.error("Error parsing SSE data:",e),o&&"function"==typeof o)try{o({type:"ERROR",message:"Parsing error"})}catch(e){console.error("Error calling onError:",e)}}({done:m,value:f}=await u.read())}}catch(e){console.error("Stream error:",e),o&&"function"==typeof o&&o({type:"ERROR",message:"Stream error"})}}(),s},getSessionStatus:e=>{const r=Date.now();if(!e)throw new Error("session ID is required");return t.get(`sessions/${e}/status`).finally((()=>{a({name:"get_session_status",startTime:r})}))},getAnalyticsEvents:s}};return t})())); |
class EventQueue { | ||
constructor() { | ||
constructor(client) { | ||
if (EventQueue.instance) { | ||
@@ -11,3 +11,3 @@ return EventQueue.instance; | ||
this.sendEventsTimer = null; | ||
this.client = client; | ||
this.loadQueueFromLocalStorage(); | ||
@@ -17,3 +17,3 @@ | ||
window.addEventListener("beforeunload", () => { | ||
this.sendRemainingEvents(); | ||
this.sendEvents(); | ||
}); | ||
@@ -32,5 +32,5 @@ | ||
static getInstance() { | ||
static getInstance(client) { | ||
if (!EventQueue.instance) { | ||
EventQueue.instance = new EventQueue(); | ||
EventQueue.instance = new EventQueue(client); | ||
} | ||
@@ -63,2 +63,17 @@ return EventQueue.instance; | ||
enqueueEvent(event) { | ||
// save the latest widget state if provided | ||
// and pass it to the next event if it doesn't have one | ||
const widgetState = event.metadata.widgetState; | ||
if (widgetState) { | ||
// make sure it's object and not empty | ||
if (typeof widgetState === "object" && Object.keys(widgetState).length) { | ||
this.latestWidgetState = widgetState; | ||
} | ||
} | ||
// if the event already has a widget state, we are setting | ||
// the event's widget state to it again. while if it doesn't | ||
// then this event will have the latest widget state from the previous event | ||
if (this.latestWidgetState) { | ||
event.metadata.widgetState = this.latestWidgetState; | ||
} | ||
this.eventQueue.push(event); | ||
@@ -93,53 +108,10 @@ this.saveQueueToLocalStorage(); | ||
console.log("Sending events:", eventsToSend); | ||
// try { | ||
// const userId = localStorage.getItem("userIdKey"); | ||
// const sessionId = localStorage.getItem("sessionIdKey"); | ||
// if (!userId || !sessionId) { | ||
// console.error("Missing userId or sessionId"); | ||
// // Re-add events to the queue | ||
// this.eventQueue = [...eventsToSend, ...this.eventQueue]; | ||
// this.saveQueueToLocalStorage(); | ||
// return; | ||
// } | ||
// const response = await fetch("/api/events", { | ||
// method: "POST", | ||
// headers: { | ||
// "Content-Type": "application/json", | ||
// "User-Id": userId, | ||
// "Session-Id": sessionId, | ||
// }, | ||
// body: JSON.stringify({ events: eventsToSend }), | ||
// }); | ||
// if (!response.ok) { | ||
// throw new Error(`Server responded with status ${response.status}`); | ||
// } | ||
// const responseData = await response.json(); | ||
// if (responseData.newSessionId) { | ||
// localStorage.setItem("sessionIdKey", responseData.newSessionId); | ||
// } | ||
// } catch (error) { | ||
// console.error("Failed to send events:", error); | ||
// // Re-add events to the queue | ||
// this.eventQueue = [...eventsToSend, ...this.eventQueue]; | ||
// this.saveQueueToLocalStorage(); | ||
// } | ||
} | ||
sendRemainingEvents() { | ||
if (this.eventQueue.length > 0) { | ||
const userId = localStorage.getItem("userIdKey"); | ||
const sessionId = localStorage.getItem("sessionIdKey"); | ||
console.log("Send remaining events", userId, sessionId, this.eventQueue); | ||
// if (userId && sessionId) { | ||
// const payload = JSON.stringify({ | ||
// userId, | ||
// sessionId, | ||
// events: this.eventQueue, | ||
// }); | ||
// navigator.sendBeacon("/api/events", payload); | ||
// } | ||
try { | ||
await this.client.post("/analytics/event", { | ||
events: eventsToSend, | ||
}); | ||
} catch (error) { | ||
console.error("Failed to send events:", error); | ||
this.eventQueue = [...eventsToSend, ...this.eventQueue]; | ||
this.saveQueueToLocalStorage(); | ||
} | ||
@@ -146,0 +118,0 @@ } |
106
index.js
import { createClient, sessionIdKey, userIdKey } from "./client"; | ||
import { getAnalyticsEvents } from "./analytics"; | ||
import { analyticsEventsWrapper } from "./analytics"; | ||
@@ -18,2 +18,9 @@ /** | ||
const getAnalyticsEvents = analyticsEventsWrapper(client); | ||
const createRequestEvent = (props) => { | ||
const createCustomEvent = getAnalyticsEvents({}).addCustomEvent; | ||
return createCustomEvent("server_request", props); | ||
}; | ||
const initAnalytics = async () => { | ||
@@ -29,16 +36,23 @@ return client.post("analytics/init").then((response) => { | ||
const getGarment = (id) => { | ||
const startTime = Date.now(); | ||
if (!id) { | ||
throw new Error("garment ID is required"); | ||
} | ||
return client.get(`garments/${id}`); | ||
return client.get(`garments/${id}`).finally(() => { | ||
createRequestEvent({ name: "get_garment", startTime, garmentId: id }); | ||
}); | ||
}; | ||
const getSurvey = (id) => { | ||
const startTime = Date.now(); | ||
if (!id) { | ||
throw new Error("survey ID is required"); | ||
} | ||
return client.get(`surveys/${id}`); | ||
return client.get(`surveys/${id}`).finally(() => { | ||
createRequestEvent({ name: "get_survey", startTime, surveyId: id }); | ||
}); | ||
}; | ||
const createSurvey = (surveyData) => { | ||
const startTime = Date.now(); | ||
if (!surveyData) { | ||
@@ -48,30 +62,57 @@ throw new Error("survey data is required"); | ||
// TODO: Validate survey data on SDK side | ||
return client.post("surveys", surveyData); | ||
return client.post("surveys", surveyData).finally(() => { | ||
createRequestEvent({ name: "create_survey", startTime }); | ||
}); | ||
}; | ||
const getScan = (id) => { | ||
const startTime = Date.now(); | ||
if (!id) { | ||
throw new Error("scan ID is required"); | ||
} | ||
return client.get(`scans/${id}`); | ||
return client.get(`scans/${id}`).finally(() => { | ||
createRequestEvent({ name: "get_scan", startTime, scanId: id }); | ||
}); | ||
}; | ||
const createScan = (sessionId) => { | ||
const startTime = Date.now(); | ||
if (!sessionId) { | ||
throw new Error("session ID is required"); | ||
} | ||
return client.post("scans", { sessionId }); | ||
return client.post("scans", { sessionId }).finally(() => { | ||
createRequestEvent({ name: "create_scan", startTime }); | ||
}); | ||
}; | ||
const getPrediction = (id) => { | ||
const startTime = Date.now(); | ||
if (!id) { | ||
throw new Error("prediction ID is required"); | ||
} | ||
return client.get(`predictions/${id}`); | ||
return client.get(`predictions/${id}`).finally(() => { | ||
createRequestEvent({ | ||
name: "get_prediction", | ||
startTime, | ||
predictionId: id, | ||
}); | ||
}); | ||
}; | ||
const createSession = () => client.post("sessions"); | ||
const createSession = () => { | ||
const startTime = Date.now(); | ||
return client.post("sessions").finally(() => { | ||
createRequestEvent({ name: "create_session", startTime }); | ||
}); | ||
}; | ||
const getSessionStatus = (sessionId) => | ||
client.get(`sessions/${sessionId}/status`); | ||
const getSessionStatus = (sessionId) => { | ||
const startTime = Date.now(); | ||
if (!sessionId) { | ||
throw new Error("session ID is required"); | ||
} | ||
return client.get(`sessions/${sessionId}/status`).finally(() => { | ||
createRequestEvent({ name: "get_session_status", startTime }); | ||
}); | ||
}; | ||
@@ -82,6 +123,15 @@ const getRealtimeSession = (sessionId, { onChange, onSuccess, onError }) => { | ||
try { | ||
const startTime = Date.now(); | ||
const controller = new AbortController(); | ||
const signal = controller.signal; | ||
unsubscribe = () => controller.abort(); | ||
const resBody = await client.get(`sessions/${sessionId}`, signal); | ||
const resBody = await client | ||
.get(`sessions/${sessionId}`, signal) | ||
.finally(() => { | ||
createRequestEvent({ | ||
name: "get_realtime_session", | ||
startTime, | ||
sessionId, | ||
}); | ||
}); | ||
const reader = resBody?.getReader(); | ||
@@ -164,2 +214,3 @@ const decoder = new TextDecoder(); | ||
const createScanPrediction = async (garmentId, sessionId) => { | ||
const startTime = Date.now(); | ||
if (!garmentId) { | ||
@@ -175,6 +226,16 @@ throw new Error("garment ID is required"); | ||
} | ||
return client.post("predictions", { garmentId, creatorId: scan.id }); | ||
return client | ||
.post("predictions", { garmentId, creatorId: scan.id }) | ||
.finally(() => { | ||
createRequestEvent({ | ||
name: "create_scan_prediction", | ||
startTime, | ||
garmentId, | ||
sessionId, | ||
}); | ||
}); | ||
}; | ||
const createSurveyPrediction = async (garmentId, surveyData) => { | ||
const startTime = Date.now(); | ||
if (!garmentId) { | ||
@@ -190,6 +251,16 @@ throw new Error("garment ID is required"); | ||
} | ||
return client.post("predictions", { garmentId, creatorId: survey.id }); | ||
return client | ||
.post("predictions", { garmentId, creatorId: survey.id }) | ||
.finally(() => { | ||
createRequestEvent({ | ||
name: "create_survey_prediction", | ||
startTime, | ||
garmentId, | ||
surveyId: survey.id, | ||
}); | ||
}); | ||
}; | ||
const createPredictionFromId = (garmentId, creatorId) => { | ||
const startTime = Date.now(); | ||
if (!garmentId) { | ||
@@ -201,3 +272,10 @@ throw new Error("garment ID is required"); | ||
} | ||
return client.post("predictions", { garmentId, creatorId }); | ||
return client.post("predictions", { garmentId, creatorId }).finally(() => { | ||
createRequestEvent({ | ||
name: "create_background_prediction", | ||
startTime, | ||
garmentId, | ||
creatorId, | ||
}); | ||
}); | ||
}; | ||
@@ -204,0 +282,0 @@ |
{ | ||
"name": "ttf-api", | ||
"version": "0.2.27", | ||
"version": "0.2.28", | ||
"description": "The TrueToForm API SDK", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
39250
958