@segment/analytics-browser-actions-utils 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +19 -0
- package/src/__tests__/index.test.ts +25 -0
- package/src/generated-types.ts +12 -0
- package/src/index.ts +43 -0
- package/src/throttle/__tests__/index.test.ts +379 -0
- package/src/throttle/generated-types.ts +12 -0
- package/src/throttle/index.ts +69 -0
- package/tsconfig.json +9 -0
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@segment/analytics-browser-actions-utils",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"main": "./dist/cjs",
|
|
6
|
+
"module": "./dist/esm",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "yarn build:esm && yarn build:cjs",
|
|
9
|
+
"build:cjs": "tsc --module commonjs --outDir ./dist/cjs",
|
|
10
|
+
"build:esm": "tsc --outDir ./dist/esm"
|
|
11
|
+
},
|
|
12
|
+
"typings": "./dist/esm",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@segment/browser-destination-runtime": "^1.0.0"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"@segment/analytics-next": "*"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Analytics, Context } from '@segment/analytics-next'
|
|
2
|
+
import segmentUtilitiesDestination, { destination } from '../index'
|
|
3
|
+
|
|
4
|
+
describe('Segment Utilities Web', () => {
|
|
5
|
+
test('loads the destination', async () => {
|
|
6
|
+
const [throttle] = await segmentUtilitiesDestination({
|
|
7
|
+
throttleWindow: 3000,
|
|
8
|
+
passThroughCount: 1,
|
|
9
|
+
subscriptions: [
|
|
10
|
+
{
|
|
11
|
+
partnerAction: 'throttle',
|
|
12
|
+
name: 'Throttle',
|
|
13
|
+
enabled: true,
|
|
14
|
+
subscribe: 'type = "track"',
|
|
15
|
+
mapping: {}
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
jest.spyOn(destination, 'initialize')
|
|
21
|
+
|
|
22
|
+
await throttle.load(Context.system(), {} as Analytics)
|
|
23
|
+
expect(destination.initialize).toHaveBeenCalled()
|
|
24
|
+
})
|
|
25
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Generated file. DO NOT MODIFY IT BY HAND.
|
|
2
|
+
|
|
3
|
+
export interface Settings {
|
|
4
|
+
/**
|
|
5
|
+
* The window of time in milliseconds to throttle events.
|
|
6
|
+
*/
|
|
7
|
+
throttleWindow?: number
|
|
8
|
+
/**
|
|
9
|
+
* Number of events to pass through while waiting for the throttle time to expire.
|
|
10
|
+
*/
|
|
11
|
+
passThroughCount?: number
|
|
12
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Settings } from './generated-types'
|
|
2
|
+
import type { BrowserDestinationDefinition } from '@segment/browser-destination-runtime/types'
|
|
3
|
+
import { browserDestination } from '@segment/browser-destination-runtime/shim'
|
|
4
|
+
|
|
5
|
+
import throttle from './throttle'
|
|
6
|
+
|
|
7
|
+
export type SegmentUtilitiesInstance = {
|
|
8
|
+
eventMap: Record<string, { windowStarted: number; receivedCount: number }>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Switch from unknown to the partner SDK client types
|
|
12
|
+
export const destination: BrowserDestinationDefinition<Settings, SegmentUtilitiesInstance> = {
|
|
13
|
+
name: 'Segment Utilities Web',
|
|
14
|
+
slug: 'actions-segment-utilities-web',
|
|
15
|
+
mode: 'device',
|
|
16
|
+
|
|
17
|
+
settings: {
|
|
18
|
+
throttleWindow: {
|
|
19
|
+
label: 'Throttle time',
|
|
20
|
+
description: 'The window of time in milliseconds to throttle events.',
|
|
21
|
+
type: 'number',
|
|
22
|
+
default: 3000
|
|
23
|
+
},
|
|
24
|
+
passThroughCount: {
|
|
25
|
+
label: 'Number of events to pass through',
|
|
26
|
+
description: 'Number of events to pass through while waiting for the throttle time to expire.',
|
|
27
|
+
type: 'number',
|
|
28
|
+
default: 1
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
initialize: async () => {
|
|
33
|
+
return {
|
|
34
|
+
eventMap: {}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
actions: {
|
|
39
|
+
throttle
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default browserDestination(destination)
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { Analytics, Context } from '@segment/analytics-next'
|
|
2
|
+
import segmentUtilities from '../../index'
|
|
3
|
+
|
|
4
|
+
let ajs: Analytics
|
|
5
|
+
|
|
6
|
+
describe('throttle', () => {
|
|
7
|
+
beforeEach(async () => {
|
|
8
|
+
ajs = new Analytics({
|
|
9
|
+
writeKey: 'shall_never_be_revealed'
|
|
10
|
+
})
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test('throttles events', async () => {
|
|
14
|
+
const [throttle] = await segmentUtilities({
|
|
15
|
+
throttleWindow: 3000,
|
|
16
|
+
passThroughCount: 1,
|
|
17
|
+
subscriptions: [
|
|
18
|
+
{
|
|
19
|
+
partnerAction: 'throttle',
|
|
20
|
+
name: 'Throttle',
|
|
21
|
+
enabled: true,
|
|
22
|
+
subscribe: 'type = "track"',
|
|
23
|
+
mapping: {}
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
await throttle.load(Context.system(), ajs)
|
|
29
|
+
let ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
30
|
+
|
|
31
|
+
expect(ctx?.event.integrations).not.toBeDefined()
|
|
32
|
+
|
|
33
|
+
ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
34
|
+
|
|
35
|
+
expect(ctx?.event.integrations).toBeDefined()
|
|
36
|
+
expect(ctx?.event.integrations?.['Segment.io']).toEqual(false)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('does not throttle if no settings provided', async () => {
|
|
40
|
+
const [throttle] = await segmentUtilities({
|
|
41
|
+
subscriptions: [
|
|
42
|
+
{
|
|
43
|
+
partnerAction: 'throttle',
|
|
44
|
+
name: 'Throttle',
|
|
45
|
+
enabled: true,
|
|
46
|
+
subscribe: 'type = "track"',
|
|
47
|
+
mapping: {}
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
await throttle.load(Context.system(), ajs)
|
|
53
|
+
let ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
54
|
+
|
|
55
|
+
expect(ctx?.event.integrations).not.toBeDefined()
|
|
56
|
+
|
|
57
|
+
ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
58
|
+
|
|
59
|
+
expect(ctx?.event.integrations).not.toBeDefined()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('accept overrides', async () => {
|
|
63
|
+
const [throttle] = await segmentUtilities({
|
|
64
|
+
throttleWindow: 3000,
|
|
65
|
+
passThroughCount: 1,
|
|
66
|
+
subscriptions: [
|
|
67
|
+
{
|
|
68
|
+
partnerAction: 'throttle',
|
|
69
|
+
name: 'Throttle',
|
|
70
|
+
enabled: true,
|
|
71
|
+
subscribe: 'type = "track"',
|
|
72
|
+
mapping: {
|
|
73
|
+
passThroughCount: 2
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
await throttle.load(Context.system(), ajs)
|
|
80
|
+
let ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
81
|
+
|
|
82
|
+
expect(ctx?.event.integrations).not.toBeDefined()
|
|
83
|
+
|
|
84
|
+
ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
85
|
+
|
|
86
|
+
expect(ctx?.event.integrations).not.toBeDefined()
|
|
87
|
+
|
|
88
|
+
ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
89
|
+
|
|
90
|
+
expect(ctx?.event.integrations).toBeDefined()
|
|
91
|
+
expect(ctx?.event.integrations?.['Segment.io']).toEqual(false)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('ignores invalid overrides', async () => {
|
|
95
|
+
const [throttle] = await segmentUtilities({
|
|
96
|
+
throttleWindow: 3000,
|
|
97
|
+
passThroughCount: 1,
|
|
98
|
+
subscriptions: [
|
|
99
|
+
{
|
|
100
|
+
partnerAction: 'throttle',
|
|
101
|
+
name: 'Throttle',
|
|
102
|
+
enabled: true,
|
|
103
|
+
subscribe: 'type = "track"',
|
|
104
|
+
mapping: {
|
|
105
|
+
passThroughCount: {
|
|
106
|
+
'@path': '$.event'
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
await throttle.load(Context.system(), ajs)
|
|
114
|
+
let ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
115
|
+
|
|
116
|
+
expect(ctx?.event.integrations).not.toBeDefined()
|
|
117
|
+
|
|
118
|
+
ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
119
|
+
|
|
120
|
+
expect(ctx?.event.integrations).toBeDefined()
|
|
121
|
+
expect(ctx?.event.integrations?.['Segment.io']).toEqual(false)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('does not allow any event to pass through if the pass through count is 0', async () => {
|
|
125
|
+
const [throttle] = await segmentUtilities({
|
|
126
|
+
throttleWindow: 3000,
|
|
127
|
+
passThroughCount: 0,
|
|
128
|
+
subscriptions: [
|
|
129
|
+
{
|
|
130
|
+
partnerAction: 'throttle',
|
|
131
|
+
name: 'Throttle',
|
|
132
|
+
enabled: true,
|
|
133
|
+
subscribe: 'type = "track"',
|
|
134
|
+
mapping: {}
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
await throttle.load(Context.system(), ajs)
|
|
140
|
+
let ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
141
|
+
|
|
142
|
+
expect(ctx?.event.integrations).toBeDefined()
|
|
143
|
+
expect(ctx?.event.integrations?.['Segment.io']).toEqual(false)
|
|
144
|
+
|
|
145
|
+
ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
146
|
+
|
|
147
|
+
expect(ctx?.event.integrations).toBeDefined()
|
|
148
|
+
expect(ctx?.event.integrations?.['Segment.io']).toEqual(false)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test('throttles multiple events names separately', async () => {
|
|
152
|
+
const [throttle] = await segmentUtilities({
|
|
153
|
+
throttleWindow: 3000,
|
|
154
|
+
passThroughCount: 1,
|
|
155
|
+
subscriptions: [
|
|
156
|
+
{
|
|
157
|
+
partnerAction: 'throttle',
|
|
158
|
+
name: 'Throttle',
|
|
159
|
+
enabled: true,
|
|
160
|
+
subscribe: 'type = "track"',
|
|
161
|
+
mapping: {}
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
await throttle.load(Context.system(), ajs)
|
|
167
|
+
let ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
168
|
+
|
|
169
|
+
expect(ctx?.event.integrations).not.toBeDefined()
|
|
170
|
+
|
|
171
|
+
ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Stopped' }))
|
|
172
|
+
expect(ctx?.event.integrations).not.toBeDefined()
|
|
173
|
+
|
|
174
|
+
ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
175
|
+
expect(ctx?.event.integrations).toBeDefined()
|
|
176
|
+
expect(ctx?.event.integrations?.['Segment.io']).toEqual(false)
|
|
177
|
+
|
|
178
|
+
ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Stopped' }))
|
|
179
|
+
expect(ctx?.event.integrations).toBeDefined()
|
|
180
|
+
expect(ctx?.event.integrations?.['Segment.io']).toEqual(false)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
test('Allow n events to pass through during the window', async () => {
|
|
184
|
+
const [throttle] = await segmentUtilities({
|
|
185
|
+
throttleWindow: 3000,
|
|
186
|
+
passThroughCount: 3,
|
|
187
|
+
subscriptions: [
|
|
188
|
+
{
|
|
189
|
+
partnerAction: 'throttle',
|
|
190
|
+
name: 'Throttle',
|
|
191
|
+
enabled: true,
|
|
192
|
+
subscribe: 'type = "track"',
|
|
193
|
+
mapping: {}
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
await throttle.load(Context.system(), ajs)
|
|
199
|
+
let passedThrough = 0
|
|
200
|
+
for (let i = 0; i < 10; i++) {
|
|
201
|
+
const ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
202
|
+
if (ctx?.event.integrations?.['Segment.io'] !== false) {
|
|
203
|
+
passedThrough++
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
expect(passedThrough).toBe(3)
|
|
208
|
+
|
|
209
|
+
const [throttle2] = await segmentUtilities({
|
|
210
|
+
throttleWindow: 3000,
|
|
211
|
+
passThroughCount: 8,
|
|
212
|
+
subscriptions: [
|
|
213
|
+
{
|
|
214
|
+
partnerAction: 'throttle',
|
|
215
|
+
name: 'Throttle',
|
|
216
|
+
enabled: true,
|
|
217
|
+
subscribe: 'type = "track"',
|
|
218
|
+
mapping: {}
|
|
219
|
+
}
|
|
220
|
+
]
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
await throttle2.load(Context.system(), ajs)
|
|
224
|
+
passedThrough = 0
|
|
225
|
+
|
|
226
|
+
for (let i = 0; i < 10; i++) {
|
|
227
|
+
const ctx = await throttle2.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
228
|
+
if (ctx?.event.integrations?.['Segment.io'] !== false) {
|
|
229
|
+
passedThrough++
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
expect(passedThrough).toBe(8)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
test('Preserves the integrations object while flipping the Segment destination to false', async () => {
|
|
237
|
+
const [throttle] = await segmentUtilities({
|
|
238
|
+
throttleWindow: 3000,
|
|
239
|
+
passThroughCount: 1,
|
|
240
|
+
subscriptions: [
|
|
241
|
+
{
|
|
242
|
+
partnerAction: 'throttle',
|
|
243
|
+
name: 'Throttle',
|
|
244
|
+
enabled: true,
|
|
245
|
+
subscribe: 'type = "track"',
|
|
246
|
+
mapping: {}
|
|
247
|
+
}
|
|
248
|
+
]
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
const integrations = {
|
|
252
|
+
All: false,
|
|
253
|
+
'Segment.io': true,
|
|
254
|
+
'Google Analytics': true,
|
|
255
|
+
'Customer.io': true,
|
|
256
|
+
Amplitude: {
|
|
257
|
+
sessionId: 12345
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
await throttle.load(Context.system(), ajs)
|
|
262
|
+
let ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played', integrations }))
|
|
263
|
+
|
|
264
|
+
expect(ctx?.event.integrations).toEqual(integrations)
|
|
265
|
+
|
|
266
|
+
ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played', integrations }))
|
|
267
|
+
expect(ctx?.event.integrations).toEqual({ ...integrations, 'Segment.io': false })
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
test('Time-based throttling', async () => {
|
|
271
|
+
const [throttle] = await segmentUtilities({
|
|
272
|
+
throttleWindow: 3000,
|
|
273
|
+
passThroughCount: 1,
|
|
274
|
+
subscriptions: [
|
|
275
|
+
{
|
|
276
|
+
partnerAction: 'throttle',
|
|
277
|
+
name: 'Throttle',
|
|
278
|
+
enabled: true,
|
|
279
|
+
subscribe: 'type = "track"',
|
|
280
|
+
mapping: {}
|
|
281
|
+
}
|
|
282
|
+
]
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
await throttle.load(Context.system(), ajs)
|
|
286
|
+
|
|
287
|
+
// Canada Day
|
|
288
|
+
jest.spyOn(global.Date, 'now').mockImplementationOnce(() => new Date('2022-07-01T00:00:00.000Z').valueOf())
|
|
289
|
+
|
|
290
|
+
let ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
291
|
+
|
|
292
|
+
expect(ctx?.event.integrations).not.toBeDefined()
|
|
293
|
+
|
|
294
|
+
// One second later, throttle this event
|
|
295
|
+
jest.spyOn(global.Date, 'now').mockImplementationOnce(() => new Date('2022-07-01T00:00:01.000Z').valueOf())
|
|
296
|
+
|
|
297
|
+
ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
298
|
+
|
|
299
|
+
expect(ctx?.event.integrations).toBeDefined()
|
|
300
|
+
expect(ctx?.event.integrations?.['Segment.io']).toEqual(false)
|
|
301
|
+
|
|
302
|
+
// Three seconds later, let this event through
|
|
303
|
+
jest.spyOn(global.Date, 'now').mockImplementationOnce(() => new Date('2022-07-01T00:00:03.100Z').valueOf())
|
|
304
|
+
|
|
305
|
+
ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
306
|
+
expect(ctx?.event.integrations).not.toBeDefined()
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
test('Complex Time-based throttling', async () => {
|
|
310
|
+
const [throttle] = await segmentUtilities({
|
|
311
|
+
throttleWindow: 3000,
|
|
312
|
+
passThroughCount: 2,
|
|
313
|
+
subscriptions: [
|
|
314
|
+
{
|
|
315
|
+
partnerAction: 'throttle',
|
|
316
|
+
name: 'Throttle',
|
|
317
|
+
enabled: true,
|
|
318
|
+
subscribe: 'type = "track"',
|
|
319
|
+
mapping: {}
|
|
320
|
+
}
|
|
321
|
+
]
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
await throttle.load(Context.system(), ajs)
|
|
325
|
+
|
|
326
|
+
// Canada Day
|
|
327
|
+
jest.spyOn(global.Date, 'now').mockImplementationOnce(() => new Date('2022-07-01T00:00:00.000Z').valueOf())
|
|
328
|
+
let ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
329
|
+
expect(ctx?.event.integrations).not.toBeDefined()
|
|
330
|
+
|
|
331
|
+
// One second later, still allowed because of the passThroughCount
|
|
332
|
+
jest.spyOn(global.Date, 'now').mockImplementationOnce(() => new Date('2022-07-01T00:00:01.000Z').valueOf())
|
|
333
|
+
ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
334
|
+
expect(ctx?.event.integrations).not.toBeDefined()
|
|
335
|
+
|
|
336
|
+
// One second later, throttle this event
|
|
337
|
+
jest.spyOn(global.Date, 'now').mockImplementationOnce(() => new Date('2022-07-01T00:00:02.000Z').valueOf())
|
|
338
|
+
ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
339
|
+
expect(ctx?.event.integrations).toBeDefined()
|
|
340
|
+
expect(ctx?.event.integrations?.['Segment.io']).toEqual(false)
|
|
341
|
+
|
|
342
|
+
// One second later, window expired, let this event through
|
|
343
|
+
jest.spyOn(global.Date, 'now').mockImplementationOnce(() => new Date('2022-07-01T00:00:03.100Z').valueOf())
|
|
344
|
+
ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
345
|
+
expect(ctx?.event.integrations).not.toBeDefined()
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
test('Only throttles the events that match the subscription', async () => {
|
|
349
|
+
const [throttle] = await segmentUtilities({
|
|
350
|
+
throttleWindow: 3000,
|
|
351
|
+
passThroughCount: 1,
|
|
352
|
+
subscriptions: [
|
|
353
|
+
{
|
|
354
|
+
partnerAction: 'throttle',
|
|
355
|
+
name: 'Throttle',
|
|
356
|
+
enabled: true,
|
|
357
|
+
subscribe: 'type = "track" and event = "Video Played"',
|
|
358
|
+
mapping: {}
|
|
359
|
+
}
|
|
360
|
+
]
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
await throttle.load(Context.system(), ajs)
|
|
364
|
+
let ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
365
|
+
|
|
366
|
+
expect(ctx?.event.integrations).not.toBeDefined()
|
|
367
|
+
|
|
368
|
+
ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Played' }))
|
|
369
|
+
expect(ctx?.event.integrations).toBeDefined()
|
|
370
|
+
expect(ctx?.event.integrations?.['Segment.io']).toEqual(false)
|
|
371
|
+
|
|
372
|
+
ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Stopped' }))
|
|
373
|
+
|
|
374
|
+
expect(ctx?.event.integrations).not.toBeDefined()
|
|
375
|
+
|
|
376
|
+
ctx = await throttle.track?.(new Context({ type: 'track', event: 'Video Stopped' }))
|
|
377
|
+
expect(ctx?.event.integrations).not.toBeDefined()
|
|
378
|
+
})
|
|
379
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { SegmentUtilitiesInstance } from '..'
|
|
2
|
+
import type { BrowserActionDefinition } from '@segment/browser-destination-runtime/types'
|
|
3
|
+
import type { Settings } from '../generated-types'
|
|
4
|
+
import type { Payload } from './generated-types'
|
|
5
|
+
|
|
6
|
+
const action: BrowserActionDefinition<Settings, SegmentUtilitiesInstance, Payload> = {
|
|
7
|
+
title: 'Throttle by event name',
|
|
8
|
+
description: 'Throttle events sent to Segment. Throttling is on a per event name basis.',
|
|
9
|
+
platform: 'web',
|
|
10
|
+
defaultSubscription: 'type = "track"',
|
|
11
|
+
fields: {
|
|
12
|
+
passThroughCount: {
|
|
13
|
+
label: 'Number of events to pass through',
|
|
14
|
+
description: 'Override the global pass through count.',
|
|
15
|
+
type: 'integer',
|
|
16
|
+
allowNull: true
|
|
17
|
+
},
|
|
18
|
+
throttleWindow: {
|
|
19
|
+
label: 'Throttle time',
|
|
20
|
+
description: 'Override the global throttle time.',
|
|
21
|
+
type: 'number',
|
|
22
|
+
allowNull: true
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
lifecycleHook: 'before',
|
|
26
|
+
perform: ({ eventMap }, data) => {
|
|
27
|
+
const { context, settings, payload } = data
|
|
28
|
+
|
|
29
|
+
const overridePassThroughCount = isNaN(Number(payload.passThroughCount))
|
|
30
|
+
? undefined
|
|
31
|
+
: Number(payload.passThroughCount)
|
|
32
|
+
const overrideThrottleWindow = isNaN(Number(payload.throttleWindow)) ? undefined : Number(payload.throttleWindow)
|
|
33
|
+
|
|
34
|
+
const passThroughCount = overridePassThroughCount ?? settings.passThroughCount ?? 1
|
|
35
|
+
const throttleWindow = overrideThrottleWindow ?? settings.throttleWindow ?? 0
|
|
36
|
+
const event = context.event.event
|
|
37
|
+
|
|
38
|
+
if (!event) {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (eventMap[event]) {
|
|
43
|
+
const { windowStarted } = eventMap[event]
|
|
44
|
+
const now = Date.now()
|
|
45
|
+
if (now - windowStarted < throttleWindow) {
|
|
46
|
+
// within the throttle window
|
|
47
|
+
eventMap[event].receivedCount++
|
|
48
|
+
} else {
|
|
49
|
+
eventMap[event] = {
|
|
50
|
+
// reset the window
|
|
51
|
+
windowStarted: Date.now(),
|
|
52
|
+
receivedCount: 1
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
// first time we've seen this event, start the window
|
|
57
|
+
eventMap[event] = {
|
|
58
|
+
windowStarted: Date.now(),
|
|
59
|
+
receivedCount: 1
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (eventMap[event].receivedCount > passThroughCount) {
|
|
64
|
+
context.updateEvent('integrations', { ...context.event.integrations, 'Segment.io': false })
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default action
|