Skip to content

Commit 2da7141

Browse files
committed
Make segment settings type safe
1 parent 60efa4f commit 2da7141

File tree

28 files changed

+263
-101
lines changed

28 files changed

+263
-101
lines changed

.changeset/heavy-taxis-suffer.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
---
22
'@segment/analytics-next': minor
33
---
4-
Add new `headers` setting, along with `fetchPriority`.
4+
- Make Segment.io config type-safe
5+
- Add new `headers` setting, along with `priority`.
56

67
```ts
78
analytics.load("<YOUR_WRITE_KEY>",
@@ -19,5 +20,6 @@ analytics.load("<YOUR_WRITE_KEY>",
1920
},
2021
}
2122
)
22-
2323
```
24+
25+

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: 7 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
},
@@ -1331,6 +1333,7 @@ describe('Segment.io overrides', () => {
13311333
integrations: {
13321334
'Segment.io': {
13331335
apiHost: 'https://my.endpoint.com',
1336+
// @ts-ignore
13341337
anotherSettings: '👻',
13351338
},
13361339
},
@@ -1526,13 +1529,13 @@ describe('Options', () => {
15261529
const disableSpy = jest.fn().mockReturnValue(true)
15271530
const [analytics] = await AnalyticsBrowser.load(
15281531
{
1529-
cdnSettings: { integrations: {}, foo: 123 },
1532+
cdnSettings: cdnSettingsMinimal,
15301533
writeKey,
15311534
},
15321535
{ disable: disableSpy }
15331536
)
15341537
expect(analytics).toBeInstanceOf(NullAnalytics)
1535-
expect(disableSpy).toBeCalledWith({ integrations: {}, foo: 123 })
1538+
expect(disableSpy).toHaveBeenCalledWith(cdnSettingsMinimal)
15361539
})
15371540
})
15381541
})

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: 58 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 { DeliveryStrategy } from '../plugins/segmentio/shared-dispatcher'
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,21 @@ 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
147+
}
148+
149+
export interface BrowserIntegrationsOptions extends IntegrationsOptions {
150+
'Segment.io'?: // subset of the SegmentSettings, since we don't want to expose all of them to the user
151+
| {
152+
apiHost?: string
153+
protocol?: 'http' | 'https'
154+
deliveryStrategy?: DeliveryStrategy
155+
}
156+
| boolean
111157
}
112158

113159
/**
@@ -121,7 +167,7 @@ export interface AnalyticsBrowserSettings {
121167
* If provided, `AnalyticsBrowser` will not fetch remote settings
122168
* for the source.
123169
*/
124-
cdnSettings?: CDNSettings & Record<string, unknown>
170+
cdnSettings?: CDNSettings
125171
/**
126172
* If provided, will override the default Segment CDN (https://cdn.segment.com) for this application.
127173
*/
@@ -164,7 +210,7 @@ export interface InitOptions {
164210
storage?: StorageSettings
165211
user?: UserOptions
166212
group?: UserOptions
167-
integrations?: IntegrationsOptions
213+
integrations?: BrowserIntegrationsOptions
168214
plan?: Plan
169215
retryQueue?: boolean
170216
obfuscate?: boolean

packages/browser/src/core/analytics/index.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { Emitter } from '@segment/analytics-generic-utils'
1818
import {
1919
Callback,
2020
EventFactory,
21-
IntegrationsOptions,
2221
EventProperties,
2322
SegmentEvent,
2423
} from '../events'
@@ -52,7 +51,11 @@ import {
5251
isSegmentPlugin,
5352
SegmentIOPluginMetadata,
5453
} from '../../plugins/segmentio'
55-
import { AnalyticsSettings, InitOptions } from '../../browser/settings'
54+
import {
55+
AnalyticsSettings,
56+
BrowserIntegrationsOptions,
57+
InitOptions,
58+
} from '../../browser/settings'
5659

5760
export type { InitOptions, AnalyticsSettings }
5861

@@ -99,10 +102,17 @@ export class AnalyticsInstanceSettings {
99102
this._getSegmentPluginMetadata = () =>
100103
queue.plugins.find(isSegmentPlugin)?.metadata
101104
this.writeKey = settings.writeKey
102-
this.cdnSettings = settings.cdnSettings ?? {
103-
integrations: {},
104-
edgeFunction: {},
105+
106+
// this is basically just to satisfy typescript / so we don't need to change the function sig of every test.
107+
// when loadAnalytics is called, cdnSettings will always be available.
108+
const emptyCDNSettings: CDNSettings = {
109+
integrations: {
110+
'Segment.io': {
111+
apiKey: '',
112+
},
113+
},
105114
}
115+
this.cdnSettings = settings.cdnSettings ?? emptyCDNSettings
106116
this.cdnURL = settings.cdnURL
107117
}
108118
}
@@ -124,7 +134,7 @@ export class Analytics
124134
private _universalStorage: UniversalStorage
125135

126136
initialized = false
127-
integrations: IntegrationsOptions
137+
integrations: BrowserIntegrationsOptions
128138
options: InitOptions
129139
queue: EventQueue
130140

0 commit comments

Comments
 (0)