import Cookies from 'js-cookie' import { trackEvent } from '@/app/components/base/amplitude' import { AppModeEnum } from '@/types/app' const CREATE_APP_EXTERNAL_ATTRIBUTION_STORAGE_KEY = 'create_app_external_attribution' const EXTERNAL_UTM_SOURCE_MAP = { blog: 'blog', dify_blog: 'blog', linkedin: 'linkedin', newsletter: 'blog', twitter: 'twitter/x', x: 'twitter/x', } as const type SearchParamReader = { get: (name: string) => string | null } type OriginalCreateAppMode = 'workflow' | 'chatflow' | 'agent' type TrackCreateAppParams = { appMode: AppModeEnum } type ExternalCreateAppAttribution = { utmSource: typeof EXTERNAL_UTM_SOURCE_MAP[keyof typeof EXTERNAL_UTM_SOURCE_MAP] utmCampaign?: string } const normalizeString = (value?: string | null) => { const trimmed = value?.trim() return trimmed || undefined } const getObjectStringValue = (value: unknown) => { return typeof value === 'string' ? normalizeString(value) : undefined } const getSearchParamValue = (searchParams?: SearchParamReader | null, key?: string) => { if (!searchParams || !key) return undefined return normalizeString(searchParams.get(key)) } const parseJSONRecord = (value?: string | null): Record | null => { if (!value) return null try { const parsed = JSON.parse(value) return parsed && typeof parsed === 'object' ? parsed as Record : null } catch { return null } } const getCookieUtmInfo = () => { return parseJSONRecord(Cookies.get('utm_info')) } const mapExternalUtmSource = (value?: string) => { if (!value) return undefined const normalized = value.toLowerCase() return EXTERNAL_UTM_SOURCE_MAP[normalized as keyof typeof EXTERNAL_UTM_SOURCE_MAP] } const padTimeValue = (value: number) => String(value).padStart(2, '0') const formatCreateAppTime = (date: Date) => { return `${padTimeValue(date.getMonth() + 1)}-${padTimeValue(date.getDate())}-${padTimeValue(date.getHours())}:${padTimeValue(date.getMinutes())}:${padTimeValue(date.getSeconds())}` } const mapOriginalCreateAppMode = (appMode: AppModeEnum): OriginalCreateAppMode => { if (appMode === AppModeEnum.WORKFLOW) return 'workflow' if (appMode === AppModeEnum.AGENT_CHAT) return 'agent' return 'chatflow' } export const extractExternalCreateAppAttribution = ({ searchParams, utmInfo, }: { searchParams?: SearchParamReader | null utmInfo?: Record | null }) => { const rawSource = getSearchParamValue(searchParams, 'utm_source') ?? getObjectStringValue(utmInfo?.utm_source) const mappedSource = mapExternalUtmSource(rawSource) if (!mappedSource) return null const utmCampaign = getSearchParamValue(searchParams, 'slug') ?? getSearchParamValue(searchParams, 'utm_campaign') ?? getObjectStringValue(utmInfo?.slug) ?? getObjectStringValue(utmInfo?.utm_campaign) return { utmSource: mappedSource, ...(utmCampaign ? { utmCampaign } : {}), } satisfies ExternalCreateAppAttribution } const readRememberedExternalCreateAppAttribution = (): ExternalCreateAppAttribution | null => { if (typeof window === 'undefined') return null return parseJSONRecord(window.sessionStorage.getItem(CREATE_APP_EXTERNAL_ATTRIBUTION_STORAGE_KEY)) as ExternalCreateAppAttribution | null } const writeRememberedExternalCreateAppAttribution = (attribution: ExternalCreateAppAttribution) => { if (typeof window === 'undefined') return window.sessionStorage.setItem(CREATE_APP_EXTERNAL_ATTRIBUTION_STORAGE_KEY, JSON.stringify(attribution)) } const clearRememberedExternalCreateAppAttribution = () => { if (typeof window === 'undefined') return window.sessionStorage.removeItem(CREATE_APP_EXTERNAL_ATTRIBUTION_STORAGE_KEY) } export const rememberCreateAppExternalAttribution = ({ searchParams, utmInfo, }: { searchParams?: SearchParamReader | null utmInfo?: Record | null } = {}) => { const attribution = extractExternalCreateAppAttribution({ searchParams, utmInfo: utmInfo ?? getCookieUtmInfo(), }) if (attribution) writeRememberedExternalCreateAppAttribution(attribution) return attribution } const resolveCurrentExternalCreateAppAttribution = () => { if (typeof window === 'undefined') return null return rememberCreateAppExternalAttribution({ searchParams: new URLSearchParams(window.location.search), }) ?? readRememberedExternalCreateAppAttribution() } export const buildCreateAppEventPayload = ( params: TrackCreateAppParams, externalAttribution?: ExternalCreateAppAttribution | null, currentTime = new Date(), ) => { if (externalAttribution) { return { source: 'external', utm_source: externalAttribution.utmSource, ...(externalAttribution.utmCampaign ? { utm_campaign: externalAttribution.utmCampaign } : {}), } satisfies Record } return { source: 'original', app_mode: mapOriginalCreateAppMode(params.appMode), time: formatCreateAppTime(currentTime), } satisfies Record } export const trackCreateApp = (params: TrackCreateAppParams) => { const externalAttribution = resolveCurrentExternalCreateAppAttribution() const payload = buildCreateAppEventPayload(params, externalAttribution) if (externalAttribution) clearRememberedExternalCreateAppAttribution() trackEvent('create_app', payload) }