@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-logger.ts
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
|
|
2
3
|
const PREFIX = '[Leanbase]'
|
|
3
4
|
|
|
4
5
|
export const logger = {
|
|
5
6
|
info: (...args: any[]) => {
|
|
6
7
|
if (typeof console !== 'undefined') {
|
|
7
|
-
// eslint-disable-next-line no-console
|
|
8
8
|
console.log(PREFIX, ...args)
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
11
|
warn: (...args: any[]) => {
|
|
12
12
|
if (typeof console !== 'undefined') {
|
|
13
|
-
// eslint-disable-next-line no-console
|
|
14
13
|
console.warn(PREFIX, ...args)
|
|
15
14
|
}
|
|
16
15
|
},
|
|
17
16
|
error: (...args: any[]) => {
|
|
18
17
|
if (typeof console !== 'undefined') {
|
|
19
|
-
// eslint-disable-next-line no-console
|
|
20
18
|
console.error(PREFIX, ...args)
|
|
21
19
|
}
|
|
22
20
|
},
|
|
21
|
+
critical: (...args: any[]) => {
|
|
22
|
+
if (typeof console !== 'undefined') {
|
|
23
|
+
console.error(PREFIX, 'CRITICAL:', ...args)
|
|
24
|
+
}
|
|
25
|
+
},
|
|
23
26
|
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/* eslint camelcase: "off" */
|
|
2
|
+
|
|
3
|
+
import { each, extend, include, stripEmptyProperties } from './utils'
|
|
4
|
+
import { cookieStore, localPlusCookieStore, localStore, memoryStore, sessionStore } from './storage'
|
|
5
|
+
import { PersistentStore, LeanbaseConfig, Properties } from './types'
|
|
6
|
+
import {
|
|
7
|
+
ENABLED_FEATURE_FLAGS,
|
|
8
|
+
EVENT_TIMERS_KEY,
|
|
9
|
+
INITIAL_CAMPAIGN_PARAMS,
|
|
10
|
+
INITIAL_PERSON_INFO,
|
|
11
|
+
INITIAL_REFERRER_INFO,
|
|
12
|
+
PERSISTENCE_RESERVED_PROPERTIES,
|
|
13
|
+
} from './constants'
|
|
14
|
+
|
|
15
|
+
import { isUndefined } from '@posthog/core'
|
|
16
|
+
import {
|
|
17
|
+
getCampaignParams,
|
|
18
|
+
getInitialPersonPropsFromInfo,
|
|
19
|
+
getPersonInfo,
|
|
20
|
+
getReferrerInfo,
|
|
21
|
+
getSearchInfo,
|
|
22
|
+
} from './utils/event-utils'
|
|
23
|
+
import { stripLeadingDollar, isEmptyObject, isObject } from '@posthog/core'
|
|
24
|
+
import { logger } from './leanbase-logger'
|
|
25
|
+
|
|
26
|
+
const CASE_INSENSITIVE_PERSISTENCE_TYPES: readonly Lowercase<LeanbaseConfig['persistence']>[] = [
|
|
27
|
+
'cookie',
|
|
28
|
+
'localstorage',
|
|
29
|
+
'localstorage+cookie',
|
|
30
|
+
'sessionstorage',
|
|
31
|
+
'memory',
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
const parseName = (config: LeanbaseConfig): string => {
|
|
35
|
+
let token = ''
|
|
36
|
+
if (config['token']) {
|
|
37
|
+
token = config['token'].replace(/\+/g, 'PL').replace(/\//g, 'SL').replace(/=/g, 'EQ')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (config['persistence_name']) {
|
|
41
|
+
return config['persistence_name']
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return 'leanbase_' + token
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Leanbase Persistence Object
|
|
49
|
+
* @constructor
|
|
50
|
+
*/
|
|
51
|
+
export class LeanbasePersistence {
|
|
52
|
+
private _config: LeanbaseConfig
|
|
53
|
+
props: Properties
|
|
54
|
+
private _storage: PersistentStore
|
|
55
|
+
private _campaign_params_saved: boolean
|
|
56
|
+
private readonly _name: string
|
|
57
|
+
_disabled: boolean | undefined
|
|
58
|
+
private _secure: boolean | undefined
|
|
59
|
+
private _expire_days: number | undefined
|
|
60
|
+
private _default_expiry: number | undefined
|
|
61
|
+
private _cross_subdomain: boolean | undefined
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {LeanbaseConfig} config initial PostHog configuration
|
|
65
|
+
* @param {boolean=} isDisabled should persistence be disabled (e.g. because of consent management)
|
|
66
|
+
*/
|
|
67
|
+
constructor(config: LeanbaseConfig, isDisabled?: boolean) {
|
|
68
|
+
this._config = config
|
|
69
|
+
this.props = {}
|
|
70
|
+
this._campaign_params_saved = false
|
|
71
|
+
this._name = parseName(config)
|
|
72
|
+
this._storage = this._buildStorage(config)
|
|
73
|
+
this.load()
|
|
74
|
+
if (config.debug) {
|
|
75
|
+
logger.info('Persistence loaded', config['persistence'], { ...this.props })
|
|
76
|
+
}
|
|
77
|
+
this.update_config(config, config, isDisabled)
|
|
78
|
+
this.save()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Returns whether persistence is disabled. Only available in SDKs > 1.257.1. Do not use on extensions, otherwise
|
|
83
|
+
* it'll break backwards compatibility for any version before 1.257.1.
|
|
84
|
+
*/
|
|
85
|
+
public isDisabled?(): boolean {
|
|
86
|
+
return !!this._disabled
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private _buildStorage(config: LeanbaseConfig) {
|
|
90
|
+
if (
|
|
91
|
+
CASE_INSENSITIVE_PERSISTENCE_TYPES.indexOf(
|
|
92
|
+
config['persistence'].toLowerCase() as Lowercase<LeanbaseConfig['persistence']>
|
|
93
|
+
) === -1
|
|
94
|
+
) {
|
|
95
|
+
logger.info('Unknown persistence type ' + config['persistence'] + '; falling back to localStorage+cookie')
|
|
96
|
+
config['persistence'] = 'localStorage+cookie'
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let store: PersistentStore
|
|
100
|
+
const storage_type = config['persistence'].toLowerCase() as Lowercase<LeanbaseConfig['persistence']>
|
|
101
|
+
if (storage_type === 'localstorage' && localStore._is_supported()) {
|
|
102
|
+
store = localStore
|
|
103
|
+
} else if (storage_type === 'localstorage+cookie' && localPlusCookieStore._is_supported()) {
|
|
104
|
+
store = localPlusCookieStore
|
|
105
|
+
} else if (storage_type === 'sessionstorage' && sessionStore._is_supported()) {
|
|
106
|
+
store = sessionStore
|
|
107
|
+
} else if (storage_type === 'memory') {
|
|
108
|
+
store = memoryStore
|
|
109
|
+
} else if (storage_type === 'cookie') {
|
|
110
|
+
store = cookieStore
|
|
111
|
+
} else if (localPlusCookieStore._is_supported()) {
|
|
112
|
+
store = localPlusCookieStore
|
|
113
|
+
} else {
|
|
114
|
+
store = cookieStore
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return store
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
properties(): Properties {
|
|
121
|
+
const p: Properties = {}
|
|
122
|
+
// Filter out reserved properties
|
|
123
|
+
each(this.props, function (v, k) {
|
|
124
|
+
if (k === ENABLED_FEATURE_FLAGS && isObject(v)) {
|
|
125
|
+
const keys = Object.keys(v)
|
|
126
|
+
for (let i = 0; i < keys.length; i++) {
|
|
127
|
+
p[`$feature/${keys[i]}`] = v[keys[i]]
|
|
128
|
+
}
|
|
129
|
+
} else if (!include(PERSISTENCE_RESERVED_PROPERTIES, k)) {
|
|
130
|
+
p[k] = v
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
return p
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
load(): void {
|
|
137
|
+
if (this._disabled) {
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const entry = this._storage._parse(this._name)
|
|
142
|
+
|
|
143
|
+
if (entry) {
|
|
144
|
+
this.props = extend({}, entry)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* NOTE: Saving frequently causes issues with Recordings and Consent Management Platform (CMP) tools which
|
|
150
|
+
* observe cookie changes, and modify their UI, often causing infinite loops.
|
|
151
|
+
* As such callers of this should ideally check that the data has changed beforehand
|
|
152
|
+
*/
|
|
153
|
+
save(): void {
|
|
154
|
+
if (this._disabled) {
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
this._storage._set(
|
|
158
|
+
this._name,
|
|
159
|
+
this.props,
|
|
160
|
+
this._expire_days,
|
|
161
|
+
this._cross_subdomain,
|
|
162
|
+
this._secure,
|
|
163
|
+
this._config.debug
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
remove(): void {
|
|
168
|
+
this._storage._remove(this._name, false)
|
|
169
|
+
this._storage._remove(this._name, true)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
clear(): void {
|
|
173
|
+
this.remove()
|
|
174
|
+
this.props = {}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @param {Object} props
|
|
179
|
+
* @param {*=} default_value
|
|
180
|
+
* @param {number=} days
|
|
181
|
+
*/
|
|
182
|
+
|
|
183
|
+
register_once(props: Properties, default_value: any, days?: number): boolean {
|
|
184
|
+
if (isObject(props)) {
|
|
185
|
+
if (isUndefined(default_value)) {
|
|
186
|
+
default_value = 'None'
|
|
187
|
+
}
|
|
188
|
+
this._expire_days = isUndefined(days) ? this._default_expiry : days
|
|
189
|
+
|
|
190
|
+
let hasChanges = false
|
|
191
|
+
|
|
192
|
+
each(props, (val, prop) => {
|
|
193
|
+
if (!this.props.hasOwnProperty(prop) || this.props[prop] === default_value) {
|
|
194
|
+
this.props[prop] = val
|
|
195
|
+
hasChanges = true
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
if (hasChanges) {
|
|
200
|
+
this.save()
|
|
201
|
+
return true
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return false
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @param {Object} props
|
|
209
|
+
* @param {number=} days
|
|
210
|
+
*/
|
|
211
|
+
register(props: Properties, days?: number): boolean {
|
|
212
|
+
if (isObject(props)) {
|
|
213
|
+
this._expire_days = isUndefined(days) ? this._default_expiry : days
|
|
214
|
+
|
|
215
|
+
let hasChanges = false
|
|
216
|
+
|
|
217
|
+
each(props, (val, prop) => {
|
|
218
|
+
if (props.hasOwnProperty(prop) && this.props[prop] !== val) {
|
|
219
|
+
this.props[prop] = val
|
|
220
|
+
hasChanges = true
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
if (hasChanges) {
|
|
225
|
+
this.save()
|
|
226
|
+
return true
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return false
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
unregister(prop: string): void {
|
|
233
|
+
if (prop in this.props) {
|
|
234
|
+
delete this.props[prop]
|
|
235
|
+
this.save()
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
update_campaign_params(): void {
|
|
240
|
+
if (!this._campaign_params_saved) {
|
|
241
|
+
const campaignParams = getCampaignParams(
|
|
242
|
+
this._config.custom_campaign_params,
|
|
243
|
+
this._config.mask_personal_data_properties,
|
|
244
|
+
this._config.custom_personal_data_properties
|
|
245
|
+
)
|
|
246
|
+
if (!isEmptyObject(stripEmptyProperties(campaignParams))) {
|
|
247
|
+
this.register(campaignParams)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
this._campaign_params_saved = true
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
update_search_keyword(): void {
|
|
254
|
+
this.register(getSearchInfo())
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
update_referrer_info(): void {
|
|
258
|
+
this.register_once(getReferrerInfo(), undefined)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
set_initial_person_info(): void {
|
|
262
|
+
if (this.props[INITIAL_CAMPAIGN_PARAMS] || this.props[INITIAL_REFERRER_INFO]) {
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
this.register_once(
|
|
267
|
+
{
|
|
268
|
+
[INITIAL_PERSON_INFO]: getPersonInfo(
|
|
269
|
+
this._config.mask_personal_data_properties,
|
|
270
|
+
this._config.custom_personal_data_properties
|
|
271
|
+
),
|
|
272
|
+
},
|
|
273
|
+
undefined
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
get_initial_props(): Properties {
|
|
278
|
+
const p: Properties = {}
|
|
279
|
+
each([INITIAL_REFERRER_INFO, INITIAL_CAMPAIGN_PARAMS], (key) => {
|
|
280
|
+
const initialReferrerInfo = this.props[key]
|
|
281
|
+
if (initialReferrerInfo) {
|
|
282
|
+
each(initialReferrerInfo, function (v, k) {
|
|
283
|
+
p['$initial_' + stripLeadingDollar(k)] = v
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
const initialPersonInfo = this.props[INITIAL_PERSON_INFO]
|
|
288
|
+
if (initialPersonInfo) {
|
|
289
|
+
const initialPersonProps = getInitialPersonPropsFromInfo(initialPersonInfo)
|
|
290
|
+
extend(p, initialPersonProps)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return p
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
safe_merge(props: Properties): Properties {
|
|
297
|
+
each(this.props, function (val, prop) {
|
|
298
|
+
if (!(prop in props)) {
|
|
299
|
+
props[prop] = val
|
|
300
|
+
}
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
return props
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
update_config(config: LeanbaseConfig, oldConfig: LeanbaseConfig, isDisabled?: boolean): void {
|
|
307
|
+
this._default_expiry = this._expire_days = config['cookie_expiration']
|
|
308
|
+
this.set_disabled(config['disable_persistence'] || !!isDisabled)
|
|
309
|
+
this.set_cross_subdomain(config['cross_subdomain_cookie'])
|
|
310
|
+
this.set_secure(config['secure_cookie'])
|
|
311
|
+
|
|
312
|
+
if (config.persistence !== oldConfig.persistence) {
|
|
313
|
+
const newStore = this._buildStorage(config)
|
|
314
|
+
const props = this.props
|
|
315
|
+
|
|
316
|
+
this.clear()
|
|
317
|
+
this._storage = newStore
|
|
318
|
+
this.props = props
|
|
319
|
+
|
|
320
|
+
this.save()
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
set_disabled(disabled: boolean): void {
|
|
325
|
+
this._disabled = disabled
|
|
326
|
+
if (this._disabled) {
|
|
327
|
+
return this.remove()
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
this.save()
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
set_cross_subdomain(cross_subdomain: boolean): void {
|
|
334
|
+
if (cross_subdomain !== this._cross_subdomain) {
|
|
335
|
+
this._cross_subdomain = cross_subdomain
|
|
336
|
+
this.remove()
|
|
337
|
+
this.save()
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
set_secure(secure: boolean): void {
|
|
342
|
+
if (secure !== this._secure) {
|
|
343
|
+
this._secure = secure
|
|
344
|
+
this.remove()
|
|
345
|
+
this.save()
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
set_event_timer(event_name: string, timestamp: number): void {
|
|
350
|
+
const timers = this.props[EVENT_TIMERS_KEY] || {}
|
|
351
|
+
timers[event_name] = timestamp
|
|
352
|
+
this.props[EVENT_TIMERS_KEY] = timers
|
|
353
|
+
this.save()
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
remove_event_timer(event_name: string): number {
|
|
357
|
+
const timers = this.props[EVENT_TIMERS_KEY] || {}
|
|
358
|
+
const timestamp = timers[event_name]
|
|
359
|
+
if (!isUndefined(timestamp)) {
|
|
360
|
+
delete this.props[EVENT_TIMERS_KEY][event_name]
|
|
361
|
+
this.save()
|
|
362
|
+
}
|
|
363
|
+
return timestamp
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
get_property(prop: string): any {
|
|
367
|
+
return this.props[prop]
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
set_property(prop: string, to: any): void {
|
|
371
|
+
this.props[prop] = to
|
|
372
|
+
this.save()
|
|
373
|
+
}
|
|
374
|
+
}
|