@leanbase.com/js 0.1.3 → 0.2.0-alpha.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 (137) hide show
  1. package/LICENSE +37 -0
  2. package/dist/autocapture-utils.d.ts +17 -0
  3. package/dist/autocapture.d.ts +35 -0
  4. package/dist/config.d.ts +5 -0
  5. package/dist/constants.d.ts +54 -0
  6. package/dist/entrypoints/main.cjs.d.ts +4 -0
  7. package/dist/entrypoints/module.es.d.ts +4 -0
  8. package/dist/extensions/rageclick.d.ts +9 -0
  9. package/dist/iife.d.ts +19 -0
  10. package/dist/index.d.ts +2 -779
  11. package/dist/leanbase-logger.d.ts +6 -0
  12. package/dist/leanbase-persistence.d.ts +64 -0
  13. package/dist/leanbase.d.ts +49 -0
  14. package/dist/leanbase.iife.js +1 -4747
  15. package/dist/leanbase.iife.js.map +1 -1
  16. package/dist/main.js +2 -0
  17. package/dist/main.js.map +1 -0
  18. package/dist/module.d.ts +780 -0
  19. package/dist/module.js +2 -0
  20. package/dist/module.js.map +1 -0
  21. package/dist/page-view.d.ts +29 -0
  22. package/dist/scroll-manager.d.ts +21 -0
  23. package/dist/session-props.d.ts +32 -0
  24. package/dist/sessionid.d.ts +50 -0
  25. package/dist/storage.d.ts +24 -0
  26. package/{src/types.ts → dist/types.d.ts} +145 -235
  27. package/dist/utils/blocked-uas.d.ts +17 -0
  28. package/dist/utils/element-utils.d.ts +5 -0
  29. package/dist/utils/event-utils.d.ts +22 -0
  30. package/dist/utils/index.d.ts +44 -0
  31. package/dist/utils/request-utils.d.ts +12 -0
  32. package/dist/utils/simple-event-emitter.d.ts +6 -0
  33. package/dist/utils/user-agent-utils.d.ts +18 -0
  34. package/dist/uuidv7.d.ts +43 -0
  35. package/dist/version.d.ts +1 -0
  36. package/lib/autocapture-utils.d.ts +17 -0
  37. package/{src/autocapture-utils.ts → lib/autocapture-utils.js} +196 -280
  38. package/lib/autocapture-utils.js.map +1 -0
  39. package/lib/autocapture.d.ts +35 -0
  40. package/lib/autocapture.js +311 -0
  41. package/lib/autocapture.js.map +1 -0
  42. package/lib/config.d.ts +5 -0
  43. package/lib/config.js +7 -0
  44. package/lib/config.js.map +1 -0
  45. package/lib/constants.d.ts +54 -0
  46. package/{src/constants.ts → lib/constants.js} +50 -57
  47. package/lib/constants.js.map +1 -0
  48. package/lib/entrypoints/main.cjs.d.ts +4 -0
  49. package/lib/entrypoints/main.cjs.js +3 -0
  50. package/lib/entrypoints/main.cjs.js.map +1 -0
  51. package/lib/entrypoints/module.es.d.ts +4 -0
  52. package/lib/entrypoints/module.es.js +3 -0
  53. package/lib/entrypoints/module.es.js.map +1 -0
  54. package/lib/extensions/rageclick.d.ts +9 -0
  55. package/lib/extensions/rageclick.js +27 -0
  56. package/lib/extensions/rageclick.js.map +1 -0
  57. package/lib/iife.d.ts +19 -0
  58. package/lib/iife.js +67 -0
  59. package/lib/iife.js.map +1 -0
  60. package/{src/index.ts → lib/index.d.ts} +2 -2
  61. package/lib/index.js +2 -0
  62. package/lib/index.js.map +1 -0
  63. package/lib/leanbase-logger.d.ts +6 -0
  64. package/lib/leanbase-logger.js +25 -0
  65. package/lib/leanbase-logger.js.map +1 -0
  66. package/lib/leanbase-persistence.d.ts +64 -0
  67. package/lib/leanbase-persistence.js +287 -0
  68. package/lib/leanbase-persistence.js.map +1 -0
  69. package/lib/leanbase.d.ts +49 -0
  70. package/lib/leanbase.js +294 -0
  71. package/lib/leanbase.js.map +1 -0
  72. package/lib/page-view.d.ts +29 -0
  73. package/lib/page-view.js +81 -0
  74. package/lib/page-view.js.map +1 -0
  75. package/lib/scroll-manager.d.ts +21 -0
  76. package/lib/scroll-manager.js +79 -0
  77. package/lib/scroll-manager.js.map +1 -0
  78. package/lib/session-props.d.ts +32 -0
  79. package/lib/session-props.js +73 -0
  80. package/lib/session-props.js.map +1 -0
  81. package/lib/sessionid.d.ts +50 -0
  82. package/{src/sessionid.ts → lib/sessionid.js} +128 -204
  83. package/lib/sessionid.js.map +1 -0
  84. package/lib/storage.d.ts +24 -0
  85. package/{src/storage.ts → lib/storage.js} +182 -225
  86. package/lib/storage.js.map +1 -0
  87. package/lib/types.d.ts +544 -0
  88. package/lib/types.js +7 -0
  89. package/lib/types.js.map +1 -0
  90. package/lib/utils/blocked-uas.d.ts +17 -0
  91. package/{src/utils/blocked-uas.ts → lib/utils/blocked-uas.js} +19 -48
  92. package/lib/utils/blocked-uas.js.map +1 -0
  93. package/lib/utils/element-utils.d.ts +5 -0
  94. package/{src/utils/element-utils.ts → lib/utils/element-utils.js} +13 -17
  95. package/lib/utils/element-utils.js.map +1 -0
  96. package/lib/utils/event-utils.d.ts +22 -0
  97. package/lib/utils/event-utils.js +258 -0
  98. package/lib/utils/event-utils.js.map +1 -0
  99. package/lib/utils/index.d.ts +44 -0
  100. package/lib/utils/index.js +183 -0
  101. package/lib/utils/index.js.map +1 -0
  102. package/lib/utils/request-utils.d.ts +12 -0
  103. package/lib/utils/request-utils.js +107 -0
  104. package/lib/utils/request-utils.js.map +1 -0
  105. package/lib/utils/simple-event-emitter.d.ts +6 -0
  106. package/lib/utils/simple-event-emitter.js +24 -0
  107. package/lib/utils/simple-event-emitter.js.map +1 -0
  108. package/lib/utils/user-agent-utils.d.ts +18 -0
  109. package/lib/utils/user-agent-utils.js +369 -0
  110. package/lib/utils/user-agent-utils.js.map +1 -0
  111. package/lib/uuidv7.d.ts +43 -0
  112. package/{src/uuidv7.ts → lib/uuidv7.js} +103 -131
  113. package/lib/uuidv7.js.map +1 -0
  114. package/lib/version.d.ts +1 -0
  115. package/lib/version.js +2 -0
  116. package/lib/version.js.map +1 -0
  117. package/package.json +56 -45
  118. package/dist/index.cjs +0 -3034
  119. package/dist/index.cjs.map +0 -1
  120. package/dist/index.mjs +0 -3032
  121. package/dist/index.mjs.map +0 -1
  122. package/src/autocapture.ts +0 -415
  123. package/src/config.ts +0 -8
  124. package/src/extensions/rageclick.ts +0 -34
  125. package/src/iife.ts +0 -87
  126. package/src/leanbase-logger.ts +0 -26
  127. package/src/leanbase-persistence.ts +0 -374
  128. package/src/leanbase.ts +0 -424
  129. package/src/page-view.ts +0 -124
  130. package/src/scroll-manager.ts +0 -103
  131. package/src/session-props.ts +0 -114
  132. package/src/utils/event-utils.ts +0 -304
  133. package/src/utils/index.ts +0 -222
  134. package/src/utils/request-utils.ts +0 -128
  135. package/src/utils/simple-event-emitter.ts +0 -27
  136. package/src/utils/user-agent-utils.ts +0 -357
  137. package/src/version.ts +0 -1
