Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add analytics.settings api #1090

Merged
merged 7 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/violet-actors-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@segment/analytics-next': minor
---

- Add public settings API
- Do not expose loadLegacySettings / loadCDNSettings (private API)
47 changes: 37 additions & 10 deletions packages/browser/src/browser/__tests__/integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
/* eslint-disable @typescript-eslint/no-floating-promises */
import { cdnSettingsKitchenSink } from '../../test-helpers/fixtures/cdn-settings'
import {
cdnSettingsKitchenSink,
cdnSettingsMinimal,
} from '../../test-helpers/fixtures/cdn-settings'
import { createMockFetchImplementation } from '../../test-helpers/fixtures/create-fetch-method'
import { Context } from '../../core/context'
import { Plugin } from '../../core/plugin'
import { JSDOM } from 'jsdom'
import { Analytics, InitOptions } from '../../core/analytics'
import { LegacyDestination } from '../../plugins/ajs-destination'
import { PersistedPriorityQueue } from '../../lib/priority-queue/persisted'
// @ts-ignore loadLegacySettings mocked dependency is accused as unused
import { AnalyticsBrowser, loadLegacySettings } from '..'
// @ts-ignore loadCDNSettings mocked dependency is accused as unused
import { AnalyticsBrowser, loadCDNSettings } from '..'
// @ts-ignore isOffline mocked dependency is accused as unused
import { isOffline } from '../../core/connection'
import * as SegmentPlugin from '../../plugins/segmentio'
Expand All @@ -23,7 +26,8 @@ import {
highEntropyTestData,
lowEntropyTestData,
} from '../../test-helpers/fixtures/client-hints'
import { getGlobalAnalytics, NullAnalytics } from '../..'
import { getGlobalAnalytics } from '../../lib/global-analytics-helper'
import { NullAnalytics } from '../../core/analytics'
import { recordIntegrationMetric } from '../../core/stats/metric-helpers'

let fetchCalls: ReturnType<typeof parseFetchCall>[] = []
Expand Down Expand Up @@ -1030,24 +1034,47 @@ describe('use', () => {
})
})

