Skip to content

Commit 99dca22

Browse files
committed
add priority and headers
1 parent 7f9d5a0 commit 99dca22

File tree

8 files changed

+245
-30
lines changed

8 files changed

+245
-30
lines changed

.changeset/heavy-taxis-suffer.md

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

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,3 +1536,37 @@ describe('Options', () => {
15361536
})
15371537
})
15381538
})
1539+
1540+
describe('setting headers', () => {
1541+
it('allows setting headers', async () => {
1542+
const [ajs] = await AnalyticsBrowser.load(
1543+
{
1544+
writeKey,
1545+
},
1546+
{
1547+
integrations: {
1548+
'Segment.io': {
1549+
deliveryStrategy: {
1550+
config: {
1551+
headers: {
1552+
'X-Test': 'foo',
1553+
},
1554+
},
1555+
},
1556+
},
1557+
},
1558+
}
1559+
)
1560+
1561+
await ajs.track('sup')
1562+
1563+
await sleep(10)
1564+
const [call] = fetchCalls.filter((el) =>
1565+
el.url.toString().includes('api.segment.io')
1566+
)
1567+
expect(call.headers).toEqual({
1568+
'Content-Type': 'text/plain',
1569+
'X-Test': 'foo',
1570+
})
1571+
})
1572+
})