package/src/leanbase.ts DELETED
@@ -1,424 +0,0 @@
1
- import {
2
- PostHogCore,
3
- getFetch,
4
- isEmptyObject,
5
- isEmptyString,
6
- isNumber,
7
- isObject,
8
- isString,
9
- isUndefined,
10
- } from '@posthog/core'
11
- import type {
12
- PostHogEventProperties,
13
- PostHogFetchOptions,
14
- PostHogFetchResponse,
15
- PostHogPersistedProperty,
16
- } from '@posthog/core'
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'
36
- import { logger } from './leanbase-logger'
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'
45
-
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
- })
72
-
73
- export class Leanbase extends PostHogCore {
74
- config: LeanbaseConfig
75
- scrollManager: ScrollManager
76
- pageViewManager: PageViewManager
77
-
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
- }
101
-
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()
112
-
113
- if (this.config.preloadFeatureFlags !== false) {
114
- this.reloadFeatureFlags()
115
- }
116
-
117
- this.config.loaded?.(this)
118
- if (this.config.capture_pageview) {
119
- setTimeout(() => {
120
- if (this.config.cookieless_mode === 'always') {
121
- this.captureInitialPageview()
122
- }
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)
143
- }
144
-
145
- return
146
- }
147
-
148
- if (!this.initialPageviewCaptured) {
149
- this.initialPageviewCaptured = true
150
- this.capture('$pageview', { title: document.title })
151
-
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')
167
- }
168
- }
169
-
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
-
191
- fetch(url: string, options: PostHogFetchOptions): Promise<PostHogFetchResponse> {
192
- const fetchFn = getFetch()
193
- if (!fetchFn) {
194
- return Promise.reject(new Error('Fetch API is not available in this environment.'))
195
- }
196
-
197
- return fetchFn(url, options)
198
- }
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
-
214
- getLibraryId(): string {
215
- return 'leanbase'
216
- }
217
-
218
- getLibraryVersion(): string {
219
- return Config.LIB_VERSION
220
- }
221
-
222
- getCustomUserAgent(): void {
223
- return
224
- }
225
-
226
- getPersistedProperty<T>(key: PostHogPersistedProperty): T | undefined {
227
- return this.persistence?.get_property(key)
228
- }
229
-
230
- setPersistedProperty<T>(key: PostHogPersistedProperty, value: T | null): void {
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')
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())
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
350
- }
351
-
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)
415
- }
416
-
417
- identify(distinctId?: string, properties?: PostHogEventProperties, options?: LeanbaseCaptureOptions): void {
418
- super.identify(distinctId, properties, options)
419
- }
420
-
421
- destroy(): void {
422
- this.persistence?.clear()
423
- }
424
- }
package/src/page-view.ts DELETED
@@ -1,124 +0,0 @@
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
- }
@@ -1,103 +0,0 @@
1
- import { window } from './utils'
2
- import { Leanbase } from './leanbase'
3
- import { addEventListener } from './utils'
4
- import { isArray } from '@posthog/core'
5
-
6
- export interface ScrollContext {
7
- // scroll is how far down the page the user has scrolled,
8
- // content is how far down the page the user can view content
9
- // (e.g. if the page is 1000 tall, but the user's screen is only 500 tall,
10
- // and they don't scroll at all, then scroll is 0 and content is 500)
11
- maxScrollHeight?: number
12
- maxScrollY?: number
13
- lastScrollY?: number
14
- maxContentHeight?: number
15
- maxContentY?: number
16
- lastContentY?: number
17
- }
18
-
19
- // This class is responsible for tracking scroll events and maintaining the scroll context
20
- export class ScrollManager {
21
- private _context: ScrollContext | undefined
22
-
23
- constructor(private _instance: Leanbase) {}
24
-
25
- getContext(): ScrollContext | undefined {
26
- return this._context
27
- }
28
-
29
- resetContext(): ScrollContext | undefined {
30
- const ctx = this._context
31
-
32
- // update the scroll properties for the new page, but wait until the next tick
33
- // of the event loop
34
- setTimeout(this._updateScrollData, 0)
35
-
36
- return ctx
37
- }
38
-
39
- private _updateScrollData = () => {
40
- if (!this._context) {
41
- this._context = {}
42
- }
43
-
44
- const el = this.scrollElement()
45
-
46
- const scrollY = this.scrollY()
47
- const scrollHeight = el ? Math.max(0, el.scrollHeight - el.clientHeight) : 0
48
- const contentY = scrollY + (el?.clientHeight || 0)
49
- const contentHeight = el?.scrollHeight || 0
50
-
51
- this._context.lastScrollY = Math.ceil(scrollY)
52
- this._context.maxScrollY = Math.max(scrollY, this._context.maxScrollY ?? 0)
53
- this._context.maxScrollHeight = Math.max(scrollHeight, this._context.maxScrollHeight ?? 0)
54
-
55
- this._context.lastContentY = contentY
56
- this._context.maxContentY = Math.max(contentY, this._context.maxContentY ?? 0)
57
- this._context.maxContentHeight = Math.max(contentHeight, this._context.maxContentHeight ?? 0)
58
- }
59
-
60
- // `capture: true` is required to get scroll events for other scrollable elements
61
- // on the page, not just the window
62
- // see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#usecapture
63
- startMeasuringScrollPosition() {
64
- addEventListener(window, 'scroll', this._updateScrollData, { capture: true })
65
- addEventListener(window, 'scrollend', this._updateScrollData, { capture: true })
66
- addEventListener(window, 'resize', this._updateScrollData)
67
- }
68
-
69
- public scrollElement(): Element | undefined {
70
- if (this._instance.config.scroll_root_selector) {
71
- const selectors = isArray(this._instance.config.scroll_root_selector)
72
- ? this._instance.config.scroll_root_selector
73
- : [this._instance.config.scroll_root_selector]
74
- for (const selector of selectors) {
75
- const element = window?.document.querySelector(selector)
76
- if (element) {
77
- return element
78
- }
79
- }
80
- return undefined
81
- } else {
82
- return window?.document.documentElement
83
- }
84
- }
85
-
86
- public scrollY(): number {
87
- if (this._instance.config.scroll_root_selector) {
88
- const element = this.scrollElement()
89
- return (element && element.scrollTop) || 0
90
- } else {
91
- return window ? window.scrollY || window.pageYOffset || window.document.documentElement.scrollTop || 0 : 0
92
- }
93
- }
94
-
95
- public scrollX(): number {
96
- if (this._instance.config.scroll_root_selector) {
97
- const element = this.scrollElement()
98
- return (element && element.scrollLeft) || 0
99
- } else {
100
- return window ? window.scrollX || window.pageXOffset || window.document.documentElement.scrollLeft || 0 : 0
101
- }
102
- }
103
- }