@leanbase-giangnd/js 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -0
- package/dist/index.cjs +6012 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +1484 -0
- package/dist/index.mjs +6010 -0
- package/dist/index.mjs.map +1 -0
- package/dist/leanbase.iife.js +13431 -0
- package/dist/leanbase.iife.js.map +1 -0
- package/package.json +48 -0
- package/src/autocapture-utils.ts +550 -0
- package/src/autocapture.ts +415 -0
- package/src/config.ts +8 -0
- package/src/constants.ts +108 -0
- package/src/extensions/rageclick.ts +34 -0
- package/src/extensions/replay/external/config.ts +278 -0
- package/src/extensions/replay/external/denylist.ts +32 -0
- package/src/extensions/replay/external/lazy-loaded-session-recorder.ts +1376 -0
- package/src/extensions/replay/external/mutation-throttler.ts +109 -0
- package/src/extensions/replay/external/network-plugin.ts +701 -0
- package/src/extensions/replay/external/sessionrecording-utils.ts +141 -0
- package/src/extensions/replay/external/triggerMatching.ts +422 -0
- package/src/extensions/replay/rrweb-plugins/patch.ts +39 -0
- package/src/extensions/replay/session-recording.ts +285 -0
- package/src/extensions/replay/types/rrweb-types.ts +575 -0
- package/src/extensions/replay/types/rrweb.ts +114 -0
- package/src/extensions/sampling.ts +26 -0
- package/src/iife.ts +87 -0
- package/src/index.ts +2 -0
- package/src/leanbase-logger.ts +26 -0
- package/src/leanbase-persistence.ts +374 -0
- package/src/leanbase.ts +457 -0
- 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/fflate.d.ts +5 -0
- package/src/types/rrweb-record.d.ts +8 -0
- package/src/types.ts +807 -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/logger.ts +26 -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 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { eventWithTime, mutationCallbackParam } from '../types/rrweb-types'
|
|
2
|
+
import { INCREMENTAL_SNAPSHOT_EVENT_TYPE, MUTATION_SOURCE_TYPE } from './sessionrecording-utils'
|
|
3
|
+
import type { rrwebRecord } from '../types/rrweb'
|
|
4
|
+
import { BucketedRateLimiter } from '@posthog/core'
|
|
5
|
+
import { logger } from '../../../utils/logger'
|
|
6
|
+
|
|
7
|
+
export class MutationThrottler {
|
|
8
|
+
private _loggedTracker: Record<string, boolean> = {}
|
|
9
|
+
private _rateLimiter: BucketedRateLimiter<number>
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
private readonly _rrweb: rrwebRecord,
|
|
13
|
+
private readonly _options: {
|
|
14
|
+
bucketSize?: number
|
|
15
|
+
refillRate?: number
|
|
16
|
+
onBlockedNode?: (id: number, node: Node | null) => void
|
|
17
|
+
} = {}
|
|
18
|
+
) {
|
|
19
|
+
const configuredBucketSize = this._options.bucketSize ?? 100
|
|
20
|
+
const effectiveBucketSize = Math.max(configuredBucketSize - 1, 1)
|
|
21
|
+
|
|
22
|
+
this._rateLimiter = new BucketedRateLimiter({
|
|
23
|
+
bucketSize: effectiveBucketSize,
|
|
24
|
+
refillRate: this._options.refillRate ?? 10,
|
|
25
|
+
refillInterval: 1000, // one second
|
|
26
|
+
_onBucketRateLimited: this._onNodeRateLimited,
|
|
27
|
+
_logger: logger,
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private _onNodeRateLimited = (key: number) => {
|
|
32
|
+
if (!this._loggedTracker[key]) {
|
|
33
|
+
this._loggedTracker[key] = true
|
|
34
|
+
const node = this._getNode(key)
|
|
35
|
+
this._options.onBlockedNode?.(key, node)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private _getNodeOrRelevantParent = (id: number): [number, Node | null] => {
|
|
40
|
+
// For some nodes we know they are part of a larger tree such as an SVG.
|
|
41
|
+
// For those we want to block the entire node, not just the specific attribute
|
|
42
|
+
|
|
43
|
+
const node = this._getNode(id)
|
|
44
|
+
|
|
45
|
+
// Check if the node is an Element and then find the closest parent that is an SVG
|
|
46
|
+
if (node?.nodeName !== 'svg' && node instanceof Element) {
|
|
47
|
+
const closestSVG = node.closest('svg')
|
|
48
|
+
|
|
49
|
+
if (closestSVG) {
|
|
50
|
+
return [this._rrweb.mirror.getId(closestSVG), closestSVG]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return [id, node]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private _getNode = (id: number) => this._rrweb.mirror.getNode(id)
|
|
58
|
+
|
|
59
|
+
private _numberOfChanges = (data: Partial<mutationCallbackParam>) => {
|
|
60
|
+
return (
|
|
61
|
+
(data.removes?.length ?? 0) +
|
|
62
|
+
(data.attributes?.length ?? 0) +
|
|
63
|
+
(data.texts?.length ?? 0) +
|
|
64
|
+
(data.adds?.length ?? 0)
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public throttleMutations = (event: eventWithTime) => {
|
|
69
|
+
if (event.type !== INCREMENTAL_SNAPSHOT_EVENT_TYPE || event.data.source !== MUTATION_SOURCE_TYPE) {
|
|
70
|
+
return event
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const data = event.data as Partial<mutationCallbackParam>
|
|
74
|
+
const initialMutationCount = this._numberOfChanges(data)
|
|
75
|
+
|
|
76
|
+
if (data.attributes) {
|
|
77
|
+
// Most problematic mutations come from attrs where the style or minor properties are changed rapidly
|
|
78
|
+
data.attributes = data.attributes.filter((attr) => {
|
|
79
|
+
const [nodeId] = this._getNodeOrRelevantParent(attr.id)
|
|
80
|
+
|
|
81
|
+
const isRateLimited = this._rateLimiter.consumeRateLimit(nodeId)
|
|
82
|
+
|
|
83
|
+
if (isRateLimited) {
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return attr
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check if every part of the mutation is empty in which case there is nothing to do
|
|
92
|
+
const mutationCount = this._numberOfChanges(data)
|
|
93
|
+
|
|
94
|
+
if (mutationCount === 0 && initialMutationCount !== mutationCount) {
|
|
95
|
+
// If we have modified the mutation count and the remaining count is 0, then we don't need the event.
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
return event
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public reset() {
|
|
102
|
+
this._loggedTracker = {}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public stop() {
|
|
106
|
+
this._rateLimiter.stop()
|
|
107
|
+
this.reset()
|
|
108
|
+
}
|
|
109
|
+
}
|