Skip to content

Commit

Permalink
feat: support custom global buffer key
Browse files Browse the repository at this point in the history
  • Loading branch information
oscb committed Jul 28, 2023
1 parent f3183f2 commit f03b2f2
Show file tree
Hide file tree
Showing 13 changed files with 115 additions and 52 deletions.
6 changes: 6 additions & 0 deletions .changeset/mean-apricots-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@segment/analytics-next': minor
'@internal/consent-tools-integration-tests': minor
---

Adds `bufferKey` option for setting custom global window buffers
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,29 @@ describe('Pre-initialization', () => {
expect(onTrackCb).toBeCalledWith('foo', {}, undefined)
expect(onTrackCb).toBeCalledWith('bar', {}, undefined)
})
test('events can be buffered under a custom window key', async () => {
const onTrackCb = jest.fn()
const onTrack = ['on', 'track', onTrackCb]
const track = ['track', 'foo']
const track2 = ['track', 'bar']
const identify = ['identify']

;(window as any).segment = [onTrack, track, track2, identify]

await AnalyticsBrowser.standalone(writeKey, { bufferKey: 'segment' })

await sleep(100) // the snippet does not return a promise (pre-initialization) ... it sometimes has a callback as the third argument.
expect(trackSpy).toBeCalledWith('foo')
expect(trackSpy).toBeCalledWith('bar')
expect(trackSpy).toBeCalledTimes(2)

expect(identifySpy).toBeCalledTimes(1)

expect(getOnSpyCalls('track').length).toBe(1)
expect(onTrackCb).toBeCalledTimes(2) // gets called once for each track event
expect(onTrackCb).toBeCalledWith('foo', {}, undefined)
expect(onTrackCb).toBeCalledWith('bar', {}, undefined)
})
})

