통계 콘텐츠 세션 원장 추가
This commit is contained in:
@@ -18,11 +18,17 @@ const READ_MIN_SCROLL_RATIO = 0.5
|
||||
/** @type {number} heartbeat 전송 간격(ms) */
|
||||
const HEARTBEAT_INTERVAL_MS = 20000
|
||||
|
||||
/** @type {Set<string>} 세션 내 전송 완료된 pageview 키 */
|
||||
const sentViewKeys = new Set()
|
||||
/** @type {string} 마지막 pageview 키 */
|
||||
let lastPageviewKey = ''
|
||||
|
||||
/** @type {Set<string>} 세션 내 전송 완료된 read slug */
|
||||
const sentReadSlugs = new Set()
|
||||
/** @type {number} 마지막 pageview 전송 시각 */
|
||||
let lastPageviewSentAt = 0
|
||||
|
||||
/** @type {string} 마지막 pageview 콘텐츠 세션 ID */
|
||||
let lastPageviewContentSessionId = ''
|
||||
|
||||
/** @type {Set<string>} 전송 완료된 read 콘텐츠 세션 ID */
|
||||
const sentReadContentSessionIds = new Set()
|
||||
|
||||
/** @type {number | null} read 폴링 타이머 */
|
||||
let readPollTimer = null
|
||||
@@ -51,6 +57,9 @@ let currentPostSlug = ''
|
||||
/** @type {string} 현재 추적 페이지 slug */
|
||||
let currentPageSlug = ''
|
||||
|
||||
/** @type {string} 현재 콘텐츠 단위 세션 ID */
|
||||
let currentContentSessionId = ''
|
||||
|
||||
/** @type {number} 현재 페이지 체류 시작 시각 */
|
||||
let pageStartedAt = 0
|
||||
|
||||
@@ -76,6 +85,18 @@ const getClientSessionId = () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 콘텐츠 단위 세션 ID를 생성한다.
|
||||
* @returns {string} 콘텐츠 세션 ID
|
||||
*/
|
||||
const createContentSessionId = () => {
|
||||
try {
|
||||
return crypto.randomUUID()
|
||||
} catch {
|
||||
return `content-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 탭 최초 유입 referrer를 반환한다.
|
||||
* @returns {string} referrer
|
||||
@@ -189,6 +210,7 @@ const clearPageTracking = () => {
|
||||
currentPath = ''
|
||||
currentPostSlug = ''
|
||||
currentPageSlug = ''
|
||||
currentContentSessionId = ''
|
||||
pageStartedAt = 0
|
||||
maxScrollRatio = 0
|
||||
}
|
||||
@@ -223,7 +245,7 @@ const sendAnalyticsPayload = (endpoint, payload) => {
|
||||
|
||||
/**
|
||||
* pageview 이벤트를 전송한다.
|
||||
* @param {{ path: string, postSlug?: string, pageSlug?: string, read?: boolean }} payload - 전송 본문
|
||||
* @param {{ path: string, postSlug?: string, pageSlug?: string, contentSessionId?: string, read?: boolean }} payload - 전송 본문
|
||||
* @returns {void}
|
||||
*/
|
||||
const sendPageviewEvent = (payload) => {
|
||||
@@ -231,6 +253,8 @@ const sendPageviewEvent = (payload) => {
|
||||
path: payload.path,
|
||||
postSlug: payload.postSlug || '',
|
||||
pageSlug: payload.pageSlug || '',
|
||||
clientSessionId: getClientSessionId(),
|
||||
contentSessionId: payload.contentSessionId || '',
|
||||
referrer: getInitialReferrer(),
|
||||
currentUrl: window.location.href,
|
||||
read: Boolean(payload.read)
|
||||
@@ -239,7 +263,7 @@ const sendPageviewEvent = (payload) => {
|
||||
|
||||
/**
|
||||
* heartbeat 이벤트를 전송한다.
|
||||
* @param {{ path: string, postSlug?: string, pageSlug?: string, durationSeconds: number, maxScrollRatio: number }} payload - 전송 본문
|
||||
* @param {{ path: string, postSlug?: string, pageSlug?: string, contentSessionId?: string, durationSeconds: number, maxScrollRatio: number }} payload - 전송 본문
|
||||
* @returns {void}
|
||||
*/
|
||||
const sendHeartbeatEvent = (payload) => {
|
||||
@@ -248,6 +272,7 @@ const sendHeartbeatEvent = (payload) => {
|
||||
postSlug: payload.postSlug || '',
|
||||
pageSlug: payload.pageSlug || '',
|
||||
clientSessionId: getClientSessionId(),
|
||||
contentSessionId: payload.contentSessionId || '',
|
||||
durationSeconds: payload.durationSeconds,
|
||||
maxScrollRatio: payload.maxScrollRatio
|
||||
})
|
||||
@@ -268,6 +293,7 @@ const sendCurrentHeartbeat = () => {
|
||||
path: currentPath,
|
||||
postSlug: currentPostSlug,
|
||||
pageSlug: currentPageSlug,
|
||||
contentSessionId: currentContentSessionId,
|
||||
durationSeconds: getCurrentDurationSeconds(),
|
||||
maxScrollRatio
|
||||
})
|
||||
@@ -278,7 +304,7 @@ const sendCurrentHeartbeat = () => {
|
||||
* @returns {void}
|
||||
*/
|
||||
const trySendReadEvent = () => {
|
||||
if (!readPostSlug || sentReadSlugs.has(readPostSlug)) {
|
||||
if (!readPostSlug || !currentContentSessionId || sentReadContentSessionIds.has(currentContentSessionId)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -293,10 +319,11 @@ const trySendReadEvent = () => {
|
||||
return
|
||||
}
|
||||
|
||||
sentReadSlugs.add(readPostSlug)
|
||||
sentReadContentSessionIds.add(currentContentSessionId)
|
||||
sendPageviewEvent({
|
||||
path: readPath,
|
||||
postSlug: readPostSlug,
|
||||
contentSessionId: currentContentSessionId,
|
||||
read: true
|
||||
})
|
||||
}
|
||||
@@ -356,13 +383,21 @@ const trackRouteAnalytics = (route) => {
|
||||
const postSlug = extractPostSlugFromRoute(route)
|
||||
const pageSlug = postSlug ? '' : extractPageSlugFromRoute(route)
|
||||
const viewKey = `view:${path}:${postSlug}:${pageSlug}`
|
||||
const now = Date.now()
|
||||
const shouldSendPageview = viewKey !== lastPageviewKey || now - lastPageviewSentAt > 1000
|
||||
const contentSessionId = shouldSendPageview
|
||||
? createContentSessionId()
|
||||
: lastPageviewContentSessionId || createContentSessionId()
|
||||
|
||||
if (!sentViewKeys.has(viewKey)) {
|
||||
sentViewKeys.add(viewKey)
|
||||
if (shouldSendPageview) {
|
||||
lastPageviewKey = viewKey
|
||||
lastPageviewSentAt = now
|
||||
lastPageviewContentSessionId = contentSessionId
|
||||
sendPageviewEvent({
|
||||
path,
|
||||
postSlug,
|
||||
pageSlug,
|
||||
contentSessionId,
|
||||
read: false
|
||||
})
|
||||
}
|
||||
@@ -372,6 +407,7 @@ const trackRouteAnalytics = (route) => {
|
||||
currentPath = path
|
||||
currentPostSlug = postSlug
|
||||
currentPageSlug = pageSlug
|
||||
currentContentSessionId = contentSessionId
|
||||
pageStartedAt = Date.now()
|
||||
maxScrollRatio = getDocumentScrollRatio()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user