packages/browser/src/plugins/segmentio/__tests__/batched-dispatcher.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ describe('Batching', () => {
102102
},
103103
"keepalive": false,
104104
"method": "post",
105+
"priority": undefined,
105106
},
106107
]
107108
`)
@@ -185,6 +186,7 @@ describe('Batching', () => {
185186
},
186187
"keepalive": false,
187188
"method": "post",
189+
"priority": undefined,
188190
},
189191
]
190192
`)
@@ -220,6 +222,7 @@ describe('Batching', () => {
220222
},
221223
"keepalive": false,
222224
"method": "post",
225+
"priority": undefined,
223226
},
224227
]
225228
`)
@@ -234,6 +237,7 @@ describe('Batching', () => {
234237
},
235238
"keepalive": false,
236239
"method": "post",
240+
"priority": undefined,
237241
},
238242
]
239243
`)
@@ -265,6 +269,7 @@ describe('Batching', () => {
265269
},
266270
"keepalive": false,
267271
"method": "post",
272+
"priority": undefined,
268273
},
269274
]
270275
`)

packages/browser/src/plugins/segmentio/__tests__/index.test.ts

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,95 @@ describe('Segment.io', () => {
6565
})
6666
})
6767

68-
describe('configuring a keep alive', () => {
68+
describe('configuring headers', () => {
69+
it('should accept additional headers', async () => {
70+
const analytics = new Analytics({ writeKey: 'foo' })
71+
72+
await analytics.register(
73+
await segmentio(analytics, {
74+
apiKey: '',
75+
deliveryStrategy: {
76+
config: {
77+
headers: {
78+
'X-My-Header': 'foo',
79+
},
80+
},
81+
},
82+
})
83+
)
84+
85+
await analytics.track('foo')
86+
const [_, params] = spyMock.mock.lastCall
87+
expect(params.headers['X-My-Header']).toBe('foo')
88+
expect(params.headers['Content-Type']).toBe('text/plain')
89+
})
90+
91+
it('should allow additional headers to be a function', async () => {
92+
const analytics = new Analytics({ writeKey: 'foo' })
93+
94+
await analytics.register(
95+
await segmentio(analytics, {
96+
apiKey: '',
97+
deliveryStrategy: {
98+
config: {
99+
headers: () => ({
100+
'X-My-Header': 'foo',
101+
}),
102+
},
103+
},
104+
})
105+
)
106+
107+
await analytics.track('foo')
108+
const [_, params] = spyMock.mock.lastCall
109+
expect(params.headers['X-My-Header']).toBe('foo')
110+
expect(params.headers['Content-Type']).toBe('text/plain')
111+
})
112+
113+
it('should allow content type to be overridden', async () => {
114+
const analytics = new Analytics({ writeKey: 'foo' })
115+
116+
await analytics.register(
117+
await segmentio(analytics, {
118+
apiKey: '',
119+
deliveryStrategy: {
120+
config: {
121+
headers: () => ({
122+
'Content-Type': 'bar',
123+
}),
124+
},
125+
},
126+
})
127+
)
128+
129+
await analytics.track('foo')
130+
const [_, params] = spyMock.mock.lastCall
131+
expect(params.headers['Content-Type']).toBe('bar')
132+
})
133+
})
134+
135+
describe('configuring fetch priority', () => {
136+
it('should accept fetch priority configuration', async () => {
137+
const analytics = new Analytics({ writeKey: 'foo' })
138+
139+
await analytics.register(
140+
await segmentio(analytics, {
141+
apiKey: '',
142+
deliveryStrategy: {
143+
config: {
144+
priority: 'high',
145+
},
146+
},
147+
})
148+
)
149+
150+
await analytics.track('foo')
151+
const [_, params] = spyMock.mock.lastCall
152+
expect(params.priority).toBe('high')
153+
})
154+
})
155+
156+
describe('configuring keepalive', () => {
69157
it('should accept keepalive configuration', async () => {
70158
const analytics = new Analytics({ writeKey: 'foo' })
71159

packages/browser/src/plugins/segmentio/batched-dispatcher.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,7 @@ import { onPageChange } from '../../lib/on-page-change'
44
import { SegmentFacade } from '../../lib/to-facade'
55
import { RateLimitError } from './ratelimit-error'
66
import { Context } from '../../core/context'
7-
8-
export type BatchingDispatchConfig = {
9-
size?: number
10-
timeout?: number
11-
maxRetries?: number
12-
keepalive?: boolean
13-
}
7+
import { BatchingDispatchConfig, createHeaders } from './shared-dispatcher'
148

159
const MAX_PAYLOAD_SIZE = 500
1610
const MAX_KEEPALIVE_SIZE = 64
@@ -84,15 +78,15 @@ export default function batch(
8478

8579
return fetch(`https://${apiHost}/b`, {
8680
keepalive: config?.keepalive || pageUnloaded,
87-
headers: {
88-
'Content-Type': 'text/plain',
89-
},
81+
headers: createHeaders(config?.headers),
9082
method: 'post',
9183
body: JSON.stringify({
9284
writeKey,
9385
batch: updatedBatch,
9486
sentAt: new Date().toISOString(),
9587
}),
88+
// @ts-ignore - not in the ts lib yet
89+
priority: config?.priority,
9690
}).then((res) => {
9791
if (res.status >= 500) {
9892
throw new Error(`Bad response from server: ${res.status}`)

packages/browser/src/plugins/segmentio/fetch-dispatcher.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
import { fetch } from '../../lib/fetch'
22
import { RateLimitError } from './ratelimit-error'
3-
3+
import { createHeaders, StandardDispatcherConfig } from './shared-dispatcher'
44
export type Dispatcher = (url: string, body: object) => Promise<unknown>
55

6-
export type StandardDispatcherConfig = {
7-
keepalive?: boolean
8-
}
9-
106
export default function (config?: StandardDispatcherConfig): {
117
dispatch: Dispatcher
128
} {
139
function dispatch(url: string, body: object): Promise<unknown> {
1410
return fetch(url, {
1511
keepalive: config?.keepalive,
16-
headers: { 'Content-Type': 'text/plain' },
12+
headers: createHeaders(config?.headers),
1713
method: 'post',
1814
body: JSON.stringify(body),
15+
// @ts-ignore - not in the ts lib yet
16+
priority: config?.priority,
1917
}).then((res) => {
2018
if (res.status >= 500) {
2119
throw new Error(`Bad response from server: ${res.status}`)

packages/browser/src/plugins/segmentio/index.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,12 @@ import { Plugin } from '../../core/plugin'
77
import { PriorityQueue } from '../../lib/priority-queue'
88
import { PersistedPriorityQueue } from '../../lib/priority-queue/persisted'
99
import { toFacade } from '../../lib/to-facade'
10-
import batch, { BatchingDispatchConfig } from './batched-dispatcher'
11-
import standard, { StandardDispatcherConfig } from './fetch-dispatcher'
10+
import batch from './batched-dispatcher'
11+
import standard from './fetch-dispatcher'
1212
import { normalize } from './normalize'
1313
import { scheduleFlush } from './schedule-flush'
1414
import { SEGMENT_API_HOST } from '../../core/constants'
15-
16-
type DeliveryStrategy =
17-
| {
18-
strategy?: 'standard'
19-
config?: StandardDispatcherConfig
20-
}
21-
| {
22-
strategy?: 'batching'
23-
config?: BatchingDispatchConfig
24-
}
15+
import { DeliveryStrategy } from './shared-dispatcher'
2516

2617
export type SegmentioSettings = {
2718
apiKey: string
@@ -95,7 +86,7 @@ export function segmentio(
9586
const client =
9687
deliveryStrategy?.strategy === 'batching'
9788
? batch(apiHost, deliveryStrategy.config)
98-
: standard(deliveryStrategy?.config as StandardDispatcherConfig)
89+
: standard(deliveryStrategy?.config)
9990

10091
async function send(ctx: Context): Promise<Context> {
10192
if (isOffline()) {
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
export const createHeaders = (
2+
headerSettings: AdditionalHeaders | undefined
3+
): Record<string, string> => {
4+
return {
5+
'Content-Type': 'text/plain',
6+
...(typeof headerSettings === 'function'
7+
? headerSettings()
8+
: headerSettings),
9+
}
10+
}
11+
12+
/**
13+
* Additional headers to be sent with the request.
14+
* Default is `Content-Type: text/plain`. This can be overridden.
15+
* If a function is provided, it will be called before each request.
16+
*/
17+
export type AdditionalHeaders =
18+
| Record<string, string>
19+
| (() => Record<string, string>)
20+
21+
export type RequestPriority = 'high' | 'low' | 'auto'
22+
23+
/**
24+
* These are the options that can be passed to the fetch dispatcher.
25+
* They more/less map to the Fetch RequestInit type.
26+
*/
27+
interface DispatchFetchConfig {
28+
/**
29+
* This is useful for ensuring that an event is sent even if the user navigates away from the page.
30+
* However, it may increase the likelihood of events being lost, as there is a 64kb limit for *all* fetch requests (not just ones to segment) with keepalive (which is why it's disabled by default). So, if you're sending a lot of data, this will likely cause events to be dropped.
31+
32+
* @default false
33+
*/
34+
keepalive?: boolean
35+
/**
36+
* Additional headers to be sent with the request.
37+
* Default is `Content-Type: text/plain`. This can be overridden.
38+
* If a function is provided, it will be called before each request.
39+
* @example { 'Content-Type': 'application/json' } or () => { 'Content-Type': 'application/json' }
40+
*/
41+
headers?: AdditionalHeaders
42+
/**
43+
* 'Request Priority' of the request
44+
* @see https://developer.mozilla.org/en-US/docs/Web/API/RequestInit#priority
45+
*/
46+
priority?: RequestPriority
47+
}
48+
49+
export interface BatchingDispatchConfig extends DispatchFetchConfig {
50+
/**
51+
* The maximum number of events to send in a single request. If the batch reaches this size, a request will automatically be sent.
52+
*
53+
* @default 10
54+
*/
55+
size?: number
56+
/**
57+
* The maximum time, in milliseconds, to wait before sending a request.
58+
* This won't alaways be relevant, as the request will be sent when the size is reached.
59+
* However, if the size is never reached, the request will be sent after this time.
60+
* When it comes to retries, if there is a rate limit timeout header, that will be respected over the value here.
61+
*
62+
* @default 5000
63+
*/
64+
timeout?: number
65+
/**
66+
* The maximum number of retries to attempt before giving up.
67+
* @default 10
68+
*/
69+
maxRetries?: number
70+
}
71+
72+
export interface StandardDispatcherConfig extends DispatchFetchConfig {}
73+
74+
export type DeliveryStrategy =
75+
| {
76+
strategy?: 'standard'
77+
config?: StandardDispatcherConfig
78+
}
79+
| {
80+
strategy?: 'batching'
81+
config?: BatchingDispatchConfig
82+
}

0 commit comments

Comments
 (0)