@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.
- package/LICENSE +37 -0
- package/dist/autocapture-utils.d.ts +17 -0
- package/dist/autocapture.d.ts +35 -0
- package/dist/config.d.ts +5 -0
- package/dist/constants.d.ts +54 -0
- package/dist/entrypoints/main.cjs.d.ts +4 -0
- package/dist/entrypoints/module.es.d.ts +4 -0
- package/dist/extensions/rageclick.d.ts +9 -0
- package/dist/iife.d.ts +19 -0
- package/dist/index.d.ts +2 -779
- package/dist/leanbase-logger.d.ts +6 -0
- package/dist/leanbase-persistence.d.ts +64 -0
- package/dist/leanbase.d.ts +49 -0
- package/dist/leanbase.iife.js +1 -4747
- package/dist/leanbase.iife.js.map +1 -1
- package/dist/main.js +2 -0
- package/dist/main.js.map +1 -0
- package/dist/module.d.ts +780 -0
- package/dist/module.js +2 -0
- package/dist/module.js.map +1 -0
- package/dist/page-view.d.ts +29 -0
- package/dist/scroll-manager.d.ts +21 -0
- package/dist/session-props.d.ts +32 -0
- package/dist/sessionid.d.ts +50 -0
- package/dist/storage.d.ts +24 -0
- package/{src/types.ts → dist/types.d.ts} +145 -235
- package/dist/utils/blocked-uas.d.ts +17 -0
- package/dist/utils/element-utils.d.ts +5 -0
- package/dist/utils/event-utils.d.ts +22 -0
- package/dist/utils/index.d.ts +44 -0
- package/dist/utils/request-utils.d.ts +12 -0
- package/dist/utils/simple-event-emitter.d.ts +6 -0
- package/dist/utils/user-agent-utils.d.ts +18 -0
- package/dist/uuidv7.d.ts +43 -0
- package/dist/version.d.ts +1 -0
- package/lib/autocapture-utils.d.ts +17 -0
- package/{src/autocapture-utils.ts → lib/autocapture-utils.js} +196 -280
- package/lib/autocapture-utils.js.map +1 -0
- package/lib/autocapture.d.ts +35 -0
- package/lib/autocapture.js +311 -0
- package/lib/autocapture.js.map +1 -0
- package/lib/config.d.ts +5 -0
- package/lib/config.js +7 -0
- package/lib/config.js.map +1 -0
- package/lib/constants.d.ts +54 -0
- package/{src/constants.ts → lib/constants.js} +50 -57
- package/lib/constants.js.map +1 -0
- package/lib/entrypoints/main.cjs.d.ts +4 -0
- package/lib/entrypoints/main.cjs.js +3 -0
- package/lib/entrypoints/main.cjs.js.map +1 -0
- package/lib/entrypoints/module.es.d.ts +4 -0
- package/lib/entrypoints/module.es.js +3 -0
- package/lib/entrypoints/module.es.js.map +1 -0
- package/lib/extensions/rageclick.d.ts +9 -0
- package/lib/extensions/rageclick.js +27 -0
- package/lib/extensions/rageclick.js.map +1 -0
- package/lib/iife.d.ts +19 -0
- package/lib/iife.js +67 -0
- package/lib/iife.js.map +1 -0
- package/{src/index.ts → lib/index.d.ts} +2 -2
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -0
- package/lib/leanbase-logger.d.ts +6 -0
- package/lib/leanbase-logger.js +25 -0
- package/lib/leanbase-logger.js.map +1 -0
- package/lib/leanbase-persistence.d.ts +64 -0
- package/lib/leanbase-persistence.js +287 -0
- package/lib/leanbase-persistence.js.map +1 -0
- package/lib/leanbase.d.ts +49 -0
- package/lib/leanbase.js +294 -0
- package/lib/leanbase.js.map +1 -0
- package/lib/page-view.d.ts +29 -0
- package/lib/page-view.js +81 -0
- package/lib/page-view.js.map +1 -0
- package/lib/scroll-manager.d.ts +21 -0
- package/lib/scroll-manager.js +79 -0
- package/lib/scroll-manager.js.map +1 -0
- package/lib/session-props.d.ts +32 -0
- package/lib/session-props.js +73 -0
- package/lib/session-props.js.map +1 -0
- package/lib/sessionid.d.ts +50 -0
- package/{src/sessionid.ts → lib/sessionid.js} +128 -204
- package/lib/sessionid.js.map +1 -0
- package/lib/storage.d.ts +24 -0
- package/{src/storage.ts → lib/storage.js} +182 -225
- package/lib/storage.js.map +1 -0
- package/lib/types.d.ts +544 -0
- package/lib/types.js +7 -0
- package/lib/types.js.map +1 -0
- package/lib/utils/blocked-uas.d.ts +17 -0
- package/{src/utils/blocked-uas.ts → lib/utils/blocked-uas.js} +19 -48
- package/lib/utils/blocked-uas.js.map +1 -0
- package/lib/utils/element-utils.d.ts +5 -0
- package/{src/utils/element-utils.ts → lib/utils/element-utils.js} +13 -17
- package/lib/utils/element-utils.js.map +1 -0
- package/lib/utils/event-utils.d.ts +22 -0
- package/lib/utils/event-utils.js +258 -0
- package/lib/utils/event-utils.js.map +1 -0
- package/lib/utils/index.d.ts +44 -0
- package/lib/utils/index.js +183 -0
- package/lib/utils/index.js.map +1 -0
- package/lib/utils/request-utils.d.ts +12 -0
- package/lib/utils/request-utils.js +107 -0
- package/lib/utils/request-utils.js.map +1 -0
- package/lib/utils/simple-event-emitter.d.ts +6 -0
- package/lib/utils/simple-event-emitter.js +24 -0
- package/lib/utils/simple-event-emitter.js.map +1 -0
- package/lib/utils/user-agent-utils.d.ts +18 -0
- package/lib/utils/user-agent-utils.js +369 -0
- package/lib/utils/user-agent-utils.js.map +1 -0
- package/lib/uuidv7.d.ts +43 -0
- package/{src/uuidv7.ts → lib/uuidv7.js} +103 -131
- package/lib/uuidv7.js.map +1 -0
- package/lib/version.d.ts +1 -0
- package/lib/version.js +2 -0
- package/lib/version.js.map +1 -0
- package/package.json +56 -45
- package/dist/index.cjs +0 -3034
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs +0 -3032
- package/dist/index.mjs.map +0 -1
- package/src/autocapture.ts +0 -415
- package/src/config.ts +0 -8
- package/src/extensions/rageclick.ts +0 -34
- package/src/iife.ts +0 -87
- package/src/leanbase-logger.ts +0 -26
- package/src/leanbase-persistence.ts +0 -374
- package/src/leanbase.ts +0 -424
- package/src/page-view.ts +0 -124
- package/src/scroll-manager.ts +0 -103
- package/src/session-props.ts +0 -114
- package/src/utils/event-utils.ts +0 -304
- package/src/utils/index.ts +0 -222
- package/src/utils/request-utils.ts +0 -128
- package/src/utils/simple-event-emitter.ts +0 -27
- package/src/utils/user-agent-utils.ts +0 -357
- package/src/version.ts +0 -1
package/src/autocapture.ts
DELETED
|
@@ -1,415 +0,0 @@
|
|
|
1
|
-
import { addEventListener, each, extend } from './utils'
|
|
2
|
-
import {
|
|
3
|
-
autocaptureCompatibleElements,
|
|
4
|
-
getClassNames,
|
|
5
|
-
getDirectAndNestedSpanText,
|
|
6
|
-
getElementsChainString,
|
|
7
|
-
getEventTarget,
|
|
8
|
-
getSafeText,
|
|
9
|
-
isAngularStyleAttr,
|
|
10
|
-
isSensitiveElement,
|
|
11
|
-
makeSafeText,
|
|
12
|
-
shouldCaptureDomEvent,
|
|
13
|
-
shouldCaptureElement,
|
|
14
|
-
shouldCaptureRageclick,
|
|
15
|
-
shouldCaptureValue,
|
|
16
|
-
splitClassString,
|
|
17
|
-
} from './autocapture-utils'
|
|
18
|
-
|
|
19
|
-
import RageClick from './extensions/rageclick'
|
|
20
|
-
import { AutocaptureConfig, COPY_AUTOCAPTURE_EVENT, EventName, Properties, RemoteConfig } from './types'
|
|
21
|
-
import { AUTOCAPTURE_DISABLED_SERVER_SIDE } from './constants'
|
|
22
|
-
|
|
23
|
-
import { isBoolean, isFunction, isNull, isObject } from '@posthog/core'
|
|
24
|
-
import { document, window } from './utils'
|
|
25
|
-
import { convertToURL } from './utils/request-utils'
|
|
26
|
-
import { isDocumentFragment, isElementNode, isTag, isTextNode } from './utils/element-utils'
|
|
27
|
-
import { includes } from '@posthog/core'
|
|
28
|
-
import { logger } from './leanbase-logger'
|
|
29
|
-
import { Leanbase } from './leanbase'
|
|
30
|
-
|
|
31
|
-
function limitText(length: number, text: string): string {
|
|
32
|
-
if (text.length > length) {
|
|
33
|
-
return text.slice(0, length) + '...'
|
|
34
|
-
}
|
|
35
|
-
return text
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function getAugmentPropertiesFromElement(elem: Element): Properties {
|
|
39
|
-
const shouldCaptureEl = shouldCaptureElement(elem)
|
|
40
|
-
if (!shouldCaptureEl) {
|
|
41
|
-
return {}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const props: Properties = {}
|
|
45
|
-
|
|
46
|
-
each(elem.attributes, function (attr: Attr) {
|
|
47
|
-
if (attr.name && attr.name.indexOf('data-ph-capture-attribute') === 0) {
|
|
48
|
-
const propertyKey = attr.name.replace('data-ph-capture-attribute-', '')
|
|
49
|
-
const propertyValue = attr.value
|
|
50
|
-
if (propertyKey && propertyValue && shouldCaptureValue(propertyValue)) {
|
|
51
|
-
props[propertyKey] = propertyValue
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
return props
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function previousElementSibling(el: Element): Element | null {
|
|
60
|
-
if (el.previousElementSibling) {
|
|
61
|
-
return el.previousElementSibling
|
|
62
|
-
}
|
|
63
|
-
let _el: Element | null = el
|
|
64
|
-
do {
|
|
65
|
-
_el = _el.previousSibling as Element | null // resolves to ChildNode->Node, which is Element's parent class
|
|
66
|
-
} while (_el && !isElementNode(_el))
|
|
67
|
-
return _el
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function getDefaultProperties(eventType: string): Properties {
|
|
71
|
-
return {
|
|
72
|
-
$event_type: eventType,
|
|
73
|
-
$ce_version: 1,
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function getPropertiesFromElement(
|
|
78
|
-
elem: Element,
|
|
79
|
-
maskAllAttributes: boolean,
|
|
80
|
-
maskText: boolean,
|
|
81
|
-
elementAttributeIgnorelist: string[] | undefined
|
|
82
|
-
): Properties {
|
|
83
|
-
const tag_name = elem.tagName.toLowerCase()
|
|
84
|
-
const props: Properties = {
|
|
85
|
-
tag_name: tag_name,
|
|
86
|
-
}
|
|
87
|
-
if (autocaptureCompatibleElements.indexOf(tag_name) > -1 && !maskText) {
|
|
88
|
-
if (tag_name.toLowerCase() === 'a' || tag_name.toLowerCase() === 'button') {
|
|
89
|
-
props['$el_text'] = limitText(1024, getDirectAndNestedSpanText(elem))
|
|
90
|
-
} else {
|
|
91
|
-
props['$el_text'] = limitText(1024, getSafeText(elem))
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const classes = getClassNames(elem)
|
|
96
|
-
if (classes.length > 0)
|
|
97
|
-
props['classes'] = classes.filter(function (c) {
|
|
98
|
-
return c !== ''
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
// capture the deny list here because this not-a-class class makes it tricky to use this.config in the function below
|
|
102
|
-
each(elem.attributes, function (attr: Attr) {
|
|
103
|
-
// Only capture attributes we know are safe
|
|
104
|
-
if (isSensitiveElement(elem) && ['name', 'id', 'class', 'aria-label'].indexOf(attr.name) === -1) return
|
|
105
|
-
|
|
106
|
-
if (elementAttributeIgnorelist?.includes(attr.name)) return
|
|
107
|
-
|
|
108
|
-
if (!maskAllAttributes && shouldCaptureValue(attr.value) && !isAngularStyleAttr(attr.name)) {
|
|
109
|
-
let value = attr.value
|
|
110
|
-
if (attr.name === 'class') {
|
|
111
|
-
// html attributes can _technically_ contain linebreaks,
|
|
112
|
-
// but we're very intolerant of them in the class string,
|
|
113
|
-
// so we strip them.
|
|
114
|
-
value = splitClassString(value).join(' ')
|
|
115
|
-
}
|
|
116
|
-
props['attr__' + attr.name] = limitText(1024, value)
|
|
117
|
-
}
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
let nthChild = 1
|
|
121
|
-
let nthOfType = 1
|
|
122
|
-
let currentElem: Element | null = elem
|
|
123
|
-
while ((currentElem = previousElementSibling(currentElem))) {
|
|
124
|
-
// eslint-disable-line no-cond-assign
|
|
125
|
-
nthChild++
|
|
126
|
-
if (currentElem.tagName === elem.tagName) {
|
|
127
|
-
nthOfType++
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
props['nth_child'] = nthChild
|
|
131
|
-
props['nth_of_type'] = nthOfType
|
|
132
|
-
|
|
133
|
-
return props
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export function autocapturePropertiesForElement(
|
|
137
|
-
target: Element,
|
|
138
|
-
{
|
|
139
|
-
e,
|
|
140
|
-
maskAllElementAttributes,
|
|
141
|
-
maskAllText,
|
|
142
|
-
elementAttributeIgnoreList,
|
|
143
|
-
elementsChainAsString,
|
|
144
|
-
}: {
|
|
145
|
-
e: Event
|
|
146
|
-
maskAllElementAttributes: boolean
|
|
147
|
-
maskAllText: boolean
|
|
148
|
-
elementAttributeIgnoreList?: string[] | undefined
|
|
149
|
-
elementsChainAsString: boolean
|
|
150
|
-
}
|
|
151
|
-
): { props: Properties; explicitNoCapture?: boolean } {
|
|
152
|
-
const targetElementList = [target]
|
|
153
|
-
let curEl = target
|
|
154
|
-
while (curEl.parentNode && !isTag(curEl, 'body')) {
|
|
155
|
-
if (isDocumentFragment(curEl.parentNode)) {
|
|
156
|
-
targetElementList.push((curEl.parentNode as any).host)
|
|
157
|
-
curEl = (curEl.parentNode as any).host
|
|
158
|
-
continue
|
|
159
|
-
}
|
|
160
|
-
targetElementList.push(curEl.parentNode as Element)
|
|
161
|
-
curEl = curEl.parentNode as Element
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const elementsJson: Properties[] = []
|
|
165
|
-
const autocaptureAugmentProperties: Properties = {}
|
|
166
|
-
let href: string | false = false
|
|
167
|
-
let explicitNoCapture = false
|
|
168
|
-
|
|
169
|
-
each(targetElementList, (el) => {
|
|
170
|
-
const shouldCaptureEl = shouldCaptureElement(el)
|
|
171
|
-
|
|
172
|
-
// if the element or a parent element is an anchor tag
|
|
173
|
-
// include the href as a property
|
|
174
|
-
if (el.tagName.toLowerCase() === 'a') {
|
|
175
|
-
href = el.getAttribute('href')
|
|
176
|
-
href = shouldCaptureEl && href && shouldCaptureValue(href) && href
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// allow users to programmatically prevent capturing of elements by adding class 'ph-no-capture'
|
|
180
|
-
const classes = getClassNames(el)
|
|
181
|
-
if (includes(classes, 'ph-no-capture')) {
|
|
182
|
-
explicitNoCapture = true
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
elementsJson.push(
|
|
186
|
-
getPropertiesFromElement(el, maskAllElementAttributes, maskAllText, elementAttributeIgnoreList)
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
const augmentProperties = getAugmentPropertiesFromElement(el)
|
|
190
|
-
extend(autocaptureAugmentProperties, augmentProperties)
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
if (explicitNoCapture) {
|
|
194
|
-
return { props: {}, explicitNoCapture }
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (!maskAllText) {
|
|
198
|
-
// if the element is a button or anchor tag get the span text from any
|
|
199
|
-
// children and include it as/with the text property on the parent element
|
|
200
|
-
if (target.tagName.toLowerCase() === 'a' || target.tagName.toLowerCase() === 'button') {
|
|
201
|
-
elementsJson[0]['$el_text'] = getDirectAndNestedSpanText(target)
|
|
202
|
-
} else {
|
|
203
|
-
elementsJson[0]['$el_text'] = getSafeText(target)
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
let externalHref: string | undefined
|
|
208
|
-
if (href) {
|
|
209
|
-
elementsJson[0]['attr__href'] = href
|
|
210
|
-
const hrefHost = convertToURL(href)?.host
|
|
211
|
-
const locationHost = window?.location?.host
|
|
212
|
-
if (hrefHost && locationHost && hrefHost !== locationHost) {
|
|
213
|
-
externalHref = href
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const props = extend(
|
|
218
|
-
getDefaultProperties(e.type),
|
|
219
|
-
// Sending "$elements" is deprecated. Only one client on US cloud uses this.
|
|
220
|
-
!elementsChainAsString ? { $elements: elementsJson } : {},
|
|
221
|
-
// Always send $elements_chain, as it's needed downstream in site app filtering
|
|
222
|
-
{ $elements_chain: getElementsChainString(elementsJson) },
|
|
223
|
-
elementsJson[0]?.['$el_text'] ? { $el_text: elementsJson[0]?.['$el_text'] } : {},
|
|
224
|
-
externalHref && e.type === 'click' ? { $external_click_url: externalHref } : {},
|
|
225
|
-
autocaptureAugmentProperties
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
return { props }
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export class Autocapture {
|
|
232
|
-
instance: Leanbase
|
|
233
|
-
_initialized: boolean = false
|
|
234
|
-
_isDisabledServerSide: boolean | null = null
|
|
235
|
-
_elementSelectors: Set<string> | null
|
|
236
|
-
rageclicks = new RageClick()
|
|
237
|
-
_elementsChainAsString = false
|
|
238
|
-
|
|
239
|
-
constructor(instance: Leanbase) {
|
|
240
|
-
this.instance = instance
|
|
241
|
-
this._elementSelectors = null
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
private get _config(): AutocaptureConfig {
|
|
245
|
-
const config = isObject(this.instance.config.autocapture) ? this.instance.config.autocapture : {}
|
|
246
|
-
// precompile the regex
|
|
247
|
-
config.url_allowlist = config.url_allowlist?.map((url) => new RegExp(url))
|
|
248
|
-
config.url_ignorelist = config.url_ignorelist?.map((url) => new RegExp(url))
|
|
249
|
-
return config
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
_addDomEventHandlers(): void {
|
|
253
|
-
if (!this.isBrowserSupported()) {
|
|
254
|
-
logger.info('Disabling Automatic Event Collection because this browser is not supported')
|
|
255
|
-
return
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (!window || !document) {
|
|
259
|
-
return
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const handler = (e: Event) => {
|
|
263
|
-
e = e || window?.event
|
|
264
|
-
try {
|
|
265
|
-
this._captureEvent(e)
|
|
266
|
-
} catch (error) {
|
|
267
|
-
logger.error('Failed to capture event', error)
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
addEventListener(document, 'submit', handler, { capture: true })
|
|
272
|
-
addEventListener(document, 'change', handler, { capture: true })
|
|
273
|
-
addEventListener(document, 'click', handler, { capture: true })
|
|
274
|
-
|
|
275
|
-
if (this._config.capture_copied_text) {
|
|
276
|
-
const copiedTextHandler = (e: Event) => {
|
|
277
|
-
e = e || window?.event
|
|
278
|
-
this._captureEvent(e, COPY_AUTOCAPTURE_EVENT)
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
addEventListener(document, 'copy', copiedTextHandler, { capture: true })
|
|
282
|
-
addEventListener(document, 'cut', copiedTextHandler, { capture: true })
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
public startIfEnabled() {
|
|
287
|
-
if (this.isEnabled && !this._initialized) {
|
|
288
|
-
this._addDomEventHandlers()
|
|
289
|
-
this._initialized = true
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
public onRemoteConfig(response: RemoteConfig) {
|
|
294
|
-
if (response.elementsChainAsString) {
|
|
295
|
-
this._elementsChainAsString = response.elementsChainAsString
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
if (this.instance.persistence) {
|
|
299
|
-
this.instance.persistence.register({
|
|
300
|
-
[AUTOCAPTURE_DISABLED_SERVER_SIDE]: !!response['autocapture_opt_out'],
|
|
301
|
-
})
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
this._isDisabledServerSide = !!response['autocapture_opt_out']
|
|
305
|
-
this.startIfEnabled()
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
public setElementSelectors(selectors: Set<string>): void {
|
|
309
|
-
this._elementSelectors = selectors
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
public getElementSelectors(element: Element | null): string[] | null {
|
|
313
|
-
const elementSelectors: string[] = []
|
|
314
|
-
|
|
315
|
-
this._elementSelectors?.forEach((selector) => {
|
|
316
|
-
const matchedElements = document?.querySelectorAll(selector)
|
|
317
|
-
matchedElements?.forEach((matchedElement: Element) => {
|
|
318
|
-
if (element === matchedElement) {
|
|
319
|
-
elementSelectors.push(selector)
|
|
320
|
-
}
|
|
321
|
-
})
|
|
322
|
-
})
|
|
323
|
-
|
|
324
|
-
return elementSelectors
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
public get isEnabled(): boolean {
|
|
328
|
-
const persistedServerDisabled = this.instance.persistence?.props[AUTOCAPTURE_DISABLED_SERVER_SIDE]
|
|
329
|
-
const memoryDisabled = this._isDisabledServerSide
|
|
330
|
-
|
|
331
|
-
if (isNull(memoryDisabled) && !isBoolean(persistedServerDisabled)) {
|
|
332
|
-
return false
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const disabledServer = this._isDisabledServerSide ?? !!persistedServerDisabled
|
|
336
|
-
const disabledClient = !this.instance.config.autocapture
|
|
337
|
-
return !disabledClient && !disabledServer
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
private _captureEvent(e: Event, eventName: EventName = '$autocapture'): boolean | void {
|
|
341
|
-
if (!this.isEnabled) {
|
|
342
|
-
return
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/*** Don't mess with this code without running IE8 tests on it ***/
|
|
346
|
-
let target = getEventTarget(e)
|
|
347
|
-
if (isTextNode(target)) {
|
|
348
|
-
// defeat Safari bug (see: http://www.quirksmode.org/js/events_properties.html)
|
|
349
|
-
target = (target.parentNode || null) as Element | null
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (eventName === '$autocapture' && e.type === 'click' && e instanceof MouseEvent) {
|
|
353
|
-
if (
|
|
354
|
-
!!this.instance.config.rageclick &&
|
|
355
|
-
this.rageclicks?.isRageClick(e.clientX, e.clientY, new Date().getTime())
|
|
356
|
-
) {
|
|
357
|
-
if (shouldCaptureRageclick(target, this.instance.config.rageclick)) {
|
|
358
|
-
this._captureEvent(e, '$rageclick')
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const isCopyAutocapture = eventName === COPY_AUTOCAPTURE_EVENT
|
|
364
|
-
if (
|
|
365
|
-
target &&
|
|
366
|
-
shouldCaptureDomEvent(
|
|
367
|
-
target,
|
|
368
|
-
e,
|
|
369
|
-
this._config,
|
|
370
|
-
// mostly this method cares about the target element, but in the case of copy events,
|
|
371
|
-
// we want some of the work this check does without insisting on the target element's type
|
|
372
|
-
isCopyAutocapture,
|
|
373
|
-
// we also don't want to restrict copy checks to clicks,
|
|
374
|
-
// so we pass that knowledge in here, rather than add the logic inside the check
|
|
375
|
-
isCopyAutocapture ? ['copy', 'cut'] : undefined
|
|
376
|
-
)
|
|
377
|
-
) {
|
|
378
|
-
const { props, explicitNoCapture } = autocapturePropertiesForElement(target, {
|
|
379
|
-
e,
|
|
380
|
-
maskAllElementAttributes: this.instance.config.mask_all_element_attributes,
|
|
381
|
-
maskAllText: this.instance.config.mask_all_text,
|
|
382
|
-
elementAttributeIgnoreList: this._config.element_attribute_ignorelist,
|
|
383
|
-
elementsChainAsString: this._elementsChainAsString,
|
|
384
|
-
})
|
|
385
|
-
|
|
386
|
-
if (explicitNoCapture) {
|
|
387
|
-
return false
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const elementSelectors = this.getElementSelectors(target)
|
|
391
|
-
if (elementSelectors && elementSelectors.length > 0) {
|
|
392
|
-
props['$element_selectors'] = elementSelectors
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
if (eventName === COPY_AUTOCAPTURE_EVENT) {
|
|
396
|
-
// you can't read the data from the clipboard event,
|
|
397
|
-
// but you can guess that you can read it from the window's current selection
|
|
398
|
-
const selectedContent = makeSafeText(window?.getSelection()?.toString())
|
|
399
|
-
const clipType = (e as ClipboardEvent).type || 'clipboard'
|
|
400
|
-
if (!selectedContent) {
|
|
401
|
-
return false
|
|
402
|
-
}
|
|
403
|
-
props['$selected_content'] = selectedContent
|
|
404
|
-
props['$copy_type'] = clipType
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
this.instance.capture(eventName, props)
|
|
408
|
-
return true
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
isBrowserSupported(): boolean {
|
|
413
|
-
return isFunction(document?.querySelectorAll)
|
|
414
|
-
}
|
|
415
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// Naive rage click implementation: If mouse has not moved further than RAGE_CLICK_THRESHOLD_PX
|
|
2
|
-
// over RAGE_CLICK_CLICK_COUNT clicks with max RAGE_CLICK_TIMEOUT_MS between clicks, it's
|
|
3
|
-
// counted as a rage click
|
|
4
|
-
|
|
5
|
-
const RAGE_CLICK_THRESHOLD_PX = 30
|
|
6
|
-
const RAGE_CLICK_TIMEOUT_MS = 1000
|
|
7
|
-
const RAGE_CLICK_CLICK_COUNT = 3
|
|
8
|
-
|
|
9
|
-
export default class RageClick {
|
|
10
|
-
clicks: { x: number; y: number; timestamp: number }[]
|
|
11
|
-
|
|
12
|
-
constructor() {
|
|
13
|
-
this.clicks = []
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
isRageClick(x: number, y: number, timestamp: number): boolean {
|
|
17
|
-
const lastClick = this.clicks[this.clicks.length - 1]
|
|
18
|
-
if (
|
|
19
|
-
lastClick &&
|
|
20
|
-
Math.abs(x - lastClick.x) + Math.abs(y - lastClick.y) < RAGE_CLICK_THRESHOLD_PX &&
|
|
21
|
-
timestamp - lastClick.timestamp < RAGE_CLICK_TIMEOUT_MS
|
|
22
|
-
) {
|
|
23
|
-
this.clicks.push({ x, y, timestamp })
|
|
24
|
-
|
|
25
|
-
if (this.clicks.length === RAGE_CLICK_CLICK_COUNT) {
|
|
26
|
-
return true
|
|
27
|
-
}
|
|
28
|
-
} else {
|
|
29
|
-
this.clicks = [{ x, y, timestamp }]
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return false
|
|
33
|
-
}
|
|
34
|
-
}
|
package/src/iife.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { Leanbase } from './leanbase'
|
|
2
|
-
import type { LeanbaseConfig } from './types'
|
|
3
|
-
import { isArray } from '@posthog/core'
|
|
4
|
-
|
|
5
|
-
type QueuedCall = { fn: keyof typeof api; args: any[] }
|
|
6
|
-
|
|
7
|
-
const api = {
|
|
8
|
-
_instance: null as Leanbase | null,
|
|
9
|
-
_queue: [] as QueuedCall[],
|
|
10
|
-
|
|
11
|
-
init(apiKey: string, options?: LeanbaseConfig) {
|
|
12
|
-
this._instance = new Leanbase(apiKey, options)
|
|
13
|
-
const q = this._queue
|
|
14
|
-
this._queue = []
|
|
15
|
-
for (const { fn, args } of q) {
|
|
16
|
-
// @ts-expect-error dynamic dispatch to API methods
|
|
17
|
-
this[fn](...args)
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
|
|
21
|
-
capture(...args: Parameters<NonNullable<typeof this._instance>['capture']>) {
|
|
22
|
-
if (this._instance) {
|
|
23
|
-
return this._instance.capture(...args)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
this._queue.push({ fn: 'capture', args })
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
captureException(...args: Parameters<NonNullable<typeof this._instance>['captureException']>) {
|
|
30
|
-
if (this._instance) {
|
|
31
|
-
return this._instance.captureException(...args)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
this._queue.push({ fn: 'captureException', args })
|
|
35
|
-
},
|
|
36
|
-
|
|
37
|
-
identify(...args: Parameters<NonNullable<typeof this._instance>['identify']>) {
|
|
38
|
-
if (this._instance) {
|
|
39
|
-
return this._instance.identify(...args)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
this._queue.push({ fn: 'identify', args })
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
group(...args: Parameters<NonNullable<typeof this._instance>['group']>) {
|
|
46
|
-
if (this._instance) {
|
|
47
|
-
return this._instance.group(...args)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
this._queue.push({ fn: 'group', args })
|
|
51
|
-
},
|
|
52
|
-
|
|
53
|
-
alias(...args: Parameters<NonNullable<typeof this._instance>['alias']>) {
|
|
54
|
-
if (this._instance) {
|
|
55
|
-
return this._instance.alias(...args)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
this._queue.push({ fn: 'alias', args })
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
reset() {
|
|
62
|
-
this._instance?.reset()
|
|
63
|
-
this._instance = null
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
getInstance(): Leanbase | null {
|
|
67
|
-
return this._instance
|
|
68
|
-
},
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Attach to globalThis for browsers (SSR-safe guards inside Leanbase)
|
|
72
|
-
;(function attachToGlobal(g: Window & typeof globalThis & { leanbase?: typeof api; Leanbase?: typeof api }) {
|
|
73
|
-
// Prefer not to overwrite if a stub already exists (e.g., user queued calls before script loaded)
|
|
74
|
-
const existing = g.leanbase
|
|
75
|
-
if (existing && typeof existing === 'object') {
|
|
76
|
-
// If there is a pre-existing queue-compatible stub, try to drain it into our API
|
|
77
|
-
if (isArray(existing._queue)) {
|
|
78
|
-
api._queue = existing._queue as any
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
g.leanbase = api
|
|
83
|
-
// Also expose PascalCase alias for familiarity
|
|
84
|
-
g.Leanbase = g.leanbase
|
|
85
|
-
})(globalThis as Window & typeof globalThis)
|
|
86
|
-
|
|
87
|
-
export default api
|
package/src/leanbase-logger.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
|
|
3
|
-
const PREFIX = '[Leanbase]'
|
|
4
|
-
|
|
5
|
-
export const logger = {
|
|
6
|
-
info: (...args: any[]) => {
|
|
7
|
-
if (typeof console !== 'undefined') {
|
|
8
|
-
console.log(PREFIX, ...args)
|
|
9
|
-
}
|
|
10
|
-
},
|
|
11
|
-
warn: (...args: any[]) => {
|
|
12
|
-
if (typeof console !== 'undefined') {
|
|
13
|
-
console.warn(PREFIX, ...args)
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
error: (...args: any[]) => {
|
|
17
|
-
if (typeof console !== 'undefined') {
|
|
18
|
-
console.error(PREFIX, ...args)
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
critical: (...args: any[]) => {
|
|
22
|
-
if (typeof console !== 'undefined') {
|
|
23
|
-
console.error(PREFIX, 'CRITICAL:', ...args)
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
}
|