describe('Emitter methods', () => {
Expand Down
19 changes: 12 additions & 7 deletions packages/browser/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { popSnippetWindowBuffer } from '../core/buffer/snippet'
import { ClassicIntegrationSource } from '../plugins/ajs-destination/types'
import { attachInspector } from '../core/inspector'
import { Stats } from '../core/stats'
import { getGlobalAnalytics } from './utils'

export interface LegacyIntegrationConfiguration {
/* @deprecated - This does not indicate browser types anymore */
Expand Down Expand Up @@ -148,9 +149,11 @@ function hasTsubMiddleware(settings: LegacySettings): boolean {
*/
function flushPreBuffer(
analytics: Analytics,
buffer: PreInitMethodCallBuffer
buffer: PreInitMethodCallBuffer,
bufferKey?: string
): void {
buffer.push(...popSnippetWindowBuffer())
const calls = getGlobalAnalytics(bufferKey)
buffer.push(...popSnippetWindowBuffer(calls))
flushSetAnonymousID(analytics, buffer)
flushOn(analytics, buffer)
}
Expand All @@ -160,13 +163,15 @@ function flushPreBuffer(
*/
async function flushFinalBuffer(
analytics: Analytics,
buffer: PreInitMethodCallBuffer
buffer: PreInitMethodCallBuffer,
bufferKey?: string
): Promise<void> {
const calls = getGlobalAnalytics(bufferKey)
// Call popSnippetWindowBuffer before each flush task since there may be
// analytics calls during async function calls.
buffer.push(...popSnippetWindowBuffer())
buffer.push(...popSnippetWindowBuffer(calls))
await flushAddSourceMiddleware(analytics, buffer)
buffer.push(...popSnippetWindowBuffer())
buffer.push(...popSnippetWindowBuffer(calls))
flushAnalyticsCallsInNewTask(analytics, buffer)
// Clear buffer, just in case analytics is loaded twice; we don't want to fire events off again.
buffer.clear()
Expand Down Expand Up @@ -312,7 +317,7 @@ async function loadAnalytics(
Stats.initRemoteMetrics(legacySettings.metrics)

// needs to be flushed before plugins are registered
flushPreBuffer(analytics, preInitBuffer)
flushPreBuffer(analytics, preInitBuffer, options.bufferKey)

const ctx = await registerPlugins(
settings.writeKey,
Expand Down Expand Up @@ -340,7 +345,7 @@ async function loadAnalytics(
analytics.page().catch(console.error)
}

await flushFinalBuffer(analytics, preInitBuffer)
await flushFinalBuffer(analytics, preInitBuffer, options.bufferKey)

return [analytics, ctx]
}
Expand Down
28 changes: 7 additions & 21 deletions packages/browser/src/browser/standalone-analytics.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,16 @@
import { Analytics, InitOptions } from '../core/analytics'
import { AnalyticsBrowser } from '.'
import { embeddedWriteKey } from '../lib/embedded-write-key'

export interface AnalyticsSnippet extends AnalyticsStandalone {
load: (writeKey: string, options?: InitOptions) => void
}

export interface AnalyticsStandalone extends Analytics {
_loadOptions?: InitOptions
_writeKey?: string
_cdn?: string
}

declare global {
interface Window {
analytics: AnalyticsSnippet
}
}
import { AnalyticsSnippet } from './standalone-interface'
import { getGlobalAnalytics } from './utils'

function getWriteKey(): string | undefined {
if (embeddedWriteKey()) {
return embeddedWriteKey()
}

if (window.analytics._writeKey) {
return window.analytics._writeKey
const analytics = getGlobalAnalytics()
if (analytics?._writeKey) {
return analytics._writeKey
}

const regex = /http.*\/analytics\.js\/v1\/([^/]*)(\/platform)?\/analytics.*/
Expand Down Expand Up @@ -59,15 +45,15 @@ function getWriteKey(): string | undefined {

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

window.analytics = (await AnalyticsBrowser.standalone(
;(window as any).analytics = (await AnalyticsBrowser.standalone(
writeKey,
options
)) as AnalyticsSnippet
Expand Down
11 changes: 11 additions & 0 deletions packages/browser/src/browser/standalone-interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Analytics, InitOptions } from '../core/analytics'

export interface AnalyticsSnippet extends AnalyticsStandalone {
load: (writeKey: string, options?: InitOptions) => void
}

export interface AnalyticsStandalone extends Analytics {
_loadOptions?: InitOptions
_writeKey?: string
_cdn?: string
}
12 changes: 12 additions & 0 deletions packages/browser/src/browser/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { AnalyticsSnippet } from './standalone-interface'

/**
* Gets the global analytics instance/buffer
* @param key name of the window property where the buffer is stored (default: analytics)
* @returns AnalyticsSnippet
*/
export function getGlobalAnalytics(
key = 'analytics'
): AnalyticsSnippet | undefined {
return (window as any)[key]
}
7 changes: 6 additions & 1 deletion packages/browser/src/core/analytics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ export interface InitOptions {
* Array of high entropy Client Hints to request. These may be rejected by the user agent - only required hints should be requested.
*/
highEntropyValuesClientHints?: HighEntropyHint[]
/**
* Key for the global window property storing the buffered calls
* default: analytics
*/
bufferKey?: string
}

/* analytics-classic stubs */
Expand Down Expand Up @@ -489,7 +494,7 @@ export class Analytics

noConflict(): Analytics {
console.warn(deprecationWarning)
window.analytics = _analytics ?? this
;(window as any).analytics = _analytics ?? this
return this
}

Expand Down
9 changes: 6 additions & 3 deletions packages/browser/src/core/buffer/snippet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
PreInitMethodName,
PreInitMethodParams,
} from '.'
import { getGlobalAnalytics } from '../../browser/utils'

export function transformSnippetCall([
methodName,
Expand All @@ -29,14 +30,16 @@ type SnippetWindowBufferedMethodCall<
* A list of the method calls before initialization for snippet users
* For example, [["track", "foo", {bar: 123}], ["page"], ["on", "ready", function(){..}]
*/
type SnippetBuffer = SnippetWindowBufferedMethodCall[]
export type SnippetBuffer = SnippetWindowBufferedMethodCall[]

/**
* Fetch the buffered method calls from the window object and normalize them.
* This removes existing buffered calls from the window object.
*/
export const popSnippetWindowBuffer = (): PreInitMethodCall[] => {
const wa = window.analytics
export const popSnippetWindowBuffer = (
buffer: unknown = getGlobalAnalytics()
): PreInitMethodCall[] => {
const wa = buffer
if (!Array.isArray(wa)) return []
const buffered = wa.splice(0, wa.length)
return normalizeSnippetBuffer(buffered)
Expand Down
3 changes: 2 additions & 1 deletion packages/browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export * from './core/events'
export * from './core/plugin'
export * from './core/user'

export type { AnalyticsSnippet } from './browser/standalone-analytics'
export type { AnalyticsSnippet } from './browser/standalone-interface'
export type { MiddlewareFunction } from './plugins/middleware'
export { getGlobalAnalytics } from './browser/utils'
10 changes: 6 additions & 4 deletions packages/browser/src/lib/parse-cdn.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getGlobalAnalytics } from '../browser/utils'
import { embeddedWriteKey } from './embedded-write-key'

const analyticsScriptRegex =
Expand All @@ -20,13 +21,14 @@ const getCDNUrlFromScriptTag = (): string | undefined => {

let _globalCDN: string | undefined // set globalCDN as in-memory singleton
const getGlobalCDNUrl = (): string | undefined => {
const result = _globalCDN ?? window.analytics?._cdn
const result = _globalCDN ?? getGlobalAnalytics()?._cdn
return result
}

export const setGlobalCDNUrl = (cdn: string) => {
if (window.analytics) {
window.analytics._cdn = cdn
const globalAnalytics = getGlobalAnalytics()
if (globalAnalytics) {
globalAnalytics._cdn = cdn
}
_globalCDN = cdn
}
Expand Down Expand Up @@ -60,7 +62,7 @@ export const getNextIntegrationsURL = () => {
* @returns the path to Analytics JS 1.0
**/
export function getLegacyAJSPath(): string {
const writeKey = embeddedWriteKey() ?? window.analytics._writeKey
const writeKey = embeddedWriteKey() ?? getGlobalAnalytics()?._writeKey

const scripts = Array.prototype.slice.call(
document.querySelectorAll('script')
Expand Down
27 changes: 17 additions & 10 deletions packages/browser/src/tester/ajs-tester.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getGlobalAnalytics } from '../browser/utils'
import { Analytics } from '../core/analytics'
import { SerializedContext } from '../core/context'
import mem from 'micro-memoize'
Expand All @@ -13,7 +14,7 @@ function makeStub(page: playwright.Page) {
): Promise<SerializedContext> {
return await page.evaluate((innerArgs) => {
// @ts-ignore
return window.analytics
return getGlobalAnalytics()
.register(...innerArgs)
.then((ctx) => ctx.toJSON())
// @ts-ignore
Expand All @@ -25,9 +26,11 @@ function makeStub(page: playwright.Page) {
// @ts-expect-error
const ctx = await page.evaluate((innerArgs) => {
// @ts-ignore
return window.analytics.track(...innerArgs).then((ctx) => {
return ctx.toJSON()
})
return getGlobalAnalytics()
.track(...innerArgs)
.then((ctx) => {
return ctx.toJSON()
})
// @ts-ignore
}, args)

Expand All @@ -38,9 +41,11 @@ function makeStub(page: playwright.Page) {
): Promise<SerializedContext> {
const ctx = await page.evaluate(async (innerArgs) => {
// @ts-ignore
return window.analytics.page(...innerArgs).then((ctx) => {
return ctx.toJSON()
})
return getGlobalAnalytics()
.page(...innerArgs)
.then((ctx) => {
return ctx.toJSON()
})
// @ts-ignore
}, args)

Expand All @@ -52,9 +57,11 @@ function makeStub(page: playwright.Page) {
): Promise<SerializedContext> {
const ctx = await page.evaluate((innerArgs) => {
// @ts-ignore
return window.analytics.identify(...innerArgs).then((ctx) => {
return ctx.toJSON()
})
return getGlobalAnalytics()
.identify(...innerArgs)
.then((ctx) => {
return ctx.toJSON()
})
// @ts-ignore
}, args)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AnalyticsBrowser } from '@segment/analytics-next'
import { AnalyticsBrowser, getGlobalAnalytics } from '@segment/analytics-next'
import { oneTrust } from '@segment/analytics-consent-wrapper-onetrust'

export const analytics = new AnalyticsBrowser()
Expand All @@ -12,4 +12,4 @@ oneTrust(analytics, {
;(window as any).analytics = analytics

analytics.load({ writeKey: '9lSrez3BlfLAJ7NOChrqWtILiATiycoc' })
void window.analytics.page().then(console.log)
void getGlobalAnalytics().page().then(console.log)
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
/* eslint-disable @typescript-eslint/no-floating-promises */
import { AnyAnalytics } from '@segment/analytics-consent-tools'
import { oneTrust } from '@segment/analytics-consent-wrapper-onetrust'
import { getGlobalAnalytics } from '@segment/analytics-next'

oneTrust(window.analytics, {
oneTrust(getGlobalAnalytics() as AnyAnalytics, {
integrationCategoryMappings: {
Fullstory: ['C0001'],
'Actions Amplitude': ['C0004'],
},
})

window.analytics.load('9lSrez3BlfLAJ7NOChrqWtILiATiycoc')
window.analytics.track('Hello from the snippet')
getGlobalAnalytics()?.load('9lSrez3BlfLAJ7NOChrqWtILiATiycoc')
getGlobalAnalytics()?.track('Hello from the snippet')

0 comments on commit f03b2f2

Please sign in to comment.