Skip to content

Commit f5cdb82

Browse files
Collect timezone and allow userAgentData to be overridden (#956)
Co-authored-by: Daniel Jackins <djackins@twilio.com>
1 parent 03a0a9b commit f5cdb82

File tree

5 files changed

+63
-54
lines changed

5 files changed

+63
-54
lines changed

.changeset/cool-peaches-pay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@segment/analytics-next': minor
3+
---
4+
5+
Set timezone and allow userAgentData to be overridden

packages/browser/src/plugins/page-enrichment/__tests__/index.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import { pick } from '../../../lib/pick'
66
import { SegmentioSettings } from '../../segmentio'
77
import { version } from '../../../generated/version'
88
import { CoreExtraContext } from '@segment/analytics-core'
9+
import { UADataValues } from '../../../lib/client-hints/interfaces'
10+
import {
11+
highEntropyTestData,
12+
lowEntropyTestData,
13+
} from '../../../test-helpers/fixtures/client-hints'
914

1015
let ajs: Analytics
1116

@@ -262,6 +267,29 @@ describe('pageDefaults', () => {
262267
describe('Other visitor metadata', () => {
263268
let options: SegmentioSettings
264269
let analytics: Analytics
270+
;(window.navigator as any).userAgentData = {
271+
...lowEntropyTestData,
272+
getHighEntropyValues: jest
273+
.fn()
274+
.mockImplementation((hints: string[]): Promise<UADataValues> => {
275+
let result = {}
276+
Object.entries(highEntropyTestData).forEach(([k, v]) => {
277+
if (hints.includes(k)) {
278+
result = {
279+
...result,
280+
[k]: v,
281+
}
282+
}
283+
})
284+
return Promise.resolve({
285+
...lowEntropyTestData,
286+
...result,
287+
})
288+
}),
289+
toJSON: jest.fn(() => {
290+
return lowEntropyTestData
291+
}),
292+
}
265293

266294
const amendSearchParams = (search?: any): CoreExtraContext => ({
267295
page: { search },
@@ -283,6 +311,11 @@ describe('Other visitor metadata', () => {
283311
}
284312
})
285313

314+
it('should add .timezone', async () => {
315+
const ctx = await analytics.track('test')
316+
assert(typeof ctx.event.context?.timezone === 'string')
317+
})
318+
286319
it('should add .library', async () => {
287320
const ctx = await analytics.track('test')
288321
assert(ctx.event.context?.library)
@@ -313,6 +346,11 @@ describe('Other visitor metadata', () => {
313346
assert(userAgent1 === userAgent2)
314347
})
315348

349+
it('should add .userAgentData when available', async () => {
350+
const ctx = await analytics.track('event')
351+
expect(ctx.event.context?.userAgentData).toEqual(lowEntropyTestData)
352+
})
353+
316354
it('should add .locale', async () => {
317355
const ctx = await analytics.track('test')
318356
assert(ctx.event.context?.locale === navigator.language)

packages/browser/src/plugins/page-enrichment/index.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { tld } from '../../core/user/tld'
1010
import { gracefulDecodeURIComponent } from '../../core/query-string/gracefulDecodeURIComponent'
1111
import { CookieStorage, UniversalStorage } from '../../core/storage'
1212
import { Analytics } from '../../core/analytics'
13+
import { clientHints } from '../../lib/client-hints'
14+
import { UADataValues } from '../../lib/client-hints/interfaces'
1315

1416
interface PageDefault {
1517
[key: string]: unknown
@@ -174,13 +176,21 @@ function referrerId(
174176

175177
class PageEnrichmentPlugin implements Plugin {
176178
private instance!: Analytics
179+
private userAgentData: UADataValues | undefined
177180

178181
name = 'Page Enrichment'
179182
type: PluginType = 'before'
180183
version = '0.1.0'
181184
isLoaded = () => true
182-
load = (_ctx: Context, instance: Analytics) => {
185+
load = async (_ctx: Context, instance: Analytics) => {
183186
this.instance = instance
187+
try {
188+
this.userAgentData = await clientHints(
189+
this.instance.options.highEntropyValuesClientHints
190+
)
191+
} catch (_) {
192+
// if client hints API doesn't return anything leave undefined
193+
}
184194
return Promise.resolve()
185195
}
186196

@@ -213,6 +223,7 @@ class PageEnrichmentPlugin implements Plugin {
213223
const query: string = evtCtx.page.search || ''
214224

215225
evtCtx.userAgent = navigator.userAgent
226+
evtCtx.userAgentData = this.userAgentData
216227

217228
// @ts-ignore
218229
const locale = navigator.userLanguage || navigator.language
@@ -241,6 +252,12 @@ class PageEnrichmentPlugin implements Plugin {
241252
this.instance.options.disableClientPersistence ?? false
242253
)
243254

255+
try {
256+
evtCtx.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
257+
} catch (_) {
258+
// If browser doesn't have support leave timezone undefined
259+
}
260+
244261
return ctx
245262
}
246263

packages/browser/src/plugins/segmentio/__tests__/index.test.ts

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@ import { Analytics } from '../../../core/analytics'
55
import { Plugin } from '../../../core/plugin'
66
import { pageEnrichment } from '../../page-enrichment'
77
import cookie from 'js-cookie'
8-
import { UADataValues } from '../../../lib/client-hints/interfaces'
9-
import {
10-
highEntropyTestData,
11-
lowEntropyTestData,
12-
} from '../../../test-helpers/fixtures/client-hints'
138

149
jest.mock('unfetch', () => {
1510
return jest.fn()
@@ -24,29 +19,6 @@ describe('Segment.io', () => {
2419
beforeEach(async () => {
2520
jest.resetAllMocks()
2621
jest.restoreAllMocks()
27-
;(window.navigator as any).userAgentData = {
28-
...lowEntropyTestData,
29-
getHighEntropyValues: jest
30-
.fn()
31-
.mockImplementation((hints: string[]): Promise<UADataValues> => {
32-
let result = {}
33-
Object.entries(highEntropyTestData).forEach(([k, v]) => {
34-
if (hints.includes(k)) {
35-
result = {
36-
...result,
37-
[k]: v,
38-
}
39-
}
40-
})
41-
return Promise.resolve({
42-
...lowEntropyTestData,
43-
...result,
44-
})
45-
}),
46-
toJSON: jest.fn(() => {
47-
return lowEntropyTestData
48-
}),
49-
}
5022

5123
options = { apiKey: 'foo' }
5224
analytics = new Analytics({ writeKey: options.apiKey })
@@ -199,14 +171,6 @@ describe('Segment.io', () => {
199171
assert(body.traits == null)
200172
assert(body.timestamp)
201173
})
202-
203-
it('should add userAgentData when available', async () => {
204-
await analytics.track('event')
205-
const [_, params] = spyMock.mock.calls[0]
206-
const body = JSON.parse(params.body)
207-
208-
expect(body.context?.userAgentData).toEqual(lowEntropyTestData)
209-
})
210174
})
211175

212176
describe('#group', () => {

packages/browser/src/plugins/segmentio/index.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ import standard, { StandardDispatcherConfig } from './fetch-dispatcher'
1212
import { normalize } from './normalize'
1313
import { scheduleFlush } from './schedule-flush'
1414
import { SEGMENT_API_HOST } from '../../core/constants'
15-
import { clientHints } from '../../lib/client-hints'
16-
import { UADataValues } from '../../lib/client-hints/interfaces'
1715

1816
type DeliveryStrategy =
1917
| {
@@ -52,11 +50,11 @@ function onAlias(analytics: Analytics, json: JSON): JSON {
5250
return json
5351
}
5452

55-
export async function segmentio(
53+
export function segmentio(
5654
analytics: Analytics,
5755
settings?: SegmentioSettings,
5856
integrations?: LegacySettings['integrations']
59-
): Promise<Plugin> {
57+
): Plugin {
6058
// Attach `pagehide` before buffer is created so that inflight events are added
6159
// to the buffer before the buffer persists events in its own `pagehide` handler.
6260
window.addEventListener('pagehide', () => {
@@ -86,15 +84,6 @@ export async function segmentio(
8684
? batch(apiHost, deliveryStrategy.config)
8785
: standard(deliveryStrategy?.config as StandardDispatcherConfig)
8886

89-
let userAgentData: UADataValues | undefined
90-
try {
91-
userAgentData = await clientHints(
92-
analytics.options.highEntropyValuesClientHints
93-
)
94-
} catch {
95-
userAgentData = undefined
96-
}
97-
9887
async function send(ctx: Context): Promise<Context> {
9988
if (isOffline()) {
10089
buffer.push(ctx)
@@ -107,10 +96,6 @@ export async function segmentio(
10796

10897
const path = ctx.event.type.charAt(0)
10998

110-
if (userAgentData && ctx.event.context) {
111-
ctx.event.context.userAgentData = userAgentData
112-
}
113-
11499
let json = toFacade(ctx.event).json()
115100

116101
if (ctx.event.type === 'track') {

0 commit comments

Comments
 (0)