@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.
Files changed (71) hide show
  1. package/README.md +31 -0
  2. package/dist/cjs/generated-types.d.ts +3 -0
  3. package/dist/cjs/generated-types.js +3 -0
  4. package/dist/cjs/generated-types.js.map +1 -0
  5. package/dist/cjs/identifyUser/generated-types.d.ts +7 -0
  6. package/dist/cjs/identifyUser/generated-types.js +3 -0
  7. package/dist/cjs/identifyUser/generated-types.js.map +1 -0
  8. package/dist/cjs/identifyUser/index.d.ts +6 -0
  9. package/dist/cjs/identifyUser/index.js +53 -0
  10. package/dist/cjs/identifyUser/index.js.map +1 -0
  11. package/dist/cjs/index.d.ts +11 -0
  12. package/dist/cjs/index.js +58 -0
  13. package/dist/cjs/index.js.map +1 -0
  14. package/dist/cjs/recordEvent/generated-types.d.ts +6 -0
  15. package/dist/cjs/recordEvent/generated-types.js +3 -0
  16. package/dist/cjs/recordEvent/generated-types.js.map +1 -0
  17. package/dist/cjs/recordEvent/index.d.ts +6 -0
  18. package/dist/cjs/recordEvent/index.js +34 -0
  19. package/dist/cjs/recordEvent/index.js.map +1 -0
  20. package/dist/cjs/types.d.ts +11 -0
  21. package/dist/cjs/types.js +6 -0
  22. package/dist/cjs/types.js.map +1 -0
  23. package/dist/cjs/visitPage/generated-types.d.ts +5 -0
  24. package/dist/cjs/visitPage/generated-types.js +3 -0
  25. package/dist/cjs/visitPage/generated-types.js.map +1 -0
  26. package/dist/cjs/visitPage/index.d.ts +6 -0
  27. package/dist/cjs/visitPage/index.js +27 -0
  28. package/dist/cjs/visitPage/index.js.map +1 -0
  29. package/dist/esm/generated-types.d.ts +3 -0
  30. package/dist/esm/generated-types.js +2 -0
  31. package/dist/esm/generated-types.js.map +1 -0
  32. package/dist/esm/identifyUser/generated-types.d.ts +7 -0
  33. package/dist/esm/identifyUser/generated-types.js +2 -0
  34. package/dist/esm/identifyUser/generated-types.js.map +1 -0
  35. package/dist/esm/identifyUser/index.d.ts +6 -0
  36. package/dist/esm/identifyUser/index.js +51 -0
  37. package/dist/esm/identifyUser/index.js.map +1 -0
  38. package/dist/esm/index.d.ts +11 -0
  39. package/dist/esm/index.js +54 -0
  40. package/dist/esm/index.js.map +1 -0
  41. package/dist/esm/recordEvent/generated-types.d.ts +6 -0
  42. package/dist/esm/recordEvent/generated-types.js +2 -0
  43. package/dist/esm/recordEvent/generated-types.js.map +1 -0
  44. package/dist/esm/recordEvent/index.d.ts +6 -0
  45. package/dist/esm/recordEvent/index.js +32 -0
  46. package/dist/esm/recordEvent/index.js.map +1 -0
  47. package/dist/esm/types.d.ts +11 -0
  48. package/dist/esm/types.js +3 -0
  49. package/dist/esm/types.js.map +1 -0
  50. package/dist/esm/visitPage/generated-types.d.ts +5 -0
  51. package/dist/esm/visitPage/generated-types.js +2 -0
  52. package/dist/esm/visitPage/generated-types.js.map +1 -0
  53. package/dist/esm/visitPage/index.d.ts +6 -0
  54. package/dist/esm/visitPage/index.js +25 -0
  55. package/dist/esm/visitPage/index.js.map +1 -0
  56. package/dist/tsconfig.tsbuildinfo +1 -0
  57. package/package.json +25 -0
  58. package/src/__tests__/index.test.ts +177 -0
  59. package/src/generated-types.ts +8 -0
  60. package/src/identifyUser/__tests__/index.test.ts +315 -0
  61. package/src/identifyUser/generated-types.ts +18 -0
  62. package/src/identifyUser/index.ts +66 -0
  63. package/src/index.ts +68 -0
  64. package/src/recordEvent/__tests__/index.test.ts +274 -0
  65. package/src/recordEvent/generated-types.ts +14 -0
  66. package/src/recordEvent/index.ts +37 -0
  67. package/src/types.ts +14 -0
  68. package/src/visitPage/__tests__/index.test.ts +312 -0
  69. package/src/visitPage/generated-types.ts +10 -0
  70. package/src/visitPage/index.ts +31 -0
  71. package/tsconfig.json +9 -0
