diff --git a/.changeset/cool-peaches-pay.md b/.changeset/cool-peaches-pay.md new file mode 100644 index 000000000..3ad96cd01 --- /dev/null +++ b/.changeset/cool-peaches-pay.md @@ -0,0 +1,5 @@ +--- +'@segment/analytics-next': minor +--- + +Set timezone and allow userAgentData to be overridden diff --git a/packages/browser/src/plugins/page-enrichment/__tests__/index.test.ts b/packages/browser/src/plugins/page-enrichment/__tests__/index.test.ts index 90723cfcd..e0bee7514 100644 --- a/packages/browser/src/plugins/page-enrichment/__tests__/index.test.ts +++ b/packages/browser/src/plugins/page-enrichment/__tests__/index.test.ts @@ -6,6 +6,11 @@ import { pick } from '../../../lib/pick' import { SegmentioSettings } from '../../segmentio' import { version } from '../../../generated/version' import { CoreExtraContext } from '@segment/analytics-core' +import { UADataValues } from '../../../lib/client-hints/interfaces' +import { + highEntropyTestData, + lowEntropyTestData, +} from '../../../test-helpers/fixtures/client-hints' let ajs: Analytics @@ -262,6 +267,29 @@ describe('pageDefaults', () => { describe('Other visitor metadata', () => { let options: SegmentioSettings let analytics: Analytics + ;(window.navigator as any).userAgentData = { + ...lowEntropyTestData, + getHighEntropyValues: jest + .fn() + .mockImplementation((hints: string[]): Promise => { + let result = {} + Object.entries(highEntropyTestData).forEach(([k, v]) => { + if (hints.includes(k)) { + result = { + ...result, + [k]: v, + } + } + }) + return Promise.resolve({ + ...lowEntropyTestData, + ...result, + }) + }), + toJSON: jest.fn(() => { + return lowEntropyTestData + }), + } const amendSearchParams = (search?: any): CoreExtraContext => ({ page: { search }, @@ -283,6 +311,11 @@ describe('Other visitor metadata', () => { } }) + it('should add .timezone', async () => { + const ctx = await analytics.track('test') + assert(typeof ctx.event.context?.timezone === 'string') + }) + it('should add .library', async () => { const ctx = await analytics.track('test') assert(ctx.event.context?.library) @@ -313,6 +346,11 @@ describe('Other visitor metadata', () => { assert(userAgent1 === userAgent2) }) + it('should add .userAgentData when available', async () => { + const ctx = await analytics.track('event') + expect(ctx.event.context?.userAgentData).toEqual(lowEntropyTestData) + }) + it('should add .locale', async () => { const ctx = await analytics.track('test') assert(ctx.event.context?.locale === navigator.language) diff --git a/packages/browser/src/plugins/page-enrichment/index.ts b/packages/browser/src/plugins/page-enrichment/index.ts index 91cf1162f..b113dc337 100644 --- a/packages/browser/src/plugins/page-enrichment/index.ts +++ b/packages/browser/src/plugins/page-enrichment/index.ts @@ -10,6 +10,8 @@ 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' interface PageDefault { [key: string]: unknown @@ -174,13 +176,21 @@ function referrerId( class PageEnrichmentPlugin implements Plugin { private instance!: Analytics + private userAgentData: UADataValues | undefined name = 'Page Enrichment' type: PluginType = 'before' version = '0.1.0' isLoaded = () => true - load = (_ctx: Context, instance: Analytics) => { + 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() } @@ -213,6 +223,7 @@ class PageEnrichmentPlugin implements Plugin { const query: string = evtCtx.page.search || '' evtCtx.userAgent = navigator.userAgent + evtCtx.userAgentData = this.userAgentData // @ts-ignore const locale = navigator.userLanguage || navigator.language @@ -241,6 +252,12 @@ class PageEnrichmentPlugin implements Plugin { this.instance.options.disableClientPersistence ?? false ) + try { + evtCtx.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone + } catch (_) { + // If browser doesn't have support leave timezone undefined + } + return ctx } diff --git a/packages/browser/src/plugins/segmentio/__tests__/index.test.ts b/packages/browser/src/plugins/segmentio/__tests__/index.test.ts index d9858cb57..fdb9be1d8 100644 --- a/packages/browser/src/plugins/segmentio/__tests__/index.test.ts +++ b/packages/browser/src/plugins/segmentio/__tests__/index.test.ts @@ -5,11 +5,6 @@ import { Analytics } from '../../../core/analytics' import { Plugin } from '../../../core/plugin' import { pageEnrichment } from '../../page-enrichment' import cookie from 'js-cookie' -import { UADataValues } from '../../../lib/client-hints/interfaces' -import { - highEntropyTestData, - lowEntropyTestData, -} from '../../../test-helpers/fixtures/client-hints' jest.mock('unfetch', () => { return jest.fn() @@ -24,29 +19,6 @@ describe('Segment.io', () => { beforeEach(async () => { jest.resetAllMocks() jest.restoreAllMocks() - ;(window.navigator as any).userAgentData = { - ...lowEntropyTestData, - getHighEntropyValues: jest - .fn() - .mockImplementation((hints: string[]): Promise => { - let result = {} - Object.entries(highEntropyTestData).forEach(([k, v]) => { - if (hints.includes(k)) { - result = { - ...result, - [k]: v, - } - } - }) - return Promise.resolve({ - ...lowEntropyTestData, - ...result, - }) - }), - toJSON: jest.fn(() => { - return lowEntropyTestData - }), - } options = { apiKey: 'foo' } analytics = new Analytics({ writeKey: options.apiKey }) @@ -199,14 +171,6 @@ describe('Segment.io', () => { assert(body.traits == null) assert(body.timestamp) }) - - it('should add userAgentData when available', async () => { - await analytics.track('event') - const [_, params] = spyMock.mock.calls[0] - const body = JSON.parse(params.body) - - expect(body.context?.userAgentData).toEqual(lowEntropyTestData) - }) }) describe('#group', () => { diff --git a/packages/browser/src/plugins/segmentio/index.ts b/packages/browser/src/plugins/segmentio/index.ts index 2dc8b2967..38f012d74 100644 --- a/packages/browser/src/plugins/segmentio/index.ts +++ b/packages/browser/src/plugins/segmentio/index.ts @@ -12,8 +12,6 @@ import standard, { StandardDispatcherConfig } from './fetch-dispatcher' import { normalize } from './normalize' import { scheduleFlush } from './schedule-flush' import { SEGMENT_API_HOST } from '../../core/constants' -import { clientHints } from '../../lib/client-hints' -import { UADataValues } from '../../lib/client-hints/interfaces' type DeliveryStrategy = | { @@ -52,11 +50,11 @@ function onAlias(analytics: Analytics, json: JSON): JSON { return json } -export async function segmentio( +export function segmentio( analytics: Analytics, settings?: SegmentioSettings, integrations?: LegacySettings['integrations'] -): Promise { +): Plugin { // Attach `pagehide` before buffer is created so that inflight events are added // to the buffer before the buffer persists events in its own `pagehide` handler. window.addEventListener('pagehide', () => { @@ -86,15 +84,6 @@ export async function segmentio( ? batch(apiHost, deliveryStrategy.config) : standard(deliveryStrategy?.config as StandardDispatcherConfig) - let userAgentData: UADataValues | undefined - try { - userAgentData = await clientHints( - analytics.options.highEntropyValuesClientHints - ) - } catch { - userAgentData = undefined - } - async function send(ctx: Context): Promise { if (isOffline()) { buffer.push(ctx) @@ -107,10 +96,6 @@ export async function segmentio( const path = ctx.event.type.charAt(0) - if (userAgentData && ctx.event.context) { - ctx.event.context.userAgentData = userAgentData - } - let json = toFacade(ctx.event).json() if (ctx.event.type === 'track') {