@segment/analytics-browser-actions-fullsession 1.1.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/README.md +31 -0
- package/dist/cjs/generated-types.d.ts +3 -0
- package/dist/cjs/generated-types.js +3 -0
- package/dist/cjs/generated-types.js.map +1 -0
- package/dist/cjs/identifyUser/generated-types.d.ts +7 -0
- package/dist/cjs/identifyUser/generated-types.js +3 -0
- package/dist/cjs/identifyUser/generated-types.js.map +1 -0
- package/dist/cjs/identifyUser/index.d.ts +6 -0
- package/dist/cjs/identifyUser/index.js +53 -0
- package/dist/cjs/identifyUser/index.js.map +1 -0
- package/dist/cjs/index.d.ts +11 -0
- package/dist/cjs/index.js +58 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/recordEvent/generated-types.d.ts +6 -0
- package/dist/cjs/recordEvent/generated-types.js +3 -0
- package/dist/cjs/recordEvent/generated-types.js.map +1 -0
- package/dist/cjs/recordEvent/index.d.ts +6 -0
- package/dist/cjs/recordEvent/index.js +34 -0
- package/dist/cjs/recordEvent/index.js.map +1 -0
- package/dist/cjs/types.d.ts +11 -0
- package/dist/cjs/types.js +6 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/visitPage/generated-types.d.ts +5 -0
- package/dist/cjs/visitPage/generated-types.js +3 -0
- package/dist/cjs/visitPage/generated-types.js.map +1 -0
- package/dist/cjs/visitPage/index.d.ts +6 -0
- package/dist/cjs/visitPage/index.js +27 -0
- package/dist/cjs/visitPage/index.js.map +1 -0
- package/dist/esm/generated-types.d.ts +3 -0
- package/dist/esm/generated-types.js +2 -0
- package/dist/esm/generated-types.js.map +1 -0
- package/dist/esm/identifyUser/generated-types.d.ts +7 -0
- package/dist/esm/identifyUser/generated-types.js +2 -0
- package/dist/esm/identifyUser/generated-types.js.map +1 -0
- package/dist/esm/identifyUser/index.d.ts +6 -0
- package/dist/esm/identifyUser/index.js +51 -0
- package/dist/esm/identifyUser/index.js.map +1 -0
- package/dist/esm/index.d.ts +11 -0
- package/dist/esm/index.js +54 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/recordEvent/generated-types.d.ts +6 -0
- package/dist/esm/recordEvent/generated-types.js +2 -0
- package/dist/esm/recordEvent/generated-types.js.map +1 -0
- package/dist/esm/recordEvent/index.d.ts +6 -0
- package/dist/esm/recordEvent/index.js +32 -0
- package/dist/esm/recordEvent/index.js.map +1 -0
- package/dist/esm/types.d.ts +11 -0
- package/dist/esm/types.js +3 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/visitPage/generated-types.d.ts +5 -0
- package/dist/esm/visitPage/generated-types.js +2 -0
- package/dist/esm/visitPage/generated-types.js.map +1 -0
- package/dist/esm/visitPage/index.d.ts +6 -0
- package/dist/esm/visitPage/index.js +25 -0
- package/dist/esm/visitPage/index.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +25 -0
- package/src/__tests__/index.test.ts +177 -0
- package/src/generated-types.ts +8 -0
- package/src/identifyUser/__tests__/index.test.ts +315 -0
- package/src/identifyUser/generated-types.ts +18 -0
- package/src/identifyUser/index.ts +66 -0
- package/src/index.ts +68 -0
- package/src/recordEvent/__tests__/index.test.ts +274 -0
- package/src/recordEvent/generated-types.ts +14 -0
- package/src/recordEvent/index.ts +37 -0
- package/src/types.ts +14 -0
- package/src/visitPage/__tests__/index.test.ts +312 -0
- package/src/visitPage/generated-types.ts +10 -0
- package/src/visitPage/index.ts +31 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { Analytics, Context } from '@segment/analytics-next'
|
|
2
|
+
import fullSessionDestination from '../../index'
|
|
3
|
+
import { Subscription } from '@segment/browser-destination-runtime/types'
|
|
4
|
+
|
|
5
|
+
// Mock the browser destination runtime functions
|
|
6
|
+
jest.mock('@segment/browser-destination-runtime/load-script', () => ({
|
|
7
|
+
loadScript: (_src: any, _attributes: any) => Promise.resolve()
|
|
8
|
+
}))
|
|
9
|
+
|
|
10
|
+
jest.mock('@segment/browser-destination-runtime/resolve-when', () => ({
|
|
11
|
+
resolveWhen: (_fn: any, _timeout: any) => Promise.resolve()
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
// Mock the fullsession package
|
|
15
|
+
jest.mock('fullsession', () => ({
|
|
16
|
+
fullSessionTracker: {
|
|
17
|
+
initialize: jest.fn(),
|
|
18
|
+
identify: jest.fn(),
|
|
19
|
+
setSessionAttributes: jest.fn(),
|
|
20
|
+
event: jest.fn()
|
|
21
|
+
}
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
const trackSubscription: Subscription = {
|
|
25
|
+
partnerAction: 'recordEvent',
|
|
26
|
+
name: 'Record Event',
|
|
27
|
+
enabled: true,
|
|
28
|
+
subscribe: 'type = "track"',
|
|
29
|
+
mapping: {
|
|
30
|
+
name: {
|
|
31
|
+
'@path': '$.event'
|
|
32
|
+
},
|
|
33
|
+
properties: {
|
|
34
|
+
'@path': '$.properties'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('FullSession recordEvent action', () => {
|
|
40
|
+
const mockCustomerId = 'test-customer-id'
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
jest.clearAllMocks()
|
|
44
|
+
// Setup window.FUS mock if needed
|
|
45
|
+
if (typeof window !== 'undefined') {
|
|
46
|
+
// @ts-ignore
|
|
47
|
+
window.FUS = {}
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
jest.clearAllMocks()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('should record event with name and properties', async () => {
|
|
56
|
+
const { fullSessionTracker } = await import('fullsession')
|
|
57
|
+
|
|
58
|
+
const [event] = await fullSessionDestination({
|
|
59
|
+
customerId: mockCustomerId,
|
|
60
|
+
subscriptions: [trackSubscription] as any
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
await event.load(Context.system(), {} as Analytics)
|
|
64
|
+
|
|
65
|
+
const eventName = 'Product Purchased'
|
|
66
|
+
const properties = {
|
|
67
|
+
product_id: 'ABC123',
|
|
68
|
+
product_name: 'Widget',
|
|
69
|
+
price: 29.99,
|
|
70
|
+
currency: 'USD',
|
|
71
|
+
category: 'Electronics'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await event.track?.(
|
|
75
|
+
new Context({
|
|
76
|
+
type: 'track',
|
|
77
|
+
event: eventName,
|
|
78
|
+
properties
|
|
79
|
+
})
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
expect(fullSessionTracker.event).toHaveBeenCalledWith(eventName, properties)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('should record event with only name', async () => {
|
|
86
|
+
const { fullSessionTracker } = await import('fullsession')
|
|
87
|
+
|
|
88
|
+
const [event] = await fullSessionDestination({
|
|
89
|
+
customerId: mockCustomerId,
|
|
90
|
+
subscriptions: [trackSubscription] as any
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
await event.load(Context.system(), {} as Analytics)
|
|
94
|
+
|
|
95
|
+
const eventName = 'Simple Event'
|
|
96
|
+
|
|
97
|
+
await event.track?.(
|
|
98
|
+
new Context({
|
|
99
|
+
type: 'track',
|
|
100
|
+
event: eventName
|
|
101
|
+
})
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
expect(fullSessionTracker.event).toHaveBeenCalledWith(eventName, {})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('should record event with string and number properties', async () => {
|
|
108
|
+
const { fullSessionTracker } = await import('fullsession')
|
|
109
|
+
|
|
110
|
+
const [event] = await fullSessionDestination({
|
|
111
|
+
customerId: mockCustomerId,
|
|
112
|
+
subscriptions: [trackSubscription] as any
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
await event.load(Context.system(), {} as Analytics)
|
|
116
|
+
|
|
117
|
+
const eventName = 'Page View'
|
|
118
|
+
const properties = {
|
|
119
|
+
page_url: 'https://example.com/product/123',
|
|
120
|
+
page_title: 'Product Details',
|
|
121
|
+
load_time: 1.5,
|
|
122
|
+
session_id: 'sess_123',
|
|
123
|
+
user_count: 42
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await event.track?.(
|
|
127
|
+
new Context({
|
|
128
|
+
type: 'track',
|
|
129
|
+
event: eventName,
|
|
130
|
+
properties
|
|
131
|
+
})
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
expect(fullSessionTracker.event).toHaveBeenCalledWith(eventName, properties)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test('should handle empty properties object', async () => {
|
|
138
|
+
const { fullSessionTracker } = await import('fullsession')
|
|
139
|
+
|
|
140
|
+
const [event] = await fullSessionDestination({
|
|
141
|
+
customerId: mockCustomerId,
|
|
142
|
+
subscriptions: [trackSubscription] as any
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
await event.load(Context.system(), {} as Analytics)
|
|
146
|
+
|
|
147
|
+
const eventName = 'Button Clicked'
|
|
148
|
+
|
|
149
|
+
await event.track?.(
|
|
150
|
+
new Context({
|
|
151
|
+
type: 'track',
|
|
152
|
+
event: eventName,
|
|
153
|
+
properties: {}
|
|
154
|
+
})
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
expect(fullSessionTracker.event).toHaveBeenCalledWith(eventName, {})
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
test('should handle null properties', async () => {
|
|
161
|
+
const { fullSessionTracker } = await import('fullsession')
|
|
162
|
+
|
|
163
|
+
const [event] = await fullSessionDestination({
|
|
164
|
+
customerId: mockCustomerId,
|
|
165
|
+
subscriptions: [trackSubscription] as any
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
await event.load(Context.system(), {} as Analytics)
|
|
169
|
+
|
|
170
|
+
const eventName = 'Null Properties Event'
|
|
171
|
+
|
|
172
|
+
await event.track?.(
|
|
173
|
+
new Context({
|
|
174
|
+
type: 'track',
|
|
175
|
+
event: eventName,
|
|
176
|
+
properties: undefined
|
|
177
|
+
})
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
expect(fullSessionTracker.event).toHaveBeenCalledWith(eventName, {})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
test('should record multiple events in sequence', async () => {
|
|
184
|
+
const { fullSessionTracker } = await import('fullsession')
|
|
185
|
+
|
|
186
|
+
const [event] = await fullSessionDestination({
|
|
187
|
+
customerId: mockCustomerId,
|
|
188
|
+
subscriptions: [trackSubscription] as any
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
await event.load(Context.system(), {} as Analytics)
|
|
192
|
+
|
|
193
|
+
// First event
|
|
194
|
+
await event.track?.(
|
|
195
|
+
new Context({
|
|
196
|
+
type: 'track',
|
|
197
|
+
event: 'Event 1',
|
|
198
|
+
properties: { prop1: 'value1' }
|
|
199
|
+
})
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
// Second event
|
|
203
|
+
await event.track?.(
|
|
204
|
+
new Context({
|
|
205
|
+
type: 'track',
|
|
206
|
+
event: 'Event 2',
|
|
207
|
+
properties: { prop2: 'value2', count: 10 }
|
|
208
|
+
})
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
expect(fullSessionTracker.event).toHaveBeenCalledTimes(2)
|
|
212
|
+
expect(fullSessionTracker.event).toHaveBeenNthCalledWith(1, 'Event 1', { prop1: 'value1' })
|
|
213
|
+
expect(fullSessionTracker.event).toHaveBeenNthCalledWith(2, 'Event 2', { prop2: 'value2', count: 10 })
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
test('should record event with mixed property types', async () => {
|
|
217
|
+
const { fullSessionTracker } = await import('fullsession')
|
|
218
|
+
|
|
219
|
+
const [event] = await fullSessionDestination({
|
|
220
|
+
customerId: mockCustomerId,
|
|
221
|
+
subscriptions: [trackSubscription] as any
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
await event.load(Context.system(), {} as Analytics)
|
|
225
|
+
|
|
226
|
+
const eventName = 'Mixed Properties Event'
|
|
227
|
+
const properties = {
|
|
228
|
+
string_prop: 'test string',
|
|
229
|
+
number_prop: 123.45,
|
|
230
|
+
boolean_prop: true,
|
|
231
|
+
array_prop: [1, 2, 3],
|
|
232
|
+
object_prop: { nested: 'value' },
|
|
233
|
+
null_prop: null,
|
|
234
|
+
undefined_prop: undefined
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
await event.track?.(
|
|
238
|
+
new Context({
|
|
239
|
+
type: 'track',
|
|
240
|
+
event: eventName,
|
|
241
|
+
properties
|
|
242
|
+
})
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
expect(fullSessionTracker.event).toHaveBeenCalledWith(eventName, properties)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
test('should handle event with special characters in name', async () => {
|
|
249
|
+
const { fullSessionTracker } = await import('fullsession')
|
|
250
|
+
|
|
251
|
+
const [event] = await fullSessionDestination({
|
|
252
|
+
customerId: mockCustomerId,
|
|
253
|
+
subscriptions: [trackSubscription] as any
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
await event.load(Context.system(), {} as Analytics)
|
|
257
|
+
|
|
258
|
+
const eventName = 'Event with Special Characters! @#$%^&*()'
|
|
259
|
+
const properties = {
|
|
260
|
+
special_chars: 'åäöüñ',
|
|
261
|
+
unicode: '🎉🚀'
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
await event.track?.(
|
|
265
|
+
new Context({
|
|
266
|
+
type: 'track',
|
|
267
|
+
event: eventName,
|
|
268
|
+
properties
|
|
269
|
+
})
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
expect(fullSessionTracker.event).toHaveBeenCalledWith(eventName, properties)
|
|
273
|
+
})
|
|
274
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Generated file. DO NOT MODIFY IT BY HAND.
|
|
2
|
+
|
|
3
|
+
export interface Payload {
|
|
4
|
+
/**
|
|
5
|
+
* The name of the event being tracked.
|
|
6
|
+
*/
|
|
7
|
+
name: string
|
|
8
|
+
/**
|
|
9
|
+
* Additional properties and metadata associated with the event.
|
|
10
|
+
*/
|
|
11
|
+
properties?: {
|
|
12
|
+
[k: string]: unknown
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { BrowserActionDefinition } from '@segment/browser-destination-runtime/types'
|
|
2
|
+
import type { Settings } from '../generated-types'
|
|
3
|
+
import type { Payload } from './generated-types'
|
|
4
|
+
import type { FUS, CustomEventData } from '../types'
|
|
5
|
+
|
|
6
|
+
const action: BrowserActionDefinition<Settings, FUS, Payload> = {
|
|
7
|
+
title: 'Track Event',
|
|
8
|
+
description: 'Track custom events and user interactions in FullSession for behavioral analysis.',
|
|
9
|
+
platform: 'web',
|
|
10
|
+
defaultSubscription: 'type = "track"',
|
|
11
|
+
fields: {
|
|
12
|
+
name: {
|
|
13
|
+
description: 'The name of the event being tracked.',
|
|
14
|
+
label: 'Event Name',
|
|
15
|
+
required: true,
|
|
16
|
+
type: 'string',
|
|
17
|
+
default: {
|
|
18
|
+
'@path': '$.event'
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
properties: {
|
|
22
|
+
description: 'Additional properties and metadata associated with the event.',
|
|
23
|
+
label: 'Event Properties',
|
|
24
|
+
required: false,
|
|
25
|
+
type: 'object',
|
|
26
|
+
default: {
|
|
27
|
+
'@path': '$.properties'
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
perform: (FUS, data) => {
|
|
32
|
+
const { name, properties } = data.payload
|
|
33
|
+
FUS.event(name, (properties ?? {}) as CustomEventData)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default action
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { fullSessionTracker } from 'fullsession'
|
|
2
|
+
|
|
3
|
+
type CustomEventData = {
|
|
4
|
+
[key: string]: string | number
|
|
5
|
+
[stringKey: `${string}_str`]: string
|
|
6
|
+
[numberKey: `${string}_real`]: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type UserCustomAttributes = {
|
|
10
|
+
[attribute: string]: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type FUS = typeof fullSessionTracker
|
|
14
|
+
export { fullSessionTracker, CustomEventData, UserCustomAttributes }
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { Analytics, Context } from '@segment/analytics-next'
|
|
2
|
+
import fullSessionDestination from '../../index'
|
|
3
|
+
import { Subscription } from '@segment/browser-destination-runtime/types'
|
|
4
|
+
|
|
5
|
+
// Mock the browser destination runtime functions
|
|
6
|
+
jest.mock('@segment/browser-destination-runtime/load-script', () => ({
|
|
7
|
+
loadScript: (_src: any, _attributes: any) => Promise.resolve()
|
|
8
|
+
}))
|
|
9
|
+
|
|
10
|
+
jest.mock('@segment/browser-destination-runtime/resolve-when', () => ({
|
|
11
|
+
resolveWhen: (_fn: any, _timeout: any) => Promise.resolve()
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
// Mock the fullsession package
|
|
15
|
+
jest.mock('fullsession', () => ({
|
|
16
|
+
fullSessionTracker: {
|
|
17
|
+
initialize: jest.fn(),
|
|
18
|
+
identify: jest.fn(),
|
|
19
|
+
setPageAttributes: jest.fn(),
|
|
20
|
+
event: jest.fn()
|
|
21
|
+
}
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
const pageSubscription: Subscription = {
|
|
25
|
+
partnerAction: 'visitPage',
|
|
26
|
+
name: 'Visit Page',
|
|
27
|
+
enabled: true,
|
|
28
|
+
subscribe: 'type = "page"',
|
|
29
|
+
mapping: {
|
|
30
|
+
properties: {
|
|
31
|
+
'@path': '$.properties'
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe('FullSession visitPage action', () => {
|
|
37
|
+
const mockCustomerId = 'test-customer-id'
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
jest.clearAllMocks()
|
|
41
|
+
// Setup window.FUS mock if needed
|
|
42
|
+
if (typeof window !== 'undefined') {
|
|
43
|
+
// @ts-ignore
|
|
44
|
+
window.FUS = {}
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('should set session attributes with page properties', async () => {
|
|
49
|
+
const { fullSessionTracker } = await import('fullsession')
|
|
50
|
+
|
|
51
|
+
const [event] = await fullSessionDestination({
|
|
52
|
+
customerId: mockCustomerId,
|
|
53
|
+
subscriptions: [pageSubscription] as any
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
await event.load(Context.system(), {} as Analytics)
|
|
57
|
+
|
|
58
|
+
const properties = {
|
|
59
|
+
title: 'Welcome to Our Site',
|
|
60
|
+
url: 'https://example.com',
|
|
61
|
+
path: '/',
|
|
62
|
+
referrer: 'https://google.com',
|
|
63
|
+
search: '?utm_source=google'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
await event.page?.(
|
|
67
|
+
new Context({
|
|
68
|
+
type: 'page',
|
|
69
|
+
name: 'Home Page',
|
|
70
|
+
properties
|
|
71
|
+
})
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
expect(fullSessionTracker.setPageAttributes).toHaveBeenCalledWith({
|
|
75
|
+
...properties
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('should handle page with category and properties', async () => {
|
|
80
|
+
const { fullSessionTracker } = await import('fullsession')
|
|
81
|
+
|
|
82
|
+
const [event] = await fullSessionDestination({
|
|
83
|
+
customerId: mockCustomerId,
|
|
84
|
+
subscriptions: [pageSubscription] as any
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
await event.load(Context.system(), {} as Analytics)
|
|
88
|
+
|
|
89
|
+
const properties = {
|
|
90
|
+
title: 'Amazing Product',
|
|
91
|
+
url: 'https://example.com/products/amazing-product',
|
|
92
|
+
product_id: 'prod_123'
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await event.page?.(
|
|
96
|
+
new Context({
|
|
97
|
+
type: 'page',
|
|
98
|
+
name: 'Product Details',
|
|
99
|
+
category: 'Products',
|
|
100
|
+
properties
|
|
101
|
+
})
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
expect(fullSessionTracker.setPageAttributes).toHaveBeenCalledWith({
|
|
105
|
+
...properties
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('should handle page with properties', async () => {
|
|
110
|
+
const { fullSessionTracker } = await import('fullsession')
|
|
111
|
+
|
|
112
|
+
const [event] = await fullSessionDestination({
|
|
113
|
+
customerId: mockCustomerId,
|
|
114
|
+
subscriptions: [pageSubscription] as any
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
await event.load(Context.system(), {} as Analytics)
|
|
118
|
+
|
|
119
|
+
const properties = {
|
|
120
|
+
title: 'About Our Company',
|
|
121
|
+
url: 'https://example.com/about',
|
|
122
|
+
section: 'company'
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
await event.page?.(
|
|
126
|
+
new Context({
|
|
127
|
+
type: 'page',
|
|
128
|
+
name: 'About Us',
|
|
129
|
+
properties
|
|
130
|
+
})
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
expect(fullSessionTracker.setPageAttributes).toHaveBeenCalledWith({
|
|
134
|
+
...properties
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('should handle page with empty properties', async () => {
|
|
139
|
+
const { fullSessionTracker } = await import('fullsession')
|
|
140
|
+
|
|
141
|
+
const [event] = await fullSessionDestination({
|
|
142
|
+
customerId: mockCustomerId,
|
|
143
|
+
subscriptions: [pageSubscription] as any
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
await event.load(Context.system(), {} as Analytics)
|
|
147
|
+
|
|
148
|
+
await event.page?.(
|
|
149
|
+
new Context({
|
|
150
|
+
type: 'page',
|
|
151
|
+
name: 'Simple Page',
|
|
152
|
+
properties: {}
|
|
153
|
+
})
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
expect(fullSessionTracker.setPageAttributes).toHaveBeenCalledWith({})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
test('should handle page with null properties', async () => {
|
|
160
|
+
const { fullSessionTracker } = await import('fullsession')
|
|
161
|
+
|
|
162
|
+
const [event] = await fullSessionDestination({
|
|
163
|
+
customerId: mockCustomerId,
|
|
164
|
+
subscriptions: [pageSubscription] as any
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
await event.load(Context.system(), {} as Analytics)
|
|
168
|
+
|
|
169
|
+
await event.page?.(
|
|
170
|
+
new Context({
|
|
171
|
+
type: 'page',
|
|
172
|
+
name: 'Null Properties Page',
|
|
173
|
+
properties: undefined
|
|
174
|
+
})
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
expect(fullSessionTracker.setPageAttributes).toHaveBeenCalledWith({})
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
test('should handle page without name and category', async () => {
|
|
181
|
+
const { fullSessionTracker } = await import('fullsession')
|
|
182
|
+
|
|
183
|
+
const [event] = await fullSessionDestination({
|
|
184
|
+
customerId: mockCustomerId,
|
|
185
|
+
subscriptions: [pageSubscription] as any
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
await event.load(Context.system(), {} as Analytics)
|
|
189
|
+
|
|
190
|
+
const properties = {
|
|
191
|
+
title: 'Unnamed Page',
|
|
192
|
+
url: 'https://example.com/unnamed',
|
|
193
|
+
path: '/unnamed'
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
await event.page?.(
|
|
197
|
+
new Context({
|
|
198
|
+
type: 'page',
|
|
199
|
+
properties
|
|
200
|
+
})
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
expect(fullSessionTracker.setPageAttributes).toHaveBeenCalledWith({
|
|
204
|
+
...properties
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
test('should handle complex properties with various data types', async () => {
|
|
209
|
+
const { fullSessionTracker } = await import('fullsession')
|
|
210
|
+
|
|
211
|
+
const [event] = await fullSessionDestination({
|
|
212
|
+
customerId: mockCustomerId,
|
|
213
|
+
subscriptions: [pageSubscription] as any
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
await event.load(Context.system(), {} as Analytics)
|
|
217
|
+
|
|
218
|
+
const properties = {
|
|
219
|
+
title: 'Complex Page Title',
|
|
220
|
+
url: 'https://example.com/complex',
|
|
221
|
+
load_time: 2.5,
|
|
222
|
+
user_agent: 'Mozilla/5.0...',
|
|
223
|
+
is_authenticated: true,
|
|
224
|
+
visitor_count: 1500,
|
|
225
|
+
tags: ['homepage', 'featured'],
|
|
226
|
+
metadata: {
|
|
227
|
+
experiment_id: 'exp_123',
|
|
228
|
+
variant: 'A'
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
await event.page?.(
|
|
233
|
+
new Context({
|
|
234
|
+
type: 'page',
|
|
235
|
+
name: 'Complex Page',
|
|
236
|
+
properties
|
|
237
|
+
})
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
expect(fullSessionTracker.setPageAttributes).toHaveBeenCalledWith({
|
|
241
|
+
...properties
|
|
242
|
+
})
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
test('should track multiple page visits in sequence', async () => {
|
|
246
|
+
const { fullSessionTracker } = await import('fullsession')
|
|
247
|
+
|
|
248
|
+
const [event] = await fullSessionDestination({
|
|
249
|
+
customerId: mockCustomerId,
|
|
250
|
+
subscriptions: [pageSubscription] as any
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
await event.load(Context.system(), {} as Analytics)
|
|
254
|
+
|
|
255
|
+
// First page visit
|
|
256
|
+
await event.page?.(
|
|
257
|
+
new Context({
|
|
258
|
+
type: 'page',
|
|
259
|
+
name: 'Home',
|
|
260
|
+
properties: { url: 'https://example.com' }
|
|
261
|
+
})
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
expect(fullSessionTracker.setPageAttributes).toHaveBeenCalledWith({
|
|
265
|
+
url: 'https://example.com'
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
// Second page visit
|
|
269
|
+
await event.page?.(
|
|
270
|
+
new Context({
|
|
271
|
+
type: 'page',
|
|
272
|
+
name: 'Products',
|
|
273
|
+
properties: { url: 'https://example.com/products' }
|
|
274
|
+
})
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
expect(fullSessionTracker.setPageAttributes).toHaveBeenCalledWith({
|
|
278
|
+
url: 'https://example.com/products'
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
expect(fullSessionTracker.setPageAttributes).toHaveBeenCalledTimes(2)
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
test('should handle page visit with special characters in properties', async () => {
|
|
285
|
+
const { fullSessionTracker } = await import('fullsession')
|
|
286
|
+
|
|
287
|
+
const [event] = await fullSessionDestination({
|
|
288
|
+
customerId: mockCustomerId,
|
|
289
|
+
subscriptions: [pageSubscription] as any
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
await event.load(Context.system(), {} as Analytics)
|
|
293
|
+
|
|
294
|
+
const properties = {
|
|
295
|
+
title: 'Title with émojis 🚀 and symbols!',
|
|
296
|
+
url: 'https://example.com/special-chars?q=test&lang=en',
|
|
297
|
+
description: 'A page with special characters: àáâãäåæçèéêë'
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
await event.page?.(
|
|
301
|
+
new Context({
|
|
302
|
+
type: 'page',
|
|
303
|
+
name: 'Page with "Special" Characters & Symbols',
|
|
304
|
+
properties
|
|
305
|
+
})
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
expect(fullSessionTracker.setPageAttributes).toHaveBeenCalledWith({
|
|
309
|
+
...properties
|
|
310
|
+
})
|
|
311
|
+
})
|
|
312
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { BrowserActionDefinition } from '@segment/browser-destination-runtime/types'
|
|
2
|
+
import type { Settings } from '../generated-types'
|
|
3
|
+
import type { Payload } from './generated-types'
|
|
4
|
+
import type { FUS, UserCustomAttributes } from '../types'
|
|
5
|
+
|
|
6
|
+
// Change from unknown to the partner SDK types
|
|
7
|
+
const action: BrowserActionDefinition<Settings, FUS, Payload> = {
|
|
8
|
+
title: 'Page View',
|
|
9
|
+
description: 'Track page views and set page-specific attributes in FullSession for navigation analysis.',
|
|
10
|
+
defaultSubscription: 'type = "page"',
|
|
11
|
+
platform: 'web',
|
|
12
|
+
fields: {
|
|
13
|
+
properties: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
required: false,
|
|
16
|
+
description: 'Properties and metadata associated with the page being viewed',
|
|
17
|
+
label: 'Page Properties',
|
|
18
|
+
default: {
|
|
19
|
+
'@path': '$.properties'
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
perform: (FUS, data) => {
|
|
24
|
+
const { properties } = data.payload
|
|
25
|
+
FUS.setPageAttributes({
|
|
26
|
+
...properties
|
|
27
|
+
} as UserCustomAttributes)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default action
|