@shware/analytics 3.0.1 → 3.0.2
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/hooks/use-web-analytics.cjs +2 -0
- package/dist/hooks/use-web-analytics.cjs.map +1 -1
- package/dist/hooks/use-web-analytics.mjs +2 -0
- package/dist/hooks/use-web-analytics.mjs.map +1 -1
- package/dist/track/index.cjs +3 -1
- package/dist/track/index.cjs.map +1 -1
- package/dist/track/index.mjs +3 -1
- package/dist/track/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -58,10 +58,12 @@ function getScrollPercent() {
|
|
|
58
58
|
return (scrollTop + windowHeight) * 100 / docHeight;
|
|
59
59
|
}
|
|
60
60
|
function onPageHide() {
|
|
61
|
+
console.log("onPageHide");
|
|
61
62
|
import_session.session.pagehide();
|
|
62
63
|
sendUserEngagement("pagehide");
|
|
63
64
|
}
|
|
64
65
|
function onVisibilityChange() {
|
|
66
|
+
console.log("onVisibilityChange");
|
|
65
67
|
import_session.session.visibilitychange(document.visibilityState);
|
|
66
68
|
if (document.visibilityState === "hidden") {
|
|
67
69
|
sendUserEngagement("visibilitychange");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hooks/use-web-analytics.ts"],"sourcesContent":["import { throttle } from '@shware/utils';\nimport { useEffect, useRef } from 'react';\nimport { config } from '../setup/index';\nimport { session } from '../setup/session';\nimport { sendBeacon, track } from '../track/index';\nimport { usePrevious } from './use-previous';\n\nfunction sendFirstVisit(pathname: string) {\n const key = 'first_visit_time';\n if (config.storage.getItem(key)) return;\n track('first_visit', {\n page_path: pathname,\n page_title: document.title,\n page_referrer: document.referrer,\n page_location: window.location.href,\n });\n config.storage.setItem(key, new Date().toISOString());\n}\n\nfunction sendUserEngagement(trigger: 'pagehide' | 'visibilitychange') {\n const engagement_time_msec = session.flush();\n if (engagement_time_msec <= 0) return;\n sendBeacon('user_engagement', { engagement_time_msec, trigger });\n}\n\nfunction sendScroll() {\n const engagement_time_msec = session.flush();\n if (engagement_time_msec <= 0) return;\n track('scroll', { engagement_time_msec });\n}\n\nfunction getScrollPercent() {\n const scrollTop = window.scrollY || document.documentElement.scrollTop;\n const windowHeight = window.innerHeight;\n const docHeight = document.documentElement.scrollHeight;\n if (docHeight === 0) return 0;\n return ((scrollTop + windowHeight) * 100) / docHeight;\n}\n\nfunction onPageHide() {\n session.pagehide();\n sendUserEngagement('pagehide');\n}\n\nfunction onVisibilityChange() {\n session.visibilitychange(document.visibilityState);\n if (document.visibilityState === 'hidden') {\n sendUserEngagement('visibilitychange');\n }\n}\n\n/**\n * 1. send session_start event when the page is loaded\n * 2. send scroll event when the user scrolls more than 90% of the page\n * 3. send user_engagement event when the page is hidden or the user is not focused\n */\nexport function useWebAnalytics(pathname: string) {\n const prevPathname = usePrevious(pathname);\n\n // reset state when the pathname changes and send scroll when the user navigates to a new page\n const hasSendScroll = useRef(false);\n useEffect(() => {\n hasSendScroll.current = false;\n }, [pathname]);\n\n useEffect(() => {\n sendFirstVisit(pathname);\n track('session_start', undefined);\n\n const onScroll = throttle(() => {\n session.updateAccumulator();\n if (hasSendScroll.current) return;\n // only send scroll when the user has scrolled more than 90% of the page\n if (getScrollPercent() < 90) return;\n hasSendScroll.current = true;\n sendScroll();\n }, 500);\n\n const checkpointEvents = ['mousedown', 'keydown', 'touchstart'];\n const checkpoint = throttle(session.updateAccumulator, 1000);\n\n window.addEventListener('focus', session.focus, { passive: true });\n window.addEventListener('blur', session.blur, { passive: true });\n window.addEventListener('pageshow', session.pageshow, { passive: true });\n window.addEventListener('pagehide', onPageHide, { passive: true });\n window.addEventListener('scroll', onScroll, { passive: true });\n document.addEventListener('visibilitychange', onVisibilityChange, { passive: true });\n\n // save checkpoint\n checkpointEvents.forEach((event) => {\n window.addEventListener(event, checkpoint, { passive: true, capture: true });\n });\n\n return () => {\n window.removeEventListener('focus', session.focus);\n window.removeEventListener('blur', session.blur);\n window.removeEventListener('pageshow', session.pageshow);\n window.removeEventListener('pagehide', onPageHide);\n window.removeEventListener('scroll', onScroll);\n document.removeEventListener('visibilitychange', onVisibilityChange);\n\n checkpointEvents.forEach((event) => {\n window.removeEventListener(event, checkpoint);\n });\n\n onScroll.cancel();\n checkpoint.cancel();\n };\n }, []);\n\n useEffect(() => {\n track('page_view', {\n page_path: pathname,\n page_title: document.title,\n page_referrer: document.referrer,\n page_location: window.location.href,\n previous_page_path: prevPathname ?? undefined,\n engagement_time_msec: prevPathname ? session.flush() : undefined,\n });\n }, [pathname]);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/use-web-analytics.ts"],"sourcesContent":["import { throttle } from '@shware/utils';\nimport { useEffect, useRef } from 'react';\nimport { config } from '../setup/index';\nimport { session } from '../setup/session';\nimport { sendBeacon, track } from '../track/index';\nimport { usePrevious } from './use-previous';\n\nfunction sendFirstVisit(pathname: string) {\n const key = 'first_visit_time';\n if (config.storage.getItem(key)) return;\n track('first_visit', {\n page_path: pathname,\n page_title: document.title,\n page_referrer: document.referrer,\n page_location: window.location.href,\n });\n config.storage.setItem(key, new Date().toISOString());\n}\n\nfunction sendUserEngagement(trigger: 'pagehide' | 'visibilitychange') {\n const engagement_time_msec = session.flush();\n if (engagement_time_msec <= 0) return;\n sendBeacon('user_engagement', { engagement_time_msec, trigger });\n}\n\nfunction sendScroll() {\n const engagement_time_msec = session.flush();\n if (engagement_time_msec <= 0) return;\n track('scroll', { engagement_time_msec });\n}\n\nfunction getScrollPercent() {\n const scrollTop = window.scrollY || document.documentElement.scrollTop;\n const windowHeight = window.innerHeight;\n const docHeight = document.documentElement.scrollHeight;\n if (docHeight === 0) return 0;\n return ((scrollTop + windowHeight) * 100) / docHeight;\n}\n\nfunction onPageHide() {\n console.log('onPageHide');\n session.pagehide();\n sendUserEngagement('pagehide');\n}\n\nfunction onVisibilityChange() {\n console.log('onVisibilityChange');\n session.visibilitychange(document.visibilityState);\n if (document.visibilityState === 'hidden') {\n sendUserEngagement('visibilitychange');\n }\n}\n\n/**\n * 1. send session_start event when the page is loaded\n * 2. send scroll event when the user scrolls more than 90% of the page\n * 3. send user_engagement event when the page is hidden or the user is not focused\n */\nexport function useWebAnalytics(pathname: string) {\n const prevPathname = usePrevious(pathname);\n\n // reset state when the pathname changes and send scroll when the user navigates to a new page\n const hasSendScroll = useRef(false);\n useEffect(() => {\n hasSendScroll.current = false;\n }, [pathname]);\n\n useEffect(() => {\n sendFirstVisit(pathname);\n track('session_start', undefined);\n\n const onScroll = throttle(() => {\n session.updateAccumulator();\n if (hasSendScroll.current) return;\n // only send scroll when the user has scrolled more than 90% of the page\n if (getScrollPercent() < 90) return;\n hasSendScroll.current = true;\n sendScroll();\n }, 500);\n\n const checkpointEvents = ['mousedown', 'keydown', 'touchstart'];\n const checkpoint = throttle(session.updateAccumulator, 1000);\n\n window.addEventListener('focus', session.focus, { passive: true });\n window.addEventListener('blur', session.blur, { passive: true });\n window.addEventListener('pageshow', session.pageshow, { passive: true });\n window.addEventListener('pagehide', onPageHide, { passive: true });\n window.addEventListener('scroll', onScroll, { passive: true });\n document.addEventListener('visibilitychange', onVisibilityChange, { passive: true });\n\n // save checkpoint\n checkpointEvents.forEach((event) => {\n window.addEventListener(event, checkpoint, { passive: true, capture: true });\n });\n\n return () => {\n window.removeEventListener('focus', session.focus);\n window.removeEventListener('blur', session.blur);\n window.removeEventListener('pageshow', session.pageshow);\n window.removeEventListener('pagehide', onPageHide);\n window.removeEventListener('scroll', onScroll);\n document.removeEventListener('visibilitychange', onVisibilityChange);\n\n checkpointEvents.forEach((event) => {\n window.removeEventListener(event, checkpoint);\n });\n\n onScroll.cancel();\n checkpoint.cancel();\n };\n }, []);\n\n useEffect(() => {\n track('page_view', {\n page_path: pathname,\n page_title: document.title,\n page_referrer: document.referrer,\n page_location: window.location.href,\n previous_page_path: prevPathname ?? undefined,\n engagement_time_msec: prevPathname ? session.flush() : undefined,\n });\n }, [pathname]);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -34,10 +34,12 @@ function getScrollPercent() {
|
|
|
34
34
|
return (scrollTop + windowHeight) * 100 / docHeight;
|
|
35
35
|
}
|
|
36
36
|
function onPageHide() {
|
|
37
|
+
console.log("onPageHide");
|
|
37
38
|
session.pagehide();
|
|
38
39
|
sendUserEngagement("pagehide");
|
|
39
40
|
}
|
|
40
41
|
function onVisibilityChange() {
|
|
42
|
+
console.log("onVisibilityChange");
|
|
41
43
|
session.visibilitychange(document.visibilityState);
|
|
42
44
|
if (document.visibilityState === "hidden") {
|
|
43
45
|
sendUserEngagement("visibilitychange");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hooks/use-web-analytics.ts"],"sourcesContent":["import { throttle } from '@shware/utils';\nimport { useEffect, useRef } from 'react';\nimport { config } from '../setup/index';\nimport { session } from '../setup/session';\nimport { sendBeacon, track } from '../track/index';\nimport { usePrevious } from './use-previous';\n\nfunction sendFirstVisit(pathname: string) {\n const key = 'first_visit_time';\n if (config.storage.getItem(key)) return;\n track('first_visit', {\n page_path: pathname,\n page_title: document.title,\n page_referrer: document.referrer,\n page_location: window.location.href,\n });\n config.storage.setItem(key, new Date().toISOString());\n}\n\nfunction sendUserEngagement(trigger: 'pagehide' | 'visibilitychange') {\n const engagement_time_msec = session.flush();\n if (engagement_time_msec <= 0) return;\n sendBeacon('user_engagement', { engagement_time_msec, trigger });\n}\n\nfunction sendScroll() {\n const engagement_time_msec = session.flush();\n if (engagement_time_msec <= 0) return;\n track('scroll', { engagement_time_msec });\n}\n\nfunction getScrollPercent() {\n const scrollTop = window.scrollY || document.documentElement.scrollTop;\n const windowHeight = window.innerHeight;\n const docHeight = document.documentElement.scrollHeight;\n if (docHeight === 0) return 0;\n return ((scrollTop + windowHeight) * 100) / docHeight;\n}\n\nfunction onPageHide() {\n session.pagehide();\n sendUserEngagement('pagehide');\n}\n\nfunction onVisibilityChange() {\n session.visibilitychange(document.visibilityState);\n if (document.visibilityState === 'hidden') {\n sendUserEngagement('visibilitychange');\n }\n}\n\n/**\n * 1. send session_start event when the page is loaded\n * 2. send scroll event when the user scrolls more than 90% of the page\n * 3. send user_engagement event when the page is hidden or the user is not focused\n */\nexport function useWebAnalytics(pathname: string) {\n const prevPathname = usePrevious(pathname);\n\n // reset state when the pathname changes and send scroll when the user navigates to a new page\n const hasSendScroll = useRef(false);\n useEffect(() => {\n hasSendScroll.current = false;\n }, [pathname]);\n\n useEffect(() => {\n sendFirstVisit(pathname);\n track('session_start', undefined);\n\n const onScroll = throttle(() => {\n session.updateAccumulator();\n if (hasSendScroll.current) return;\n // only send scroll when the user has scrolled more than 90% of the page\n if (getScrollPercent() < 90) return;\n hasSendScroll.current = true;\n sendScroll();\n }, 500);\n\n const checkpointEvents = ['mousedown', 'keydown', 'touchstart'];\n const checkpoint = throttle(session.updateAccumulator, 1000);\n\n window.addEventListener('focus', session.focus, { passive: true });\n window.addEventListener('blur', session.blur, { passive: true });\n window.addEventListener('pageshow', session.pageshow, { passive: true });\n window.addEventListener('pagehide', onPageHide, { passive: true });\n window.addEventListener('scroll', onScroll, { passive: true });\n document.addEventListener('visibilitychange', onVisibilityChange, { passive: true });\n\n // save checkpoint\n checkpointEvents.forEach((event) => {\n window.addEventListener(event, checkpoint, { passive: true, capture: true });\n });\n\n return () => {\n window.removeEventListener('focus', session.focus);\n window.removeEventListener('blur', session.blur);\n window.removeEventListener('pageshow', session.pageshow);\n window.removeEventListener('pagehide', onPageHide);\n window.removeEventListener('scroll', onScroll);\n document.removeEventListener('visibilitychange', onVisibilityChange);\n\n checkpointEvents.forEach((event) => {\n window.removeEventListener(event, checkpoint);\n });\n\n onScroll.cancel();\n checkpoint.cancel();\n };\n }, []);\n\n useEffect(() => {\n track('page_view', {\n page_path: pathname,\n page_title: document.title,\n page_referrer: document.referrer,\n page_location: window.location.href,\n previous_page_path: prevPathname ?? undefined,\n engagement_time_msec: prevPathname ? session.flush() : undefined,\n });\n }, [pathname]);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/use-web-analytics.ts"],"sourcesContent":["import { throttle } from '@shware/utils';\nimport { useEffect, useRef } from 'react';\nimport { config } from '../setup/index';\nimport { session } from '../setup/session';\nimport { sendBeacon, track } from '../track/index';\nimport { usePrevious } from './use-previous';\n\nfunction sendFirstVisit(pathname: string) {\n const key = 'first_visit_time';\n if (config.storage.getItem(key)) return;\n track('first_visit', {\n page_path: pathname,\n page_title: document.title,\n page_referrer: document.referrer,\n page_location: window.location.href,\n });\n config.storage.setItem(key, new Date().toISOString());\n}\n\nfunction sendUserEngagement(trigger: 'pagehide' | 'visibilitychange') {\n const engagement_time_msec = session.flush();\n if (engagement_time_msec <= 0) return;\n sendBeacon('user_engagement', { engagement_time_msec, trigger });\n}\n\nfunction sendScroll() {\n const engagement_time_msec = session.flush();\n if (engagement_time_msec <= 0) return;\n track('scroll', { engagement_time_msec });\n}\n\nfunction getScrollPercent() {\n const scrollTop = window.scrollY || document.documentElement.scrollTop;\n const windowHeight = window.innerHeight;\n const docHeight = document.documentElement.scrollHeight;\n if (docHeight === 0) return 0;\n return ((scrollTop + windowHeight) * 100) / docHeight;\n}\n\nfunction onPageHide() {\n console.log('onPageHide');\n session.pagehide();\n sendUserEngagement('pagehide');\n}\n\nfunction onVisibilityChange() {\n console.log('onVisibilityChange');\n session.visibilitychange(document.visibilityState);\n if (document.visibilityState === 'hidden') {\n sendUserEngagement('visibilitychange');\n }\n}\n\n/**\n * 1. send session_start event when the page is loaded\n * 2. send scroll event when the user scrolls more than 90% of the page\n * 3. send user_engagement event when the page is hidden or the user is not focused\n */\nexport function useWebAnalytics(pathname: string) {\n const prevPathname = usePrevious(pathname);\n\n // reset state when the pathname changes and send scroll when the user navigates to a new page\n const hasSendScroll = useRef(false);\n useEffect(() => {\n hasSendScroll.current = false;\n }, [pathname]);\n\n useEffect(() => {\n sendFirstVisit(pathname);\n track('session_start', undefined);\n\n const onScroll = throttle(() => {\n session.updateAccumulator();\n if (hasSendScroll.current) return;\n // only send scroll when the user has scrolled more than 90% of the page\n if (getScrollPercent() < 90) return;\n hasSendScroll.current = true;\n sendScroll();\n }, 500);\n\n const checkpointEvents = ['mousedown', 'keydown', 'touchstart'];\n const checkpoint = throttle(session.updateAccumulator, 1000);\n\n window.addEventListener('focus', session.focus, { passive: true });\n window.addEventListener('blur', session.blur, { passive: true });\n window.addEventListener('pageshow', session.pageshow, { passive: true });\n window.addEventListener('pagehide', onPageHide, { passive: true });\n window.addEventListener('scroll', onScroll, { passive: true });\n document.addEventListener('visibilitychange', onVisibilityChange, { passive: true });\n\n // save checkpoint\n checkpointEvents.forEach((event) => {\n window.addEventListener(event, checkpoint, { passive: true, capture: true });\n });\n\n return () => {\n window.removeEventListener('focus', session.focus);\n window.removeEventListener('blur', session.blur);\n window.removeEventListener('pageshow', session.pageshow);\n window.removeEventListener('pagehide', onPageHide);\n window.removeEventListener('scroll', onScroll);\n document.removeEventListener('visibilitychange', onVisibilityChange);\n\n checkpointEvents.forEach((event) => {\n window.removeEventListener(event, checkpoint);\n });\n\n onScroll.cancel();\n checkpoint.cancel();\n };\n }, []);\n\n useEffect(() => {\n track('page_view', {\n page_path: pathname,\n page_title: document.title,\n page_referrer: document.referrer,\n page_location: window.location.href,\n previous_page_path: prevPathname ?? undefined,\n engagement_time_msec: prevPathname ? session.flush() : undefined,\n });\n }, [pathname]);\n}\n"],"mappings":"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","names":[]}
|
package/dist/track/index.cjs
CHANGED
|
@@ -128,7 +128,9 @@ function sendBeacon(name, properties) {
|
|
|
128
128
|
}
|
|
129
129
|
];
|
|
130
130
|
const blob = new Blob([JSON.stringify(dto)], { type: "application/json" });
|
|
131
|
-
navigator.sendBeacon(`${import_setup.config.endpoint}/events`, blob);
|
|
131
|
+
const success = navigator.sendBeacon(`${import_setup.config.endpoint}/events`, blob);
|
|
132
|
+
if (success) return;
|
|
133
|
+
console.warn("Failed to send beacon", name, properties);
|
|
132
134
|
}
|
|
133
135
|
// Annotate the CommonJS export names for ESM import in node:
|
|
134
136
|
0 && (module.exports = {
|
package/dist/track/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/track/index.ts"],"sourcesContent":["import { TokenBucket, fetch } from '@shware/utils';\nimport { cache, config } from '../setup/index';\nimport { session } from '../setup/session';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { getVisitor } from '../visitor/index';\nimport type { EventName, TrackEventResponse, TrackName, TrackProperties } from './types';\nimport type { CreateTrackEventDTO } from '../schema/index';\n\nexport interface TrackOptions {\n enableThirdPartyTracking?: boolean;\n onSucceed?: (response?: TrackEventResponse[number]) => void;\n onError?: (error: unknown) => void;\n}\n\nconst defaultOptions: TrackOptions = { enableThirdPartyTracking: true };\nconst tokenBucket = new TokenBucket({ rate: 1, capacity: 20, requested: 2 });\n\ntype Item = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n name: TrackName<any>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n properties: TrackProperties<any>;\n timestamp: string;\n options: TrackOptions;\n};\n\nasync function sendEvents(events: Item[]) {\n try {\n if (events.length === 0) return;\n\n if (session.isExpired()) {\n session.refresh();\n events.unshift({\n name: 'session_start',\n properties: {},\n options: { enableThirdPartyTracking: false },\n timestamp: new Date().toISOString(),\n });\n } else {\n session.updateLastActiveTime();\n }\n\n await tokenBucket.removeTokens();\n\n const tags = await config.getTags();\n const visitor_id = (await getVisitor()).id;\n\n const dto: CreateTrackEventDTO = events.map((event) => ({\n name: event.name,\n properties: event.properties,\n tags,\n visitor_id,\n session_id: session.getId(),\n platform: config.platform,\n environment: config.environment,\n timestamp: event.timestamp,\n }));\n\n const response = await fetch(`${config.endpoint}/events`, {\n method: 'POST',\n credentials: 'include',\n headers: await config.getHeaders(),\n body: JSON.stringify(dto),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to send track event: ${response.status} ${await response.text()}`);\n }\n\n const data = (await response.json()) as TrackEventResponse;\n\n let index = 0;\n while (events.length > 0) {\n const { options, name, properties } = events.shift()!;\n const eventId = data[index].id;\n options.onSucceed?.({ id: eventId });\n index++;\n if (\n !config.thirdPartyTrackers ||\n !options.enableThirdPartyTracking ||\n IGNORED_EVENTS.includes(name)\n ) {\n continue;\n }\n config.thirdPartyTrackers.forEach((tracker) => tracker(name, properties, eventId));\n }\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n events.forEach((event) => event.options.onError?.(e));\n }\n}\n\nconst batch = 10;\nconst delay = 2000;\nconst list: Item[] = [];\nlet timer: ReturnType<typeof setTimeout> | null = null;\n\nexport function track<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n list.push({ name, properties, options, timestamp: new Date().toISOString() });\n if (list.length >= batch) {\n const copy = [...list];\n list.length = 0;\n sendEvents(copy);\n return;\n }\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n const copy = [...list];\n list.length = 0;\n sendEvents(copy);\n }, delay);\n}\n\nexport async function trackAsync<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n await sendEvents([{ name, properties, options, timestamp: new Date().toISOString() }]);\n}\n\nexport function sendBeacon<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n) {\n if (!cache.tags || !cache.visitor) return;\n
|
|
1
|
+
{"version":3,"sources":["../../src/track/index.ts"],"sourcesContent":["import { TokenBucket, fetch } from '@shware/utils';\nimport { cache, config } from '../setup/index';\nimport { session } from '../setup/session';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { getVisitor } from '../visitor/index';\nimport type { EventName, TrackEventResponse, TrackName, TrackProperties } from './types';\nimport type { CreateTrackEventDTO } from '../schema/index';\n\nexport interface TrackOptions {\n enableThirdPartyTracking?: boolean;\n onSucceed?: (response?: TrackEventResponse[number]) => void;\n onError?: (error: unknown) => void;\n}\n\nconst defaultOptions: TrackOptions = { enableThirdPartyTracking: true };\nconst tokenBucket = new TokenBucket({ rate: 1, capacity: 20, requested: 2 });\n\ntype Item = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n name: TrackName<any>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n properties: TrackProperties<any>;\n timestamp: string;\n options: TrackOptions;\n};\n\nasync function sendEvents(events: Item[]) {\n try {\n if (events.length === 0) return;\n\n if (session.isExpired()) {\n session.refresh();\n events.unshift({\n name: 'session_start',\n properties: {},\n options: { enableThirdPartyTracking: false },\n timestamp: new Date().toISOString(),\n });\n } else {\n session.updateLastActiveTime();\n }\n\n await tokenBucket.removeTokens();\n\n const tags = await config.getTags();\n const visitor_id = (await getVisitor()).id;\n\n const dto: CreateTrackEventDTO = events.map((event) => ({\n name: event.name,\n properties: event.properties,\n tags,\n visitor_id,\n session_id: session.getId(),\n platform: config.platform,\n environment: config.environment,\n timestamp: event.timestamp,\n }));\n\n const response = await fetch(`${config.endpoint}/events`, {\n method: 'POST',\n credentials: 'include',\n headers: await config.getHeaders(),\n body: JSON.stringify(dto),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to send track event: ${response.status} ${await response.text()}`);\n }\n\n const data = (await response.json()) as TrackEventResponse;\n\n let index = 0;\n while (events.length > 0) {\n const { options, name, properties } = events.shift()!;\n const eventId = data[index].id;\n options.onSucceed?.({ id: eventId });\n index++;\n if (\n !config.thirdPartyTrackers ||\n !options.enableThirdPartyTracking ||\n IGNORED_EVENTS.includes(name)\n ) {\n continue;\n }\n config.thirdPartyTrackers.forEach((tracker) => tracker(name, properties, eventId));\n }\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n events.forEach((event) => event.options.onError?.(e));\n }\n}\n\nconst batch = 10;\nconst delay = 2000;\nconst list: Item[] = [];\nlet timer: ReturnType<typeof setTimeout> | null = null;\n\nexport function track<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n list.push({ name, properties, options, timestamp: new Date().toISOString() });\n if (list.length >= batch) {\n const copy = [...list];\n list.length = 0;\n sendEvents(copy);\n return;\n }\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n const copy = [...list];\n list.length = 0;\n sendEvents(copy);\n }, delay);\n}\n\nexport async function trackAsync<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n await sendEvents([{ name, properties, options, timestamp: new Date().toISOString() }]);\n}\n\nexport function sendBeacon<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n) {\n if (!cache.tags || !cache.visitor) return;\n session.updateLastActiveTime();\n\n const dto: CreateTrackEventDTO = [\n {\n name,\n properties,\n tags: cache.tags,\n visitor_id: cache.visitor.id,\n session_id: session.getId(),\n platform: config.platform,\n environment: config.environment,\n timestamp: new Date().toISOString(),\n },\n ];\n const blob = new Blob([JSON.stringify(dto)], { type: 'application/json' });\n const success = navigator.sendBeacon(`${config.endpoint}/events`, blob);\n if (success) return;\n console.warn('Failed to send beacon', name, properties);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAmC;AACnC,mBAA8B;AAC9B,qBAAwB;AACxB,4BAA+B;AAC/B,qBAA2B;AAU3B,IAAM,iBAA+B,EAAE,0BAA0B,KAAK;AACtE,IAAM,cAAc,IAAI,yBAAY,EAAE,MAAM,GAAG,UAAU,IAAI,WAAW,EAAE,CAAC;AAW3E,eAAe,WAAW,QAAgB;AA1B1C;AA2BE,MAAI;AACF,QAAI,OAAO,WAAW,EAAG;AAEzB,QAAI,uBAAQ,UAAU,GAAG;AACvB,6BAAQ,QAAQ;AAChB,aAAO,QAAQ;AAAA,QACb,MAAM;AAAA,QACN,YAAY,CAAC;AAAA,QACb,SAAS,EAAE,0BAA0B,MAAM;AAAA,QAC3C,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,CAAC;AAAA,IACH,OAAO;AACL,6BAAQ,qBAAqB;AAAA,IAC/B;AAEA,UAAM,YAAY,aAAa;AAE/B,UAAM,OAAO,MAAM,oBAAO,QAAQ;AAClC,UAAM,cAAc,UAAM,2BAAW,GAAG;AAExC,UAAM,MAA2B,OAAO,IAAI,CAAC,WAAW;AAAA,MACtD,MAAM,MAAM;AAAA,MACZ,YAAY,MAAM;AAAA,MAClB;AAAA,MACA;AAAA,MACA,YAAY,uBAAQ,MAAM;AAAA,MAC1B,UAAU,oBAAO;AAAA,MACjB,aAAa,oBAAO;AAAA,MACpB,WAAW,MAAM;AAAA,IACnB,EAAE;AAEF,UAAM,WAAW,UAAM,oBAAM,GAAG,oBAAO,QAAQ,WAAW;AAAA,MACxD,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,SAAS,MAAM,oBAAO,WAAW;AAAA,MACjC,MAAM,KAAK,UAAU,GAAG;AAAA,IAC1B,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,+BAA+B,SAAS,MAAM,IAAI,MAAM,SAAS,KAAK,CAAC,EAAE;AAAA,IAC3F;AAEA,UAAM,OAAQ,MAAM,SAAS,KAAK;AAElC,QAAI,QAAQ;AACZ,WAAO,OAAO,SAAS,GAAG;AACxB,YAAM,EAAE,SAAS,MAAM,WAAW,IAAI,OAAO,MAAM;AACnD,YAAM,UAAU,KAAK,KAAK,EAAE;AAC5B,oBAAQ,cAAR,iCAAoB,EAAE,IAAI,QAAQ;AAClC;AACA,UACE,CAAC,oBAAO,sBACR,CAAC,QAAQ,4BACT,qCAAe,SAAS,IAAI,GAC5B;AACA;AAAA,MACF;AACA,0BAAO,mBAAmB,QAAQ,CAAC,YAAY,QAAQ,MAAM,YAAY,OAAO,CAAC;AAAA,IACnF;AAAA,EACF,SAAS,GAAY;AACnB,QAAI,aAAa,MAAO,SAAQ,IAAI,EAAE,OAAO;AAC7C,WAAO,QAAQ,CAAC,UAAO;AAxF3B,UAAAA,KAAA;AAwF8B,oBAAAA,MAAA,MAAM,SAAQ,YAAd,wBAAAA,KAAwB;AAAA,KAAE;AAAA,EACtD;AACF;AAEA,IAAM,QAAQ;AACd,IAAM,QAAQ;AACd,IAAM,OAAe,CAAC;AACtB,IAAI,QAA8C;AAE3C,SAAS,MACd,MACA,YACA,UAAwB,gBACxB;AACA,OAAK,KAAK,EAAE,MAAM,YAAY,SAAS,YAAW,oBAAI,KAAK,GAAE,YAAY,EAAE,CAAC;AAC5E,MAAI,KAAK,UAAU,OAAO;AACxB,UAAM,OAAO,CAAC,GAAG,IAAI;AACrB,SAAK,SAAS;AACd,eAAW,IAAI;AACf;AAAA,EACF;AACA,MAAI,MAAO,cAAa,KAAK;AAC7B,UAAQ,WAAW,MAAM;AACvB,YAAQ;AACR,UAAM,OAAO,CAAC,GAAG,IAAI;AACrB,SAAK,SAAS;AACd,eAAW,IAAI;AAAA,EACjB,GAAG,KAAK;AACV;AAEA,eAAsB,WACpB,MACA,YACA,UAAwB,gBACxB;AACA,QAAM,WAAW,CAAC,EAAE,MAAM,YAAY,SAAS,YAAW,oBAAI,KAAK,GAAE,YAAY,EAAE,CAAC,CAAC;AACvF;AAEO,SAAS,WACd,MACA,YACA;AACA,MAAI,CAAC,mBAAM,QAAQ,CAAC,mBAAM,QAAS;AACnC,yBAAQ,qBAAqB;AAE7B,QAAM,MAA2B;AAAA,IAC/B;AAAA,MACE;AAAA,MACA;AAAA,MACA,MAAM,mBAAM;AAAA,MACZ,YAAY,mBAAM,QAAQ;AAAA,MAC1B,YAAY,uBAAQ,MAAM;AAAA,MAC1B,UAAU,oBAAO;AAAA,MACjB,aAAa,oBAAO;AAAA,MACpB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AAAA,EACF;AACA,QAAM,OAAO,IAAI,KAAK,CAAC,KAAK,UAAU,GAAG,CAAC,GAAG,EAAE,MAAM,mBAAmB,CAAC;AACzE,QAAM,UAAU,UAAU,WAAW,GAAG,oBAAO,QAAQ,WAAW,IAAI;AACtE,MAAI,QAAS;AACb,UAAQ,KAAK,yBAAyB,MAAM,UAAU;AACxD;","names":["_a"]}
|
package/dist/track/index.mjs
CHANGED
|
@@ -102,7 +102,9 @@ function sendBeacon(name, properties) {
|
|
|
102
102
|
}
|
|
103
103
|
];
|
|
104
104
|
const blob = new Blob([JSON.stringify(dto)], { type: "application/json" });
|
|
105
|
-
navigator.sendBeacon(`${config.endpoint}/events`, blob);
|
|
105
|
+
const success = navigator.sendBeacon(`${config.endpoint}/events`, blob);
|
|
106
|
+
if (success) return;
|
|
107
|
+
console.warn("Failed to send beacon", name, properties);
|
|
106
108
|
}
|
|
107
109
|
export {
|
|
108
110
|
sendBeacon,
|
package/dist/track/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/track/index.ts"],"sourcesContent":["import { TokenBucket, fetch } from '@shware/utils';\nimport { cache, config } from '../setup/index';\nimport { session } from '../setup/session';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { getVisitor } from '../visitor/index';\nimport type { EventName, TrackEventResponse, TrackName, TrackProperties } from './types';\nimport type { CreateTrackEventDTO } from '../schema/index';\n\nexport interface TrackOptions {\n enableThirdPartyTracking?: boolean;\n onSucceed?: (response?: TrackEventResponse[number]) => void;\n onError?: (error: unknown) => void;\n}\n\nconst defaultOptions: TrackOptions = { enableThirdPartyTracking: true };\nconst tokenBucket = new TokenBucket({ rate: 1, capacity: 20, requested: 2 });\n\ntype Item = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n name: TrackName<any>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n properties: TrackProperties<any>;\n timestamp: string;\n options: TrackOptions;\n};\n\nasync function sendEvents(events: Item[]) {\n try {\n if (events.length === 0) return;\n\n if (session.isExpired()) {\n session.refresh();\n events.unshift({\n name: 'session_start',\n properties: {},\n options: { enableThirdPartyTracking: false },\n timestamp: new Date().toISOString(),\n });\n } else {\n session.updateLastActiveTime();\n }\n\n await tokenBucket.removeTokens();\n\n const tags = await config.getTags();\n const visitor_id = (await getVisitor()).id;\n\n const dto: CreateTrackEventDTO = events.map((event) => ({\n name: event.name,\n properties: event.properties,\n tags,\n visitor_id,\n session_id: session.getId(),\n platform: config.platform,\n environment: config.environment,\n timestamp: event.timestamp,\n }));\n\n const response = await fetch(`${config.endpoint}/events`, {\n method: 'POST',\n credentials: 'include',\n headers: await config.getHeaders(),\n body: JSON.stringify(dto),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to send track event: ${response.status} ${await response.text()}`);\n }\n\n const data = (await response.json()) as TrackEventResponse;\n\n let index = 0;\n while (events.length > 0) {\n const { options, name, properties } = events.shift()!;\n const eventId = data[index].id;\n options.onSucceed?.({ id: eventId });\n index++;\n if (\n !config.thirdPartyTrackers ||\n !options.enableThirdPartyTracking ||\n IGNORED_EVENTS.includes(name)\n ) {\n continue;\n }\n config.thirdPartyTrackers.forEach((tracker) => tracker(name, properties, eventId));\n }\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n events.forEach((event) => event.options.onError?.(e));\n }\n}\n\nconst batch = 10;\nconst delay = 2000;\nconst list: Item[] = [];\nlet timer: ReturnType<typeof setTimeout> | null = null;\n\nexport function track<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n list.push({ name, properties, options, timestamp: new Date().toISOString() });\n if (list.length >= batch) {\n const copy = [...list];\n list.length = 0;\n sendEvents(copy);\n return;\n }\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n const copy = [...list];\n list.length = 0;\n sendEvents(copy);\n }, delay);\n}\n\nexport async function trackAsync<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n await sendEvents([{ name, properties, options, timestamp: new Date().toISOString() }]);\n}\n\nexport function sendBeacon<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n) {\n if (!cache.tags || !cache.visitor) return;\n
|
|
1
|
+
{"version":3,"sources":["../../src/track/index.ts"],"sourcesContent":["import { TokenBucket, fetch } from '@shware/utils';\nimport { cache, config } from '../setup/index';\nimport { session } from '../setup/session';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { getVisitor } from '../visitor/index';\nimport type { EventName, TrackEventResponse, TrackName, TrackProperties } from './types';\nimport type { CreateTrackEventDTO } from '../schema/index';\n\nexport interface TrackOptions {\n enableThirdPartyTracking?: boolean;\n onSucceed?: (response?: TrackEventResponse[number]) => void;\n onError?: (error: unknown) => void;\n}\n\nconst defaultOptions: TrackOptions = { enableThirdPartyTracking: true };\nconst tokenBucket = new TokenBucket({ rate: 1, capacity: 20, requested: 2 });\n\ntype Item = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n name: TrackName<any>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n properties: TrackProperties<any>;\n timestamp: string;\n options: TrackOptions;\n};\n\nasync function sendEvents(events: Item[]) {\n try {\n if (events.length === 0) return;\n\n if (session.isExpired()) {\n session.refresh();\n events.unshift({\n name: 'session_start',\n properties: {},\n options: { enableThirdPartyTracking: false },\n timestamp: new Date().toISOString(),\n });\n } else {\n session.updateLastActiveTime();\n }\n\n await tokenBucket.removeTokens();\n\n const tags = await config.getTags();\n const visitor_id = (await getVisitor()).id;\n\n const dto: CreateTrackEventDTO = events.map((event) => ({\n name: event.name,\n properties: event.properties,\n tags,\n visitor_id,\n session_id: session.getId(),\n platform: config.platform,\n environment: config.environment,\n timestamp: event.timestamp,\n }));\n\n const response = await fetch(`${config.endpoint}/events`, {\n method: 'POST',\n credentials: 'include',\n headers: await config.getHeaders(),\n body: JSON.stringify(dto),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to send track event: ${response.status} ${await response.text()}`);\n }\n\n const data = (await response.json()) as TrackEventResponse;\n\n let index = 0;\n while (events.length > 0) {\n const { options, name, properties } = events.shift()!;\n const eventId = data[index].id;\n options.onSucceed?.({ id: eventId });\n index++;\n if (\n !config.thirdPartyTrackers ||\n !options.enableThirdPartyTracking ||\n IGNORED_EVENTS.includes(name)\n ) {\n continue;\n }\n config.thirdPartyTrackers.forEach((tracker) => tracker(name, properties, eventId));\n }\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n events.forEach((event) => event.options.onError?.(e));\n }\n}\n\nconst batch = 10;\nconst delay = 2000;\nconst list: Item[] = [];\nlet timer: ReturnType<typeof setTimeout> | null = null;\n\nexport function track<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n list.push({ name, properties, options, timestamp: new Date().toISOString() });\n if (list.length >= batch) {\n const copy = [...list];\n list.length = 0;\n sendEvents(copy);\n return;\n }\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n const copy = [...list];\n list.length = 0;\n sendEvents(copy);\n }, delay);\n}\n\nexport async function trackAsync<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n await sendEvents([{ name, properties, options, timestamp: new Date().toISOString() }]);\n}\n\nexport function sendBeacon<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n) {\n if (!cache.tags || !cache.visitor) return;\n session.updateLastActiveTime();\n\n const dto: CreateTrackEventDTO = [\n {\n name,\n properties,\n tags: cache.tags,\n visitor_id: cache.visitor.id,\n session_id: session.getId(),\n platform: config.platform,\n environment: config.environment,\n timestamp: new Date().toISOString(),\n },\n ];\n const blob = new Blob([JSON.stringify(dto)], { type: 'application/json' });\n const success = navigator.sendBeacon(`${config.endpoint}/events`, blob);\n if (success) return;\n console.warn('Failed to send beacon', name, properties);\n}\n"],"mappings":"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","names":["_a"]}
|