@leanbase-giangnd/js 0.0.4 → 0.0.7
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 +261 -2875
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +95 -685
- package/dist/index.mjs +262 -2876
- package/dist/index.mjs.map +1 -1
- package/dist/leanbase.iife.js +236 -8815
- package/dist/leanbase.iife.js.map +1 -1
- package/package.json +5 -6
- package/src/extensions/replay/external/README.md +5 -0
- package/src/extensions/replay/external/config.ts +25 -33
- package/src/extensions/replay/external/lazy-loaded-session-recorder.ts +71 -67
- package/src/extensions/replay/external/mutation-throttler.ts +1 -4
- package/src/extensions/replay/external/network-plugin.ts +3 -10
- package/src/extensions/replay/external/triggerMatching.ts +13 -13
- package/src/extensions/replay/rrweb-plugins/patch.ts +7 -0
- package/src/extensions/replay/session-recording.ts +61 -50
- package/src/extensions/utils/stylesheet-loader.ts +27 -0
- package/src/leanbase.ts +51 -154
- package/src/posthog-core.ts +12 -0
- package/src/types.ts +119 -123
- package/src/utils/globals.ts +239 -0
- package/src/utils/logger.ts +51 -13
- package/src/utils/request-router.ts +77 -0
- package/src/utils/type-utils.ts +139 -0
- package/src/version.ts +1 -1
- package/src/types/fflate.d.ts +0 -6
- package/src/types/rrweb-record.d.ts +0 -8
|
@@ -1,29 +1,28 @@
|
|
|
1
1
|
import { SESSION_RECORDING_IS_SAMPLED, SESSION_RECORDING_REMOTE_CONFIG } from '../../constants'
|
|
2
|
-
import {
|
|
2
|
+
import { PostHog } from '../../posthog-core'
|
|
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
|
-
|
|
7
|
+
import { createLogger } from '../../utils/logger'
|
|
8
|
+
import {
|
|
9
|
+
assignableWindow,
|
|
10
|
+
LazyLoadedSessionRecordingInterface,
|
|
11
|
+
PostHogExtensionKind,
|
|
12
|
+
window,
|
|
13
|
+
} from '../../utils/globals'
|
|
10
14
|
import { DISABLED, LAZY_LOADING, SessionRecordingStatus, TriggerType } from './external/triggerMatching'
|
|
11
15
|
|
|
12
16
|
const LOGGER_PREFIX = '[SessionRecording]'
|
|
13
|
-
const
|
|
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
|
-
}
|
|
17
|
+
const logger = createLogger(LOGGER_PREFIX)
|
|
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
|
|
24
23
|
|
|
25
24
|
private _persistFlagsOnSessionListener: (() => void) | undefined = undefined
|
|
26
|
-
private _lazyLoadedSessionRecording:
|
|
25
|
+
private _lazyLoadedSessionRecording: LazyLoadedSessionRecordingInterface | undefined
|
|
27
26
|
|
|
28
27
|
public get started(): boolean {
|
|
29
28
|
return !!this._lazyLoadedSessionRecording?.isStarted
|
|
@@ -45,9 +44,9 @@ export class SessionRecording {
|
|
|
45
44
|
return LAZY_LOADING
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
constructor(private readonly _instance:
|
|
47
|
+
constructor(private readonly _instance: PostHog) {
|
|
49
48
|
if (!this._instance.sessionManager) {
|
|
50
|
-
|
|
49
|
+
logger.error('started without valid sessionManager')
|
|
51
50
|
throw new Error(LOGGER_PREFIX + ' started without valid sessionManager. This is a bug.')
|
|
52
51
|
}
|
|
53
52
|
|
|
@@ -57,30 +56,14 @@ export class SessionRecording {
|
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
private get _isRecordingEnabled() {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (!this._serverRecordingEnabled) {
|
|
65
|
-
return false
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (this._instance.config.disable_session_recording) {
|
|
69
|
-
return false
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return true
|
|
59
|
+
const enabled_server_side = !!this._instance.get_property(SESSION_RECORDING_REMOTE_CONFIG)?.enabled
|
|
60
|
+
const enabled_client_side = !this._instance.config.disable_session_recording
|
|
61
|
+
const isDisabled = this._instance.config.disable_session_recording || !!this._instance.consent?.isOptedOut?.()
|
|
62
|
+
return window && enabled_server_side && enabled_client_side && !isDisabled
|
|
73
63
|
}
|
|
74
64
|
|
|
75
65
|
startIfEnabledOrStop(startReason?: SessionStartReason) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (!this._isRecordingEnabled || !canRunReplay) {
|
|
79
|
-
this.stopRecording()
|
|
80
|
-
return
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (this._lazyLoadedSessionRecording?.isStarted) {
|
|
66
|
+
if (this._isRecordingEnabled && this._lazyLoadedSessionRecording?.isStarted) {
|
|
84
67
|
return
|
|
85
68
|
}
|
|
86
69
|
|
|
@@ -91,8 +74,13 @@ export class SessionRecording {
|
|
|
91
74
|
// However, MutationObserver does exist on IE11, it just doesn't work well and does not detect all changes.
|
|
92
75
|
// Instead, when we load "recorder.js", the first JS error is about "Object.assign" and "Array.from" being undefined.
|
|
93
76
|
// Thus instead of MutationObserver, we look for this function and block recording if it's undefined.
|
|
94
|
-
|
|
95
|
-
|
|
77
|
+
const canRunReplay = !isUndefined(Object.assign) && !isUndefined(Array.from)
|
|
78
|
+
if (this._isRecordingEnabled && canRunReplay) {
|
|
79
|
+
this._lazyLoadAndStart(startReason)
|
|
80
|
+
logger.info('starting')
|
|
81
|
+
} else {
|
|
82
|
+
this.stopRecording()
|
|
83
|
+
}
|
|
96
84
|
}
|
|
97
85
|
|
|
98
86
|
/**
|
|
@@ -109,7 +97,25 @@ export class SessionRecording {
|
|
|
109
97
|
return
|
|
110
98
|
}
|
|
111
99
|
|
|
112
|
-
|
|
100
|
+
// If recorder.js is already loaded (if array.full.js snippet is used or posthog-js/dist/recorder is
|
|
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
|
+
}
|
|
113
119
|
}
|
|
114
120
|
|
|
115
121
|
stopRecording() {
|
|
@@ -173,28 +179,20 @@ export class SessionRecording {
|
|
|
173
179
|
}
|
|
174
180
|
}
|
|
175
181
|
|
|
176
|
-
private _clearRemoteConfig() {
|
|
177
|
-
this._instance.persistence?.unregister(SESSION_RECORDING_REMOTE_CONFIG)
|
|
178
|
-
this._resetSampling()
|
|
179
|
-
}
|
|
180
|
-
|
|
181
182
|
onRemoteConfig(response: RemoteConfig) {
|
|
182
183
|
if (!('sessionRecording' in response)) {
|
|
183
184
|
// if sessionRecording is not in the response, we do nothing
|
|
184
|
-
|
|
185
|
+
logger.info('skipping remote config with no sessionRecording', response)
|
|
185
186
|
return
|
|
186
187
|
}
|
|
187
|
-
this._receivedFlags = true
|
|
188
|
-
|
|
189
188
|
if (response.sessionRecording === false) {
|
|
190
|
-
|
|
191
|
-
this.
|
|
192
|
-
this.stopRecording()
|
|
189
|
+
// remotely disabled
|
|
190
|
+
this._receivedFlags = true
|
|
193
191
|
return
|
|
194
192
|
}
|
|
195
193
|
|
|
196
|
-
this._serverRecordingEnabled = true
|
|
197
194
|
this._persistRemoteConfig(response)
|
|
195
|
+
this._receivedFlags = true
|
|
198
196
|
this.startIfEnabledOrStop()
|
|
199
197
|
}
|
|
200
198
|
|
|
@@ -206,9 +204,22 @@ export class SessionRecording {
|
|
|
206
204
|
}
|
|
207
205
|
}
|
|
208
206
|
|
|
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
|
+
|
|
209
214
|
private _onScriptLoaded(startReason?: SessionStartReason) {
|
|
215
|
+
if (!assignableWindow.__PosthogExtensions__?.initSessionRecording) {
|
|
216
|
+
throw Error('Called on script loaded before session recording is available')
|
|
217
|
+
}
|
|
218
|
+
|
|
210
219
|
if (!this._lazyLoadedSessionRecording) {
|
|
211
|
-
this._lazyLoadedSessionRecording =
|
|
220
|
+
this._lazyLoadedSessionRecording = assignableWindow.__PosthogExtensions__?.initSessionRecording(
|
|
221
|
+
this._instance
|
|
222
|
+
)
|
|
212
223
|
;(this._lazyLoadedSessionRecording as any)._forceAllowLocalhostNetworkCapture =
|
|
213
224
|
this._forceAllowLocalhostNetworkCapture
|
|
214
225
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { PostHog } from '../../posthog-core'
|
|
2
|
+
import { createLogger } from '../../utils/logger'
|
|
3
|
+
import { isUndefined, isNull } from '@posthog/core'
|
|
4
|
+
|
|
5
|
+
const logger = createLogger('[Stylesheet Loader]')
|
|
6
|
+
|
|
7
|
+
export const prepareStylesheet = (document: Document, innerText: string, posthog?: PostHog) => {
|
|
8
|
+
// Forcing the existence of `document` requires this function to be called in a browser environment
|
|
9
|
+
let stylesheet: HTMLStyleElement | null = document.createElement('style')
|
|
10
|
+
stylesheet.innerText = innerText
|
|
11
|
+
|
|
12
|
+
if (posthog?.config?.prepare_external_dependency_stylesheet) {
|
|
13
|
+
const result = posthog.config.prepare_external_dependency_stylesheet(stylesheet)
|
|
14
|
+
if (!isUndefined(result) && !isNull(result)) {
|
|
15
|
+
stylesheet = result
|
|
16
|
+
} else {
|
|
17
|
+
stylesheet = null
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!stylesheet) {
|
|
22
|
+
logger.error('prepare_external_dependency_stylesheet returned null')
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return stylesheet
|
|
27
|
+
}
|
package/src/leanbase.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PostHogCore,
|
|
3
3
|
getFetch,
|
|
4
|
-
isArray,
|
|
5
4
|
isEmptyObject,
|
|
6
5
|
isEmptyString,
|
|
7
6
|
isNumber,
|
|
8
7
|
isObject,
|
|
9
8
|
isString,
|
|
10
9
|
isUndefined,
|
|
10
|
+
isFunction,
|
|
11
11
|
} from '@posthog/core'
|
|
12
12
|
import type {
|
|
13
13
|
PostHogEventProperties,
|
|
@@ -32,31 +32,25 @@ import {
|
|
|
32
32
|
navigator,
|
|
33
33
|
userAgent,
|
|
34
34
|
} from './utils'
|
|
35
|
-
import { decompressSync, strFromU8 } from 'fflate'
|
|
36
35
|
import Config from './config'
|
|
37
36
|
import { Autocapture } from './autocapture'
|
|
38
37
|
import { logger } from './leanbase-logger'
|
|
39
|
-
import { COOKIELESS_MODE_FLAG_PROPERTY, USER_STATE
|
|
38
|
+
import { COOKIELESS_MODE_FLAG_PROPERTY, USER_STATE } from './constants'
|
|
40
39
|
import { getEventProperties } from './utils/event-utils'
|
|
41
40
|
import { SessionIdManager } from './sessionid'
|
|
42
41
|
import { SessionPropsManager } from './session-props'
|
|
42
|
+
import { RequestRouter } from './utils/request-router'
|
|
43
43
|
import { uuidv7 } from './uuidv7'
|
|
44
44
|
import { PageViewManager } from './page-view'
|
|
45
45
|
import { ScrollManager } from './scroll-manager'
|
|
46
46
|
import { isLikelyBot } from './utils/blocked-uas'
|
|
47
|
-
import { SessionRecording } from './extensions/replay/session-recording'
|
|
48
47
|
|
|
49
48
|
const defaultConfig = (): LeanbaseConfig => ({
|
|
50
49
|
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,
|
|
60
54
|
persistence: 'localStorage+cookie',
|
|
61
55
|
capture_pageview: 'history_change',
|
|
62
56
|
capture_pageleave: 'if_capture_pageview',
|
|
@@ -77,6 +71,7 @@ const defaultConfig = (): LeanbaseConfig => ({
|
|
|
77
71
|
opt_out_useragent_filter: false,
|
|
78
72
|
properties_string_max_length: 65535,
|
|
79
73
|
loaded: () => {},
|
|
74
|
+
session_recording: {} as any,
|
|
80
75
|
})
|
|
81
76
|
|
|
82
77
|
export class Leanbase extends PostHogCore {
|
|
@@ -89,7 +84,8 @@ export class Leanbase extends PostHogCore {
|
|
|
89
84
|
sessionPersistence?: LeanbasePersistence
|
|
90
85
|
sessionManager?: SessionIdManager
|
|
91
86
|
sessionPropsManager?: SessionPropsManager
|
|
92
|
-
sessionRecording?:
|
|
87
|
+
sessionRecording?: any
|
|
88
|
+
requestRouter: RequestRouter
|
|
93
89
|
isRemoteConfigLoaded?: boolean
|
|
94
90
|
personProcessingSetOncePropertiesSent = false
|
|
95
91
|
isLoaded: boolean = false
|
|
@@ -106,6 +102,7 @@ export class Leanbase extends PostHogCore {
|
|
|
106
102
|
this.initialPageviewCaptured = false
|
|
107
103
|
this.scrollManager = new ScrollManager(this)
|
|
108
104
|
this.pageViewManager = new PageViewManager(this)
|
|
105
|
+
this.requestRouter = new RequestRouter(this)
|
|
109
106
|
this.init(token, mergedConfig)
|
|
110
107
|
}
|
|
111
108
|
|
|
@@ -117,16 +114,19 @@ export class Leanbase extends PostHogCore {
|
|
|
117
114
|
)
|
|
118
115
|
this.isLoaded = true
|
|
119
116
|
this.persistence = new LeanbasePersistence(this.config)
|
|
120
|
-
|
|
121
|
-
if (this.config.cookieless_mode !== 'always') {
|
|
122
|
-
this.sessionManager = new SessionIdManager(this)
|
|
123
|
-
this.sessionPropsManager = new SessionPropsManager(this, this.sessionManager, this.persistence)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
117
|
this.replayAutocapture = new Autocapture(this)
|
|
127
118
|
this.replayAutocapture.startIfEnabled()
|
|
128
119
|
|
|
129
|
-
|
|
120
|
+
// Initialize session manager and props before session recording (matches browser behavior)
|
|
121
|
+
if (this.config.cookieless_mode !== 'always') {
|
|
122
|
+
if (!this.sessionManager) {
|
|
123
|
+
this.sessionManager = new SessionIdManager(this)
|
|
124
|
+
this.sessionPropsManager = new SessionPropsManager(this, this.sessionManager, this.persistence as any)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// runtime require to lazy-load replay code; allowed for browser parity
|
|
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
|
|
130
130
|
this.sessionRecording = new SessionRecording(this)
|
|
131
131
|
this.sessionRecording.startIfEnabledOrStop()
|
|
132
132
|
}
|
|
@@ -207,135 +207,12 @@ export class Leanbase extends PostHogCore {
|
|
|
207
207
|
|
|
208
208
|
this.isRemoteConfigLoaded = true
|
|
209
209
|
this.replayAutocapture?.onRemoteConfig(config)
|
|
210
|
-
this.sessionRecording?.onRemoteConfig(config)
|
|
211
210
|
}
|
|
212
211
|
|
|
213
|
-
|
|
212
|
+
fetch(url: string, options: PostHogFetchOptions): Promise<PostHogFetchResponse> {
|
|
214
213
|
const fetchFn = getFetch()
|
|
215
214
|
if (!fetchFn) {
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
try {
|
|
220
|
-
const isPost = !options.method || options.method.toUpperCase() === 'POST'
|
|
221
|
-
const isBatchEndpoint = typeof url === 'string' && url.endsWith('/batch/')
|
|
222
|
-
|
|
223
|
-
if (isPost && isBatchEndpoint && options && options.body) {
|
|
224
|
-
let parsed: any = null
|
|
225
|
-
try {
|
|
226
|
-
const headers = (options.headers || {}) as Record<string, any>
|
|
227
|
-
const contentEncoding = (
|
|
228
|
-
headers['Content-Encoding'] ||
|
|
229
|
-
headers['content-encoding'] ||
|
|
230
|
-
''
|
|
231
|
-
).toLowerCase()
|
|
232
|
-
|
|
233
|
-
const toUint8 = async (body: any): Promise<Uint8Array | null> => {
|
|
234
|
-
if (typeof body === 'string') return new TextEncoder().encode(body)
|
|
235
|
-
if (typeof Blob !== 'undefined' && body instanceof Blob) {
|
|
236
|
-
const ab = await body.arrayBuffer()
|
|
237
|
-
return new Uint8Array(ab)
|
|
238
|
-
}
|
|
239
|
-
if (body instanceof ArrayBuffer) return new Uint8Array(body)
|
|
240
|
-
if (ArrayBuffer.isView(body)) return new Uint8Array((body as any).buffer ?? body)
|
|
241
|
-
try {
|
|
242
|
-
return new TextEncoder().encode(String(body))
|
|
243
|
-
} catch {
|
|
244
|
-
return null
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (contentEncoding === 'gzip' || contentEncoding === 'deflate') {
|
|
249
|
-
const u8 = await toUint8(options.body)
|
|
250
|
-
if (u8) {
|
|
251
|
-
try {
|
|
252
|
-
const dec = decompressSync(u8)
|
|
253
|
-
const s = strFromU8(dec)
|
|
254
|
-
parsed = JSON.parse(s)
|
|
255
|
-
} catch {
|
|
256
|
-
parsed = null
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
} else {
|
|
260
|
-
if (typeof options.body === 'string') {
|
|
261
|
-
parsed = JSON.parse(options.body)
|
|
262
|
-
} else {
|
|
263
|
-
const u8 = await toUint8(options.body)
|
|
264
|
-
if (u8) {
|
|
265
|
-
try {
|
|
266
|
-
parsed = JSON.parse(new TextDecoder().decode(u8))
|
|
267
|
-
} catch {
|
|
268
|
-
parsed = null
|
|
269
|
-
}
|
|
270
|
-
} else {
|
|
271
|
-
try {
|
|
272
|
-
parsed = JSON.parse(String(options.body))
|
|
273
|
-
} catch {
|
|
274
|
-
parsed = null
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
} catch {
|
|
280
|
-
parsed = null
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (parsed && isArray(parsed.batch)) {
|
|
284
|
-
const hasSnapshot = parsed.batch.some((item: any) => item && item.event === '$snapshot')
|
|
285
|
-
// Debug logging to help diagnose routing issues
|
|
286
|
-
try {
|
|
287
|
-
// eslint-disable-next-line no-console
|
|
288
|
-
console.debug(
|
|
289
|
-
'[Leanbase.fetch] parsed.batch.length=',
|
|
290
|
-
parsed.batch.length,
|
|
291
|
-
'hasSnapshot=',
|
|
292
|
-
hasSnapshot
|
|
293
|
-
)
|
|
294
|
-
} catch {}
|
|
295
|
-
|
|
296
|
-
// If remote config has explicitly disabled session recording, drop snapshot events
|
|
297
|
-
try {
|
|
298
|
-
// Read persisted remote config that SessionRecording stores
|
|
299
|
-
const persisted: any = this.get_property(SESSION_RECORDING_REMOTE_CONFIG)
|
|
300
|
-
const serverAllowsRecording = !(
|
|
301
|
-
(persisted && persisted.enabled === false) ||
|
|
302
|
-
this.config.disable_session_recording === true
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
if (!serverAllowsRecording && hasSnapshot) {
|
|
306
|
-
// remove snapshot events from the batch before sending to /batch/
|
|
307
|
-
parsed.batch = parsed.batch.filter((item: any) => !(item && item.event === '$snapshot'))
|
|
308
|
-
// If no events remain, short-circuit and avoid sending an empty batch
|
|
309
|
-
if (!parsed.batch.length) {
|
|
310
|
-
try {
|
|
311
|
-
// eslint-disable-next-line no-console
|
|
312
|
-
console.debug(
|
|
313
|
-
'[Leanbase.fetch] sessionRecording disabled, dropping snapshot-only batch'
|
|
314
|
-
)
|
|
315
|
-
} catch {}
|
|
316
|
-
return { status: 200, json: async () => ({}) } as any
|
|
317
|
-
}
|
|
318
|
-
// re-encode the body so the underlying fetch receives the modified batch
|
|
319
|
-
try {
|
|
320
|
-
const newBody = JSON.stringify(parsed)
|
|
321
|
-
options = { ...options, body: newBody }
|
|
322
|
-
} catch {}
|
|
323
|
-
}
|
|
324
|
-
} catch {}
|
|
325
|
-
|
|
326
|
-
if (hasSnapshot) {
|
|
327
|
-
const host = (this.config && this.config.host) || ''
|
|
328
|
-
const newUrl = host ? `${host.replace(/\/$/, '')}/s/` : url
|
|
329
|
-
try {
|
|
330
|
-
// eslint-disable-next-line no-console
|
|
331
|
-
console.debug('[Leanbase.fetch] routing snapshot batch to', newUrl)
|
|
332
|
-
} catch {}
|
|
333
|
-
return fetchFn(newUrl, options)
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
} catch {
|
|
338
|
-
return fetchFn(url, options)
|
|
215
|
+
return Promise.reject(new Error('Fetch API is not available in this environment.'))
|
|
339
216
|
}
|
|
340
217
|
|
|
341
218
|
return fetchFn(url, options)
|
|
@@ -347,7 +224,6 @@ export class Leanbase extends PostHogCore {
|
|
|
347
224
|
extend(this.config, config)
|
|
348
225
|
this.persistence?.update_config(this.config, oldConfig)
|
|
349
226
|
this.replayAutocapture?.startIfEnabled()
|
|
350
|
-
this.sessionRecording?.startIfEnabledOrStop()
|
|
351
227
|
}
|
|
352
228
|
|
|
353
229
|
const isTempStorage = this.config.persistence === 'sessionStorage' || this.config.persistence === 'memory'
|
|
@@ -372,14 +248,43 @@ export class Leanbase extends PostHogCore {
|
|
|
372
248
|
return this.persistence?.get_property(key)
|
|
373
249
|
}
|
|
374
250
|
|
|
375
|
-
|
|
251
|
+
setPersistedProperty<T>(key: PostHogPersistedProperty, value: T | null): void {
|
|
252
|
+
this.persistence?.set_property(key, value)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Backwards-compatible aliases expected by replay/browser code
|
|
256
|
+
get_property(key: string): any {
|
|
376
257
|
return this.persistence?.get_property(key)
|
|
377
258
|
}
|
|
378
259
|
|
|
379
|
-
|
|
260
|
+
set_property(key: string, value: any): void {
|
|
380
261
|
this.persistence?.set_property(key, value)
|
|
381
262
|
}
|
|
382
263
|
|
|
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
|
+
|
|
383
288
|
calculateEventProperties(
|
|
384
289
|
eventName: string,
|
|
385
290
|
eventProperties: PostHogEventProperties,
|
|
@@ -429,14 +334,6 @@ export class Leanbase extends PostHogCore {
|
|
|
429
334
|
extend(properties, this.sessionPropsManager.getSessionProps())
|
|
430
335
|
}
|
|
431
336
|
|
|
432
|
-
try {
|
|
433
|
-
if (this.sessionRecording) {
|
|
434
|
-
extend(properties, this.sessionRecording.sdkDebugProperties)
|
|
435
|
-
}
|
|
436
|
-
} catch (e: any) {
|
|
437
|
-
properties['$sdk_debug_error_capturing_properties'] = String(e)
|
|
438
|
-
}
|
|
439
|
-
|
|
440
337
|
let pageviewProperties: Record<string, any> = this.pageViewManager.doEvent()
|
|
441
338
|
if (eventName === '$pageview' && !readOnly) {
|
|
442
339
|
pageviewProperties = this.pageViewManager.doPageView(timestamp, uuid)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Leanbase } from './leanbase'
|
|
2
|
+
|
|
3
|
+
// PostHog shim: Leanbase runtime with a widened onFeatureFlags signature
|
|
4
|
+
export type PostHog = Leanbase & {
|
|
5
|
+
onFeatureFlags?: (cb: (...args: any[]) => void) => () => void
|
|
6
|
+
consent?: {
|
|
7
|
+
isOptedOut?: () => boolean
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export { default as Config } from './config'
|
|
12
|
+
export { logger } from './utils/logger'
|