Skip to content

Commit 8e0162b

Browse files
authored
Make fetch priority and headers configurable, and define types for segment.io and integration options. (#1204)
1 parent 4c479f6 commit 8e0162b

File tree

38 files changed

+554
-254
lines changed

38 files changed

+554
-254
lines changed

.changeset/heavy-taxis-suffer.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@segment/analytics-next': minor
3+
---
4+
- Make Segment.io config type-safe
5+
- Add new `headers` setting, along with `priority`.
6+
7+
```ts
8+
analytics.load("<YOUR_WRITE_KEY>",
9+
{
10+
integrations: {
11+
'Segment.io': {
12+
deliveryStrategy: {
13+
strategy: "standard" // also works for 'batching'
14+
config: {
15+
headers: { 'x-api-key': 'foo' } or () => {...}
16+
priority: 'low',
17+
},
18+
},
19+
},
20+
},
21+
}
22+
)
23+
```
24+
25+

.changeset/nasty-peaches-watch.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@segment/analytics-consent-tools': patch
3+
'@segment/analytics-signals': patch
4+
---
5+
6+
Update types

packages/browser/src/browser/__tests__/analytics-lazy-init.integration.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { CorePlugin, PluginType, sleep } from '@segment/analytics-core'
22
import {
3+
cdnSettingsMinimal,
34
createMockFetchImplementation,
45
createRemotePlugin,
56
getBufferedPageCtxFixture,
@@ -92,7 +93,9 @@ describe('Lazy destination loading', () => {
9293
beforeEach(() => {
9394
jest.mocked(unfetch).mockImplementation(
9495
createMockFetchImplementation({
96+
...cdnSettingsMinimal,
9597
integrations: {
98+
...cdnSettingsMinimal.integrations,
9699
braze: {},
97100
google: {},
98101
},

packages/browser/src/browser/__tests__/anon-id-and-reset.integration.test.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,14 @@ const helpers = {
1414
.mockImplementation(() => createSuccess({ integrations: {} }))
1515
},
1616
loadAnalytics() {
17-
return AnalyticsBrowser.load({ writeKey: 'foo' })
17+
return AnalyticsBrowser.load(
18+
{ writeKey: 'foo' },
19+
{
20+
integrations: {
21+
'Segment.io': {},
22+
},
23+
}
24+
)
1825
},
1926
}
2027

packages/browser/src/browser/__tests__/csp-detection.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ import { CDNSettings } from '..'
44
import { pWhile } from '../../lib/p-while'
55
import { snippet } from '../../tester/__fixtures__/segment-snippet'
66
import * as Factory from '../../test-helpers/factories'
7+
import { cdnSettingsMinimal } from '../../test-helpers/fixtures'
78

89
const cdnResponse: CDNSettings = {
10+
...cdnSettingsMinimal,
911
integrations: {
12+
...cdnSettingsMinimal.integrations,
1013
Zapier: {
1114
type: 'server',
1215
},

packages/browser/src/browser/__tests__/integration.test.ts

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { Analytics, InitOptions } from '../../core/analytics'
1111
import { LegacyDestination } from '../../plugins/ajs-destination'
1212
import { PersistedPriorityQueue } from '../../lib/priority-queue/persisted'
1313
// @ts-ignore loadCDNSettings mocked dependency is accused as unused
14-
import { AnalyticsBrowser, loadCDNSettings } from '..'
14+
import { AnalyticsBrowser, CDNSettings, loadCDNSettings } from '..'
1515
// @ts-ignore isOffline mocked dependency is accused as unused
1616
import { isOffline } from '../../core/connection'
1717
import * as SegmentPlugin from '../../plugins/segmentio'
@@ -376,7 +376,7 @@ describe('Initialization', () => {
376376
it('does not fetch source settings if cdnSettings is set', async () => {
377377
await AnalyticsBrowser.load({
378378
writeKey,
379-
cdnSettings: { integrations: {} },
379+
cdnSettings: cdnSettingsMinimal,
380380
})
381381

382382
expect(fetchCalls.length).toBe(0)
@@ -666,8 +666,10 @@ describe('Dispatch', () => {
666666
{
667667
writeKey,
668668
cdnSettings: {
669+
...cdnSettingsMinimal,
669670
integrations: {
670671
'Segment.io': {
672+
...cdnSettingsMinimal.integrations['Segment.io'],
671673
apiHost: 'cdnSettings.api.io',
672674
},
673675
},
@@ -1334,6 +1336,7 @@ describe('Segment.io overrides', () => {
13341336
integrations: {
13351337
'Segment.io': {
13361338
apiHost: 'https://my.endpoint.com',
1339+
// @ts-ignore
13371340
anotherSettings: '👻',
13381341
},
13391342
},
@@ -1529,13 +1532,47 @@ describe('Options', () => {
15291532
const disableSpy = jest.fn().mockReturnValue(true)
15301533
const [analytics] = await AnalyticsBrowser.load(
15311534
{
1532-
cdnSettings: { integrations: {}, foo: 123 },
1535+
cdnSettings: cdnSettingsMinimal,
15331536
writeKey,
15341537
},
15351538
{ disable: disableSpy }
15361539
)
15371540
expect(analytics).toBeInstanceOf(NullAnalytics)
1538-
expect(disableSpy).toBeCalledWith({ integrations: {}, foo: 123 })
1541+
expect(disableSpy).toHaveBeenCalledWith(cdnSettingsMinimal)
1542+
})
1543+
})
1544+
})
1545+
1546+
describe('setting headers', () => {
1547+
it('allows setting headers', async () => {
1548+
const [ajs] = await AnalyticsBrowser.load(
1549+
{
1550+
writeKey,
1551+
},
1552+
{
1553+
integrations: {
1554+
'Segment.io': {
1555+
deliveryStrategy: {
1556+
config: {
1557+
headers: {
1558+
'X-Test': 'foo',
1559+
},
1560+
},
1561+
},
1562+
},
1563+
},
1564+
}
1565+
)
1566+
1567+
await ajs.track('sup')
1568+
1569+
await sleep(10)
1570+
const [call] = fetchCalls.filter((el) =>
1571+
el.url.toString().includes('api.segment.io')
1572+
)
1573+
expect(call.headers).toEqual({
1574+
'Content-Type': 'text/plain',
1575+
'X-Test': 'foo',
15391576
})
15401577
})
15411578
})

packages/browser/src/browser/__tests__/standalone-errors.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ import { pWhile } from '../../lib/p-while'
55
import unfetch from 'unfetch'
66
import { RemoteMetrics } from '../../core/stats/remote-metrics'
77
import * as Factory from '../../test-helpers/factories'
8+
import { cdnSettingsMinimal } from '../../test-helpers/fixtures'
89

910
const cdnResponse: CDNSettings = {
11+
...cdnSettingsMinimal,
1012
integrations: {
13+
...cdnSettingsMinimal.integrations,
1114
Zapier: {
1215
type: 'server',
1316
},

packages/browser/src/browser/__tests__/standalone.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ import { pWhile } from '../../lib/p-while'
55
import { snippet } from '../../tester/__fixtures__/segment-snippet'
66
import * as Factory from '../../test-helpers/factories'
77
import { getGlobalAnalytics } from '../..'
8+
import { cdnSettingsMinimal } from '../../test-helpers/fixtures'
89

910
const cdnResponse: CDNSettings = {
11+
...cdnSettingsMinimal,
1012
integrations: {
13+
...cdnSettingsMinimal.integrations,
1114
Zapier: {
1215
type: 'server',
1316
},

packages/browser/src/browser/__tests__/update-cdn-settings.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ import { setGlobalCDNUrl } from '../../lib/parse-cdn'
33
import { remoteLoader } from '../../plugins/remote-loader'
44
import unfetch from 'unfetch'
55
import { createSuccess } from '../../test-helpers/factories'
6+
import { cdnSettingsMinimal } from '../../test-helpers/fixtures'
67
jest.mock('unfetch')
78

89
const INTG_TO_DELETE = 'deleteMe'
910

1011
const cdnSettings = {
12+
...cdnSettingsMinimal,
1113
integrations: {
14+
...cdnSettingsMinimal.integrations,
1215
[INTG_TO_DELETE]: { bar: true },
1316
otherIntegration: { foo: true },
1417
},

packages/browser/src/browser/settings.ts

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* These settings will be exposed via the public API
33
*/
4-
import { IntegrationsOptions, Plan } from '../core/events'
4+
import { Plan } from '../core/events'
55
import { MetricsOptions } from '../core/stats/remote-metrics'
66
import { ClassicIntegrationSource } from '../plugins/ajs-destination/types'
77
import { PluginFactory, RemotePlugin } from '../plugins/remote-loader'
@@ -10,17 +10,20 @@ import { RoutingRule } from '../plugins/routing-middleware'
1010
import { CookieOptions, StorageSettings } from '../core/storage'
1111
import { UserOptions } from '../core/user'
1212
import { HighEntropyHint } from '../lib/client-hints/interfaces'
13+
import { IntegrationsOptions } from '@segment/analytics-core'
14+
import { SegmentioSettings } from '../plugins/segmentio'
15+
16+
interface VersionSettings {
17+
version?: string
18+
override?: string
19+
componentTypes?: ('browser' | 'android' | 'ios' | 'server')[]
20+
}
1321

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

18-
versionSettings?: {
19-
version?: string
20-
override?: string
21-
componentTypes?: ('browser' | 'android' | 'ios' | 'server')[]
22-
}
23-
26+
versionSettings?: VersionSettings
2427
/**
2528
* We know if an integration is device mode if it has `bundlingStatus: 'bundled'` and the `browser` componentType in `versionSettings`.
2629
* 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.
@@ -38,20 +41,48 @@ export interface RemoteIntegrationSettings {
3841
categories: string[]
3942
}
4043

41-
// Segment.io specific
42-
retryQueue?: boolean
43-
4444
// any extra unknown settings
4545
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4646
[key: string]: any
4747
}
4848

49+
export interface RemoteSegmentIOIntegrationSettings
50+
extends RemoteIntegrationSettings {
51+
/**
52+
* Segment write key
53+
*/
54+
apiKey: string
55+
56+
/**
57+
* Whether or not offline events are stored and retried.
58+
*
59+
* Originally, each plugin was concieved to use global retry logic (e.g. throwing magic errors would result in retries),
60+
* 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.
61+
* The segmentio plugin itself has its own internal retry queue and is not affected by this setting.
62+
*
63+
* @default true
64+
*/
65+
retryQueue?: boolean
66+
67+
/**
68+
* Host of the segment cdn - this may need to be manually enabled on the source.
69+
* @default 'api.segment.io/v1'
70+
*/
71+
apiHost?: string
72+
addBundledMetadata?: boolean
73+
unbundledIntegrations?: string[]
74+
bundledConfigIds?: string[]
75+
unbundledConfigIds?: string[]
76+
maybeBundledConfigIds?: Record<string, string[]>
77+
}
78+
4979
/**
5080
* The remote settings object for a source, typically fetched from the Segment CDN.
5181
* Warning: this is an *unstable* object.
5282
*/
5383
export interface CDNSettings {
5484
integrations: {
85+
'Segment.io': RemoteSegmentIOIntegrationSettings
5586
[creationName: string]: RemoteIntegrationSettings
5687
}
5788

@@ -108,6 +139,11 @@ export interface CDNSettings {
108139
autoInstrumentationSettings?: {
109140
sampleRate: number
110141
}
142+
143+
/**
144+
* Allow misc settings to be passed through, but
145+
*/
146+
[key: string]: unknown
111147
}
112148

113149
/**
@@ -121,7 +157,7 @@ export interface AnalyticsBrowserSettings {
121157
* If provided, `AnalyticsBrowser` will not fetch remote settings
122158
* for the source.
123159
*/
124-
cdnSettings?: CDNSettings & Record<string, unknown>
160+
cdnSettings?: CDNSettings
125161
/**
126162
* If provided, will override the default Segment CDN (https://cdn.segment.com) for this application.
127163
*/
@@ -145,6 +181,26 @@ export interface AnalyticsSettings {
145181
cdnURL?: string
146182
}
147183

184+
/**
185+
* Public Segment.io integration options.
186+
* We don't expose all the settings for Segment.io, only the ones that are overridable
187+
* (For example, we don't want `maybeBundledConfigIds to be exposed to the public API.)
188+
*/
189+
export type SegmentioIntegrationInitOptions = Pick<
190+
SegmentioSettings,
191+
'apiHost' | 'protocol' | 'deliveryStrategy'
192+
>
193+
194+
/**
195+
* Configurable Integrations Options -- these are the settings that are passed to the `analytics` instance.
196+
*/
197+
export interface IntegrationsInitOptions extends IntegrationsOptions {
198+
/**
199+
* Segment.io integration options -- note: Segment.io is not overridable OR disableable
200+
*/
201+
'Segment.io'?: SegmentioIntegrationInitOptions | boolean
202+
}
203+
148204
export interface InitOptions {
149205
/**
150206
* Disables storing any data on the client-side via cookies or localstorage.
@@ -164,7 +220,7 @@ export interface InitOptions {
164220
storage?: StorageSettings
165221
user?: UserOptions
166222
group?: UserOptions
167-
integrations?: IntegrationsOptions
223+
integrations?: IntegrationsInitOptions
168224
plan?: Plan
169225
retryQueue?: boolean
170226
obfuscate?: boolean

0 commit comments

Comments
 (0)