@leanbase-giangnd/js 0.1.5 → 0.2.3
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 +270 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.mjs +270 -23
- package/dist/index.mjs.map +1 -1
- package/dist/leanbase.iife.js +5418 -92
- package/dist/leanbase.iife.js.map +1 -1
- package/package.json +1 -1
- package/src/extensions/replay/extension-shim.ts +102 -3
- package/src/extensions/replay/external/lazy-loaded-session-recorder.ts +66 -13
- package/src/extensions/replay/session-recording.ts +59 -2
- package/src/leanbase.ts +68 -6
- package/src/utils/index.ts +3 -0
- package/src/version.ts +1 -1
package/package.json
CHANGED
|
@@ -1,5 +1,103 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
1
2
|
import { window as win } from '../../utils'
|
|
2
|
-
|
|
3
|
+
|
|
4
|
+
// We avoid importing '@rrweb/record' at module load time to prevent IIFE builds
|
|
5
|
+
// from requiring a top-level global. Instead, expose a lazy proxy that will
|
|
6
|
+
// dynamically import the module the first time it's used.
|
|
7
|
+
|
|
8
|
+
let _cachedRRWeb: any | null = null
|
|
9
|
+
|
|
10
|
+
async function _loadRRWebModule(): Promise<any> {
|
|
11
|
+
if (_cachedRRWeb) return _cachedRRWeb
|
|
12
|
+
try {
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
const mod: any = await import('@rrweb/record')
|
|
15
|
+
_cachedRRWeb = mod
|
|
16
|
+
return _cachedRRWeb
|
|
17
|
+
} catch (e) {
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// queue for method calls before rrweb loads
|
|
23
|
+
const _queuedCalls: Array<() => void> = []
|
|
24
|
+
|
|
25
|
+
// Create a proxy function that delegates to the real rrweb.record when called
|
|
26
|
+
const rrwebRecordProxy: any = function (...args: any[]) {
|
|
27
|
+
let realStop: (() => void) | undefined
|
|
28
|
+
let calledReal = false
|
|
29
|
+
|
|
30
|
+
// Start loading asynchronously and call the real record when available
|
|
31
|
+
void (async () => {
|
|
32
|
+
const mod = await _loadRRWebModule()
|
|
33
|
+
const real = mod && (mod.record ?? mod.default?.record)
|
|
34
|
+
if (real) {
|
|
35
|
+
try {
|
|
36
|
+
calledReal = true
|
|
37
|
+
realStop = real(...args)
|
|
38
|
+
// flush any queued calls that were waiting for rrweb
|
|
39
|
+
while (_queuedCalls.length) {
|
|
40
|
+
try {
|
|
41
|
+
const fn = _queuedCalls.shift()!
|
|
42
|
+
fn()
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// ignore
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
// ignore
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
})()
|
|
52
|
+
|
|
53
|
+
// return a stop function that will call the real stop when available
|
|
54
|
+
return () => {
|
|
55
|
+
if (realStop) {
|
|
56
|
+
try {
|
|
57
|
+
realStop()
|
|
58
|
+
} catch (e) {
|
|
59
|
+
// ignore
|
|
60
|
+
}
|
|
61
|
+
} else if (!calledReal) {
|
|
62
|
+
// If rrweb hasn't been initialised yet, queue a stop request that will
|
|
63
|
+
// call the real stop once available.
|
|
64
|
+
_queuedCalls.push(() => {
|
|
65
|
+
try {
|
|
66
|
+
realStop?.()
|
|
67
|
+
} catch (e) {
|
|
68
|
+
// ignore
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// methods that can be called on the rrweb.record object - queue until real module is available
|
|
76
|
+
rrwebRecordProxy.addCustomEvent = function (tag?: string, payload?: any) {
|
|
77
|
+
const call = () => {
|
|
78
|
+
try {
|
|
79
|
+
const real = _cachedRRWeb && (_cachedRRWeb.record ?? _cachedRRWeb.default?.record)
|
|
80
|
+
real?.addCustomEvent?.(tag, payload)
|
|
81
|
+
} catch (e) {
|
|
82
|
+
// ignore
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (_cachedRRWeb) call()
|
|
86
|
+
else _queuedCalls.push(call)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
rrwebRecordProxy.takeFullSnapshot = function () {
|
|
90
|
+
const call = () => {
|
|
91
|
+
try {
|
|
92
|
+
const real = _cachedRRWeb && (_cachedRRWeb.record ?? _cachedRRWeb.default?.record)
|
|
93
|
+
real?.takeFullSnapshot?.()
|
|
94
|
+
} catch (e) {
|
|
95
|
+
// ignore
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (_cachedRRWeb) call()
|
|
99
|
+
else _queuedCalls.push(call)
|
|
100
|
+
}
|
|
3
101
|
// Delay importing heavy modules to avoid circular dependencies at build time.
|
|
4
102
|
// They will be required lazily when used at runtime.
|
|
5
103
|
// We avoid requiring the lazy-loaded recorder here to prevent circular dependencies during bundling.
|
|
@@ -12,9 +110,10 @@ const _target: any = (win as any) ?? (globalThis as any)
|
|
|
12
110
|
|
|
13
111
|
_target.__PosthogExtensions__ = _target.__PosthogExtensions__ || {}
|
|
14
112
|
|
|
15
|
-
// Expose rrweb.record under the same contract
|
|
113
|
+
// Expose rrweb.record under the same contract. We provide a lazy proxy so
|
|
114
|
+
// builds that execute this file don't require rrweb at module evaluation time.
|
|
16
115
|
_target.__PosthogExtensions__.rrweb = _target.__PosthogExtensions__.rrweb || {
|
|
17
|
-
record:
|
|
116
|
+
record: rrwebRecordProxy,
|
|
18
117
|
}
|
|
19
118
|
|
|
20
119
|
// Provide initSessionRecording if not present — return a new LazyLoadedSessionRecording when called
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/* eslint-disable posthog-js/no-direct-function-check */
|
|
2
|
-
import { record as rrwebRecord } from '@rrweb/record'
|
|
3
2
|
import '../extension-shim'
|
|
4
3
|
import { clampToRange, includes, isBoolean, isNullish, isNumber, isObject, isString, isUndefined } from '@posthog/core'
|
|
5
4
|
import type { recordOptions, rrwebRecord as rrwebRecordType } from '../types/rrweb'
|
|
@@ -145,7 +144,43 @@ function getRRWebRecord(): rrwebRecordType | undefined {
|
|
|
145
144
|
// ignore
|
|
146
145
|
}
|
|
147
146
|
|
|
148
|
-
|
|
147
|
+
// If we've previously loaded rrweb via dynamic import, return the cached reference
|
|
148
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
149
|
+
const cached = (getRRWebRecord as any)._cachedRRWebRecord as rrwebRecordType | undefined
|
|
150
|
+
return cached as unknown as rrwebRecordType | undefined
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function loadRRWeb(): Promise<rrwebRecordType | null> {
|
|
154
|
+
try {
|
|
155
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
156
|
+
const ext = (globalThis as any).__PosthogExtensions__
|
|
157
|
+
if (ext && ext.rrweb && ext.rrweb.record) {
|
|
158
|
+
;(getRRWebRecord as any)._cachedRRWebRecord = ext.rrweb.record as unknown as rrwebRecordType
|
|
159
|
+
return ext.rrweb.record as unknown as rrwebRecordType
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// If already cached, return it
|
|
163
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
164
|
+
const already = (getRRWebRecord as any)._cachedRRWebRecord as rrwebRecordType | undefined
|
|
165
|
+
if (already) {
|
|
166
|
+
return already
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Dynamic import - let the bundler (IIFE build) include rrweb in the bundle or allow lazy-load
|
|
170
|
+
// Note: we intentionally use a dynamic import so rrweb is not referenced at the module top-level
|
|
171
|
+
// which would cause IIFE builds to assume a global is present at script execution.
|
|
172
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
173
|
+
const mod: any = await import('@rrweb/record')
|
|
174
|
+
const rr = (mod && (mod.record ?? (mod.default && mod.default.record))) as rrwebRecordType
|
|
175
|
+
if (rr) {
|
|
176
|
+
;(getRRWebRecord as any)._cachedRRWebRecord = rr
|
|
177
|
+
return rr
|
|
178
|
+
}
|
|
179
|
+
} catch (e) {
|
|
180
|
+
logger.error('could not dynamically load rrweb', e)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return null
|
|
149
184
|
}
|
|
150
185
|
|
|
151
186
|
export type compressedFullSnapshotEvent = {
|
|
@@ -681,7 +716,7 @@ export class LazyLoadedSessionRecording {
|
|
|
681
716
|
return parsedConfig as SessionRecordingPersistedConfig
|
|
682
717
|
}
|
|
683
718
|
|
|
684
|
-
start(startReason?: SessionStartReason) {
|
|
719
|
+
async start(startReason?: SessionStartReason) {
|
|
685
720
|
const config = this._remoteConfig
|
|
686
721
|
if (!config) {
|
|
687
722
|
logger.info('remote config must be stored in persistence before recording can start')
|
|
@@ -722,7 +757,13 @@ export class LazyLoadedSessionRecording {
|
|
|
722
757
|
})
|
|
723
758
|
|
|
724
759
|
this._makeSamplingDecision(this.sessionId)
|
|
725
|
-
this._startRecorder()
|
|
760
|
+
await this._startRecorder()
|
|
761
|
+
|
|
762
|
+
// If rrweb failed to load/start, do not proceed further.
|
|
763
|
+
// This prevents installing listeners that assume rrweb is active.
|
|
764
|
+
if (!this.isStarted) {
|
|
765
|
+
return
|
|
766
|
+
}
|
|
726
767
|
|
|
727
768
|
// calling addEventListener multiple times is safe and will not add duplicates
|
|
728
769
|
addEventListener(window, 'beforeunload', this._onBeforeUnload)
|
|
@@ -1302,7 +1343,7 @@ export class LazyLoadedSessionRecording {
|
|
|
1302
1343
|
}
|
|
1303
1344
|
}
|
|
1304
1345
|
|
|
1305
|
-
private _startRecorder() {
|
|
1346
|
+
private async _startRecorder() {
|
|
1306
1347
|
if (this._stopRrweb) {
|
|
1307
1348
|
return
|
|
1308
1349
|
}
|
|
@@ -1353,7 +1394,13 @@ export class LazyLoadedSessionRecording {
|
|
|
1353
1394
|
sessionRecordingOptions.blockSelector = this._masking.blockSelector ?? undefined
|
|
1354
1395
|
}
|
|
1355
1396
|
|
|
1356
|
-
|
|
1397
|
+
// Ensure rrweb is loaded (either via global extension or dynamic import)
|
|
1398
|
+
let rrwebRecord = getRRWebRecord()
|
|
1399
|
+
if (!rrwebRecord) {
|
|
1400
|
+
const loaded = await loadRRWeb()
|
|
1401
|
+
rrwebRecord = loaded ?? undefined
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1357
1404
|
if (!rrwebRecord) {
|
|
1358
1405
|
logger.error(
|
|
1359
1406
|
'_startRecorder was called but rrwebRecord is not available. This indicates something has gone wrong.'
|
|
@@ -1377,13 +1424,19 @@ export class LazyLoadedSessionRecording {
|
|
|
1377
1424
|
})
|
|
1378
1425
|
|
|
1379
1426
|
const activePlugins = this._gatherRRWebPlugins()
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1427
|
+
try {
|
|
1428
|
+
this._stopRrweb = rrwebRecord({
|
|
1429
|
+
emit: (event) => {
|
|
1430
|
+
this.onRRwebEmit(event)
|
|
1431
|
+
},
|
|
1432
|
+
plugins: activePlugins,
|
|
1433
|
+
...sessionRecordingOptions,
|
|
1434
|
+
})
|
|
1435
|
+
} catch (e) {
|
|
1436
|
+
logger.error('failed to start rrweb recorder', e)
|
|
1437
|
+
this._stopRrweb = undefined
|
|
1438
|
+
return
|
|
1439
|
+
}
|
|
1387
1440
|
|
|
1388
1441
|
// We reset the last activity timestamp, resetting the idle timer
|
|
1389
1442
|
this._lastActivityTimestamp = Date.now()
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable posthog-js/no-direct-function-check */
|
|
1
2
|
import { SESSION_RECORDING_IS_SAMPLED, SESSION_RECORDING_REMOTE_CONFIG } from '../../constants'
|
|
2
3
|
import { Leanbase } from '../../leanbase'
|
|
3
4
|
import { Properties, RemoteConfig, SessionRecordingPersistedConfig, SessionStartReason } from '../../types'
|
|
@@ -5,7 +6,7 @@ import { type eventWithTime } from './types/rrweb-types'
|
|
|
5
6
|
|
|
6
7
|
import { isNullish, isUndefined } from '@posthog/core'
|
|
7
8
|
import { logger } from '../../leanbase-logger'
|
|
8
|
-
import { window } from '../../utils'
|
|
9
|
+
import { assignableWindow, window } from '../../utils'
|
|
9
10
|
import { LazyLoadedSessionRecording } from './external/lazy-loaded-session-recorder'
|
|
10
11
|
import { DISABLED, LAZY_LOADING, SessionRecordingStatus, TriggerType } from './external/triggerMatching'
|
|
11
12
|
|
|
@@ -91,6 +92,19 @@ export class SessionRecording {
|
|
|
91
92
|
return
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
// If extensions provide a loader, use it. Otherwise fallback to the local _onScriptLoaded which
|
|
96
|
+
// will create the local LazyLoadedSessionRecording (so tests that mock it work correctly).
|
|
97
|
+
const loader = assignableWindow.__PosthogExtensions__?.loadExternalDependency
|
|
98
|
+
if (typeof loader === 'function') {
|
|
99
|
+
loader(this._instance, this._scriptName as any, (err: any) => {
|
|
100
|
+
if (err) {
|
|
101
|
+
return log.error('could not load recorder', err)
|
|
102
|
+
}
|
|
103
|
+
this._onScriptLoaded(startReason)
|
|
104
|
+
})
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
94
108
|
this._onScriptLoaded(startReason)
|
|
95
109
|
}
|
|
96
110
|
|
|
@@ -188,14 +202,57 @@ export class SessionRecording {
|
|
|
188
202
|
}
|
|
189
203
|
}
|
|
190
204
|
|
|
205
|
+
private get _scriptName() {
|
|
206
|
+
const remoteConfig: SessionRecordingPersistedConfig | undefined = this._instance?.persistence?.get_property(
|
|
207
|
+
SESSION_RECORDING_REMOTE_CONFIG
|
|
208
|
+
)
|
|
209
|
+
return (remoteConfig?.scriptConfig?.script as any) || 'lazy-recorder'
|
|
210
|
+
}
|
|
211
|
+
|
|
191
212
|
private _onScriptLoaded(startReason?: SessionStartReason) {
|
|
213
|
+
// If extensions provide an init function, use it. Otherwise, fall back to the local LazyLoadedSessionRecording
|
|
214
|
+
if (assignableWindow.__PosthogExtensions__?.initSessionRecording) {
|
|
215
|
+
if (!this._lazyLoadedSessionRecording) {
|
|
216
|
+
const maybeRecording = assignableWindow.__PosthogExtensions__?.initSessionRecording(this._instance)
|
|
217
|
+
if (maybeRecording && typeof (maybeRecording as any).start === 'function') {
|
|
218
|
+
this._lazyLoadedSessionRecording = maybeRecording
|
|
219
|
+
;(this._lazyLoadedSessionRecording as any)._forceAllowLocalhostNetworkCapture =
|
|
220
|
+
this._forceAllowLocalhostNetworkCapture
|
|
221
|
+
} else {
|
|
222
|
+
log.warn(
|
|
223
|
+
'initSessionRecording was present but did not return a recorder instance; falling back to local recorder'
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (this._lazyLoadedSessionRecording) {
|
|
229
|
+
try {
|
|
230
|
+
const maybePromise: any = this._lazyLoadedSessionRecording.start(startReason)
|
|
231
|
+
if (maybePromise && typeof maybePromise.catch === 'function') {
|
|
232
|
+
maybePromise.catch((e: any) => logger.error('error starting session recording', e))
|
|
233
|
+
}
|
|
234
|
+
} catch (e: any) {
|
|
235
|
+
logger.error('error starting session recording', e)
|
|
236
|
+
}
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
192
241
|
if (!this._lazyLoadedSessionRecording) {
|
|
193
242
|
this._lazyLoadedSessionRecording = new LazyLoadedSessionRecording(this._instance)
|
|
194
243
|
;(this._lazyLoadedSessionRecording as any)._forceAllowLocalhostNetworkCapture =
|
|
195
244
|
this._forceAllowLocalhostNetworkCapture
|
|
196
245
|
}
|
|
197
246
|
|
|
198
|
-
|
|
247
|
+
// start may perform a dynamic import; handle both sync and Promise returns
|
|
248
|
+
try {
|
|
249
|
+
const maybePromise: any = this._lazyLoadedSessionRecording!.start(startReason)
|
|
250
|
+
if (maybePromise && typeof maybePromise.catch === 'function') {
|
|
251
|
+
maybePromise.catch((e: any) => logger.error('error starting session recording', e))
|
|
252
|
+
}
|
|
253
|
+
} catch (e: any) {
|
|
254
|
+
logger.error('error starting session recording', e)
|
|
255
|
+
}
|
|
199
256
|
}
|
|
200
257
|
|
|
201
258
|
/**
|
package/src/leanbase.ts
CHANGED
|
@@ -93,6 +93,10 @@ export class Leanbase extends PostHogCore {
|
|
|
93
93
|
consent!: ConsentManager
|
|
94
94
|
sessionRecording?: SessionRecording
|
|
95
95
|
isRemoteConfigLoaded?: boolean
|
|
96
|
+
private _remoteConfigLoadAttempted: boolean = false
|
|
97
|
+
private _remoteConfigResolved: boolean = false
|
|
98
|
+
private _featureFlagsResolved: boolean = false
|
|
99
|
+
private _maybeStartedSessionRecording: boolean = false
|
|
96
100
|
personProcessingSetOncePropertiesSent = false
|
|
97
101
|
isLoaded: boolean = false
|
|
98
102
|
initialPageviewCaptured: boolean
|
|
@@ -130,11 +134,21 @@ export class Leanbase extends PostHogCore {
|
|
|
130
134
|
|
|
131
135
|
if (this.sessionManager && this.config.cookieless_mode !== 'always') {
|
|
132
136
|
this.sessionRecording = new SessionRecording(this)
|
|
133
|
-
this.sessionRecording.startIfEnabledOrStop()
|
|
134
137
|
}
|
|
135
138
|
|
|
139
|
+
// Start session recording only once flags + remote config have been resolved.
|
|
140
|
+
// This matches the PostHog browser SDK where replay activation is driven by remote config and flags.
|
|
136
141
|
if (this.config.preloadFeatureFlags !== false) {
|
|
137
|
-
this.reloadFeatureFlags(
|
|
142
|
+
this.reloadFeatureFlags({
|
|
143
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
144
|
+
cb: (_err) => {
|
|
145
|
+
this._featureFlagsResolved = true
|
|
146
|
+
this._maybeStartSessionRecording()
|
|
147
|
+
},
|
|
148
|
+
})
|
|
149
|
+
} else {
|
|
150
|
+
// If feature flags preload is explicitly disabled, treat this requirement as satisfied.
|
|
151
|
+
this._featureFlagsResolved = true
|
|
138
152
|
}
|
|
139
153
|
|
|
140
154
|
this.config.loaded?.(this)
|
|
@@ -146,9 +160,25 @@ export class Leanbase extends PostHogCore {
|
|
|
146
160
|
}, 1)
|
|
147
161
|
}
|
|
148
162
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
163
|
+
const triggerRemoteConfigLoad = (reason: 'immediate' | 'dom' | 'no-document') => {
|
|
164
|
+
logger.info(`remote config load triggered via ${reason}`)
|
|
165
|
+
void this.loadRemoteConfig()
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (document) {
|
|
169
|
+
if (document.readyState === 'loading') {
|
|
170
|
+
logger.info('remote config load deferred until DOMContentLoaded')
|
|
171
|
+
const onDomReady = () => {
|
|
172
|
+
document?.removeEventListener('DOMContentLoaded', onDomReady)
|
|
173
|
+
triggerRemoteConfigLoad('dom')
|
|
174
|
+
}
|
|
175
|
+
addEventListener(document, 'DOMContentLoaded', onDomReady, { once: true } as any)
|
|
176
|
+
} else {
|
|
177
|
+
triggerRemoteConfigLoad('immediate')
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
triggerRemoteConfigLoad('no-document')
|
|
181
|
+
}
|
|
152
182
|
addEventListener(window, 'onpagehide' in self ? 'pagehide' : 'unload', this.capturePageLeave.bind(this), {
|
|
153
183
|
passive: false,
|
|
154
184
|
})
|
|
@@ -191,11 +221,20 @@ export class Leanbase extends PostHogCore {
|
|
|
191
221
|
}
|
|
192
222
|
|
|
193
223
|
async loadRemoteConfig() {
|
|
194
|
-
if (
|
|
224
|
+
if (this._remoteConfigLoadAttempted) {
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
this._remoteConfigLoadAttempted = true
|
|
228
|
+
|
|
229
|
+
try {
|
|
195
230
|
const remoteConfig = await this.reloadRemoteConfigAsync()
|
|
196
231
|
if (remoteConfig) {
|
|
197
232
|
this.onRemoteConfig(remoteConfig as RemoteConfig)
|
|
198
233
|
}
|
|
234
|
+
} finally {
|
|
235
|
+
// Regardless of success/failure, we consider remote config "resolved" so replay isn't blocked forever.
|
|
236
|
+
this._remoteConfigResolved = true
|
|
237
|
+
this._maybeStartSessionRecording()
|
|
199
238
|
}
|
|
200
239
|
}
|
|
201
240
|
|
|
@@ -210,6 +249,29 @@ export class Leanbase extends PostHogCore {
|
|
|
210
249
|
this.isRemoteConfigLoaded = true
|
|
211
250
|
this.replayAutocapture?.onRemoteConfig(config)
|
|
212
251
|
this.sessionRecording?.onRemoteConfig(config)
|
|
252
|
+
|
|
253
|
+
// Remote config has been applied; allow replay start if flags are also ready.
|
|
254
|
+
this._remoteConfigResolved = true
|
|
255
|
+
this._maybeStartSessionRecording()
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private _maybeStartSessionRecording(): void {
|
|
259
|
+
if (this._maybeStartedSessionRecording) {
|
|
260
|
+
return
|
|
261
|
+
}
|
|
262
|
+
if (!this.sessionRecording) {
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
if (!this._featureFlagsResolved || !this._remoteConfigResolved) {
|
|
266
|
+
return
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
this._maybeStartedSessionRecording = true
|
|
270
|
+
try {
|
|
271
|
+
this.sessionRecording.startIfEnabledOrStop()
|
|
272
|
+
} catch (e) {
|
|
273
|
+
logger.error('Failed to start session recording', e)
|
|
274
|
+
}
|
|
213
275
|
}
|
|
214
276
|
|
|
215
277
|
fetch(url: string, options: PostHogFetchOptions): Promise<PostHogFetchResponse> {
|
package/src/utils/index.ts
CHANGED
|
@@ -22,6 +22,9 @@ export const AbortController = global?.AbortController
|
|
|
22
22
|
export const userAgent = navigator?.userAgent
|
|
23
23
|
export { win as window }
|
|
24
24
|
|
|
25
|
+
// assignableWindow mirrors browser package's assignableWindow for extension loading shims
|
|
26
|
+
export const assignableWindow: (Window & typeof globalThis) | any = win ?? ({} as any)
|
|
27
|
+
|
|
25
28
|
export function eachArray<E = any>(
|
|
26
29
|
obj: E[] | null | undefined,
|
|
27
30
|
iterator: (value: E, key: number) => void | Breaker,
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '0.
|
|
1
|
+
export const version = '0.2.3'
|