Skip to content

Commit

Permalink
Make fetch priority and headers configurable, and define types for se…
Browse files Browse the repository at this point in the history
…gment.io and integration options. (#1204)
  • Loading branch information
silesky authored Jan 21, 2025
1 parent 4c479f6 commit 8e0162b
Show file tree
Hide file tree
Showing 38 changed files with 554 additions and 254 deletions.
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 @@ -1334,6 +1336,7 @@ describe('Segment.io overrides', () => {
integrations: {
'Segment.io': {
apiHost: 'https://my.endpoint.com',
// @ts-ignore
anotherSettings: '👻',
},
},
Expand Down Expand Up @@ -1529,13 +1532,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

0 comments on commit 8e0162b

Please sign in to comment.