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) => !analytics || Object.keys(analytics).length === 0 // ============================================================================ // Analytics 데이터 생성 // ============================================================================ /** * 로그 데이터를 생성하는 composable (직접 객체 반환) * @param analytics 트래킹 데이터 * @param type 'page' | 'template' * @returns 분석용 로그 데이터 객체 (ref 없이) */ export const useAnalyticsData = ( analytics: string | TrackingObject ): Partial => { 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 * @param param1 mcode, options */ const sendSA = ( analytics: Partial, { 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 */ const sendGA = (analytics: Partial) => { 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 */ 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, } }