Skip to content

Commit 7f4232c

Browse files
authored
feat: support custom global analytics key (#928)
1 parent a435d66 commit 7f4232c

File tree

18 files changed

+210
-50
lines changed

18 files changed

+210
-50
lines changed

.changeset/mean-apricots-hammer.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@segment/analytics-next': minor
3+
'@segment/analytics-core': minor
4+
---
5+
6+
Adds `globalAnalyticsKey` option for setting custom global window buffers

examples/with-next-js/pages/partytown/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const WebWorker: React.FC = () => {
4444
}}
4545
value="Track!"
4646
onClick={() => {
47-
void window.analytics.track(
47+
void (window as any).analytics.track(
4848
'Party Town Click',
4949
{ myProp: 'hello' },
5050
{ traits: { age: 8 } }

packages/browser/src/browser/__tests__/analytics-pre-init.integration.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,31 @@ describe('Pre-initialization', () => {
278278
expect(onTrackCb).toBeCalledWith('foo', {}, undefined)
279279
expect(onTrackCb).toBeCalledWith('bar', {}, undefined)
280280
})
281+
test('events can be buffered under a custom window key', async () => {
282+
const onTrackCb = jest.fn()
283+
const onTrack = ['on', 'track', onTrackCb]
284+
const track = ['track', 'foo']
285+
const track2 = ['track', 'bar']
286+
const identify = ['identify']
287+
288+
;(window as any).segment = [onTrack, track, track2, identify]
289+
290+
await AnalyticsBrowser.standalone(writeKey, {
291+
globalAnalyticsKey: 'segment',
292+
})
293+
294+
await sleep(100) // the snippet does not return a promise (pre-initialization) ... it sometimes has a callback as the third argument.
295+
expect(trackSpy).toBeCalledWith('foo')
296+
expect(trackSpy).toBeCalledWith('bar')
297+
expect(trackSpy).toBeCalledTimes(2)
298+
299+
expect(identifySpy).toBeCalledTimes(1)
300+
301+
expect(getOnSpyCalls('track').length).toBe(1)
302+
expect(onTrackCb).toBeCalledTimes(2) // gets called once for each track event
303+
expect(onTrackCb).toBeCalledWith('foo', {}, undefined)
304+
expect(onTrackCb).toBeCalledWith('bar', {}, undefined)
305+
})
281306
})
282307

283308
describe('Emitter methods', () => {

packages/browser/src/browser/__tests__/cdn.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AnalyticsBrowser } from '../..'
1+
import { AnalyticsBrowser, getGlobalAnalytics } from '../..'
22
import unfetch from 'unfetch'
33
import { createSuccess } from '../../test-helpers/factories'
44
import { setGlobalCDNUrl } from '../../lib/parse-cdn'
@@ -45,5 +45,5 @@ it('if CDN is overridden, sets the overridden CDN global variable', async () =>
4545
writeKey,
4646
cdnURL: mockCdn,
4747
})
48-
expect(window.analytics._cdn).toBe(mockCdn)
48+
expect(getGlobalAnalytics()?._cdn).toBe(mockCdn)
4949
})

packages/browser/src/browser/__tests__/integration.test.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
highEntropyTestData,
2424
lowEntropyTestData,
2525
} from '../../test-helpers/fixtures/client-hints'
26+
import { getGlobalAnalytics } from '../..'
2627

2728
let fetchCalls: ReturnType<typeof parseFetchCall>[] = []
2829

@@ -200,7 +201,7 @@ describe('Initialization', () => {
200201
{
201202
...xt,
202203
load: async () => {
203-
expect(window.analytics).toBeUndefined()
204+
expect(getGlobalAnalytics()).toBeUndefined()
204205
expect(getCDN()).toContain(overriddenCDNUrl)
205206
},
206207
},
@@ -212,6 +213,57 @@ describe('Initialization', () => {
212213
})
213214
})
214215

