@leanbase-giangnd/js 0.0.7 → 0.1.1
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/dist/index.cjs +2791 -265
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5439 -172
- package/dist/index.mjs +2792 -266
- package/dist/index.mjs.map +1 -1
- package/dist/leanbase.iife.js +3427 -276
- package/dist/leanbase.iife.js.map +1 -1
- package/package.json +6 -5
- package/src/extensions/replay/extension-shim.ts +35 -0
- package/src/extensions/replay/external/config.ts +33 -25
- package/src/extensions/replay/external/lazy-loaded-session-recorder.ts +78 -71
- package/src/extensions/replay/external/mutation-throttler.ts +4 -1
- package/src/extensions/replay/external/network-plugin.ts +10 -3
- package/src/extensions/replay/external/triggerMatching.ts +13 -13
- package/src/extensions/replay/rrweb-plugins/patch.ts +0 -7
- package/src/extensions/replay/session-recording.ts +29 -58
- package/src/leanbase.ts +35 -48
- package/src/types/fflate.d.ts +5 -0
- package/src/types/rrweb-record.d.ts +8 -0
- package/src/types.ts +130 -117
- package/src/utils/logger.ts +13 -51
- package/src/version.ts +1 -1
- package/src/extensions/replay/external/README.md +0 -5
- package/src/extensions/utils/stylesheet-loader.ts +0 -27
- package/src/posthog-core.ts +0 -12
- package/src/utils/globals.ts +0 -239
- package/src/utils/request-router.ts +0 -77
- package/src/utils/type-utils.ts +0 -139
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
import { SESSION_RECORDING_IS_SAMPLED, SESSION_RECORDING_REMOTE_CONFIG } from '../../constants'
|
|
2
|
-
import {
|
|
2
|
+
import { Leanbase } from '../../leanbase'
|
|
3
3
|
import { Properties, RemoteConfig, SessionRecordingPersistedConfig, SessionStartReason } from '../../types'
|
|
4
4
|
import { type eventWithTime } from './types/rrweb-types'
|
|
5
5
|
|
|
6
6
|
import { isNullish, isUndefined } from '@posthog/core'
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
LazyLoadedSessionRecordingInterface,
|
|
11
|
-
PostHogExtensionKind,
|
|
12
|
-
window,
|
|
13
|
-
} from '../../utils/globals'
|
|
7
|
+
import { logger } from '../../leanbase-logger'
|
|
8
|
+
import { window } from '../../utils'
|
|
9
|
+
import { LazyLoadedSessionRecording } from './external/lazy-loaded-session-recorder'
|
|
14
10
|
import { DISABLED, LAZY_LOADING, SessionRecordingStatus, TriggerType } from './external/triggerMatching'
|
|
15
11
|
|
|
16
12
|
const LOGGER_PREFIX = '[SessionRecording]'
|
|
17
|
-
const
|
|
13
|
+
const log = {
|
|
14
|
+
info: (...args: any[]) => logger.info(LOGGER_PREFIX, ...args),
|
|
15
|
+
warn: (...args: any[]) => logger.warn(LOGGER_PREFIX, ...args),
|
|
16
|
+
error: (...args: any[]) => logger.error(LOGGER_PREFIX, ...args),
|
|
17
|
+
}
|
|
18
18
|
|
|
19
19
|
export class SessionRecording {
|
|
20
20
|
_forceAllowLocalhostNetworkCapture: boolean = false
|
|
21
21
|
|
|
22
22
|
private _receivedFlags: boolean = false
|
|
23
|
+
private _serverRecordingEnabled: boolean = false
|
|
23
24
|
|
|
24
25
|
private _persistFlagsOnSessionListener: (() => void) | undefined = undefined
|
|
25
|
-
private _lazyLoadedSessionRecording:
|
|
26
|
+
private _lazyLoadedSessionRecording: LazyLoadedSessionRecording | undefined
|
|
26
27
|
|
|
27
28
|
public get started(): boolean {
|
|
28
29
|
return !!this._lazyLoadedSessionRecording?.isStarted
|
|
@@ -44,9 +45,9 @@ export class SessionRecording {
|
|
|
44
45
|
return LAZY_LOADING
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
constructor(private readonly _instance:
|
|
48
|
+
constructor(private readonly _instance: Leanbase) {
|
|
48
49
|
if (!this._instance.sessionManager) {
|
|
49
|
-
|
|
50
|
+
log.error('started without valid sessionManager')
|
|
50
51
|
throw new Error(LOGGER_PREFIX + ' started without valid sessionManager. This is a bug.')
|
|
51
52
|
}
|
|
52
53
|
|
|
@@ -58,7 +59,7 @@ export class SessionRecording {
|
|
|
58
59
|
private get _isRecordingEnabled() {
|
|
59
60
|
const enabled_server_side = !!this._instance.get_property(SESSION_RECORDING_REMOTE_CONFIG)?.enabled
|
|
60
61
|
const enabled_client_side = !this._instance.config.disable_session_recording
|
|
61
|
-
const isDisabled = this._instance.config.disable_session_recording ||
|
|
62
|
+
const isDisabled = this._instance.config.disable_session_recording || this._instance.consent?.isOptedOut?.()
|
|
62
63
|
return window && enabled_server_side && enabled_client_side && !isDisabled
|
|
63
64
|
}
|
|
64
65
|
|
|
@@ -67,17 +68,10 @@ export class SessionRecording {
|
|
|
67
68
|
return
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
// According to the rrweb docs, rrweb is not supported on IE11 and below:
|
|
71
|
-
// "rrweb does not support IE11 and below because it uses the MutationObserver API, which was supported by these browsers."
|
|
72
|
-
// https://github.com/rrweb-io/rrweb/blob/master/guide.md#compatibility-note
|
|
73
|
-
//
|
|
74
|
-
// However, MutationObserver does exist on IE11, it just doesn't work well and does not detect all changes.
|
|
75
|
-
// Instead, when we load "recorder.js", the first JS error is about "Object.assign" and "Array.from" being undefined.
|
|
76
|
-
// Thus instead of MutationObserver, we look for this function and block recording if it's undefined.
|
|
77
71
|
const canRunReplay = !isUndefined(Object.assign) && !isUndefined(Array.from)
|
|
78
72
|
if (this._isRecordingEnabled && canRunReplay) {
|
|
79
73
|
this._lazyLoadAndStart(startReason)
|
|
80
|
-
|
|
74
|
+
log.info('starting')
|
|
81
75
|
} else {
|
|
82
76
|
this.stopRecording()
|
|
83
77
|
}
|
|
@@ -97,25 +91,7 @@ export class SessionRecording {
|
|
|
97
91
|
return
|
|
98
92
|
}
|
|
99
93
|
|
|
100
|
-
|
|
101
|
-
// imported), don't load the script. Otherwise, remotely import recorder.js from cdn since it hasn't been loaded.
|
|
102
|
-
if (
|
|
103
|
-
!assignableWindow?.__PosthogExtensions__?.rrweb?.record ||
|
|
104
|
-
!assignableWindow.__PosthogExtensions__?.initSessionRecording
|
|
105
|
-
) {
|
|
106
|
-
assignableWindow.__PosthogExtensions__?.loadExternalDependency?.(
|
|
107
|
-
this._instance,
|
|
108
|
-
this._scriptName,
|
|
109
|
-
(err) => {
|
|
110
|
-
if (err) {
|
|
111
|
-
return logger.error('could not load recorder', err)
|
|
112
|
-
}
|
|
113
|
-
this._onScriptLoaded(startReason)
|
|
114
|
-
}
|
|
115
|
-
)
|
|
116
|
-
} else {
|
|
117
|
-
this._onScriptLoaded(startReason)
|
|
118
|
-
}
|
|
94
|
+
this._onScriptLoaded(startReason)
|
|
119
95
|
}
|
|
120
96
|
|
|
121
97
|
stopRecording() {
|
|
@@ -179,20 +155,28 @@ export class SessionRecording {
|
|
|
179
155
|
}
|
|
180
156
|
}
|
|
181
157
|
|
|
158
|
+
private _clearRemoteConfig() {
|
|
159
|
+
this._instance.persistence?.unregister(SESSION_RECORDING_REMOTE_CONFIG)
|
|
160
|
+
this._resetSampling()
|
|
161
|
+
}
|
|
162
|
+
|
|
182
163
|
onRemoteConfig(response: RemoteConfig) {
|
|
183
164
|
if (!('sessionRecording' in response)) {
|
|
184
165
|
// if sessionRecording is not in the response, we do nothing
|
|
185
|
-
|
|
166
|
+
log.info('skipping remote config with no sessionRecording', response)
|
|
186
167
|
return
|
|
187
168
|
}
|
|
169
|
+
this._receivedFlags = true
|
|
170
|
+
|
|
188
171
|
if (response.sessionRecording === false) {
|
|
189
|
-
|
|
190
|
-
this.
|
|
172
|
+
this._serverRecordingEnabled = false
|
|
173
|
+
this._clearRemoteConfig()
|
|
174
|
+
this.stopRecording()
|
|
191
175
|
return
|
|
192
176
|
}
|
|
193
177
|
|
|
178
|
+
this._serverRecordingEnabled = true
|
|
194
179
|
this._persistRemoteConfig(response)
|
|
195
|
-
this._receivedFlags = true
|
|
196
180
|
this.startIfEnabledOrStop()
|
|
197
181
|
}
|
|
198
182
|
|
|
@@ -204,22 +188,9 @@ export class SessionRecording {
|
|
|
204
188
|
}
|
|
205
189
|
}
|
|
206
190
|
|
|
207
|
-
private get _scriptName(): PostHogExtensionKind {
|
|
208
|
-
const remoteConfig: SessionRecordingPersistedConfig | undefined = this._instance?.persistence?.get_property(
|
|
209
|
-
SESSION_RECORDING_REMOTE_CONFIG
|
|
210
|
-
)
|
|
211
|
-
return (remoteConfig?.scriptConfig?.script as PostHogExtensionKind) || 'lazy-recorder'
|
|
212
|
-
}
|
|
213
|
-
|
|
214
191
|
private _onScriptLoaded(startReason?: SessionStartReason) {
|
|
215
|
-
if (!assignableWindow.__PosthogExtensions__?.initSessionRecording) {
|
|
216
|
-
throw Error('Called on script loaded before session recording is available')
|
|
217
|
-
}
|
|
218
|
-
|
|
219
192
|
if (!this._lazyLoadedSessionRecording) {
|
|
220
|
-
this._lazyLoadedSessionRecording =
|
|
221
|
-
this._instance
|
|
222
|
-
)
|
|
193
|
+
this._lazyLoadedSessionRecording = new LazyLoadedSessionRecording(this._instance)
|
|
223
194
|
;(this._lazyLoadedSessionRecording as any)._forceAllowLocalhostNetworkCapture =
|
|
224
195
|
this._forceAllowLocalhostNetworkCapture
|
|
225
196
|
}
|
package/src/leanbase.ts
CHANGED
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
isObject,
|
|
8
8
|
isString,
|
|
9
9
|
isUndefined,
|
|
10
|
-
isFunction,
|
|
11
10
|
} from '@posthog/core'
|
|
12
11
|
import type {
|
|
13
12
|
PostHogEventProperties,
|
|
@@ -39,18 +38,25 @@ import { COOKIELESS_MODE_FLAG_PROPERTY, USER_STATE } from './constants'
|
|
|
39
38
|
import { getEventProperties } from './utils/event-utils'
|
|
40
39
|
import { SessionIdManager } from './sessionid'
|
|
41
40
|
import { SessionPropsManager } from './session-props'
|
|
42
|
-
import { RequestRouter } from './utils/request-router'
|
|
43
41
|
import { uuidv7 } from './uuidv7'
|
|
44
42
|
import { PageViewManager } from './page-view'
|
|
45
43
|
import { ScrollManager } from './scroll-manager'
|
|
46
44
|
import { isLikelyBot } from './utils/blocked-uas'
|
|
45
|
+
import { SessionRecording } from './extensions/replay/session-recording'
|
|
46
|
+
import type { RequestRouter } from '../../browser/lib/src/utils/request-router'
|
|
47
|
+
import type { ConsentManager } from '../../browser/lib/src/consent'
|
|
47
48
|
|
|
48
49
|
const defaultConfig = (): LeanbaseConfig => ({
|
|
49
50
|
host: 'https://i.leanbase.co',
|
|
50
|
-
api_host: 'https://i.leanbase.co',
|
|
51
51
|
token: '',
|
|
52
52
|
autocapture: true,
|
|
53
53
|
rageclick: true,
|
|
54
|
+
disable_session_recording: false,
|
|
55
|
+
session_recording: {
|
|
56
|
+
// Force-enable session recording locally unless explicitly disabled via config
|
|
57
|
+
forceClientRecording: true,
|
|
58
|
+
},
|
|
59
|
+
enable_recording_console_log: undefined,
|
|
54
60
|
persistence: 'localStorage+cookie',
|
|
55
61
|
capture_pageview: 'history_change',
|
|
56
62
|
capture_pageleave: 'if_capture_pageview',
|
|
@@ -71,7 +77,6 @@ const defaultConfig = (): LeanbaseConfig => ({
|
|
|
71
77
|
opt_out_useragent_filter: false,
|
|
72
78
|
properties_string_max_length: 65535,
|
|
73
79
|
loaded: () => {},
|
|
74
|
-
session_recording: {} as any,
|
|
75
80
|
})
|
|
76
81
|
|
|
77
82
|
export class Leanbase extends PostHogCore {
|
|
@@ -84,8 +89,9 @@ export class Leanbase extends PostHogCore {
|
|
|
84
89
|
sessionPersistence?: LeanbasePersistence
|
|
85
90
|
sessionManager?: SessionIdManager
|
|
86
91
|
sessionPropsManager?: SessionPropsManager
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
requestRouter!: RequestRouter
|
|
93
|
+
consent!: ConsentManager
|
|
94
|
+
sessionRecording?: SessionRecording
|
|
89
95
|
isRemoteConfigLoaded?: boolean
|
|
90
96
|
personProcessingSetOncePropertiesSent = false
|
|
91
97
|
isLoaded: boolean = false
|
|
@@ -102,7 +108,6 @@ export class Leanbase extends PostHogCore {
|
|
|
102
108
|
this.initialPageviewCaptured = false
|
|
103
109
|
this.scrollManager = new ScrollManager(this)
|
|
104
110
|
this.pageViewManager = new PageViewManager(this)
|
|
105
|
-
this.requestRouter = new RequestRouter(this)
|
|
106
111
|
this.init(token, mergedConfig)
|
|
107
112
|
}
|
|
108
113
|
|
|
@@ -114,19 +119,16 @@ export class Leanbase extends PostHogCore {
|
|
|
114
119
|
)
|
|
115
120
|
this.isLoaded = true
|
|
116
121
|
this.persistence = new LeanbasePersistence(this.config)
|
|
117
|
-
this.replayAutocapture = new Autocapture(this)
|
|
118
|
-
this.replayAutocapture.startIfEnabled()
|
|
119
122
|
|
|
120
|
-
// Initialize session manager and props before session recording (matches browser behavior)
|
|
121
123
|
if (this.config.cookieless_mode !== 'always') {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
this.sessionManager = new SessionIdManager(this)
|
|
125
|
+
this.sessionPropsManager = new SessionPropsManager(this, this.sessionManager, this.persistence)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.replayAutocapture = new Autocapture(this)
|
|
129
|
+
this.replayAutocapture.startIfEnabled()
|
|
126
130
|
|
|
127
|
-
|
|
128
|
-
// @ts-expect-error - runtime import only available in browser build
|
|
129
|
-
const { SessionRecording } = require('./extensions/replay/session-recording') // eslint-disable-line @typescript-eslint/no-require-imports
|
|
131
|
+
if (this.sessionManager && this.config.cookieless_mode !== 'always') {
|
|
130
132
|
this.sessionRecording = new SessionRecording(this)
|
|
131
133
|
this.sessionRecording.startIfEnabledOrStop()
|
|
132
134
|
}
|
|
@@ -207,6 +209,7 @@ export class Leanbase extends PostHogCore {
|
|
|
207
209
|
|
|
208
210
|
this.isRemoteConfigLoaded = true
|
|
209
211
|
this.replayAutocapture?.onRemoteConfig(config)
|
|
212
|
+
this.sessionRecording?.onRemoteConfig(config)
|
|
210
213
|
}
|
|
211
214
|
|
|
212
215
|
fetch(url: string, options: PostHogFetchOptions): Promise<PostHogFetchResponse> {
|
|
@@ -222,8 +225,13 @@ export class Leanbase extends PostHogCore {
|
|
|
222
225
|
const oldConfig = { ...this.config }
|
|
223
226
|
if (isObject(config)) {
|
|
224
227
|
extend(this.config, config)
|
|
228
|
+
if (!this.config.api_host && this.config.host) {
|
|
229
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
230
|
+
;(this.config as any).api_host = this.config.host
|
|
231
|
+
}
|
|
225
232
|
this.persistence?.update_config(this.config, oldConfig)
|
|
226
233
|
this.replayAutocapture?.startIfEnabled()
|
|
234
|
+
this.sessionRecording?.startIfEnabledOrStop()
|
|
227
235
|
}
|
|
228
236
|
|
|
229
237
|
const isTempStorage = this.config.persistence === 'sessionStorage' || this.config.persistence === 'memory'
|
|
@@ -248,43 +256,14 @@ export class Leanbase extends PostHogCore {
|
|
|
248
256
|
return this.persistence?.get_property(key)
|
|
249
257
|
}
|
|
250
258
|
|
|
251
|
-
|
|
252
|
-
this.persistence?.set_property(key, value)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Backwards-compatible aliases expected by replay/browser code
|
|
256
|
-
get_property(key: string): any {
|
|
259
|
+
get_property<T = any>(key: string): T | undefined {
|
|
257
260
|
return this.persistence?.get_property(key)
|
|
258
261
|
}
|
|
259
262
|
|
|
260
|
-
|
|
263
|
+
setPersistedProperty<T>(key: PostHogPersistedProperty, value: T | null): void {
|
|
261
264
|
this.persistence?.set_property(key, value)
|
|
262
265
|
}
|
|
263
266
|
|
|
264
|
-
register_for_session(properties: Record<string, any>): void {
|
|
265
|
-
// PostHogCore may expose registerForSession; call it if available
|
|
266
|
-
if (isFunction((this as any).registerForSession)) {
|
|
267
|
-
;(this as any).registerForSession(properties)
|
|
268
|
-
return
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// fallback: store properties in sessionPersistence
|
|
272
|
-
if (this.sessionPersistence) {
|
|
273
|
-
Object.keys(properties).forEach((k) => this.sessionPersistence?.set_property(k, properties[k]))
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
unregister_for_session(property: string): void {
|
|
278
|
-
if (isFunction((this as any).unregisterForSession)) {
|
|
279
|
-
;(this as any).unregisterForSession(property)
|
|
280
|
-
return
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (this.sessionPersistence) {
|
|
284
|
-
this.sessionPersistence.set_property(property, null)
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
267
|
calculateEventProperties(
|
|
289
268
|
eventName: string,
|
|
290
269
|
eventProperties: PostHogEventProperties,
|
|
@@ -334,6 +313,14 @@ export class Leanbase extends PostHogCore {
|
|
|
334
313
|
extend(properties, this.sessionPropsManager.getSessionProps())
|
|
335
314
|
}
|
|
336
315
|
|
|
316
|
+
try {
|
|
317
|
+
if (this.sessionRecording) {
|
|
318
|
+
extend(properties, this.sessionRecording.sdkDebugProperties)
|
|
319
|
+
}
|
|
320
|
+
} catch (e: any) {
|
|
321
|
+
properties['$sdk_debug_error_capturing_properties'] = String(e)
|
|
322
|
+
}
|
|
323
|
+
|
|
337
324
|
let pageviewProperties: Record<string, any> = this.pageViewManager.doEvent()
|
|
338
325
|
if (eventName === '$pageview' && !readOnly) {
|
|
339
326
|
pageviewProperties = this.pageViewManager.doPageView(timestamp, uuid)
|