@@ -0,0 +1,315 @@
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 identifySubscription: Subscription = {
25
+ partnerAction: 'identifyUser',
26
+ name: 'Identify User',
27
+ enabled: true,
28
+ subscribe: 'type = "identify"',
29
+ mapping: {
30
+ userId: {
31
+ '@path': '$.userId'
32
+ },
33
+ anonymousId: {
34
+ '@path': '$.anonymousId'
35
+ },
36
+ traits: {
37
+ '@path': '$.traits'
38
+ }
39
+ }
40
+ }
41
+
42
+ describe('FullSession identifyUser action', () => {
43
+ const mockCustomerId = 'test-customer-id'
44
+
45
+ beforeEach(() => {
46
+ jest.clearAllMocks()
47
+ // Setup window.FUS mock if needed
48
+ if (typeof window !== 'undefined') {
49
+ // @ts-ignore
50
+ window.FUS = {}
51
+ }
52
+ })
53
+
54
+ test('should identify user with userId and basic traits', async () => {
55
+ const { fullSessionTracker } = await import('fullsession')
56
+
57
+ const [event] = await fullSessionDestination({
58
+ customerId: mockCustomerId,
59
+ subscriptions: [identifySubscription] as any
60
+ })
61
+
62
+ await event.load(Context.system(), {} as Analytics)
63
+
64
+ await event.identify?.(
65
+ new Context({
66
+ type: 'identify',
67
+ userId: 'user123',
68
+ anonymousId: 'anon123',
69
+ traits: {
70
+ name: 'John Doe',
71
+ email: 'john@example.com'
72
+ }
73
+ })
74
+ )
75
+
76
+ expect(fullSessionTracker.identify).toHaveBeenCalledWith('user123', {
77
+ name: 'John Doe',
78
+ email: 'john@example.com'
79
+ })
80
+
81
+ expect(fullSessionTracker.setSessionAttributes).toHaveBeenCalledWith({
82
+ segmentAnonymousId: 'anon123'
83
+ })
84
+ })
85
+
86
+ test('should identify user with userId and additional traits', async () => {
87
+ const { fullSessionTracker } = await import('fullsession')
88
+
89
+ const [event] = await fullSessionDestination({
90
+ customerId: mockCustomerId,
91
+ subscriptions: [identifySubscription] as any
92
+ })
93
+
94
+ await event.load(Context.system(), {} as Analytics)
95
+
96
+ await event.identify?.(
97
+ new Context({
98
+ type: 'identify',
99
+ userId: 'user123',
100
+ anonymousId: 'anon123',
101
+ traits: {
102
+ name: 'John Doe',
103
+ email: 'john@example.com',
104
+ company: 'Test Corp',
105
+ role: 'Developer',
106
+ age: 30
107
+ }
108
+ })
109
+ )
110
+
111
+ expect(fullSessionTracker.identify).toHaveBeenCalledWith('user123', {
112
+ name: 'John Doe',
113
+ email: 'john@example.com'
114
+ })
115
+
116
+ expect(fullSessionTracker.setSessionAttributes).toHaveBeenCalledWith({
117
+ company: 'Test Corp',
118
+ role: 'Developer',
119
+ age: 30,
120
+ segmentAnonymousId: 'anon123'
121
+ })
122
+ })
123
+
124
+ test('should identify user with anonymousId when userId is not present', async () => {
125
+ const { fullSessionTracker } = await import('fullsession')
126
+
127
+ const [event] = await fullSessionDestination({
128
+ customerId: mockCustomerId,
129
+ subscriptions: [identifySubscription] as any
130
+ })
131
+
132
+ await event.load(Context.system(), {} as Analytics)
133
+
134
+ await event.identify?.(
135
+ new Context({
136
+ type: 'identify',
137
+ anonymousId: 'anon123',
138
+ traits: {
139
+ name: 'Jane Doe',
140
+ email: 'jane@example.com',
141
+ company: 'Test Corp'
142
+ }
143
+ })
144
+ )
145
+
146
+ expect(fullSessionTracker.identify).toHaveBeenCalledWith('anon123', {
147
+ name: 'Jane Doe',
148
+ email: 'jane@example.com'
149
+ })
150
+
151
+ expect(fullSessionTracker.setSessionAttributes).toHaveBeenCalledWith({
152
+ company: 'Test Corp',
153
+ segmentAnonymousId: 'anon123'
154
+ })
155
+ })
156
+
157
+ test('should prefer userId over anonymousId when both are present', async () => {
158
+ const { fullSessionTracker } = await import('fullsession')
159
+
160
+ const [event] = await fullSessionDestination({
161
+ customerId: mockCustomerId,
162
+ subscriptions: [identifySubscription] as any
163
+ })
164
+
165
+ await event.load(Context.system(), {} as Analytics)
166
+
167
+ await event.identify?.(
168
+ new Context({
169
+ type: 'identify',
170
+ userId: 'user123',
171
+ anonymousId: 'anon123',
172
+ traits: {
173
+ name: 'John Doe',
174
+ email: 'john@example.com',
175
+ company: 'Test Corp'
176
+ }
177
+ })
178
+ )
179
+
180
+ expect(fullSessionTracker.identify).toHaveBeenCalledWith('user123', {
181
+ name: 'John Doe',
182
+ email: 'john@example.com'
183
+ })
184
+
185
+ expect(fullSessionTracker.setSessionAttributes).toHaveBeenCalledWith({
186
+ company: 'Test Corp',
187
+ segmentAnonymousId: 'anon123'
188
+ })
189
+ })
190
+
191
+ test('should handle empty or missing name and email', async () => {
192
+ const { fullSessionTracker } = await import('fullsession')
193
+
194
+ const [event] = await fullSessionDestination({
195
+ customerId: mockCustomerId,
196
+ subscriptions: [identifySubscription] as any
197
+ })
198
+
199
+ await event.load(Context.system(), {} as Analytics)
200
+
201
+ await event.identify?.(
202
+ new Context({
203
+ type: 'identify',
204
+ userId: 'user123',
205
+ anonymousId: 'anon123',
206
+ traits: {
207
+ company: 'Test Corp'
208
+ }
209
+ })
210
+ )
211
+
212
+ expect(fullSessionTracker.identify).toHaveBeenCalledWith('user123', {
213
+ name: '',
214
+ email: ''
215
+ })
216
+
217
+ expect(fullSessionTracker.setSessionAttributes).toHaveBeenCalledWith({
218
+ company: 'Test Corp',
219
+ segmentAnonymousId: 'anon123'
220
+ })
221
+ })
222
+
223
+ test('should call setSessionAttributes with empty object when only name and email are present', async () => {
224
+ const { fullSessionTracker } = await import('fullsession')
225
+
226
+ const [event] = await fullSessionDestination({
227
+ customerId: mockCustomerId,
228
+ subscriptions: [identifySubscription] as any
229
+ })
230
+
231
+ await event.load(Context.system(), {} as Analytics)
232
+
233
+ await event.identify?.(
234
+ new Context({
235
+ type: 'identify',
236
+ userId: 'user123',
237
+ anonymousId: 'anon123',
238
+ traits: {
239
+ name: 'John Doe',
240
+ email: 'john@example.com'
241
+ }
242
+ })
243
+ )
244
+
245
+ expect(fullSessionTracker.identify).toHaveBeenCalledWith('user123', {
246
+ name: 'John Doe',
247
+ email: 'john@example.com'
248
+ })
249
+
250
+ expect(fullSessionTracker.setSessionAttributes).toHaveBeenCalledWith({
251
+ segmentAnonymousId: 'anon123'
252
+ })
253
+ })
254
+
255
+ test('should handle case without anonymousId', async () => {
256
+ const { fullSessionTracker } = await import('fullsession')
257
+
258
+ const [event] = await fullSessionDestination({
259
+ customerId: mockCustomerId,
260
+ subscriptions: [identifySubscription] as any
261
+ })
262
+
263
+ await event.load(Context.system(), {} as Analytics)
264
+
265
+ await event.identify?.(
266
+ new Context({
267
+ type: 'identify',
268
+ userId: 'user123',
269
+ traits: {
270
+ name: 'John Doe',
271
+ email: 'john@example.com',
272
+ company: 'Test Corp'
273
+ }
274
+ })
275
+ )
276
+
277
+ expect(fullSessionTracker.identify).toHaveBeenCalledWith('user123', {
278
+ name: 'John Doe',
279
+ email: 'john@example.com'
280
+ })
281
+
282
+ expect(fullSessionTracker.setSessionAttributes).toHaveBeenCalledWith({
283
+ company: 'Test Corp'
284
+ })
285
+ })
286
+
287
+ test('should handle null or undefined traits', async () => {
288
+ const { fullSessionTracker } = await import('fullsession')
289
+
290
+ const [event] = await fullSessionDestination({
291
+ customerId: mockCustomerId,
292
+ subscriptions: [identifySubscription] as any
293
+ })
294
+
295
+ await event.load(Context.system(), {} as Analytics)
296
+
297
+ await event.identify?.(
298
+ new Context({
299
+ type: 'identify',
300
+ userId: 'user123',
301
+ anonymousId: 'anon123'
302
+ // traits is undefined
303
+ })
304
+ )
305
+
306
+ expect(fullSessionTracker.identify).toHaveBeenCalledWith('user123', {
307
+ name: '',
308
+ email: ''
309
+ })
310
+
311
+ expect(fullSessionTracker.setSessionAttributes).toHaveBeenCalledWith({
312
+ segmentAnonymousId: 'anon123'
313
+ })
314
+ })
315
+ })
@@ -0,0 +1,18 @@
1
+ // Generated file. DO NOT MODIFY IT BY HAND.
2
+
3
+ export interface Payload {
4
+ /**
5
+ * The user's unique identifier.
6
+ */
7
+ userId?: string
8
+ /**
9
+ * The user's anonymous identifier when no user ID is available.
10
+ */
11
+ anonymousId?: string
12
+ /**
13
+ * User traits and properties to be sent to FullSession.
14
+ */
15
+ traits?: {
16
+ [k: string]: unknown
17
+ }
18
+ }
@@ -0,0 +1,66 @@
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 { FUS } from '../types'
5
+
6
+ const action: BrowserActionDefinition<Settings, FUS, Payload> = {
7
+ title: 'Identify User',
8
+ description: 'Identify users and set their properties in FullSession.',
9
+ platform: 'web',
10
+ defaultSubscription: 'type = "identify"',
11
+ fields: {
12
+ userId: {
13
+ type: 'string',
14
+ required: false,
15
+ description: "The user's unique identifier.",
16
+ label: 'User ID',
17
+ default: {
18
+ '@path': '$.userId'
19
+ }
20
+ },
21
+ anonymousId: {
22
+ type: 'string',
23
+ required: false,
24
+ description: "The user's anonymous identifier when no user ID is available.",
25
+ label: 'Anonymous ID',
26
+ default: {
27
+ '@path': '$.anonymousId'
28
+ }
29
+ },
30
+ traits: {
31
+ type: 'object',
32
+ required: false,
33
+ description: 'User traits and properties to be sent to FullSession.',
34
+ label: 'User Traits',
35
+ default: {
36
+ '@path': '$.traits'
37
+ }
38
+ }
39
+ },
40
+ perform: (FUS, data) => {
41
+ const { userId, anonymousId, traits = {} } = data.payload
42
+
43
+ // Build the user traits object for identify
44
+ const identifyTraits = {
45
+ name: (traits.name as string) ?? '',
46
+ email: (traits.email as string) ?? ''
47
+ }
48
+
49
+ // call the identify function on either the userId or anonymousId
50
+
51
+ FUS.identify(userId ?? (anonymousId as string), identifyTraits)
52
+
53
+ // Remove name and email from traits before sending to setSessionAttributes
54
+ const { name: _ignoredName, email: _ignoredEmail, ...restTraits } = traits || {}
55
+
56
+ // Build session attributes
57
+ const sessionAttributes = {
58
+ ...restTraits,
59
+ ...(anonymousId ? { segmentAnonymousId: anonymousId } : {})
60
+ }
61
+
62
+ FUS.setSessionAttributes(sessionAttributes)
63
+ }
64
+ }
65
+
66
+ export default action
package/src/index.ts ADDED
@@ -0,0 +1,68 @@
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
+ import type { FUS } from './types'
5
+ import { fullSessionTracker } from './types'
6
+ import identifyUser from './identifyUser'
7
+ import { defaultValues } from '@segment/actions-core'
8
+
9
+ import recordEvent from './recordEvent'
10
+
11
+ import visitPage from './visitPage'
12
+
13
+ declare global {
14
+ interface Window {
15
+ FUS: FUS
16
+ }
17
+ }
18
+ // Switch from unknown to the partner SDK client types
19
+ export const destination: BrowserDestinationDefinition<Settings, FUS> = {
20
+ name: 'FullSession',
21
+ slug: 'actions-fullsession',
22
+ mode: 'device',
23
+ presets: [
24
+ {
25
+ name: 'Identify User',
26
+ subscribe: 'type = "identify"',
27
+ partnerAction: 'identifyUser',
28
+ mapping: defaultValues(identifyUser.fields),
29
+ type: 'automatic'
30
+ },
31
+ {
32
+ name: 'Record Event',
33
+ subscribe: 'type = "track"',
34
+ partnerAction: 'recordEvent',
35
+ mapping: defaultValues(recordEvent.fields),
36
+ type: 'automatic'
37
+ },
38
+ {
39
+ name: 'Visit Page',
40
+ subscribe: 'type = "page"',
41
+ partnerAction: 'visitPage',
42
+ mapping: defaultValues(visitPage.fields),
43
+ type: 'automatic'
44
+ }
45
+ ],
46
+ settings: {
47
+ customerId: {
48
+ description: 'Your FullSession Customer ID. You can find this in your FullSession dashboard under Settings .',
49
+ label: 'Customer ID',
50
+ type: 'string',
51
+ required: true
52
+ }
53
+ },
54
+
55
+ initialize: async ({ settings }, deps) => {
56
+ fullSessionTracker.initialize(settings.customerId)
57
+ await deps.resolveWhen(() => Object.prototype.hasOwnProperty.call(window, 'FUS'), 1000)
58
+ return fullSessionTracker
59
+ },
60
+
61
+ actions: {
62
+ identifyUser,
63
+ recordEvent,
64
+ visitPage
65
+ }
66
+ }
67
+
68
+ export default browserDestination(destination)