Files
web-temp/layers/composables/useAnalytics.ts
2026-02-09 06:24:48 +00:00

391 lines
10 KiB
TypeScript

import * as amplitude from '@amplitude/analytics-browser'
import type { TrackingObject } from '#layers/types/api/common'
import type { AnalyticsDetailType } from '#layers/types/AnalyticsType'
import type {
IdentityInfo,
ActionInfo,
MarketingInfo,
} from '#layers/types/Stove'
declare const svcLog: any
declare const twq: any
declare const ttq: any
// ============================================================================
// 유틸 함수
// ============================================================================
/** 브라우저 환경인지 체크 */
const isClient = () => typeof window !== 'undefined' && import.meta.client
/** Analytics 객체가 비어있는지 체크 */
const isEmptyAnalytics = (analytics?: Partial<AnalyticsDetailType>) =>
!analytics || Object.keys(analytics).length === 0
// ============================================================================
// Analytics 데이터 생성
// ============================================================================
/**
* 로그 데이터를 생성하는 composable (직접 객체 반환)
* @param analytics 트래킹 데이터
* @param type 'page' | 'template'
* @returns 분석용 로그 데이터 객체 (ref 없이)
*/
export const useAnalyticsData = (
analytics: string | TrackingObject
): Partial<AnalyticsDetailType> => {
const pageDataStore = usePageDataStore()
const { pageName, pageNameEn } = storeToRefs(pageDataStore)
if (!analytics || !pageName.value || !pageNameEn.value) {
return {}
}
const baseEvent = pageName.value
const baseViewArea = pageNameEn.value
let logData = {} as AnalyticsDetailType
// 페이지뷰
if (analytics === 'view') {
logData = {
actionType: analytics,
event: baseEvent,
eventCategory: baseEvent,
viewArea: baseViewArea,
viewType: 'pageView',
} as AnalyticsDetailType
return logData
}
// 객체인 경우
if (typeof analytics === 'object') {
const { action_type, click_sarea, click_item } = analytics
logData = {
actionType: action_type,
event: baseEvent,
eventCategory: `${baseEvent}_${click_sarea}_${click_item}`,
} as AnalyticsDetailType
if (action_type === 'click') {
logData.clickArea = baseViewArea
logData.clickSarea = click_sarea
logData.clickItem = click_item
} else if (action_type === 'view') {
logData.viewArea = baseViewArea
logData.viewType = 'view_frame'
}
}
return logData
}
// ============================================================================
// Stove Analytics(81 Plug) + Amplitude
// ============================================================================
/**
* Stove Analytics(81 Plug) 전송
*
* @param analytics Partial<AnalyticsDetailType>
* @param param1 mcode, options
*/
const sendSA = (
analytics: Partial<AnalyticsDetailType>,
{ mcode = '', options = {} }: { mcode?: string; options?: any } = {}
) => {
if (!isClient()) return
if (isEmptyAnalytics(analytics)) return
const gameDataStore = useGameDataStore()
const { gameCode } = storeToRefs(gameDataStore)
try {
const device = useDevice()
const gameNo = gameCode.value
const deviceType = device.isDesktop ? 'pcweb' : 'mobileweb'
const country = `${csrGetCountry()}`
const memberNo = `${csrGetStoveMemberNo()}`
const {
actionType = '',
logSourceType = '',
viewArea = '',
viewType = '',
clickArea = '',
clickSarea = '',
eventLocale = '',
clickItem,
} = analytics
const identityInfo: IdentityInfo = {
app_id: 'stove',
log_source_type: logSourceType,
country,
locale: eventLocale,
lang_cd: eventLocale,
member_no: memberNo,
channeling_cd: 'SO',
}
const marketingInfo: MarketingInfo = {
marketing_code: mcode || '',
device_type: deviceType,
media_type: '',
media_page: '',
}
let actionParam: ActionInfo['action_param'] = {}
if (actionType === 'view') {
actionParam = {
view_area: viewArea,
view_type: viewType,
view_info: {
game_no: gameNo,
lang_cd: eventLocale,
service_cd: 'official_home',
...options?.viewInfo,
},
}
} else if (actionType === 'click') {
actionParam = {
click_area: clickArea,
click_sarea: clickSarea,
click_item: {
pwt_click_item: clickItem,
game_no: gameNo,
lang_cd: eventLocale,
service_cd: 'official_home',
...options?.clickItem,
},
}
}
const actionInfo: ActionInfo = {
action_type: actionType,
action_param: actionParam,
marketing_info: marketingInfo,
}
const amplitudeActionInfo = {
...actionInfo,
url: isClient() ? `${location?.href || ''}` : '',
agent: isClient() ? `${navigator?.userAgent || ''}` : '',
}
const amplitudeActionParams: {
event_type: string
event_properties: ActionInfo & { url: string; agent: string }
} = {
event_type: actionType,
event_properties: amplitudeActionInfo,
}
// 81plug
svcLog.identity(identityInfo)
// 81plug warning log 제거를 위해 2번째 인자부터 빈 객체 세팅
svcLog.action(actionInfo, {}, {})
// Amplitude
amplitude.track(amplitudeActionParams)
} catch (e) {
console.error('[Exception] useAnalytics.sendSA: ', e)
}
}
// ============================================================================
// Google Analytics
// ============================================================================
/** GA 공통 설정 래퍼 */
const withGA = (callback: (gtag: any) => void) => {
if (!isClient()) return
try {
const { gtag } = useGtag()
const hostname = window?.location?.hostname || ''
gtag('set', 'cookie_domain', hostname) // env 값으로 설정 시 쿠키 생성 안 돼서 window.location.hostname으로 설정
gtag('set', 'cookie_expires', '0') // 0으로 설정 시 쿠키가 Session 기반 쿠키로 전환
callback(gtag)
} catch (e) {
console.error('[Exception] useAnalytics.withGA: ', e)
}
}
/**
* Google Analytics 전송
*
* @param analytics Partial<AnalyticsDetailType>
*/
const sendGA = (analytics: Partial<AnalyticsDetailType>) => {
if (isEmptyAnalytics(analytics)) return
withGA(gtag => {
const eventName = analytics.event || ''
const eventLocale = analytics.eventLocale || ''
const eventCategory = analytics.eventCategory || ''
// GA 클릭 이벤트 명 뒤에 언어 값 추가 노출되도록 개발. ex) GNB_자유게시판_KO
const eventLabel = `${eventCategory}_${eventLocale}`
gtag('event', `${eventName}`, {
event_category: eventLabel,
})
})
}
/**
* Google Analytics 전송 (기본 이벤트만 전송)
*
* @param gaEventName 이벤트명
*/
const sendGAEventOnly = (gaEventName: string) => {
if (!gaEventName) return
withGA(gtag => {
gtag('event', `${gaEventName}`)
})
}
// ============================================================================
// 기본 로그 일괄 전송
// ============================================================================
/**
* 기본 로그 일괄 전송
*
* @param locale 언어 코드
* @param analytics Partial<AnalyticsDetailType>
*/
const sendLog = (
locale: string,
analytics: string | TrackingObject,
options: { useGA?: boolean } = { useGA: false }
) => {
const analyticsData = useAnalyticsData(analytics)
analyticsData.eventLocale = locale.toUpperCase()
// SA 전송 : actionType, logSourceType 유무로 판별
if (analyticsData.actionType && analyticsData.actionType !== '') {
sendSA(analyticsData, {
mcode: analyticsData?.mcode,
options: analyticsData?.options,
})
if (options.useGA) {
sendGA(analyticsData)
}
}
}
// ============================================================================
// Meta / Twitter / TikTok Pixel
// ============================================================================
/**
* 메타 픽셀 전송
*
* @param fbEventName 이벤트명
* @description 수집 대상 페이지에 useHead({ meta: [loadMetaPixelMeta()] }) 선언
*/
const sendMetaPixel = (fbEventName?: string) => {
if (!isClient() || !fbEventName) return
try {
const { $fbq } = useNuxtApp()
if (typeof $fbq === 'function') {
$fbq('trackCustom', fbEventName)
}
} catch (e) {
console.error('[Exception] useAnalytics.sendMetaPixel: ', e)
}
}
/**
* X(트위터) 픽셀 전송
*
* @param twEventName 이벤트명
* @description 수집 대상 페이지에 useHead({ script: [loadTwitterPixelScript()] }) 선언
*/
const sendTwitterPixel = (twEventName?: string) => {
if (!isClient() || !twEventName) return
try {
twq('event', twEventName, {})
} catch (e) {
console.error('[Exception] useAnalytics.sendTwitterPixel: ', e)
}
}
/**
* 틱톡 픽셀 전송
*
* @param ttEventName 이벤트명
* @description 수집 대상 페이지에 onMounted(() => { loadTikTokPixelScript() }) 선언
*/
const sendTiktokPixel = (ttEventName?: string) => {
if (!isClient() || !ttEventName) return
try {
ttq.track(ttEventName)
} catch (e) {
console.error('[Exception] useAnalytics.sendTiktokPixel: ', e)
}
}
// ============================================================================
// 마케팅 스크립트 일괄 전송
// ============================================================================
/**
* 마케팅 인텔리전스 팀 요청 마케팅 스크립트 일괄 전송
*
* @param logName 'ga' | 'meta' | 'twitter' | 'tiktok'
* @param eventName 이벤트명 (ga: eventOnly, meta/twitter/tiktok: 필수)
* @param analytics GA용 analytics 데이터 (eventName 없을 때만 사용)
*/
const sendMarketingLog = ({
logName,
eventName,
}: {
logName: 'ga' | 'meta' | 'twitter' | 'tiktok'
eventName?: string
}) => {
switch (logName) {
case 'ga': {
sendGAEventOnly(eventName)
break
}
case 'meta': {
sendMetaPixel(eventName)
break
}
case 'twitter': {
sendTwitterPixel(eventName)
break
}
case 'tiktok': {
sendTiktokPixel(eventName)
break
}
default:
break
}
}
// ============================================================================
// export
// ============================================================================
export default () => {
return {
sendLog,
sendMarketingLog,
useAnalyticsData,
}
}