UNPKG

@segment/analytics-next

Version:

Analytics Next (aka Analytics 2.0) is the latest version of Segment’s JavaScript SDK - enabling you to send your data to any tool without having to learn, test, or use a new API every time.

209 lines (175 loc) 5.24 kB
import jar from 'js-cookie' import type { Context } from '../../core/context' import type { Plugin } from '../../core/plugin' import { version } from '../../generated/version' import { SegmentEvent } from '../../core/events' import { Campaign, PluginType } from '@segment/analytics-core' import { getVersionType } from '../../lib/version-type' import { tld } from '../../core/user/tld' import { gracefulDecodeURIComponent } from '../../core/query-string/gracefulDecodeURIComponent' import { CookieStorage, UniversalStorage } from '../../core/storage' import { Analytics } from '../../core/analytics' import { clientHints } from '../../lib/client-hints' import { UADataValues } from '../../lib/client-hints/interfaces' let cookieOptions: jar.CookieAttributes | undefined function getCookieOptions(): jar.CookieAttributes { if (cookieOptions) { return cookieOptions } const domain = tld(window.location.href) cookieOptions = { expires: 31536000000, // 1 year secure: false, path: '/', } if (domain) { cookieOptions.domain = domain } return cookieOptions } type Ad = { id: string; type: string } function ads(query: string): Ad | undefined { const queryIds: Record<string, string> = { btid: 'dataxu', urid: 'millennial-media', } if (query.startsWith('?')) { query = query.substring(1) } query = query.replace(/\?/g, '&') const parts = query.split('&') for (const part of parts) { const [k, v] = part.split('=') if (queryIds[k]) { return { id: v, type: queryIds[k], } } } } export function utm(query: string): Campaign { if (query.startsWith('?')) { query = query.substring(1) } query = query.replace(/\?/g, '&') return query.split('&').reduce((acc, str) => { const [k, v = ''] = str.split('=') if (k.includes('utm_') && k.length > 4) { let utmParam = k.slice(4) as keyof Campaign if (utmParam === 'campaign') { utmParam = 'name' } acc[utmParam] = gracefulDecodeURIComponent(v) } return acc }, {} as Campaign) } export function ampId(): string | undefined { const ampId = jar.get('_ga') if (ampId && ampId.startsWith('amp')) { return ampId } } function referrerId( query: string, ctx: SegmentEvent['context'], disablePersistance: boolean ): void { const storage = new UniversalStorage<{ 's:context.referrer': Ad }>(disablePersistance ? [] : [new CookieStorage(getCookieOptions())]) const stored = storage.get('s:context.referrer') const ad = ads(query) ?? stored if (!ad) { return } if (ctx) { ctx.referrer = { ...ctx.referrer, ...ad } } storage.set('s:context.referrer', ad) } /** * * @param obj e.g. { foo: 'b', bar: 'd', baz: ['123', '456']} * @returns e.g. 'foo=b&bar=d&baz=123&baz=456' */ const objectToQueryString = ( obj: Record<string, string | string[]> ): string => { try { const searchParams = new URLSearchParams() Object.entries(obj).forEach(([k, v]) => { if (Array.isArray(v)) { v.forEach((value) => searchParams.append(k, value)) } else { searchParams.append(k, v) } }) return searchParams.toString() } catch { return '' } } class EnvironmentEnrichmentPlugin implements Plugin { private instance!: Analytics private userAgentData: UADataValues | undefined name = 'Page Enrichment' type: PluginType = 'before' version = '0.1.0' isLoaded = () => true load = async (_ctx: Context, instance: Analytics) => { this.instance = instance try { this.userAgentData = await clientHints( this.instance.options.highEntropyValuesClientHints ) } catch (_) { // if client hints API doesn't return anything leave undefined } return Promise.resolve() } private enrich = (ctx: Context): Context => { // Note: Types are off - context should never be undefined here, since it is set as part of event creation. const evtCtx = ctx.event.context! const search = evtCtx.page!.search || '' const query = typeof search === 'object' ? objectToQueryString(search) : search evtCtx.userAgent = navigator.userAgent evtCtx.userAgentData = this.userAgentData // @ts-ignore const locale = navigator.userLanguage || navigator.language if (typeof evtCtx.locale === 'undefined' && typeof locale !== 'undefined') { evtCtx.locale = locale } evtCtx.library ??= { name: 'analytics.js', version: `${getVersionType() === 'web' ? 'next' : 'npm:next'}-${version}`, } if (query && !evtCtx.campaign) { evtCtx.campaign = utm(query) } const amp = ampId() if (amp) { evtCtx.amp = { id: amp } } referrerId( query, evtCtx, this.instance.options.disableClientPersistence ?? false ) try { evtCtx.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone } catch (_) { // If browser doesn't have support leave timezone undefined } return ctx } track = this.enrich identify = this.enrich page = this.enrich group = this.enrich alias = this.enrich screen = this.enrich } export const envEnrichment = new EnvironmentEnrichmentPlugin()