@segment/analytics-browser-actions-amplitude-plugins 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/generated-types.ts +3 -0
- package/src/index.ts +17 -0
- package/src/sessionId/__tests__/sessionId.test.ts +358 -0
- package/src/sessionId/generated-types.ts +8 -0
- package/src/sessionId/index.ts +88 -0
- package/tsconfig.json +9 -0
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@segment/analytics-browser-actions-amplitude-plugins",
|
|
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
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
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 sessionId from './sessionId'
|
|
5
|
+
|
|
6
|
+
export const destination: BrowserDestinationDefinition<Settings, {}> = {
|
|
7
|
+
name: 'Amplitude (Actions)',
|
|
8
|
+
mode: 'device',
|
|
9
|
+
actions: {
|
|
10
|
+
sessionId
|
|
11
|
+
},
|
|
12
|
+
initialize: async () => {
|
|
13
|
+
return {}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default browserDestination(destination)
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { Analytics, Context, Plugin } from '@segment/analytics-next'
|
|
2
|
+
import browserPluginsDestination from '../..'
|
|
3
|
+
import { Subscription } from '@segment/browser-destination-runtime/types'
|
|
4
|
+
import jar from 'js-cookie'
|
|
5
|
+
|
|
6
|
+
expect.extend({
|
|
7
|
+
toBeWithinOneSecondOf(got, expected) {
|
|
8
|
+
if (typeof got === 'string') {
|
|
9
|
+
got = parseInt(got, 10)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (typeof expected === 'string') {
|
|
13
|
+
expected = parseInt(expected, 10)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const oneSecond = 1000
|
|
17
|
+
|
|
18
|
+
const timeDiff = Math.abs(expected - got)
|
|
19
|
+
const timeDiffInSeconds = timeDiff / 1000
|
|
20
|
+
|
|
21
|
+
const pass = timeDiff < oneSecond
|
|
22
|
+
const message = () =>
|
|
23
|
+
`${got} should be within a second of ${expected}, ` + `actual difference: ${timeDiffInSeconds.toFixed(1)}s`
|
|
24
|
+
|
|
25
|
+
return { pass, message }
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const example: Subscription[] = [
|
|
30
|
+
{
|
|
31
|
+
partnerAction: 'sessionId',
|
|
32
|
+
name: 'SessionId',
|
|
33
|
+
enabled: true,
|
|
34
|
+
subscribe: 'type = "track"',
|
|
35
|
+
mapping: {}
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
let browserActions: Plugin[]
|
|
40
|
+
let sessionIdPlugin: Plugin
|
|
41
|
+
let ajs: Analytics
|
|
42
|
+
|
|
43
|
+
beforeEach(async () => {
|
|
44
|
+
browserActions = await browserPluginsDestination({ subscriptions: example })
|
|
45
|
+
sessionIdPlugin = browserActions[0]
|
|
46
|
+
|
|
47
|
+
// clear storage and cookies
|
|
48
|
+
document.cookie.split(';').forEach(function (c) {
|
|
49
|
+
document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/')
|
|
50
|
+
})
|
|
51
|
+
window.localStorage.clear()
|
|
52
|
+
|
|
53
|
+
ajs = new Analytics({
|
|
54
|
+
writeKey: 'w_123'
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('ajs-integration', () => {
|
|
59
|
+
test('updates the original event with a session id', async () => {
|
|
60
|
+
await sessionIdPlugin.load(Context.system(), ajs)
|
|
61
|
+
|
|
62
|
+
const ctx = new Context({
|
|
63
|
+
type: 'track',
|
|
64
|
+
event: 'greet',
|
|
65
|
+
properties: {
|
|
66
|
+
greeting: 'Oi!'
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const updatedCtx = await sessionIdPlugin.track?.(ctx)
|
|
71
|
+
|
|
72
|
+
// @ts-expect-error Need to fix ajs-next types to allow for complex objects in `integrations`
|
|
73
|
+
expect(updatedCtx?.event.integrations['Actions Amplitude']?.session_id).not.toBeUndefined()
|
|
74
|
+
// @ts-expect-error
|
|
75
|
+
expect(typeof updatedCtx?.event.integrations['Actions Amplitude']?.session_id).toBe('number')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('updates the original eveent when All: false but Actions Amplitude: true', async () => {
|
|
79
|
+
await sessionIdPlugin.load(Context.system(), ajs)
|
|
80
|
+
|
|
81
|
+
const ctx = new Context({
|
|
82
|
+
type: 'track',
|
|
83
|
+
event: 'greet',
|
|
84
|
+
properties: {
|
|
85
|
+
greeting: 'Oi!'
|
|
86
|
+
},
|
|
87
|
+
integrations: {
|
|
88
|
+
All: false,
|
|
89
|
+
'Actions Amplitude': true
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const updatedCtx = await sessionIdPlugin.track?.(ctx)
|
|
94
|
+
|
|
95
|
+
// @ts-expect-error Need to fix ajs-next types to allow for complex objects in `integrations`
|
|
96
|
+
expect(updatedCtx?.event.integrations['Actions Amplitude']?.session_id).not.toBeUndefined()
|
|
97
|
+
// @ts-expect-error
|
|
98
|
+
expect(typeof updatedCtx?.event.integrations['Actions Amplitude']?.session_id).toBe('number')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('doesnt update the original event with a session id when All: false', async () => {
|
|
102
|
+
await sessionIdPlugin.load(Context.system(), ajs)
|
|
103
|
+
|
|
104
|
+
const ctx = new Context({
|
|
105
|
+
type: 'track',
|
|
106
|
+
event: 'greet',
|
|
107
|
+
properties: {
|
|
108
|
+
greeting: 'Oi!'
|
|
109
|
+
},
|
|
110
|
+
integrations: {
|
|
111
|
+
All: false
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const updatedCtx = await sessionIdPlugin.track?.(ctx)
|
|
116
|
+
|
|
117
|
+
// @ts-expect-error Need to fix ajs-next types to allow for complex objects in `integrations`
|
|
118
|
+
expect(updatedCtx?.event.integrations['Actions Amplitude']?.session_id).toBeUndefined()
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('runs as an enrichment middleware', async () => {
|
|
122
|
+
await ajs.register(sessionIdPlugin)
|
|
123
|
+
jest.spyOn(sessionIdPlugin, 'track')
|
|
124
|
+
|
|
125
|
+
const ctx = new Context({
|
|
126
|
+
type: 'track',
|
|
127
|
+
event: 'greet',
|
|
128
|
+
properties: {
|
|
129
|
+
greeting: 'Oi!'
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
await ajs.track(ctx.event)
|
|
134
|
+
|
|
135
|
+
expect(sessionIdPlugin.track).toHaveBeenCalled()
|
|
136
|
+
expect(ajs.queue.plugins.map((p) => ({ name: p.name, type: p.type }))).toMatchInlineSnapshot(`
|
|
137
|
+
Array [
|
|
138
|
+
Object {
|
|
139
|
+
"name": "Amplitude (Actions) sessionId",
|
|
140
|
+
"type": "enrichment",
|
|
141
|
+
},
|
|
142
|
+
]
|
|
143
|
+
`)
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
describe('sessionId', () => {
|
|
148
|
+
beforeEach(async () => {
|
|
149
|
+
jest.useFakeTimers('legacy')
|
|
150
|
+
await sessionIdPlugin.load(Context.system(), ajs)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
const id = () => new Date().getTime()
|
|
154
|
+
|
|
155
|
+
describe('new sessions', () => {
|
|
156
|
+
test('sets a session id', async () => {
|
|
157
|
+
const ctx = new Context({
|
|
158
|
+
type: 'track',
|
|
159
|
+
event: 'greet',
|
|
160
|
+
properties: {
|
|
161
|
+
greeting: 'Oi!'
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
const updatedCtx = await sessionIdPlugin.track?.(ctx)
|
|
166
|
+
// @ts-expect-error Need to fix ajs-next types to allow for complex objects in `integrations`
|
|
167
|
+
expect(updatedCtx?.event.integrations['Actions Amplitude']?.session_id).toBeWithinOneSecondOf(id())
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
test('persists the session id', async () => {
|
|
171
|
+
const ctx = new Context({
|
|
172
|
+
type: 'track',
|
|
173
|
+
event: 'greet',
|
|
174
|
+
properties: {
|
|
175
|
+
greeting: 'Oi!'
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
await sessionIdPlugin.track?.(ctx)
|
|
180
|
+
|
|
181
|
+
// persists the session id in both cookies and local storage
|
|
182
|
+
expect(window.localStorage.getItem('analytics_session_id')).toBeWithinOneSecondOf(id().toString())
|
|
183
|
+
expect(window.localStorage.getItem('analytics_session_id.last_access')).toBeWithinOneSecondOf(id().toString())
|
|
184
|
+
expect(jar.get('analytics_session_id')).toBeWithinOneSecondOf(id().toString())
|
|
185
|
+
expect(jar.get('analytics_session_id.last_access')).toBeWithinOneSecondOf(id().toString())
|
|
186
|
+
expect(jar.get('analytics_session_id')).toBe(window.localStorage.getItem('analytics_session_id'))
|
|
187
|
+
expect(jar.get('analytics_session_id.last_access')).toBe(window.localStorage.getItem('analytics_session_id'))
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
describe('existing sessions', () => {
|
|
192
|
+
test('uses an existing session id in LS', async () => {
|
|
193
|
+
const then = id()
|
|
194
|
+
jest.advanceTimersByTime(10000)
|
|
195
|
+
|
|
196
|
+
window.localStorage.setItem('analytics_session_id', then.toString())
|
|
197
|
+
|
|
198
|
+
const ctx = new Context({
|
|
199
|
+
type: 'track',
|
|
200
|
+
event: 'greet',
|
|
201
|
+
properties: {
|
|
202
|
+
greeting: 'Oi!'
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
const updatedCtx = await sessionIdPlugin.track?.(ctx)
|
|
207
|
+
// @ts-expect-error Need to fix ajs-next types to allow for complex objects in `integrations`
|
|
208
|
+
expect(updatedCtx?.event.integrations['Actions Amplitude']?.session_id).toBeWithinOneSecondOf(then)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
test('sync session info from LS to cookies', async () => {
|
|
212
|
+
const then = id()
|
|
213
|
+
|
|
214
|
+
window.localStorage.setItem('analytics_session_id', then.toString())
|
|
215
|
+
window.localStorage.setItem('analytics_session_id.last_access', then.toString())
|
|
216
|
+
|
|
217
|
+
const ctx = new Context({
|
|
218
|
+
type: 'track',
|
|
219
|
+
event: 'greet',
|
|
220
|
+
properties: {
|
|
221
|
+
greeting: 'Oi!'
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
const updatedCtx = await sessionIdPlugin.track?.(ctx)
|
|
226
|
+
// @ts-expect-error Need to fix ajs-next types to allow for complex objects in `integrations`
|
|
227
|
+
expect(updatedCtx?.event.integrations['Actions Amplitude']?.session_id).toBeWithinOneSecondOf(then)
|
|
228
|
+
expect(jar.get('analytics_session_id')).toBeWithinOneSecondOf(then)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
test('uses an existing session id stored in cookies and sync it with local storage', async () => {
|
|
232
|
+
const then = id()
|
|
233
|
+
jest.advanceTimersByTime(10000)
|
|
234
|
+
jar.set('analytics_session_id', then.toString())
|
|
235
|
+
|
|
236
|
+
const ctx = new Context({
|
|
237
|
+
type: 'track',
|
|
238
|
+
event: 'greet',
|
|
239
|
+
properties: {
|
|
240
|
+
greeting: 'Oi!'
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
const now = id()
|
|
244
|
+
const updatedCtx = await sessionIdPlugin.track?.(ctx)
|
|
245
|
+
// @ts-expect-error Need to fix ajs-next types to allow for complex objects in `integrations`
|
|
246
|
+
expect(updatedCtx?.event.integrations['Actions Amplitude']?.session_id).toBeWithinOneSecondOf(then)
|
|
247
|
+
// synced to local storage
|
|
248
|
+
expect(window.localStorage.getItem('analytics_session_id.last_access')).toBeWithinOneSecondOf(now)
|
|
249
|
+
expect(window.localStorage.getItem('analytics_session_id')).toBeWithinOneSecondOf(then)
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
test('keeps track of when the session was last accessed', async () => {
|
|
253
|
+
const then = id()
|
|
254
|
+
jest.advanceTimersByTime(10000)
|
|
255
|
+
window.localStorage.setItem('analytics_session_id', then.toString())
|
|
256
|
+
|
|
257
|
+
const now = id()
|
|
258
|
+
|
|
259
|
+
const ctx = new Context({
|
|
260
|
+
type: 'track',
|
|
261
|
+
event: 'greet',
|
|
262
|
+
properties: {
|
|
263
|
+
greeting: 'Oi!'
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
const updatedCtx = await sessionIdPlugin.track?.(ctx)
|
|
268
|
+
// @ts-expect-error Need to fix ajs-next types to allow for complex objects in `integrations`
|
|
269
|
+
expect(updatedCtx?.event.integrations['Actions Amplitude']?.session_id).toBeWithinOneSecondOf(then)
|
|
270
|
+
|
|
271
|
+
expect(window.localStorage.getItem('analytics_session_id.last_access')).toBeWithinOneSecondOf(now)
|
|
272
|
+
expect(jar.get('analytics_session_id.last_access')).toBeWithinOneSecondOf(now)
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
test('reset session when stale', async () => {
|
|
276
|
+
const then = id()
|
|
277
|
+
window.localStorage.setItem('analytics_session_id.last_access', then.toString())
|
|
278
|
+
window.localStorage.setItem('analytics_session_id', then.toString())
|
|
279
|
+
|
|
280
|
+
const THIRTY_MINUTES = 30 * 60000
|
|
281
|
+
jest.advanceTimersByTime(THIRTY_MINUTES)
|
|
282
|
+
|
|
283
|
+
const now = id()
|
|
284
|
+
|
|
285
|
+
const ctx = new Context({
|
|
286
|
+
type: 'track',
|
|
287
|
+
event: 'greet',
|
|
288
|
+
properties: {
|
|
289
|
+
greeting: 'Oi!'
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
const updatedCtx = await sessionIdPlugin.track?.(ctx)
|
|
294
|
+
// @ts-expect-error Need to fix ajs-next types to allow for complex objects in `integrations`
|
|
295
|
+
expect(updatedCtx?.event.integrations['Actions Amplitude']?.session_id).toBeWithinOneSecondOf(now)
|
|
296
|
+
|
|
297
|
+
expect(window.localStorage.getItem('analytics_session_id')).toBeWithinOneSecondOf(now.toString())
|
|
298
|
+
expect(window.localStorage.getItem('analytics_session_id.last_access')).toBeWithinOneSecondOf(now.toString())
|
|
299
|
+
expect(jar.get('analytics_session_id')).toBeWithinOneSecondOf(now.toString())
|
|
300
|
+
expect(jar.get('analytics_session_id.last_access')).toBeWithinOneSecondOf(now.toString())
|
|
301
|
+
})
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
describe('work without AJS storage layer', () => {
|
|
305
|
+
test('uses an existing session id in LS when AJS storage layer is not available', async () => {
|
|
306
|
+
const then = id()
|
|
307
|
+
//@ts-expect-error
|
|
308
|
+
jest.spyOn(ajs, 'storage', 'get').mockReturnValue(undefined)
|
|
309
|
+
jest.advanceTimersByTime(10000)
|
|
310
|
+
|
|
311
|
+
window.localStorage.setItem('analytics_session_id', then.toString())
|
|
312
|
+
|
|
313
|
+
const ctx = new Context({
|
|
314
|
+
type: 'track',
|
|
315
|
+
event: 'greet',
|
|
316
|
+
properties: {
|
|
317
|
+
greeting: 'Oi!'
|
|
318
|
+
}
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
const updatedCtx = await sessionIdPlugin.track?.(ctx)
|
|
322
|
+
// @ts-expect-error Need to fix ajs-next types to allow for complex objects in `integrations`
|
|
323
|
+
expect(updatedCtx?.event.integrations['Actions Amplitude']?.session_id).toBeWithinOneSecondOf(then)
|
|
324
|
+
expect(jar.get('analytics_session_id')).toBe(undefined)
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
test('uses an existing session id in LS when AJS storage layer is not available', async () => {
|
|
328
|
+
const then = id()
|
|
329
|
+
//@ts-expect-error
|
|
330
|
+
jest.spyOn(ajs, 'storage', 'get').mockReturnValue(undefined)
|
|
331
|
+
|
|
332
|
+
window.localStorage.setItem('analytics_session_id.last_access', then.toString())
|
|
333
|
+
window.localStorage.setItem('analytics_session_id', then.toString())
|
|
334
|
+
|
|
335
|
+
const THIRTY_MINUTES = 30 * 60000
|
|
336
|
+
jest.advanceTimersByTime(THIRTY_MINUTES)
|
|
337
|
+
|
|
338
|
+
const now = id()
|
|
339
|
+
|
|
340
|
+
const ctx = new Context({
|
|
341
|
+
type: 'track',
|
|
342
|
+
event: 'greet',
|
|
343
|
+
properties: {
|
|
344
|
+
greeting: 'Oi!'
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
const updatedCtx = await sessionIdPlugin.track?.(ctx)
|
|
349
|
+
// @ts-expect-error Need to fix ajs-next types to allow for complex objects in `integrations`
|
|
350
|
+
expect(updatedCtx?.event.integrations['Actions Amplitude']?.session_id).toBeWithinOneSecondOf(now)
|
|
351
|
+
|
|
352
|
+
expect(window.localStorage.getItem('analytics_session_id')).toBeWithinOneSecondOf(now.toString())
|
|
353
|
+
expect(window.localStorage.getItem('analytics_session_id.last_access')).toBeWithinOneSecondOf(now.toString())
|
|
354
|
+
expect(jar.get('analytics_session_id')).toBeUndefined()
|
|
355
|
+
expect(jar.get('analytics_session_id.last_access')).toBeUndefined()
|
|
356
|
+
})
|
|
357
|
+
})
|
|
358
|
+
})
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
2
|
+
import { UniversalStorage } from '@segment/analytics-next'
|
|
3
|
+
import type { BrowserActionDefinition } from '@segment/browser-destination-runtime/types'
|
|
4
|
+
import type { Settings } from '../generated-types'
|
|
5
|
+
import type { Payload } from './generated-types'
|
|
6
|
+
|
|
7
|
+
function newSessionId(): number {
|
|
8
|
+
return now()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function now(): number {
|
|
12
|
+
return new Date().getTime()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const THIRTY_MINUTES = 30 * 60000
|
|
16
|
+
|
|
17
|
+
function stale(id: number | null, updated: number | null, length: number = THIRTY_MINUTES): id is null {
|
|
18
|
+
if (id === null || updated === null) {
|
|
19
|
+
return true
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const accessedAt = updated
|
|
23
|
+
|
|
24
|
+
if (now() - accessedAt >= length) {
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const action: BrowserActionDefinition<Settings, {}, Payload> = {
|
|
32
|
+
title: 'Session Plugin',
|
|
33
|
+
description: 'Generates a Session ID and attaches it to every Amplitude browser based event.',
|
|
34
|
+
platform: 'web',
|
|
35
|
+
hidden: true,
|
|
36
|
+
defaultSubscription: 'type = "track" or type = "identify" or type = "group" or type = "page" or type = "alias"',
|
|
37
|
+
fields: {
|
|
38
|
+
sessionLength: {
|
|
39
|
+
label: 'Session Length',
|
|
40
|
+
type: 'number',
|
|
41
|
+
required: false,
|
|
42
|
+
description: 'Time in milliseconds to be used before considering a session stale.'
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
lifecycleHook: 'enrichment',
|
|
46
|
+
perform: (_, { context, payload, analytics }) => {
|
|
47
|
+
// TODO: this can be removed when storage layer in AJS is rolled out to all customers
|
|
48
|
+
const storageFallback = {
|
|
49
|
+
get: (key: string) => {
|
|
50
|
+
const data = window.localStorage.getItem(key)
|
|
51
|
+
return data === null ? null : parseInt(data, 10)
|
|
52
|
+
},
|
|
53
|
+
set: (key: string, value: number) => {
|
|
54
|
+
return window.localStorage.setItem(key, value.toString())
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const newSession = newSessionId()
|
|
59
|
+
const storage = analytics.storage
|
|
60
|
+
? // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
61
|
+
(analytics.storage as UniversalStorage<Record<string, number>>)
|
|
62
|
+
: storageFallback
|
|
63
|
+
|
|
64
|
+
const raw = storage.get('analytics_session_id')
|
|
65
|
+
const updated = storage.get('analytics_session_id.last_access')
|
|
66
|
+
|
|
67
|
+
let id: number | null = raw
|
|
68
|
+
if (stale(raw, updated, payload.sessionLength)) {
|
|
69
|
+
id = newSession
|
|
70
|
+
storage.set('analytics_session_id', id)
|
|
71
|
+
} else {
|
|
72
|
+
// we are storing the session id regardless, so it gets synced between different storage mediums
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- id can't be null because of stale check
|
|
74
|
+
storage.set('analytics_session_id', id!)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
storage.set('analytics_session_id.last_access', newSession)
|
|
78
|
+
|
|
79
|
+
if (context.event.integrations?.All !== false || context.event.integrations['Actions Amplitude']) {
|
|
80
|
+
context.updateEvent('integrations.Actions Amplitude', {})
|
|
81
|
+
context.updateEvent('integrations.Actions Amplitude.session_id', id)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default action
|