v1.12.30 긴 접속 시간 표시와 중단 후 새 방문 처리 개선
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
import {
|
||||
ANALYTICS_MAX_DURATION_SECONDS,
|
||||
isTrackableAnalyticsPath
|
||||
} from '../lib/analytics-shared.js'
|
||||
import { isTrackableAnalyticsPath } from '../lib/analytics-shared.js'
|
||||
import { createAnalyticsDurationTracker } from '../lib/analytics-duration.js'
|
||||
|
||||
/** @type {string} 탭 단위 클라이언트 세션 storage 키 */
|
||||
const CLIENT_SESSION_STORAGE_KEY = 'sori_analytics_client_session'
|
||||
@@ -39,9 +37,6 @@ let heartbeatTimer = null
|
||||
/** @type {(() => void) | null} scroll 리스너 */
|
||||
let readScrollListener = null
|
||||
|
||||
/** @type {number} read 추적 시작 시각 */
|
||||
let readStartedAt = 0
|
||||
|
||||
/** @type {string} read 추적 중인 게시물 slug */
|
||||
let readPostSlug = ''
|
||||
|
||||
@@ -60,8 +55,8 @@ let currentPageSlug = ''
|
||||
/** @type {string} 현재 콘텐츠 단위 세션 ID */
|
||||
let currentContentSessionId = ''
|
||||
|
||||
/** @type {number} 현재 페이지 체류 시작 시각 */
|
||||
let pageStartedAt = 0
|
||||
/** @type {ReturnType<typeof createAnalyticsDurationTracker> | null} 현재 페이지 접속 시간 측정기 */
|
||||
let pageDurationTracker = null
|
||||
|
||||
/** @type {number} 현재 페이지 최대 스크롤 비율 */
|
||||
let maxScrollRatio = 0
|
||||
@@ -163,19 +158,6 @@ const getDocumentScrollRatio = () => {
|
||||
return Math.min(scrollTop / maxScroll, 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 페이지 체류시간(초)을 반환한다.
|
||||
* @returns {number} 체류시간(초)
|
||||
*/
|
||||
const getCurrentDurationSeconds = () => {
|
||||
if (!pageStartedAt) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const elapsedMs = Date.now() - pageStartedAt
|
||||
return Math.min(Math.floor(elapsedMs / 1000), ANALYTICS_MAX_DURATION_SECONDS)
|
||||
}
|
||||
|
||||
/**
|
||||
* 스크롤 비율을 갱신한다.
|
||||
* @returns {void}
|
||||
@@ -206,12 +188,11 @@ const clearPageTracking = () => {
|
||||
|
||||
readPostSlug = ''
|
||||
readPath = ''
|
||||
readStartedAt = 0
|
||||
currentPath = ''
|
||||
currentPostSlug = ''
|
||||
currentPageSlug = ''
|
||||
currentContentSessionId = ''
|
||||
pageStartedAt = 0
|
||||
pageDurationTracker = null
|
||||
maxScrollRatio = 0
|
||||
}
|
||||
|
||||
@@ -278,15 +259,31 @@ const sendHeartbeatEvent = (payload) => {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 방문 시간을 측정하고 긴 실행 중단 후 새 방문으로 전환한다.
|
||||
* @returns {number} 현재 방문의 접속 시간(초)
|
||||
*/
|
||||
const updateCurrentVisit = () => {
|
||||
if (!pageDurationTracker) return 0
|
||||
const { durationSeconds, restarted } = pageDurationTracker()
|
||||
if (restarted) {
|
||||
currentContentSessionId = createContentSessionId()
|
||||
lastPageviewContentSessionId = currentContentSessionId
|
||||
lastPageviewKey = `view:${currentPath}:${currentPostSlug}:${currentPageSlug}`
|
||||
lastPageviewSentAt = Date.now()
|
||||
maxScrollRatio = getDocumentScrollRatio()
|
||||
sendPageviewEvent({ path: currentPath, postSlug: currentPostSlug, pageSlug: currentPageSlug, contentSessionId: currentContentSessionId, read: false })
|
||||
}
|
||||
return durationSeconds
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 페이지 heartbeat를 전송한다.
|
||||
* @returns {void}
|
||||
*/
|
||||
const sendCurrentHeartbeat = () => {
|
||||
if (!currentPath || !isTrackableAnalyticsPath(currentPath)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentPath || !isTrackableAnalyticsPath(currentPath) || !pageDurationTracker) return
|
||||
const durationSeconds = updateCurrentVisit()
|
||||
updateMaxScrollRatio()
|
||||
|
||||
sendHeartbeatEvent({
|
||||
@@ -294,7 +291,7 @@ const sendCurrentHeartbeat = () => {
|
||||
postSlug: currentPostSlug,
|
||||
pageSlug: currentPageSlug,
|
||||
contentSessionId: currentContentSessionId,
|
||||
durationSeconds: getCurrentDurationSeconds(),
|
||||
durationSeconds,
|
||||
maxScrollRatio
|
||||
})
|
||||
}
|
||||
@@ -304,12 +301,11 @@ const sendCurrentHeartbeat = () => {
|
||||
* @returns {void}
|
||||
*/
|
||||
const trySendReadEvent = () => {
|
||||
if (!readPostSlug || !currentContentSessionId || sentReadContentSessionIds.has(currentContentSessionId)) {
|
||||
return
|
||||
}
|
||||
if (!readPostSlug || !currentContentSessionId) return
|
||||
const durationSeconds = updateCurrentVisit()
|
||||
if (sentReadContentSessionIds.has(currentContentSessionId)) return
|
||||
|
||||
const elapsed = Date.now() - readStartedAt
|
||||
if (elapsed < READ_MIN_DURATION_MS) {
|
||||
if (durationSeconds * 1000 < READ_MIN_DURATION_MS) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -337,7 +333,6 @@ const trySendReadEvent = () => {
|
||||
const startReadTracking = (path, postSlug) => {
|
||||
readPath = path
|
||||
readPostSlug = postSlug
|
||||
readStartedAt = Date.now()
|
||||
|
||||
if (!postSlug) {
|
||||
return
|
||||
@@ -408,7 +403,7 @@ const trackRouteAnalytics = (route) => {
|
||||
currentPostSlug = postSlug
|
||||
currentPageSlug = pageSlug
|
||||
currentContentSessionId = contentSessionId
|
||||
pageStartedAt = Date.now()
|
||||
pageDurationTracker = createAnalyticsDurationTracker()
|
||||
maxScrollRatio = getDocumentScrollRatio()
|
||||
|
||||
startReadTracking(path, postSlug)
|
||||
@@ -426,17 +421,17 @@ export default defineNuxtPlugin(() => {
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const handlePageExit = () => {
|
||||
/**
|
||||
* 탭 숨김·복귀·페이지 복원 시 접속 신호를 갱신한다.
|
||||
* @returns {void}
|
||||
*/
|
||||
const handlePageActivity = () => {
|
||||
sendCurrentHeartbeat()
|
||||
}
|
||||
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
handlePageExit()
|
||||
}
|
||||
})
|
||||
|
||||
window.addEventListener('pagehide', handlePageExit)
|
||||
document.addEventListener('visibilitychange', handlePageActivity)
|
||||
window.addEventListener('pagehide', handlePageActivity)
|
||||
window.addEventListener('pageshow', handlePageActivity)
|
||||
|
||||
router.isReady().then(() => {
|
||||
trackRouteAnalytics(router.currentRoute.value)
|
||||
|
||||
Reference in New Issue
Block a user