@leanbase.com/js 0.1.0 → 0.1.2
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 +9 -21
- package/dist/index.cjs +2974 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +760 -11
- package/dist/index.mjs +2975 -61
- package/dist/index.mjs.map +1 -1
- package/dist/leanbase.iife.js +3091 -134
- package/dist/leanbase.iife.js.map +1 -1
- package/package.json +1 -2
- package/src/autocapture-utils.ts +550 -0
- package/src/autocapture.ts +415 -0
- package/src/config.ts +8 -0
- package/src/constants.ts +98 -0
- package/src/extensions/rageclick.ts +34 -0
- package/src/iife.ts +31 -27
- package/src/index.ts +1 -1
- package/src/leanbase-logger.ts +7 -4
- package/src/leanbase-persistence.ts +374 -0
- package/src/leanbase.ts +366 -71
- package/src/page-view.ts +124 -0
- package/src/scroll-manager.ts +103 -0
- package/src/session-props.ts +114 -0
- package/src/sessionid.ts +330 -0
- package/src/storage.ts +410 -0
- package/src/types.ts +634 -0
- package/src/utils/blocked-uas.ts +162 -0
- package/src/utils/element-utils.ts +50 -0
- package/src/utils/event-utils.ts +304 -0
- package/src/utils/index.ts +222 -0
- package/src/utils/request-utils.ts +128 -0
- package/src/utils/simple-event-emitter.ts +27 -0
- package/src/utils/user-agent-utils.ts +357 -0
- package/src/uuidv7.ts +268 -0
- package/src/version.ts +1 -1
package/src/leanbase.ts
CHANGED
|
@@ -1,72 +1,195 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
PostHogCore,
|
|
3
|
+
getFetch,
|
|
4
|
+
isEmptyObject,
|
|
5
|
+
isEmptyString,
|
|
6
|
+
isNumber,
|
|
7
|
+
isObject,
|
|
8
|
+
isString,
|
|
9
|
+
isUndefined,
|
|
10
|
+
} from '@posthog/core'
|
|
2
11
|
import type {
|
|
3
|
-
PostHogCoreOptions,
|
|
4
12
|
PostHogEventProperties,
|
|
5
13
|
PostHogFetchOptions,
|
|
6
14
|
PostHogFetchResponse,
|
|
7
15
|
PostHogPersistedProperty,
|
|
8
|
-
PostHogCaptureOptions,
|
|
9
16
|
} from '@posthog/core'
|
|
10
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
LeanbaseConfig,
|
|
19
|
+
LeanbasegCaptureOptions as LeanbaseCaptureOptions,
|
|
20
|
+
RemoteConfig,
|
|
21
|
+
CaptureResult,
|
|
22
|
+
Properties,
|
|
23
|
+
} from './types'
|
|
24
|
+
import { LeanbasePersistence } from './leanbase-persistence'
|
|
25
|
+
import {
|
|
26
|
+
addEventListener,
|
|
27
|
+
copyAndTruncateStrings,
|
|
28
|
+
document,
|
|
29
|
+
extend,
|
|
30
|
+
isCrossDomainCookie,
|
|
31
|
+
navigator,
|
|
32
|
+
userAgent,
|
|
33
|
+
} from './utils'
|
|
34
|
+
import Config from './config'
|
|
35
|
+
import { Autocapture } from './autocapture'
|
|
11
36
|
import { logger } from './leanbase-logger'
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
37
|
+
import { COOKIELESS_MODE_FLAG_PROPERTY, USER_STATE } from './constants'
|
|
38
|
+
import { getEventProperties } from './utils/event-utils'
|
|
39
|
+
import { SessionIdManager } from './sessionid'
|
|
40
|
+
import { SessionPropsManager } from './session-props'
|
|
41
|
+
import { uuidv7 } from './uuidv7'
|
|
42
|
+
import { PageViewManager } from './page-view'
|
|
43
|
+
import { ScrollManager } from './scroll-manager'
|
|
44
|
+
import { isLikelyBot } from './utils/blocked-uas'
|
|
20
45
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
46
|
+
const defaultConfig = (): LeanbaseConfig => ({
|
|
47
|
+
host: 'https://i.leanbase.co',
|
|
48
|
+
token: '',
|
|
49
|
+
autocapture: true,
|
|
50
|
+
rageclick: true,
|
|
51
|
+
persistence: 'localStorage+cookie',
|
|
52
|
+
capture_pageview: 'history_change',
|
|
53
|
+
capture_pageleave: 'if_capture_pageview',
|
|
54
|
+
persistence_name: '',
|
|
55
|
+
mask_all_element_attributes: false,
|
|
56
|
+
cookie_expiration: 365,
|
|
57
|
+
cross_subdomain_cookie: isCrossDomainCookie(document?.location),
|
|
58
|
+
custom_campaign_params: [],
|
|
59
|
+
custom_personal_data_properties: [],
|
|
60
|
+
disable_persistence: false,
|
|
61
|
+
mask_personal_data_properties: false,
|
|
62
|
+
secure_cookie: window?.location?.protocol === 'https:',
|
|
63
|
+
mask_all_text: false,
|
|
64
|
+
bootstrap: {},
|
|
65
|
+
session_idle_timeout_seconds: 30 * 60,
|
|
66
|
+
save_campaign_params: true,
|
|
67
|
+
save_referrer: true,
|
|
68
|
+
opt_out_useragent_filter: false,
|
|
69
|
+
properties_string_max_length: 65535,
|
|
70
|
+
loaded: () => {},
|
|
71
|
+
})
|
|
27
72
|
|
|
28
73
|
export class Leanbase extends PostHogCore {
|
|
29
|
-
|
|
30
|
-
|
|
74
|
+
config: LeanbaseConfig
|
|
75
|
+
scrollManager: ScrollManager
|
|
76
|
+
pageViewManager: PageViewManager
|
|
31
77
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
78
|
+
replayAutocapture?: Autocapture
|
|
79
|
+
persistence?: LeanbasePersistence
|
|
80
|
+
sessionPersistence?: LeanbasePersistence
|
|
81
|
+
sessionManager?: SessionIdManager
|
|
82
|
+
sessionPropsManager?: SessionPropsManager
|
|
83
|
+
isRemoteConfigLoaded?: boolean
|
|
84
|
+
personProcessingSetOncePropertiesSent = false
|
|
85
|
+
isLoaded: boolean = false
|
|
86
|
+
initialPageviewCaptured: boolean
|
|
87
|
+
visibilityStateListener: (() => void) | null
|
|
88
|
+
|
|
89
|
+
constructor(token: string, config?: Partial<LeanbaseConfig>) {
|
|
90
|
+
const mergedConfig = extend(defaultConfig(), config || {}, {
|
|
91
|
+
token,
|
|
92
|
+
})
|
|
93
|
+
super(token, mergedConfig)
|
|
94
|
+
this.config = mergedConfig as LeanbaseConfig
|
|
95
|
+
this.visibilityStateListener = null
|
|
96
|
+
this.initialPageviewCaptured = false
|
|
97
|
+
this.scrollManager = new ScrollManager(this)
|
|
98
|
+
this.pageViewManager = new PageViewManager(this)
|
|
99
|
+
this.init(token, mergedConfig)
|
|
100
|
+
}
|
|
38
101
|
|
|
39
|
-
|
|
102
|
+
init(token: string, config: Partial<LeanbaseConfig>) {
|
|
103
|
+
this.setConfig(
|
|
104
|
+
extend(defaultConfig(), config, {
|
|
105
|
+
token,
|
|
106
|
+
})
|
|
107
|
+
)
|
|
108
|
+
this.isLoaded = true
|
|
109
|
+
this.persistence = new LeanbasePersistence(this.config)
|
|
110
|
+
this.replayAutocapture = new Autocapture(this)
|
|
111
|
+
this.replayAutocapture.startIfEnabled()
|
|
40
112
|
|
|
41
|
-
this.
|
|
113
|
+
if (this.config.preloadFeatureFlags !== false) {
|
|
114
|
+
this.reloadFeatureFlags()
|
|
115
|
+
}
|
|
42
116
|
|
|
43
|
-
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const parsed = JSON.parse(stored)
|
|
49
|
-
Object.entries(parsed).forEach(([key, value]) => {
|
|
50
|
-
this._storage.set(key, value)
|
|
51
|
-
})
|
|
117
|
+
this.config.loaded?.(this)
|
|
118
|
+
if (this.config.capture_pageview) {
|
|
119
|
+
setTimeout(() => {
|
|
120
|
+
if (this.config.cookieless_mode === 'always') {
|
|
121
|
+
this.captureInitialPageview()
|
|
52
122
|
}
|
|
53
|
-
}
|
|
54
|
-
|
|
123
|
+
}, 1)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
addEventListener(document, 'DOMContentLoaded', () => {
|
|
127
|
+
this.loadRemoteConfig()
|
|
128
|
+
})
|
|
129
|
+
addEventListener(window, 'onpagehide' in self ? 'pagehide' : 'unload', this.capturePageLeave.bind(this), {
|
|
130
|
+
passive: false,
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
captureInitialPageview(): void {
|
|
135
|
+
if (!document) {
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (document.visibilityState !== 'visible') {
|
|
140
|
+
if (!this.visibilityStateListener) {
|
|
141
|
+
this.visibilityStateListener = this.captureInitialPageview.bind(this)
|
|
142
|
+
addEventListener(document, 'visibilitychange', this.visibilityStateListener)
|
|
55
143
|
}
|
|
144
|
+
|
|
145
|
+
return
|
|
56
146
|
}
|
|
57
147
|
|
|
58
|
-
|
|
148
|
+
if (!this.initialPageviewCaptured) {
|
|
149
|
+
this.initialPageviewCaptured = true
|
|
150
|
+
this.capture('$pageview', { title: document.title })
|
|
59
151
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
152
|
+
if (this.visibilityStateListener) {
|
|
153
|
+
document.removeEventListener('visibilitychange', this.visibilityStateListener)
|
|
154
|
+
this.visibilityStateListener = null
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
capturePageLeave() {
|
|
160
|
+
const { capture_pageleave, capture_pageview } = this.config
|
|
161
|
+
if (
|
|
162
|
+
capture_pageleave === true ||
|
|
163
|
+
(capture_pageleave === 'if_capture_pageview' &&
|
|
164
|
+
(capture_pageview === true || capture_pageview === 'history_change'))
|
|
165
|
+
) {
|
|
166
|
+
this.capture('$pageleave')
|
|
63
167
|
}
|
|
64
168
|
}
|
|
65
169
|
|
|
66
|
-
|
|
170
|
+
async loadRemoteConfig() {
|
|
171
|
+
if (!this.isRemoteConfigLoaded) {
|
|
172
|
+
const remoteConfig = await this.reloadRemoteConfigAsync()
|
|
173
|
+
if (remoteConfig) {
|
|
174
|
+
this.onRemoteConfig(remoteConfig as RemoteConfig)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
onRemoteConfig(config: RemoteConfig): void {
|
|
180
|
+
if (!(document && document.body)) {
|
|
181
|
+
setTimeout(() => {
|
|
182
|
+
this.onRemoteConfig(config)
|
|
183
|
+
}, 500)
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
this.isRemoteConfigLoaded = true
|
|
188
|
+
this.replayAutocapture?.onRemoteConfig(config)
|
|
189
|
+
}
|
|
190
|
+
|
|
67
191
|
fetch(url: string, options: PostHogFetchOptions): Promise<PostHogFetchResponse> {
|
|
68
192
|
const fetchFn = getFetch()
|
|
69
|
-
|
|
70
193
|
if (!fetchFn) {
|
|
71
194
|
return Promise.reject(new Error('Fetch API is not available in this environment.'))
|
|
72
195
|
}
|
|
@@ -74,12 +197,26 @@ export class Leanbase extends PostHogCore {
|
|
|
74
197
|
return fetchFn(url, options)
|
|
75
198
|
}
|
|
76
199
|
|
|
200
|
+
setConfig(config: Partial<LeanbaseConfig>): void {
|
|
201
|
+
const oldConfig = { ...this.config }
|
|
202
|
+
if (isObject(config)) {
|
|
203
|
+
extend(this.config, config)
|
|
204
|
+
this.persistence?.update_config(this.config, oldConfig)
|
|
205
|
+
this.replayAutocapture?.startIfEnabled()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const isTempStorage = this.config.persistence === 'sessionStorage' || this.config.persistence === 'memory'
|
|
209
|
+
this.sessionPersistence = isTempStorage
|
|
210
|
+
? this.persistence
|
|
211
|
+
: new LeanbasePersistence({ ...this.config, persistence: 'sessionStorage' })
|
|
212
|
+
}
|
|
213
|
+
|
|
77
214
|
getLibraryId(): string {
|
|
78
215
|
return 'leanbase'
|
|
79
216
|
}
|
|
80
217
|
|
|
81
218
|
getLibraryVersion(): string {
|
|
82
|
-
return
|
|
219
|
+
return Config.LIB_VERSION
|
|
83
220
|
}
|
|
84
221
|
|
|
85
222
|
getCustomUserAgent(): void {
|
|
@@ -87,43 +224,201 @@ export class Leanbase extends PostHogCore {
|
|
|
87
224
|
}
|
|
88
225
|
|
|
89
226
|
getPersistedProperty<T>(key: PostHogPersistedProperty): T | undefined {
|
|
90
|
-
return this.
|
|
227
|
+
return this.persistence?.get_property(key)
|
|
91
228
|
}
|
|
92
229
|
|
|
93
230
|
setPersistedProperty<T>(key: PostHogPersistedProperty, value: T | null): void {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
231
|
+
this.persistence?.set_property(key, value)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
calculateEventProperties(
|
|
235
|
+
eventName: string,
|
|
236
|
+
eventProperties: PostHogEventProperties,
|
|
237
|
+
timestamp: Date,
|
|
238
|
+
uuid: string,
|
|
239
|
+
readOnly?: boolean
|
|
240
|
+
): Properties {
|
|
241
|
+
if (!this.persistence || !this.sessionPersistence) {
|
|
242
|
+
return eventProperties
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
timestamp = timestamp || new Date()
|
|
246
|
+
const startTimestamp = readOnly ? undefined : this.persistence?.remove_event_timer(eventName)
|
|
247
|
+
let properties = { ...eventProperties }
|
|
248
|
+
properties['token'] = this.config.token
|
|
249
|
+
if (this.config.cookieless_mode == 'always' || this.config.cookieless_mode == 'on_reject') {
|
|
250
|
+
properties[COOKIELESS_MODE_FLAG_PROPERTY] = true
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (eventName === '$snapshot') {
|
|
254
|
+
const persistenceProps = { ...this.persistence.properties() }
|
|
255
|
+
properties['distinct_id'] = persistenceProps.distinct_id
|
|
256
|
+
if (
|
|
257
|
+
!(isString(properties['distinct_id']) || isNumber(properties['distinct_id'])) ||
|
|
258
|
+
isEmptyString(properties['distinct_id'])
|
|
259
|
+
) {
|
|
260
|
+
logger.error('Invalid distinct_id for replay event. This indicates a bug in your implementation')
|
|
110
261
|
}
|
|
262
|
+
return properties
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const infoProperties = getEventProperties(
|
|
266
|
+
this.config.mask_personal_data_properties,
|
|
267
|
+
this.config.custom_personal_data_properties
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
if (this.sessionManager) {
|
|
271
|
+
const { sessionId, windowId } = this.sessionManager.checkAndGetSessionAndWindowId(
|
|
272
|
+
readOnly,
|
|
273
|
+
timestamp.getTime()
|
|
274
|
+
)
|
|
275
|
+
properties['$session_id'] = sessionId
|
|
276
|
+
properties['$window_id'] = windowId
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (this.sessionPropsManager) {
|
|
280
|
+
extend(properties, this.sessionPropsManager.getSessionProps())
|
|
111
281
|
}
|
|
282
|
+
|
|
283
|
+
let pageviewProperties: Record<string, any> = this.pageViewManager.doEvent()
|
|
284
|
+
if (eventName === '$pageview' && !readOnly) {
|
|
285
|
+
pageviewProperties = this.pageViewManager.doPageView(timestamp, uuid)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (eventName === '$pageleave' && !readOnly) {
|
|
289
|
+
pageviewProperties = this.pageViewManager.doPageLeave(timestamp)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
properties = extend(properties, pageviewProperties)
|
|
293
|
+
|
|
294
|
+
if (eventName === '$pageview' && document) {
|
|
295
|
+
properties['title'] = document.title
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (!isUndefined(startTimestamp)) {
|
|
299
|
+
const duration_in_ms = timestamp.getTime() - startTimestamp
|
|
300
|
+
properties['$duration'] = parseFloat((duration_in_ms / 1000).toFixed(3))
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (userAgent && this.config.opt_out_useragent_filter) {
|
|
304
|
+
properties['$browser_type'] = isLikelyBot(navigator, []) ? 'bot' : 'browser'
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
properties = extend(
|
|
308
|
+
{},
|
|
309
|
+
infoProperties,
|
|
310
|
+
this.persistence.properties(),
|
|
311
|
+
this.sessionPersistence.properties(),
|
|
312
|
+
properties
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
properties['$is_identified'] = this.isIdentified()
|
|
316
|
+
return properties
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
isIdentified(): boolean {
|
|
320
|
+
return (
|
|
321
|
+
this.persistence?.get_property(USER_STATE) === 'identified' ||
|
|
322
|
+
this.sessionPersistence?.get_property(USER_STATE) === 'identified'
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Add additional set_once properties to the event when creating a person profile. This allows us to create the
|
|
328
|
+
* profile with mostly-accurate properties, despite earlier events not setting them. We do this by storing them in
|
|
329
|
+
* persistence.
|
|
330
|
+
* @param dataSetOnce
|
|
331
|
+
*/
|
|
332
|
+
calculateSetOnceProperties(dataSetOnce?: Properties): Properties | undefined {
|
|
333
|
+
if (!this.persistence) {
|
|
334
|
+
return dataSetOnce
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (this.personProcessingSetOncePropertiesSent) {
|
|
338
|
+
return dataSetOnce
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const initialProps = this.persistence.get_initial_props()
|
|
342
|
+
const sessionProps = this.sessionPropsManager?.getSetOnceProps()
|
|
343
|
+
const setOnceProperties = extend({}, initialProps, sessionProps || {}, dataSetOnce || {})
|
|
344
|
+
this.personProcessingSetOncePropertiesSent = true
|
|
345
|
+
if (isEmptyObject(setOnceProperties)) {
|
|
346
|
+
return undefined
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return setOnceProperties
|
|
112
350
|
}
|
|
113
351
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
352
|
+
capture(event: string, properties?: PostHogEventProperties, options?: LeanbaseCaptureOptions): void {
|
|
353
|
+
if (!this.isLoaded || !this.sessionPersistence || !this.persistence) {
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (isUndefined(event) || !isString(event)) {
|
|
358
|
+
logger.error('No event name provided to posthog.capture')
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (properties?.$current_url && !isString(properties?.$current_url)) {
|
|
363
|
+
logger.error(
|
|
364
|
+
'Invalid `$current_url` property provided to `posthog.capture`. Input must be a string. Ignoring provided value.'
|
|
365
|
+
)
|
|
366
|
+
delete properties?.$current_url
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
this.sessionPersistence.update_search_keyword()
|
|
370
|
+
|
|
371
|
+
if (this.config.save_campaign_params) {
|
|
372
|
+
this.sessionPersistence.update_campaign_params()
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (this.config.save_referrer) {
|
|
376
|
+
this.sessionPersistence.update_referrer_info()
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (this.config.save_campaign_params || this.config.save_referrer) {
|
|
380
|
+
this.persistence.set_initial_person_info()
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const systemTime = new Date()
|
|
384
|
+
const timestamp = options?.timestamp || systemTime
|
|
385
|
+
const uuid = uuidv7()
|
|
386
|
+
let data: CaptureResult = {
|
|
387
|
+
uuid,
|
|
388
|
+
event,
|
|
389
|
+
properties: this.calculateEventProperties(event, properties || {}, timestamp, uuid),
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const setProperties = options?.$set
|
|
393
|
+
if (setProperties) {
|
|
394
|
+
data.$set = options?.$set
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const setOnceProperties = this.calculateSetOnceProperties(options?.$set_once)
|
|
398
|
+
if (setOnceProperties) {
|
|
399
|
+
data.$set_once = setOnceProperties
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
data = copyAndTruncateStrings(data, options?._noTruncate ? null : this.config.properties_string_max_length)
|
|
403
|
+
data.timestamp = timestamp
|
|
404
|
+
if (!isUndefined(options?.timestamp)) {
|
|
405
|
+
data.properties['$event_time_override_provided'] = true
|
|
406
|
+
data.properties['$event_time_override_system_time'] = systemTime
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const finalSet = { ...data.properties['$set'], ...data['$set'] }
|
|
410
|
+
if (!isEmptyObject(finalSet)) {
|
|
411
|
+
this.setPersonPropertiesForFlags(finalSet)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
super.capture(data.event, data.properties, options)
|
|
117
415
|
}
|
|
118
416
|
|
|
119
|
-
|
|
120
|
-
identify(distinctId?: string, properties?: PostHogEventProperties, options?: PostHogCaptureOptions): void {
|
|
417
|
+
identify(distinctId?: string, properties?: PostHogEventProperties, options?: LeanbaseCaptureOptions): void {
|
|
121
418
|
super.identify(distinctId, properties, options)
|
|
122
419
|
}
|
|
123
420
|
|
|
124
|
-
// Cleanup
|
|
125
421
|
destroy(): void {
|
|
126
|
-
|
|
127
|
-
this._storage.clear()
|
|
422
|
+
this.persistence?.clear()
|
|
128
423
|
}
|
|
129
424
|
}
|
package/src/page-view.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { window } from './utils'
|
|
2
|
+
import { Leanbase } from './leanbase'
|
|
3
|
+
import { clampToRange, isUndefined, Logger } from '@posthog/core'
|
|
4
|
+
import { extend } from './utils'
|
|
5
|
+
import { logger } from './leanbase-logger'
|
|
6
|
+
|
|
7
|
+
interface PageViewEventProperties {
|
|
8
|
+
$pageview_id?: string
|
|
9
|
+
$prev_pageview_id?: string
|
|
10
|
+
$prev_pageview_pathname?: string
|
|
11
|
+
$prev_pageview_duration?: number // seconds
|
|
12
|
+
$prev_pageview_last_scroll?: number
|
|
13
|
+
$prev_pageview_last_scroll_percentage?: number
|
|
14
|
+
$prev_pageview_max_scroll?: number
|
|
15
|
+
$prev_pageview_max_scroll_percentage?: number
|
|
16
|
+
$prev_pageview_last_content?: number
|
|
17
|
+
$prev_pageview_last_content_percentage?: number
|
|
18
|
+
$prev_pageview_max_content?: number
|
|
19
|
+
$prev_pageview_max_content_percentage?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// This keeps track of the PageView state (such as the previous PageView's path, timestamp, id, and scroll properties).
|
|
23
|
+
// We store the state in memory, which means that for non-SPA sites, the state will be lost on page reload. This means
|
|
24
|
+
// that non-SPA sites should always send a $pageleave event on any navigation, before the page unloads. For SPA sites,
|
|
25
|
+
// they only need to send a $pageleave event when the user navigates away from the site, as the information is not lost
|
|
26
|
+
// on an internal navigation, and is included as the $prev_pageview_ properties in the next $pageview event.
|
|
27
|
+
|
|
28
|
+
// Practically, this means that to find the scroll properties for a given pageview, you need to find the event where
|
|
29
|
+
// event name is $pageview or $pageleave and where $prev_pageview_id matches the original pageview event's id.
|
|
30
|
+
|
|
31
|
+
export class PageViewManager {
|
|
32
|
+
_currentPageview?: { timestamp: Date; pageViewId: string | undefined; pathname: string | undefined }
|
|
33
|
+
_instance: Leanbase
|
|
34
|
+
|
|
35
|
+
constructor(instance: Leanbase) {
|
|
36
|
+
this._instance = instance
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
doPageView(timestamp: Date, pageViewId?: string): PageViewEventProperties {
|
|
40
|
+
const response = this._previousPageViewProperties(timestamp, pageViewId)
|
|
41
|
+
|
|
42
|
+
// On a pageview we reset the contexts
|
|
43
|
+
this._currentPageview = { pathname: window?.location.pathname ?? '', pageViewId, timestamp }
|
|
44
|
+
this._instance.scrollManager.resetContext()
|
|
45
|
+
|
|
46
|
+
return response
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
doPageLeave(timestamp: Date): PageViewEventProperties {
|
|
50
|
+
return this._previousPageViewProperties(timestamp, this._currentPageview?.pageViewId)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
doEvent(): PageViewEventProperties {
|
|
54
|
+
return { $pageview_id: this._currentPageview?.pageViewId }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private _previousPageViewProperties(timestamp: Date, pageviewId: string | undefined): PageViewEventProperties {
|
|
58
|
+
const previousPageView = this._currentPageview
|
|
59
|
+
|
|
60
|
+
if (!previousPageView) {
|
|
61
|
+
return { $pageview_id: pageviewId }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let properties: PageViewEventProperties = {
|
|
65
|
+
$pageview_id: pageviewId,
|
|
66
|
+
$prev_pageview_id: previousPageView.pageViewId,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const scrollContext = this._instance.scrollManager.getContext()
|
|
70
|
+
|
|
71
|
+
if (scrollContext && !this._instance.config.disable_scroll_properties) {
|
|
72
|
+
let { maxScrollHeight, lastScrollY, maxScrollY, maxContentHeight, lastContentY, maxContentY } =
|
|
73
|
+
scrollContext
|
|
74
|
+
|
|
75
|
+
if (
|
|
76
|
+
!isUndefined(maxScrollHeight) &&
|
|
77
|
+
!isUndefined(lastScrollY) &&
|
|
78
|
+
!isUndefined(maxScrollY) &&
|
|
79
|
+
!isUndefined(maxContentHeight) &&
|
|
80
|
+
!isUndefined(lastContentY) &&
|
|
81
|
+
!isUndefined(maxContentY)
|
|
82
|
+
) {
|
|
83
|
+
// Use ceil, so that e.g. scrolling 999.5px of a 1000px page is considered 100% scrolled
|
|
84
|
+
maxScrollHeight = Math.ceil(maxScrollHeight)
|
|
85
|
+
lastScrollY = Math.ceil(lastScrollY)
|
|
86
|
+
maxScrollY = Math.ceil(maxScrollY)
|
|
87
|
+
maxContentHeight = Math.ceil(maxContentHeight)
|
|
88
|
+
lastContentY = Math.ceil(lastContentY)
|
|
89
|
+
maxContentY = Math.ceil(maxContentY)
|
|
90
|
+
|
|
91
|
+
// if the maximum scroll height is near 0, then the percentage is 1
|
|
92
|
+
const lastScrollPercentage =
|
|
93
|
+
maxScrollHeight <= 1 ? 1 : clampToRange(lastScrollY / maxScrollHeight, 0, 1, logger as Logger)
|
|
94
|
+
const maxScrollPercentage =
|
|
95
|
+
maxScrollHeight <= 1 ? 1 : clampToRange(maxScrollY / maxScrollHeight, 0, 1, logger as Logger)
|
|
96
|
+
const lastContentPercentage =
|
|
97
|
+
maxContentHeight <= 1 ? 1 : clampToRange(lastContentY / maxContentHeight, 0, 1, logger as Logger)
|
|
98
|
+
const maxContentPercentage =
|
|
99
|
+
maxContentHeight <= 1 ? 1 : clampToRange(maxContentY / maxContentHeight, 0, 1, logger as Logger)
|
|
100
|
+
|
|
101
|
+
properties = extend(properties, {
|
|
102
|
+
$prev_pageview_last_scroll: lastScrollY,
|
|
103
|
+
$prev_pageview_last_scroll_percentage: lastScrollPercentage,
|
|
104
|
+
$prev_pageview_max_scroll: maxScrollY,
|
|
105
|
+
$prev_pageview_max_scroll_percentage: maxScrollPercentage,
|
|
106
|
+
$prev_pageview_last_content: lastContentY,
|
|
107
|
+
$prev_pageview_last_content_percentage: lastContentPercentage,
|
|
108
|
+
$prev_pageview_max_content: maxContentY,
|
|
109
|
+
$prev_pageview_max_content_percentage: maxContentPercentage,
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (previousPageView.pathname) {
|
|
115
|
+
properties.$prev_pageview_pathname = previousPageView.pathname
|
|
116
|
+
}
|
|
117
|
+
if (previousPageView.timestamp) {
|
|
118
|
+
// Use seconds, for consistency with our other duration-related properties like $duration
|
|
119
|
+
properties.$prev_pageview_duration = (timestamp.getTime() - previousPageView.timestamp.getTime()) / 1000
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return properties
|
|
123
|
+
}
|
|
124
|
+
}
|