@leanbase-giangnd/js 0.2.2 → 0.2.4
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 +272 -116
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +272 -116
- package/dist/index.mjs.map +1 -1
- package/dist/leanbase.iife.js +273 -117
- package/dist/leanbase.iife.js.map +1 -1
- package/package.json +47 -46
- package/src/extensions/replay/external/lazy-loaded-session-recorder.ts +176 -88
- package/src/extensions/replay/external/mutation-throttler.ts +40 -27
- package/src/extensions/replay/session-recording.ts +19 -12
- package/src/leanbase.ts +68 -6
- package/src/version.ts +1 -1
- package/LICENSE +0 -37
package/dist/leanbase.iife.js
CHANGED
|
@@ -2846,7 +2846,7 @@ var leanbase = (function () {
|
|
|
2846
2846
|
}
|
|
2847
2847
|
};
|
|
2848
2848
|
|
|
2849
|
-
var version = "0.2.
|
|
2849
|
+
var version = "0.2.4";
|
|
2850
2850
|
var packageInfo = {
|
|
2851
2851
|
version: version};
|
|
2852
2852
|
|
|
@@ -6106,34 +6106,49 @@ var leanbase = (function () {
|
|
|
6106
6106
|
}
|
|
6107
6107
|
return [id, node];
|
|
6108
6108
|
};
|
|
6109
|
-
this._getNode = id =>
|
|
6109
|
+
this._getNode = id => {
|
|
6110
|
+
// eslint-disable-next-line posthog-js/no-direct-undefined-check, posthog-js/no-direct-null-check
|
|
6111
|
+
if (id === null || id === undefined) {
|
|
6112
|
+
return null;
|
|
6113
|
+
}
|
|
6114
|
+
try {
|
|
6115
|
+
return this._rrweb.mirror.getNode(id) ?? null;
|
|
6116
|
+
} catch {
|
|
6117
|
+
return null;
|
|
6118
|
+
}
|
|
6119
|
+
};
|
|
6110
6120
|
this._numberOfChanges = data => {
|
|
6111
6121
|
return (data.removes?.length ?? 0) + (data.attributes?.length ?? 0) + (data.texts?.length ?? 0) + (data.adds?.length ?? 0);
|
|
6112
6122
|
};
|
|
6113
6123
|
this.throttleMutations = event => {
|
|
6114
|
-
|
|
6124
|
+
try {
|
|
6125
|
+
if (event.type !== INCREMENTAL_SNAPSHOT_EVENT_TYPE || event.data.source !== MUTATION_SOURCE_TYPE) {
|
|
6126
|
+
return event;
|
|
6127
|
+
}
|
|
6128
|
+
const data = event.data;
|
|
6129
|
+
const initialMutationCount = this._numberOfChanges(data);
|
|
6130
|
+
if (data.attributes) {
|
|
6131
|
+
// Most problematic mutations come from attrs where the style or minor properties are changed rapidly
|
|
6132
|
+
data.attributes = data.attributes.filter(attr => {
|
|
6133
|
+
const id = attr?.id;
|
|
6134
|
+
if (typeof id !== 'number') {
|
|
6135
|
+
return true;
|
|
6136
|
+
}
|
|
6137
|
+
const [nodeId] = this._getNodeOrRelevantParent(id);
|
|
6138
|
+
const isRateLimited = this._rateLimiter.consumeRateLimit(nodeId);
|
|
6139
|
+
return !isRateLimited;
|
|
6140
|
+
});
|
|
6141
|
+
}
|
|
6142
|
+
// Check if every part of the mutation is empty in which case there is nothing to do
|
|
6143
|
+
const mutationCount = this._numberOfChanges(data);
|
|
6144
|
+
if (mutationCount === 0 && initialMutationCount !== mutationCount) {
|
|
6145
|
+
// If we have modified the mutation count and the remaining count is 0, then we don't need the event.
|
|
6146
|
+
return;
|
|
6147
|
+
}
|
|
6148
|
+
return event;
|
|
6149
|
+
} catch {
|
|
6115
6150
|
return event;
|
|
6116
6151
|
}
|
|
6117
|
-
const data = event.data;
|
|
6118
|
-
const initialMutationCount = this._numberOfChanges(data);
|
|
6119
|
-
if (data.attributes) {
|
|
6120
|
-
// Most problematic mutations come from attrs where the style or minor properties are changed rapidly
|
|
6121
|
-
data.attributes = data.attributes.filter(attr => {
|
|
6122
|
-
const [nodeId] = this._getNodeOrRelevantParent(attr.id);
|
|
6123
|
-
const isRateLimited = this._rateLimiter.consumeRateLimit(nodeId);
|
|
6124
|
-
if (isRateLimited) {
|
|
6125
|
-
return false;
|
|
6126
|
-
}
|
|
6127
|
-
return attr;
|
|
6128
|
-
});
|
|
6129
|
-
}
|
|
6130
|
-
// Check if every part of the mutation is empty in which case there is nothing to do
|
|
6131
|
-
const mutationCount = this._numberOfChanges(data);
|
|
6132
|
-
if (mutationCount === 0 && initialMutationCount !== mutationCount) {
|
|
6133
|
-
// If we have modified the mutation count and the remaining count is 0, then we don't need the event.
|
|
6134
|
-
return;
|
|
6135
|
-
}
|
|
6136
|
-
return event;
|
|
6137
6152
|
};
|
|
6138
6153
|
const configuredBucketSize = this._options.bucketSize ?? 100;
|
|
6139
6154
|
const effectiveBucketSize = Math.max(configuredBucketSize - 1, 1);
|
|
@@ -6371,6 +6386,9 @@ var leanbase = (function () {
|
|
|
6371
6386
|
*/
|
|
6372
6387
|
this._queuedRRWebEvents = [];
|
|
6373
6388
|
this._isIdle = 'unknown';
|
|
6389
|
+
// Replay should not process rrweb emits until all critical dependencies are ready.
|
|
6390
|
+
this._isFullyReady = false;
|
|
6391
|
+
this._loggedMissingEndpointFor = false;
|
|
6374
6392
|
// we need to be able to check the state of the event and url triggers separately
|
|
6375
6393
|
// as we make some decisions based on them without referencing LinkedFlag etc
|
|
6376
6394
|
this._triggerMatching = new PendingTriggerMatching();
|
|
@@ -6551,7 +6569,11 @@ var leanbase = (function () {
|
|
|
6551
6569
|
}
|
|
6552
6570
|
}
|
|
6553
6571
|
_tryAddCustomEvent(tag, payload) {
|
|
6554
|
-
|
|
6572
|
+
const rrwebRecord = getRRWebRecord();
|
|
6573
|
+
if (!rrwebRecord || typeof rrwebRecord.addCustomEvent !== 'function') {
|
|
6574
|
+
return false;
|
|
6575
|
+
}
|
|
6576
|
+
return this._tryRRWebMethod(newQueuedEvent(() => rrwebRecord.addCustomEvent(tag, payload)));
|
|
6555
6577
|
}
|
|
6556
6578
|
_pageViewFallBack() {
|
|
6557
6579
|
try {
|
|
@@ -6597,7 +6619,11 @@ var leanbase = (function () {
|
|
|
6597
6619
|
}
|
|
6598
6620
|
}
|
|
6599
6621
|
_tryTakeFullSnapshot() {
|
|
6600
|
-
|
|
6622
|
+
const rrwebRecord = getRRWebRecord();
|
|
6623
|
+
if (!rrwebRecord || typeof rrwebRecord.takeFullSnapshot !== 'function') {
|
|
6624
|
+
return false;
|
|
6625
|
+
}
|
|
6626
|
+
return this._tryRRWebMethod(newQueuedEvent(() => rrwebRecord.takeFullSnapshot()));
|
|
6601
6627
|
}
|
|
6602
6628
|
get _fullSnapshotIntervalMillis() {
|
|
6603
6629
|
if (this._triggerMatching.triggerStatus(this.sessionId) === TRIGGER_PENDING && !['sampled', 'active'].includes(this.status)) {
|
|
@@ -6673,6 +6699,7 @@ var leanbase = (function () {
|
|
|
6673
6699
|
return parsedConfig;
|
|
6674
6700
|
}
|
|
6675
6701
|
async start(startReason) {
|
|
6702
|
+
this._isFullyReady = false;
|
|
6676
6703
|
const config = this._remoteConfig;
|
|
6677
6704
|
if (!config) {
|
|
6678
6705
|
logger.info('remote config must be stored in persistence before recording can start');
|
|
@@ -6707,6 +6734,14 @@ var leanbase = (function () {
|
|
|
6707
6734
|
});
|
|
6708
6735
|
this._makeSamplingDecision(this.sessionId);
|
|
6709
6736
|
await this._startRecorder();
|
|
6737
|
+
// If rrweb failed to load/start, do not proceed further.
|
|
6738
|
+
// This prevents installing listeners that assume rrweb is active.
|
|
6739
|
+
if (!this.isStarted) {
|
|
6740
|
+
return;
|
|
6741
|
+
}
|
|
6742
|
+
// Only start processing rrweb emits once the ingestion endpoint is available.
|
|
6743
|
+
// If it isn't available, we must degrade to a no-op (never crash the host app).
|
|
6744
|
+
this._isFullyReady = this._canCaptureSnapshots();
|
|
6710
6745
|
// calling addEventListener multiple times is safe and will not add duplicates
|
|
6711
6746
|
addEventListener(win, 'beforeunload', this._onBeforeUnload);
|
|
6712
6747
|
addEventListener(win, 'offline', this._onOffline);
|
|
@@ -6784,77 +6819,100 @@ var leanbase = (function () {
|
|
|
6784
6819
|
this._queuedRRWebEvents = [];
|
|
6785
6820
|
this._stopRrweb?.();
|
|
6786
6821
|
this._stopRrweb = undefined;
|
|
6822
|
+
this._isFullyReady = false;
|
|
6787
6823
|
logger.info('stopped');
|
|
6788
6824
|
}
|
|
6825
|
+
_snapshotIngestionUrl() {
|
|
6826
|
+
const endpointFor = this._instance?.requestRouter?.endpointFor;
|
|
6827
|
+
if (typeof endpointFor !== 'function') {
|
|
6828
|
+
return null;
|
|
6829
|
+
}
|
|
6830
|
+
try {
|
|
6831
|
+
return endpointFor('api', this._endpoint);
|
|
6832
|
+
} catch {
|
|
6833
|
+
return null;
|
|
6834
|
+
}
|
|
6835
|
+
}
|
|
6836
|
+
_canCaptureSnapshots() {
|
|
6837
|
+
return !!this._snapshotIngestionUrl();
|
|
6838
|
+
}
|
|
6789
6839
|
onRRwebEmit(rawEvent) {
|
|
6790
|
-
|
|
6791
|
-
if (!
|
|
6840
|
+
// Never process rrweb emits until we're fully ready.
|
|
6841
|
+
if (!this._isFullyReady || !this.isStarted) {
|
|
6792
6842
|
return;
|
|
6793
6843
|
}
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
if (!href) {
|
|
6844
|
+
try {
|
|
6845
|
+
this._processQueuedEvents();
|
|
6846
|
+
if (!rawEvent || !isObject(rawEvent)) {
|
|
6798
6847
|
return;
|
|
6799
6848
|
}
|
|
6800
|
-
rawEvent.
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
//
|
|
6836
|
-
//
|
|
6837
|
-
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6849
|
+
if (rawEvent.type === EventType$1.Meta) {
|
|
6850
|
+
const href = this._maskUrl(rawEvent.data.href);
|
|
6851
|
+
this._lastHref = href;
|
|
6852
|
+
if (!href) {
|
|
6853
|
+
return;
|
|
6854
|
+
}
|
|
6855
|
+
rawEvent.data.href = href;
|
|
6856
|
+
} else {
|
|
6857
|
+
this._pageViewFallBack();
|
|
6858
|
+
}
|
|
6859
|
+
// Check if the URL matches any trigger patterns
|
|
6860
|
+
this._urlTriggerMatching.checkUrlTriggerConditions(() => this._pauseRecording(), () => this._resumeRecording(), triggerType => this._activateTrigger(triggerType));
|
|
6861
|
+
// always have to check if the URL is blocked really early,
|
|
6862
|
+
// or you risk getting stuck in a loop
|
|
6863
|
+
if (this._urlTriggerMatching.urlBlocked && !isRecordingPausedEvent(rawEvent)) {
|
|
6864
|
+
return;
|
|
6865
|
+
}
|
|
6866
|
+
// we're processing a full snapshot, so we should reset the timer
|
|
6867
|
+
if (rawEvent.type === EventType$1.FullSnapshot) {
|
|
6868
|
+
this._scheduleFullSnapshot();
|
|
6869
|
+
// Full snapshots reset rrweb's node IDs, so clear any logged node tracking
|
|
6870
|
+
this._mutationThrottler?.reset();
|
|
6871
|
+
}
|
|
6872
|
+
// Clear the buffer if waiting for a trigger and only keep data from after the current full snapshot
|
|
6873
|
+
// we always start trigger pending so need to wait for flags before we know if we're really pending
|
|
6874
|
+
if (rawEvent.type === EventType$1.FullSnapshot && this._triggerMatching.triggerStatus(this.sessionId) === TRIGGER_PENDING) {
|
|
6875
|
+
this._clearBufferBeforeMostRecentMeta();
|
|
6876
|
+
}
|
|
6877
|
+
const throttledEvent = this._mutationThrottler ? this._mutationThrottler.throttleMutations(rawEvent) : rawEvent;
|
|
6878
|
+
if (!throttledEvent) {
|
|
6879
|
+
return;
|
|
6880
|
+
}
|
|
6881
|
+
// TODO: Re-add ensureMaxMessageSize once we are confident in it
|
|
6882
|
+
const event = truncateLargeConsoleLogs(throttledEvent);
|
|
6883
|
+
this._updateWindowAndSessionIds(event);
|
|
6884
|
+
// When in an idle state we keep recording but don't capture the events,
|
|
6885
|
+
// we don't want to return early if idle is 'unknown'
|
|
6886
|
+
if (this._isIdle === true && !isSessionIdleEvent(event)) {
|
|
6887
|
+
return;
|
|
6888
|
+
}
|
|
6889
|
+
if (isSessionIdleEvent(event)) {
|
|
6890
|
+
// session idle events have a timestamp when rrweb sees them
|
|
6891
|
+
// which can artificially lengthen a session
|
|
6892
|
+
// we know when we detected it based on the payload and can correct the timestamp
|
|
6893
|
+
const payload = event.data.payload;
|
|
6894
|
+
if (payload) {
|
|
6895
|
+
const lastActivity = payload.lastActivityTimestamp;
|
|
6896
|
+
const threshold = payload.threshold;
|
|
6897
|
+
event.timestamp = lastActivity + threshold;
|
|
6898
|
+
}
|
|
6899
|
+
}
|
|
6900
|
+
const eventToSend = this._instance.config.session_recording?.compress_events ?? true ? compressEvent(event) : event;
|
|
6901
|
+
const size = estimateSize(eventToSend);
|
|
6902
|
+
const properties = {
|
|
6903
|
+
$snapshot_bytes: size,
|
|
6904
|
+
$snapshot_data: eventToSend,
|
|
6905
|
+
$session_id: this._sessionId,
|
|
6906
|
+
$window_id: this._windowId
|
|
6907
|
+
};
|
|
6908
|
+
if (this.status === DISABLED) {
|
|
6909
|
+
this._clearBuffer();
|
|
6910
|
+
return;
|
|
6911
|
+
}
|
|
6912
|
+
this._captureSnapshotBuffered(properties);
|
|
6913
|
+
} catch (e) {
|
|
6914
|
+
logger.error('error processing rrweb event', e);
|
|
6856
6915
|
}
|
|
6857
|
-
this._captureSnapshotBuffered(properties);
|
|
6858
6916
|
}
|
|
6859
6917
|
get status() {
|
|
6860
6918
|
return this._statusMatcher({
|
|
@@ -6932,6 +6990,16 @@ var leanbase = (function () {
|
|
|
6932
6990
|
}, RECORDING_BUFFER_TIMEOUT);
|
|
6933
6991
|
return this._buffer;
|
|
6934
6992
|
}
|
|
6993
|
+
if (!this._canCaptureSnapshots()) {
|
|
6994
|
+
if (!this._loggedMissingEndpointFor) {
|
|
6995
|
+
this._loggedMissingEndpointFor = true;
|
|
6996
|
+
logger.warn('snapshot capture skipped because requestRouter.endpointFor is unavailable');
|
|
6997
|
+
}
|
|
6998
|
+
this._flushBufferTimer = setTimeout(() => {
|
|
6999
|
+
this._flushBuffer();
|
|
7000
|
+
}, RECORDING_BUFFER_TIMEOUT);
|
|
7001
|
+
return this._buffer;
|
|
7002
|
+
}
|
|
6935
7003
|
if (this._buffer.data.length > 0) {
|
|
6936
7004
|
const snapshotEvents = splitBuffer(this._buffer);
|
|
6937
7005
|
snapshotEvents.forEach(snapshotBuffer => {
|
|
@@ -6964,13 +7032,25 @@ var leanbase = (function () {
|
|
|
6964
7032
|
}
|
|
6965
7033
|
}
|
|
6966
7034
|
_captureSnapshot(properties) {
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
7035
|
+
const url = this._snapshotIngestionUrl();
|
|
7036
|
+
if (!url) {
|
|
7037
|
+
if (!this._loggedMissingEndpointFor) {
|
|
7038
|
+
this._loggedMissingEndpointFor = true;
|
|
7039
|
+
logger.warn('snapshot capture skipped because requestRouter.endpointFor is unavailable');
|
|
7040
|
+
}
|
|
7041
|
+
return;
|
|
7042
|
+
}
|
|
7043
|
+
try {
|
|
7044
|
+
// :TRICKY: Make sure we batch these requests, use a custom endpoint and don't truncate the strings.
|
|
7045
|
+
this._instance.capture('$snapshot', properties, {
|
|
7046
|
+
_url: url,
|
|
7047
|
+
_noTruncate: true,
|
|
7048
|
+
_batchKey: SESSION_RECORDING_BATCH_KEY,
|
|
7049
|
+
skip_client_rate_limiting: true
|
|
7050
|
+
});
|
|
7051
|
+
} catch (e) {
|
|
7052
|
+
logger.error('failed to capture snapshot', e);
|
|
7053
|
+
}
|
|
6974
7054
|
}
|
|
6975
7055
|
_snapshotUrl() {
|
|
6976
7056
|
const host = this._instance.config.host || '';
|
|
@@ -7240,13 +7320,23 @@ var leanbase = (function () {
|
|
|
7240
7320
|
}
|
|
7241
7321
|
});
|
|
7242
7322
|
const activePlugins = this._gatherRRWebPlugins();
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7323
|
+
try {
|
|
7324
|
+
this._stopRrweb = rrwebRecord({
|
|
7325
|
+
emit: event => {
|
|
7326
|
+
try {
|
|
7327
|
+
this.onRRwebEmit(event);
|
|
7328
|
+
} catch (e) {
|
|
7329
|
+
logger.error('error in rrweb emit handler', e);
|
|
7330
|
+
}
|
|
7331
|
+
},
|
|
7332
|
+
plugins: activePlugins,
|
|
7333
|
+
...sessionRecordingOptions
|
|
7334
|
+
});
|
|
7335
|
+
} catch (e) {
|
|
7336
|
+
logger.error('failed to start rrweb recorder', e);
|
|
7337
|
+
this._stopRrweb = undefined;
|
|
7338
|
+
return;
|
|
7339
|
+
}
|
|
7250
7340
|
// We reset the last activity timestamp, resetting the idle timer
|
|
7251
7341
|
this._lastActivityTimestamp = Date.now();
|
|
7252
7342
|
// stay unknown if we're not sure if we're idle or not
|
|
@@ -7432,18 +7522,25 @@ var leanbase = (function () {
|
|
|
7432
7522
|
// If extensions provide an init function, use it. Otherwise, fall back to the local LazyLoadedSessionRecording
|
|
7433
7523
|
if (assignableWindow.__PosthogExtensions__?.initSessionRecording) {
|
|
7434
7524
|
if (!this._lazyLoadedSessionRecording) {
|
|
7435
|
-
|
|
7436
|
-
|
|
7525
|
+
const maybeRecording = assignableWindow.__PosthogExtensions__?.initSessionRecording(this._instance);
|
|
7526
|
+
if (maybeRecording && typeof maybeRecording.start === 'function') {
|
|
7527
|
+
this._lazyLoadedSessionRecording = maybeRecording;
|
|
7528
|
+
this._lazyLoadedSessionRecording._forceAllowLocalhostNetworkCapture = this._forceAllowLocalhostNetworkCapture;
|
|
7529
|
+
} else {
|
|
7530
|
+
log.warn('initSessionRecording was present but did not return a recorder instance; falling back to local recorder');
|
|
7531
|
+
}
|
|
7437
7532
|
}
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
maybePromise
|
|
7533
|
+
if (this._lazyLoadedSessionRecording) {
|
|
7534
|
+
try {
|
|
7535
|
+
const maybePromise = this._lazyLoadedSessionRecording.start(startReason);
|
|
7536
|
+
if (maybePromise && typeof maybePromise.catch === 'function') {
|
|
7537
|
+
maybePromise.catch(e => logger$2.error('error starting session recording', e));
|
|
7538
|
+
}
|
|
7539
|
+
} catch (e) {
|
|
7540
|
+
logger$2.error('error starting session recording', e);
|
|
7442
7541
|
}
|
|
7443
|
-
|
|
7444
|
-
logger$2.error('error starting session recording', e);
|
|
7542
|
+
return;
|
|
7445
7543
|
}
|
|
7446
|
-
return;
|
|
7447
7544
|
}
|
|
7448
7545
|
if (!this._lazyLoadedSessionRecording) {
|
|
7449
7546
|
this._lazyLoadedSessionRecording = new LazyLoadedSessionRecording(this._instance);
|
|
@@ -7558,6 +7655,10 @@ var leanbase = (function () {
|
|
|
7558
7655
|
token
|
|
7559
7656
|
});
|
|
7560
7657
|
super(token, mergedConfig);
|
|
7658
|
+
this._remoteConfigLoadAttempted = false;
|
|
7659
|
+
this._remoteConfigResolved = false;
|
|
7660
|
+
this._featureFlagsResolved = false;
|
|
7661
|
+
this._maybeStartedSessionRecording = false;
|
|
7561
7662
|
this.personProcessingSetOncePropertiesSent = false;
|
|
7562
7663
|
this.isLoaded = false;
|
|
7563
7664
|
this.config = mergedConfig;
|
|
@@ -7581,10 +7682,20 @@ var leanbase = (function () {
|
|
|
7581
7682
|
this.replayAutocapture.startIfEnabled();
|
|
7582
7683
|
if (this.sessionManager && this.config.cookieless_mode !== 'always') {
|
|
7583
7684
|
this.sessionRecording = new SessionRecording(this);
|
|
7584
|
-
this.sessionRecording.startIfEnabledOrStop();
|
|
7585
7685
|
}
|
|
7686
|
+
// Start session recording only once flags + remote config have been resolved.
|
|
7687
|
+
// This matches the PostHog browser SDK where replay activation is driven by remote config and flags.
|
|
7586
7688
|
if (this.config.preloadFeatureFlags !== false) {
|
|
7587
|
-
this.reloadFeatureFlags(
|
|
7689
|
+
this.reloadFeatureFlags({
|
|
7690
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
7691
|
+
cb: _err => {
|
|
7692
|
+
this._featureFlagsResolved = true;
|
|
7693
|
+
this._maybeStartSessionRecording();
|
|
7694
|
+
}
|
|
7695
|
+
});
|
|
7696
|
+
} else {
|
|
7697
|
+
// If feature flags preload is explicitly disabled, treat this requirement as satisfied.
|
|
7698
|
+
this._featureFlagsResolved = true;
|
|
7588
7699
|
}
|
|
7589
7700
|
this.config.loaded?.(this);
|
|
7590
7701
|
if (this.config.capture_pageview) {
|
|
@@ -7594,9 +7705,26 @@ var leanbase = (function () {
|
|
|
7594
7705
|
}
|
|
7595
7706
|
}, 1);
|
|
7596
7707
|
}
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
|
|
7708
|
+
const triggerRemoteConfigLoad = reason => {
|
|
7709
|
+
logger$2.info(`remote config load triggered via ${reason}`);
|
|
7710
|
+
void this.loadRemoteConfig();
|
|
7711
|
+
};
|
|
7712
|
+
if (document$1) {
|
|
7713
|
+
if (document$1.readyState === 'loading') {
|
|
7714
|
+
logger$2.info('remote config load deferred until DOMContentLoaded');
|
|
7715
|
+
const onDomReady = () => {
|
|
7716
|
+
document$1?.removeEventListener('DOMContentLoaded', onDomReady);
|
|
7717
|
+
triggerRemoteConfigLoad('dom');
|
|
7718
|
+
};
|
|
7719
|
+
addEventListener(document$1, 'DOMContentLoaded', onDomReady, {
|
|
7720
|
+
once: true
|
|
7721
|
+
});
|
|
7722
|
+
} else {
|
|
7723
|
+
triggerRemoteConfigLoad('immediate');
|
|
7724
|
+
}
|
|
7725
|
+
} else {
|
|
7726
|
+
triggerRemoteConfigLoad('no-document');
|
|
7727
|
+
}
|
|
7600
7728
|
addEventListener(window, 'onpagehide' in self ? 'pagehide' : 'unload', this.capturePageLeave.bind(this), {
|
|
7601
7729
|
passive: false
|
|
7602
7730
|
});
|
|
@@ -7633,11 +7761,19 @@ var leanbase = (function () {
|
|
|
7633
7761
|
}
|
|
7634
7762
|
}
|
|
7635
7763
|
async loadRemoteConfig() {
|
|
7636
|
-
if (
|
|
7764
|
+
if (this._remoteConfigLoadAttempted) {
|
|
7765
|
+
return;
|
|
7766
|
+
}
|
|
7767
|
+
this._remoteConfigLoadAttempted = true;
|
|
7768
|
+
try {
|
|
7637
7769
|
const remoteConfig = await this.reloadRemoteConfigAsync();
|
|
7638
7770
|
if (remoteConfig) {
|
|
7639
7771
|
this.onRemoteConfig(remoteConfig);
|
|
7640
7772
|
}
|
|
7773
|
+
} finally {
|
|
7774
|
+
// Regardless of success/failure, we consider remote config "resolved" so replay isn't blocked forever.
|
|
7775
|
+
this._remoteConfigResolved = true;
|
|
7776
|
+
this._maybeStartSessionRecording();
|
|
7641
7777
|
}
|
|
7642
7778
|
}
|
|
7643
7779
|
onRemoteConfig(config) {
|
|
@@ -7650,6 +7786,26 @@ var leanbase = (function () {
|
|
|
7650
7786
|
this.isRemoteConfigLoaded = true;
|
|
7651
7787
|
this.replayAutocapture?.onRemoteConfig(config);
|
|
7652
7788
|
this.sessionRecording?.onRemoteConfig(config);
|
|
7789
|
+
// Remote config has been applied; allow replay start if flags are also ready.
|
|
7790
|
+
this._remoteConfigResolved = true;
|
|
7791
|
+
this._maybeStartSessionRecording();
|
|
7792
|
+
}
|
|
7793
|
+
_maybeStartSessionRecording() {
|
|
7794
|
+
if (this._maybeStartedSessionRecording) {
|
|
7795
|
+
return;
|
|
7796
|
+
}
|
|
7797
|
+
if (!this.sessionRecording) {
|
|
7798
|
+
return;
|
|
7799
|
+
}
|
|
7800
|
+
if (!this._featureFlagsResolved || !this._remoteConfigResolved) {
|
|
7801
|
+
return;
|
|
7802
|
+
}
|
|
7803
|
+
this._maybeStartedSessionRecording = true;
|
|
7804
|
+
try {
|
|
7805
|
+
this.sessionRecording.startIfEnabledOrStop();
|
|
7806
|
+
} catch (e) {
|
|
7807
|
+
logger$2.error('Failed to start session recording', e);
|
|
7808
|
+
}
|
|
7653
7809
|
}
|
|
7654
7810
|
fetch(url, options) {
|
|
7655
7811
|
const fetchFn = getFetch();
|