Skip to content

Commit

Permalink
Add disable API boolean (#992)
Browse files Browse the repository at this point in the history
silesky authored Nov 17, 2023
1 parent dcf279c commit a72f473
Showing 7 changed files with 144 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/unlucky-kids-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@segment/analytics-next': patch
---

Add 'disable' boolean option to allow for disabling Segment in a testing environment.
2 changes: 1 addition & 1 deletion packages/browser/package.json
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@
"size-limit": [
{
"path": "dist/umd/index.js",
"limit": "29.2 KB"
"limit": "29.5 KB"
}
],
"dependencies": {
56 changes: 54 additions & 2 deletions packages/browser/src/browser/__tests__/integration.test.ts
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ import {
highEntropyTestData,
lowEntropyTestData,
} from '../../test-helpers/fixtures/client-hints'
import { getGlobalAnalytics } from '../..'
import { getGlobalAnalytics, NullAnalytics } from '../..'

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

@@ -94,11 +94,11 @@ const amplitudeWriteKey = 'bar'

beforeEach(() => {
setGlobalCDNUrl(undefined as any)
fetchCalls = []
})

describe('Initialization', () => {
beforeEach(async () => {
fetchCalls = []
jest.resetAllMocks()
jest.resetModules()
})
@@ -1209,4 +1209,56 @@ describe('Options', () => {
expect(integrationEvent.timestamp()).toBeInstanceOf(Date)
})
})

describe('disable', () => {
/**
* Note: other tests in null-analytics.test.ts cover the NullAnalytics class (including persistence)
*/
it('should return a null version of analytics / context', async () => {
const [analytics, context] = await AnalyticsBrowser.load(
{
writeKey,
},
{ disable: true }
)
expect(context).toBeInstanceOf(Context)
expect(analytics).toBeInstanceOf(NullAnalytics)
expect(analytics.initialized).toBe(true)
})

it('should not fetch cdn settings or dispatch events', async () => {
const [analytics] = await AnalyticsBrowser.load(
{
writeKey,
},
{ disable: true }
)
await analytics.track('foo')
expect(fetchCalls.length).toBe(0)
})

it('should only accept a boolean value', async () => {
const [analytics] = await AnalyticsBrowser.load(
{
writeKey,
},
// @ts-ignore
{ disable: 'true' }
)
expect(analytics).not.toBeInstanceOf(NullAnalytics)
})

it('should allow access to cdnSettings', async () => {
const disableSpy = jest.fn().mockReturnValue(true)
const [analytics] = await AnalyticsBrowser.load(
{
cdnSettings: { integrations: {}, foo: 123 },
writeKey,
},
{ disable: disableSpy }
)
expect(analytics).toBeInstanceOf(NullAnalytics)
expect(disableSpy).toBeCalledWith({ integrations: {}, foo: 123 })
})
})
})
20 changes: 19 additions & 1 deletion packages/browser/src/browser/index.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,12 @@ import { getProcessEnv } from '../lib/get-process-env'
import { getCDN, setGlobalCDNUrl } from '../lib/parse-cdn'

import { fetch } from '../lib/fetch'
import { Analytics, AnalyticsSettings, InitOptions } from '../core/analytics'
import {
Analytics,
AnalyticsSettings,
NullAnalytics,
InitOptions,
} from '../core/analytics'
import { Context } from '../core/context'
import { Plan } from '../core/events'
import { Plugin } from '../core/plugin'
@@ -300,6 +305,11 @@ async function loadAnalytics(
options: InitOptions = {},
preInitBuffer: PreInitMethodCallBuffer
): Promise<[Analytics, Context]> {
// return no-op analytics instance if disabled
if (options.disable === true) {
return [new NullAnalytics(), Context.system()]
}

if (options.globalAnalyticsKey)
setGlobalAnalyticsKey(options.globalAnalyticsKey)
// this is an ugly side-effect, but it's for the benefits of the plugins that get their cdn via getCDN()
@@ -313,6 +323,14 @@ async function loadAnalytics(
legacySettings = options.updateCDNSettings(legacySettings)
}

// if options.disable is a function, we allow user to disable analytics based on CDN Settings
if (typeof options.disable === 'function') {
const disabled = await options.disable(legacySettings)
if (disabled) {
return [new NullAnalytics(), Context.system()]
}
}

const retryQueue: boolean =
legacySettings.integrations['Segment.io']?.retryQueue ?? true

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { getAjsBrowserStorage } from '../../../test-helpers/browser-storage'
import { Analytics, NullAnalytics } from '..'

describe(NullAnalytics, () => {
it('should return an instance of Analytics / NullAnalytics', () => {
const analytics = new NullAnalytics()
expect(analytics).toBeInstanceOf(Analytics)
expect(analytics).toBeInstanceOf(NullAnalytics)
})

it('should have initialized set to true', () => {
const analytics = new NullAnalytics()
expect(analytics.initialized).toBe(true)
})

it('should have no plugins', async () => {
const analytics = new NullAnalytics()
expect(analytics.queue.plugins).toHaveLength(0)
})
it('should dispatch events', async () => {
const analytics = new NullAnalytics()
const ctx = await analytics.track('foo')
expect(ctx.event.event).toBe('foo')
})

it('should have disableClientPersistence set to true', () => {
const analytics = new NullAnalytics()
expect(analytics.options.disableClientPersistence).toBe(true)
})

it('integration: should not touch cookies or localStorage', async () => {
const analytics = new NullAnalytics()
await analytics.track('foo')
const storage = getAjsBrowserStorage()
expect(Object.values(storage).every((v) => !v)).toBe(true)
})
})
28 changes: 28 additions & 0 deletions packages/browser/src/core/analytics/index.ts
Original file line number Diff line number Diff line change
@@ -129,6 +129,24 @@ export interface InitOptions {
* default: analytics
*/
globalAnalyticsKey?: string

/**
* Disable sending any data to Segment's servers. All emitted events and API calls (including .ready()), will be no-ops, and no cookies or localstorage will be used.
*
* @example
* ### Basic (Will not not fetch any CDN settings)
* ```ts
* disable: process.env.NODE_ENV === 'test'
* ```
*
* ### Advanced (Fetches CDN Settings. Do not use this unless you require CDN settings for some reason)
* ```ts
* disable: (cdnSettings) => cdnSettings.foo === 'bar'
* ```
*/
disable?:
| boolean
| ((cdnSettings: LegacySettings) => boolean | Promise<boolean>)
}

/* analytics-classic stubs */
@@ -652,3 +670,13 @@ export class Analytics
an[method].apply(this, args)
}
}

/**
* @returns a no-op analytics instance that does not create cookies or localstorage, or send any events to segment.
*/
export class NullAnalytics extends Analytics {
constructor() {
super({ writeKey: '' }, { disableClientPersistence: true })
this.initialized = true
}
}

0 comments on commit a72f473

Please sign in to comment.