216+
describe('globalAnalyticsKey', () => {
217+
const overrideKey = 'myKey'
218+
const buffer = {
219+
foo: 'bar',
220+
}
221+
222+
beforeEach(() => {
223+
;(window as any)[overrideKey] = buffer
224+
})
225+
afterEach(() => {
226+
delete (window as any)[overrideKey]
227+
})
228+
it('should default to window.analytics', async () => {
229+
const defaultObj = { original: 'default' }
230+
;(window as any)['analytics'] = defaultObj
231+
232+
await AnalyticsBrowser.load({
233+
writeKey,
234+
plugins: [
235+
{
236+
...xt,
237+
load: async () => {
238+
expect(getGlobalAnalytics()).toBe(defaultObj)
239+
},
240+
},
241+
],
242+
})
243+
expect.assertions(1)
244+
})
245+
246+
it('should set the global window key for the analytics buffer with the setting option', async () => {
247+
await AnalyticsBrowser.load(
248+
{
249+
writeKey,
250+
plugins: [
251+
{
252+
...xt,
253+
load: async () => {
254+
expect(getGlobalAnalytics()).toBe(buffer)
255+
},
256+
},
257+
],
258+
},
259+
{
260+
globalAnalyticsKey: overrideKey,
261+
}
262+
)
263+
expect.assertions(1)
264+
})
265+
})
266+
215267
describe('Load options', () => {
216268
it('gets high entropy client hints if set', async () => {
217269
;(window.navigator as any).userAgentData = {

packages/browser/src/browser/__tests__/standalone-analytics.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import jsdom, { JSDOM } from 'jsdom'
2-
import { InitOptions } from '../../'
2+
import { InitOptions, getGlobalAnalytics } from '../../'
33
import { AnalyticsBrowser, loadLegacySettings } from '../../browser'
44
import { snippet } from '../../tester/__fixtures__/segment-snippet'
5-
import { install, AnalyticsStandalone } from '../standalone-analytics'
5+
import { install } from '../standalone-analytics'
66
import unfetch from 'unfetch'
77
import { PersistedPriorityQueue } from '../../lib/priority-queue/persisted'
88
import { sleep } from '../../lib/sleep'
99
import * as Factory from '../../test-helpers/factories'
1010
import { EventQueue } from '../../core/queue/event-queue'
11+
import { AnalyticsStandalone } from '../standalone-interface'
1112

1213
const track = jest.fn()
1314
const identify = jest.fn()
@@ -142,7 +143,7 @@ describe('standalone bundle', () => {
142143
.mockImplementation((): Promise<Response> => fetchSettings)
143144
const mockCdn = 'http://my-overridden-cdn.com'
144145

145-
window.analytics._cdn = mockCdn
146+
getGlobalAnalytics()!._cdn = mockCdn
146147
await loadLegacySettings(segmentDotCom)
147148

148149
expect(unfetch).toHaveBeenCalledWith(expect.stringContaining(mockCdn))
@@ -262,7 +263,7 @@ describe('standalone bundle', () => {
262263

263264
// register is called after flushPreBuffer in `loadAnalytics`
264265
register.mockImplementationOnce(() =>
265-
window.analytics.track('race conditions', { foo: 'bar' })
266+
getGlobalAnalytics()?.track('race conditions', { foo: 'bar' })
266267
)
267268

268269
await install()

packages/browser/src/browser/__tests__/standalone.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { LegacySettings } from '..'
44
import { pWhile } from '../../lib/p-while'
55
import { snippet } from '../../tester/__fixtures__/segment-snippet'
66
import * as Factory from '../../test-helpers/factories'
7+
import { getGlobalAnalytics } from '../..'
78

89
const cdnResponse: LegacySettings = {
910
integrations: {
@@ -86,11 +87,11 @@ describe('standalone bundle', () => {
8687
await import('../standalone')
8788

8889
await pWhile(
89-
() => window.analytics?.initialized !== true,
90+
() => getGlobalAnalytics()?.initialized !== true,
9091
() => {}
9192
)
9293

93-
expect(window.analytics).not.toBeUndefined()
94-
expect(window.analytics.initialized).toBe(true)
94+
expect(getGlobalAnalytics()).not.toBeUndefined()
95+
expect(getGlobalAnalytics()?.initialized).toBe(true)
9596
})
9697
})

packages/browser/src/browser/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { popSnippetWindowBuffer } from '../core/buffer/snippet'
3030
import { ClassicIntegrationSource } from '../plugins/ajs-destination/types'
3131
import { attachInspector } from '../core/inspector'
3232
import { Stats } from '../core/stats'
33+
import { setGlobalAnalyticsKey } from '../lib/global-analytics-helper'
3334

3435
export interface LegacyIntegrationConfiguration {
3536
/* @deprecated - This does not indicate browser types anymore */
@@ -303,6 +304,8 @@ async function loadAnalytics(
303304
options: InitOptions = {},
304305
preInitBuffer: PreInitMethodCallBuffer
305306
): Promise<[Analytics, Context]> {
307+
if (options.globalAnalyticsKey)
308+
setGlobalAnalyticsKey(options.globalAnalyticsKey)
306309
// this is an ugly side-effect, but it's for the benefits of the plugins that get their cdn via getCDN()
307310
if (settings.cdnURL) setGlobalCDNUrl(settings.cdnURL)
308311

packages/browser/src/browser/standalone-analytics.ts

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,19 @@
1-
import { Analytics, InitOptions } from '../core/analytics'
21
import { AnalyticsBrowser } from '.'
32
import { embeddedWriteKey } from '../lib/embedded-write-key'
4-
5-
export interface AnalyticsSnippet extends AnalyticsStandalone {
6-
load: (writeKey: string, options?: InitOptions) => void
7-
}
8-
9-
export interface AnalyticsStandalone extends Analytics {
10-
_loadOptions?: InitOptions
11-
_writeKey?: string
12-
_cdn?: string
13-
}
14-
15-
declare global {
16-
interface Window {
17-
analytics: AnalyticsSnippet
18-
}
19-
}
3+
import { AnalyticsSnippet } from './standalone-interface'
4+
import {
5+
getGlobalAnalytics,
6+
setGlobalAnalytics,
7+
} from '../lib/global-analytics-helper'
208

219
function getWriteKey(): string | undefined {
2210
if (embeddedWriteKey()) {
2311
return embeddedWriteKey()
2412
}
2513

26-
if (window.analytics._writeKey) {
27-
return window.analytics._writeKey
14+
const analytics = getGlobalAnalytics()
15+
if (analytics?._writeKey) {
16+
return analytics._writeKey
2817
}
2918

3019
const regex = /http.*\/analytics\.js\/v1\/([^/]*)(\/platform)?\/analytics.*/
@@ -59,16 +48,15 @@ function getWriteKey(): string | undefined {
5948

6049
export async function install(): Promise<void> {
6150
const writeKey = getWriteKey()
62-
const options = window.analytics?._loadOptions ?? {}
51+
const options = getGlobalAnalytics()?._loadOptions ?? {}
6352
if (!writeKey) {
6453
console.error(
6554
'Failed to load Write Key. Make sure to use the latest version of the Segment snippet, which can be found in your source settings.'
6655
)
6756
return
6857
}
6958

70-
window.analytics = (await AnalyticsBrowser.standalone(
71-
writeKey,
72-
options
73-
)) as AnalyticsSnippet
59+
setGlobalAnalytics(
60+
(await AnalyticsBrowser.standalone(writeKey, options)) as AnalyticsSnippet
61+
)
7462
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Analytics, InitOptions } from '../core/analytics'
2+
3+
export interface AnalyticsSnippet extends AnalyticsStandalone {
4+
load: (writeKey: string, options?: InitOptions) => void
5+
}
6+
7+
export interface AnalyticsStandalone extends Analytics {
8+
_loadOptions?: InitOptions
9+
_writeKey?: string
10+
_cdn?: string
11+
}

0 commit comments

Comments
 (0)