describe('timeout', () => {
it('has a default timeout value', async () => {
describe('public settings api', () => {
it('has expected settings', async () => {
const [analytics] = await AnalyticsBrowser.load({
writeKey,
cdnSettings: cdnSettingsMinimal,
})

expect(analytics.settings).toEqual({
writeKey,
cdnSettings: cdnSettingsMinimal,
timeout: 300,
})
//@ts-ignore
expect(analytics.settings.timeout).toEqual(300)
})

it('should have a writeKey', async () => {
const [analytics] = await AnalyticsBrowser.load({
writeKey,
})

expect(analytics.settings.writeKey).toBe(writeKey)
})

it('should have cdn settings', async () => {
const [analytics] = await AnalyticsBrowser.load({
writeKey,
cdnSettings: cdnSettingsMinimal,
})

expect(analytics.settings.cdnSettings).toEqual(cdnSettingsMinimal)
})

it('can set a timeout value', async () => {
const [analytics] = await AnalyticsBrowser.load({
writeKey,
})
expect(analytics.settings.timeout).toEqual(300)
analytics.timeout(50)
//@ts-ignore
expect(analytics.settings.timeout).toEqual(50)
})
})

describe('register', () => {
it('will not invoke any plugins that have initialization errors', async () => {
const analytics = AnalyticsBrowser.load({
Expand Down Expand Up @@ -1190,7 +1217,7 @@ describe('retries', () => {

beforeEach(async () => {
// @ts-ignore ignore reassining function
loadLegacySettings = jest.fn().mockReturnValue(
loadCDNSettings = jest.fn().mockReturnValue(
Promise.resolve({
integrations: { 'Segment.io': { retryQueue: false } },
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { JSDOM } from 'jsdom'
import { Analytics } from '../../core/analytics'
// @ts-ignore loadLegacySettings mocked dependency is accused as unused
// @ts-ignore loadCDNSettings mocked dependency is accused as unused
import { AnalyticsBrowser } from '..'
import { setGlobalCDNUrl } from '../../lib/parse-cdn'
import { TEST_WRITEKEY } from '../../test-helpers/test-writekeys'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import jsdom, { JSDOM } from 'jsdom'
import { InitOptions, getGlobalAnalytics } from '../../'
import { AnalyticsBrowser, loadLegacySettings } from '../../browser'
import { AnalyticsBrowser, loadCDNSettings } from '../../browser'
import { snippet } from '../../tester/__fixtures__/segment-snippet'
import { install } from '../standalone-analytics'
import unfetch from 'unfetch'
Expand Down Expand Up @@ -130,7 +130,7 @@ describe('standalone bundle', () => {
// @ts-ignore ignore Response required fields
.mockImplementation((): Promise<Response> => fetchSettings)

await loadLegacySettings(segmentDotCom)
await loadCDNSettings(segmentDotCom)

expect(unfetch).toHaveBeenCalledWith(
'https://cdn.foo.com/v1/projects/foo/settings'
Expand All @@ -145,7 +145,7 @@ describe('standalone bundle', () => {
const mockCdn = 'http://my-overridden-cdn.com'

getGlobalAnalytics()!._cdn = mockCdn
await loadLegacySettings(segmentDotCom)
await loadCDNSettings(segmentDotCom)

expect(unfetch).toHaveBeenCalledWith(expect.stringContaining(mockCdn))
})
Expand Down
38 changes: 21 additions & 17 deletions packages/browser/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ import { getProcessEnv } from '../lib/get-process-env'
import { getCDN, setGlobalCDNUrl } from '../lib/parse-cdn'

import { fetch } from '../lib/fetch'
import {
Analytics,
AnalyticsSettings,
NullAnalytics,
InitOptions,
} from '../core/analytics'
import { Analytics, NullAnalytics, InitOptions } from '../core/analytics'
import { Context } from '../core/context'
import { Plan } from '../core/events'
import { Plugin } from '../core/plugin'
Expand Down Expand Up @@ -111,7 +106,8 @@ export interface CDNSettings {
}
}

export interface AnalyticsBrowserSettings extends AnalyticsSettings {
export interface AnalyticsBrowserSettings {
writeKey: string
/**
* The settings for the Segment Source.
* If provided, `AnalyticsBrowser` will not fetch remote settings
Expand All @@ -122,9 +118,17 @@ export interface AnalyticsBrowserSettings extends AnalyticsSettings {
* If provided, will override the default Segment CDN (https://cdn.segment.com) for this application.
*/
cdnURL?: string
/**
* Plugins or npm-installed action destinations
*/
plugins?: (Plugin | PluginFactory)[]
/**
* npm-installed classic destinations
*/
classicIntegrations?: ClassicIntegrationSource[]
}

export function loadLegacySettings(
export function loadCDNSettings(
writeKey: string,
cdnURL?: string
): Promise<CDNSettings> {
Expand Down Expand Up @@ -330,31 +334,31 @@ async function loadAnalytics(
preInitBuffer.push(new PreInitMethodCall('page', []))
}

let legacySettings =
let cdnSettings =
settings.cdnSettings ??
(await loadLegacySettings(settings.writeKey, settings.cdnURL))
(await loadCDNSettings(settings.writeKey, settings.cdnURL))

if (options.updateCDNSettings) {
legacySettings = options.updateCDNSettings(legacySettings)
cdnSettings = options.updateCDNSettings(cdnSettings)
}

// 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)
const disabled = await options.disable(cdnSettings)
if (disabled) {
return [new NullAnalytics(), Context.system()]
}
}

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

options = {
retryQueue,
...options,
}

const analytics = new Analytics(settings, options)
const analytics = new Analytics({ ...settings, cdnSettings }, options)

attachInspector(analytics)

Expand All @@ -367,8 +371,8 @@ async function loadAnalytics(
| undefined

Stats.initRemoteMetrics({
...legacySettings.metrics,
host: segmentLoadOptions?.apiHost ?? legacySettings.metrics?.host,
...cdnSettings.metrics,
host: segmentLoadOptions?.apiHost ?? cdnSettings.metrics?.host,
protocol: segmentLoadOptions?.protocol,
})

Expand All @@ -377,7 +381,7 @@ async function loadAnalytics(

const ctx = await registerPlugins(
settings.writeKey,
legacySettings,
cdnSettings,
analytics,
options,
plugins,
Expand Down
39 changes: 28 additions & 11 deletions packages/browser/src/core/analytics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ import { EventQueue } from '../queue/event-queue'
import { Group, ID, User, UserOptions } from '../user'
import autoBind from '../../lib/bind-all'
import { PersistedPriorityQueue } from '../../lib/priority-queue/persisted'
import type {
LegacyIntegration,
ClassicIntegrationSource,
} from '../../plugins/ajs-destination/types'
import type { LegacyIntegration } from '../../plugins/ajs-destination/types'
import type {
DestinationMiddlewareFunction,
MiddlewareFunction,
Expand All @@ -52,7 +49,6 @@ import {
initializeStorages,
isArrayOfStoreType,
} from '../storage'
import { PluginFactory } from '../../plugins/remote-loader'
import { setGlobalAnalytics } from '../../lib/global-analytics-helper'
import { popPageContext } from '../buffer'

Expand All @@ -75,11 +71,33 @@ function createDefaultQueue(
return new EventQueue(priorityQueue)
}

/**
* The public settings that are set on the analytics instance
*/
export class AnalyticsInstanceSettings {
readonly writeKey: string
/**
* This is an unstable API, it may change in the future without warning.
*/
readonly cdnSettings: CDNSettings

/**
* Auto-track specific timeout setting for legacy purposes.
*/
timeout = 300

constructor(settings: AnalyticsSettings) {
this.writeKey = settings.writeKey
this.cdnSettings = settings.cdnSettings ?? { integrations: {} }
}
}

/**
* The settings that are used to configure the analytics instance
*/
export interface AnalyticsSettings {
writeKey: string
timeout?: number
plugins?: (Plugin | PluginFactory)[]
classicIntegrations?: ClassicIntegrationSource[]
cdnSettings?: CDNSettings
}

export interface InitOptions {
Expand Down Expand Up @@ -155,7 +173,7 @@ export class Analytics
extends Emitter
implements AnalyticsCore, AnalyticsClassic
{
protected settings: AnalyticsSettings
settings: AnalyticsInstanceSettings
private _user: User
private _group: Group
private eventFactory: EventFactory
Expand All @@ -177,8 +195,7 @@ export class Analytics
super()
const cookieOptions = options?.cookie
const disablePersistance = options?.disableClientPersistence ?? false
this.settings = settings
this.settings.timeout = this.settings.timeout ?? 300
this.settings = new AnalyticsInstanceSettings(settings)
this.queue =
queue ??
createDefaultQueue(
Expand Down
9 changes: 7 additions & 2 deletions packages/browser/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
export * from './core/analytics'
export * from './browser'
export { Analytics, AnalyticsSettings, InitOptions } from './core/analytics'
export {
AnalyticsBrowser,
AnalyticsBrowserSettings,
CDNSettings,
RemoteIntegrationSettings,
} from './browser'
export * from './node'

export * from './core/context'
Expand Down
Loading