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

Make fetch priority and headers configurable, and define types for segment.io and integration options. #1204

Merged
merged 12 commits into from
Jan 21, 2025
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
25 changes: 25 additions & 0 deletions .changeset/heavy-taxis-suffer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
'@segment/analytics-next': minor
---
- Make Segment.io config type-safe
- Add new `headers` setting, along with `priority`.

```ts
analytics.load("<YOUR_WRITE_KEY>",
{
integrations: {
'Segment.io': {
deliveryStrategy: {
strategy: "standard" // also works for 'batching'
config: {
headers: { 'x-api-key': 'foo' } or () => {...}
priority: 'low',
},
},
},
},
}
)
```


6 changes: 6 additions & 0 deletions .changeset/nasty-peaches-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@segment/analytics-consent-tools': patch
'@segment/analytics-signals': patch
---

Update types
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CorePlugin, PluginType, sleep } from '@segment/analytics-core'
import {
cdnSettingsMinimal,
createMockFetchImplementation,
createRemotePlugin,
getBufferedPageCtxFixture,
Expand Down Expand Up @@ -92,7 +93,9 @@ describe('Lazy destination loading', () => {
beforeEach(() => {
jest.mocked(unfetch).mockImplementation(
createMockFetchImplementation({
...cdnSettingsMinimal,
integrations: {
...cdnSettingsMinimal.integrations,
braze: {},
google: {},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ const helpers = {
.mockImplementation(() => createSuccess({ integrations: {} }))
},
loadAnalytics() {
return AnalyticsBrowser.load({ writeKey: 'foo' })
return AnalyticsBrowser.load(
{ writeKey: 'foo' },
{
integrations: {
'Segment.io': {},
},
}
)
},
}

Expand Down
3 changes: 3 additions & 0 deletions packages/browser/src/browser/__tests__/csp-detection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import { CDNSettings } from '..'
import { pWhile } from '../../lib/p-while'
import { snippet } from '../../tester/__fixtures__/segment-snippet'
import * as Factory from '../../test-helpers/factories'
import { cdnSettingsMinimal } from '../../test-helpers/fixtures'

const cdnResponse: CDNSettings = {
...cdnSettingsMinimal,
integrations: {
...cdnSettingsMinimal.integrations,
Zapier: {
type: 'server',
},
Expand Down
45 changes: 41 additions & 4 deletions packages/browser/src/browser/__tests__/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Analytics, InitOptions } from '../../core/analytics'
import { LegacyDestination } from '../../plugins/ajs-destination'
import { PersistedPriorityQueue } from '../../lib/priority-queue/persisted'
// @ts-ignore loadCDNSettings mocked dependency is accused as unused
import { AnalyticsBrowser, loadCDNSettings } from '..'
import { AnalyticsBrowser, CDNSettings, loadCDNSettings } from '..'
// @ts-ignore isOffline mocked dependency is accused as unused
import { isOffline } from '../../core/connection'
import * as SegmentPlugin from '../../plugins/segmentio'
Expand Down Expand Up @@ -376,7 +376,7 @@ describe('Initialization', () => {
it('does not fetch source settings if cdnSettings is set', async () => {
await AnalyticsBrowser.load({
writeKey,
cdnSettings: { integrations: {} },
cdnSettings: cdnSettingsMinimal,
})

expect(fetchCalls.length).toBe(0)
Expand Down Expand Up @@ -666,8 +666,10 @@ describe('Dispatch', () => {
{
writeKey,
cdnSettings: {
...cdnSettingsMinimal,
integrations: {
'Segment.io': {
...cdnSettingsMinimal.integrations['Segment.io'],
apiHost: 'cdnSettings.api.io',
},
},
Expand Down Expand Up @@ -1331,6 +1333,7 @@ describe('Segment.io overrides', () => {
integrations: {
'Segment.io': {
apiHost: 'https://my.endpoint.com',
// @ts-ignore
anotherSettings: '👻',
},
},
Expand Down Expand Up @@ -1526,13 +1529,47 @@ describe('Options', () => {
const disableSpy = jest.fn().mockReturnValue(true)
const [analytics] = await AnalyticsBrowser.load(
{
cdnSettings: { integrations: {}, foo: 123 },
cdnSettings: cdnSettingsMinimal,
writeKey,
},
{ disable: disableSpy }
)
expect(analytics).toBeInstanceOf(NullAnalytics)
expect(disableSpy).toBeCalledWith({ integrations: {}, foo: 123 })
expect(disableSpy).toHaveBeenCalledWith(cdnSettingsMinimal)
})
})
})

describe('setting headers', () => {
it('allows setting headers', async () => {
const [ajs] = await AnalyticsBrowser.load(
{
writeKey,
},
{
integrations: {
'Segment.io': {
deliveryStrategy: {
config: {
headers: {
'X-Test': 'foo',
},
},
},
},
},
}
)

await ajs.track('sup')

await sleep(10)
const [call] = fetchCalls.filter((el) =>
el.url.toString().includes('api.segment.io')
)
expect(call.headers).toEqual({
'Content-Type': 'text/plain',
'X-Test': 'foo',
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import { pWhile } from '../../lib/p-while'
import unfetch from 'unfetch'
import { RemoteMetrics } from '../../core/stats/remote-metrics'
import * as Factory from '../../test-helpers/factories'
import { cdnSettingsMinimal } from '../../test-helpers/fixtures'

const cdnResponse: CDNSettings = {
...cdnSettingsMinimal,
integrations: {
...cdnSettingsMinimal.integrations,
Zapier: {
type: 'server',
},
Expand Down
3 changes: 3 additions & 0 deletions packages/browser/src/browser/__tests__/standalone.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import { pWhile } from '../../lib/p-while'
import { snippet } from '../../tester/__fixtures__/segment-snippet'
import * as Factory from '../../test-helpers/factories'
import { getGlobalAnalytics } from '../..'
import { cdnSettingsMinimal } from '../../test-helpers/fixtures'

const cdnResponse: CDNSettings = {
...cdnSettingsMinimal,
integrations: {
...cdnSettingsMinimal.integrations,
Zapier: {
type: 'server',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import { setGlobalCDNUrl } from '../../lib/parse-cdn'
import { remoteLoader } from '../../plugins/remote-loader'
import unfetch from 'unfetch'
import { createSuccess } from '../../test-helpers/factories'
import { cdnSettingsMinimal } from '../../test-helpers/fixtures'
jest.mock('unfetch')

const INTG_TO_DELETE = 'deleteMe'

const cdnSettings = {
...cdnSettingsMinimal,
integrations: {
...cdnSettingsMinimal.integrations,
[INTG_TO_DELETE]: { bar: true },
otherIntegration: { foo: true },
},
Expand Down
80 changes: 68 additions & 12 deletions packages/browser/src/browser/settings.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* These settings will be exposed via the public API
*/
import { IntegrationsOptions, Plan } from '../core/events'
import { Plan } from '../core/events'
import { MetricsOptions } from '../core/stats/remote-metrics'
import { ClassicIntegrationSource } from '../plugins/ajs-destination/types'
import { PluginFactory, RemotePlugin } from '../plugins/remote-loader'
Expand All @@ -10,17 +10,20 @@ import { RoutingRule } from '../plugins/routing-middleware'
import { CookieOptions, StorageSettings } from '../core/storage'
import { UserOptions } from '../core/user'
import { HighEntropyHint } from '../lib/client-hints/interfaces'
import { IntegrationsOptions } from '@segment/analytics-core'
import { SegmentioSettings } from '../plugins/segmentio'

interface VersionSettings {
version?: string
override?: string
componentTypes?: ('browser' | 'android' | 'ios' | 'server')[]
}

export interface RemoteIntegrationSettings {
/* @deprecated - This does not indicate browser types anymore */
type?: string

versionSettings?: {
version?: string
override?: string
componentTypes?: ('browser' | 'android' | 'ios' | 'server')[]
}

versionSettings?: VersionSettings
/**
* We know if an integration is device mode if it has `bundlingStatus: 'bundled'` and the `browser` componentType in `versionSettings`.
* History: The term 'bundle' is left over from before action destinations, when a device mode destinations were 'bundled' in a custom bundle for every analytics.js source.
Expand All @@ -38,20 +41,48 @@ export interface RemoteIntegrationSettings {
categories: string[]
}

// Segment.io specific
retryQueue?: boolean

// any extra unknown settings
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any
}

export interface RemoteSegmentIOIntegrationSettings
extends RemoteIntegrationSettings {
/**
* Segment write key
*/
apiKey: string

/**
* Whether or not offline events are stored and retried.
*
* Originally, each plugin was concieved to use global retry logic (e.g. throwing magic errors would result in retries),
* but this was not really used and is no longer encouraged. Instead, we favor of per-plugin retry logic, since it's confusing to have multiple levels of retries, and most device mode destinations contain their own retry behavior.
* The segmentio plugin itself has its own internal retry queue and is not affected by this setting.
*
* @default true
*/
retryQueue?: boolean

/**
* Host of the segment cdn - this may need to be manually enabled on the source.
* @default 'api.segment.io/v1'
*/
apiHost?: string
addBundledMetadata?: boolean
unbundledIntegrations?: string[]
bundledConfigIds?: string[]
unbundledConfigIds?: string[]
maybeBundledConfigIds?: Record<string, string[]>
}

/**
* The remote settings object for a source, typically fetched from the Segment CDN.
* Warning: this is an *unstable* object.
*/
export interface CDNSettings {
integrations: {
'Segment.io': RemoteSegmentIOIntegrationSettings
[creationName: string]: RemoteIntegrationSettings
}

Expand Down Expand Up @@ -108,6 +139,11 @@ export interface CDNSettings {
autoInstrumentationSettings?: {
sampleRate: number
}

/**
* Allow misc settings to be passed through, but
*/
[key: string]: unknown
}

/**
Expand All @@ -121,7 +157,7 @@ export interface AnalyticsBrowserSettings {
* If provided, `AnalyticsBrowser` will not fetch remote settings
* for the source.
*/
cdnSettings?: CDNSettings & Record<string, unknown>
cdnSettings?: CDNSettings
/**
* If provided, will override the default Segment CDN (https://cdn.segment.com) for this application.
*/
Expand All @@ -145,6 +181,26 @@ export interface AnalyticsSettings {
cdnURL?: string
}

/**
* Public Segment.io integration options.
* We don't expose all the settings for Segment.io, only the ones that are overridable
* (For example, we don't want `maybeBundledConfigIds to be exposed to the public API.)
*/
export type SegmentioIntegrationInitOptions = Pick<
SegmentioSettings,
'apiHost' | 'protocol' | 'deliveryStrategy'
>

/**
* Configurable Integrations Options -- these are the settings that are passed to the `analytics` instance.
*/
export interface IntegrationsInitOptions extends IntegrationsOptions {
/**
* Segment.io integration options -- note: Segment.io is not overridable OR disableable
*/
'Segment.io'?: SegmentioIntegrationInitOptions | boolean
}

export interface InitOptions {
/**
* Disables storing any data on the client-side via cookies or localstorage.
Expand All @@ -164,7 +220,7 @@ export interface InitOptions {
storage?: StorageSettings
user?: UserOptions
group?: UserOptions
integrations?: IntegrationsOptions
integrations?: IntegrationsInitOptions
plan?: Plan
retryQueue?: boolean
obfuscate?: boolean
Expand Down
Loading
Loading