@leanbase-giangnd/js 0.3.0 → 0.3.2
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 +154 -189
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +58 -81
- package/dist/index.mjs +154 -189
- package/dist/index.mjs.map +1 -1
- package/dist/leanbase.iife.js +154 -189
- package/dist/leanbase.iife.js.map +1 -1
- package/package.json +1 -1
- package/src/extensions/replay/extension-shim.ts +1 -114
- package/src/extensions/replay/external/lazy-loaded-session-recorder.ts +130 -38
- package/src/extensions/replay/external/mutation-throttler.ts +1 -4
- package/src/extensions/replay/session-recording.ts +0 -59
- package/src/leanbase.ts +4 -1
- package/src/utils/request-router.ts +39 -0
- package/src/version.ts +1 -1
package/dist/leanbase.iife.js
CHANGED
|
@@ -2846,7 +2846,7 @@ var leanbase = (function () {
|
|
|
2846
2846
|
}
|
|
2847
2847
|
};
|
|
2848
2848
|
|
|
2849
|
-
var version = "0.3.
|
|
2849
|
+
var version = "0.3.2";
|
|
2850
2850
|
var packageInfo = {
|
|
2851
2851
|
version: version};
|
|
2852
2852
|
|
|
@@ -4785,117 +4785,20 @@ var leanbase = (function () {
|
|
|
4785
4785
|
};
|
|
4786
4786
|
|
|
4787
4787
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
4788
|
-
// We avoid importing '@rrweb/record' at module load time to prevent IIFE builds
|
|
4789
|
-
// from requiring a top-level global. Instead, expose a lazy proxy that will
|
|
4790
|
-
// dynamically import the module the first time it's used.
|
|
4791
|
-
let _cachedRRWeb = null;
|
|
4792
|
-
async function _loadRRWebModule() {
|
|
4793
|
-
if (_cachedRRWeb) return _cachedRRWeb;
|
|
4794
|
-
try {
|
|
4795
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4796
|
-
const mod = await Promise.resolve().then(function () { return record$1; });
|
|
4797
|
-
_cachedRRWeb = mod;
|
|
4798
|
-
return _cachedRRWeb;
|
|
4799
|
-
} catch (e) {
|
|
4800
|
-
return null;
|
|
4801
|
-
}
|
|
4802
|
-
}
|
|
4803
|
-
// queue for method calls before rrweb loads
|
|
4804
|
-
const _queuedCalls = [];
|
|
4805
|
-
// Create a proxy function that delegates to the real rrweb.record when called
|
|
4806
|
-
const rrwebRecordProxy = function (...args) {
|
|
4807
|
-
let realStop;
|
|
4808
|
-
let calledReal = false;
|
|
4809
|
-
// Start loading asynchronously and call the real record when available
|
|
4810
|
-
void (async () => {
|
|
4811
|
-
const mod = await _loadRRWebModule();
|
|
4812
|
-
const real = mod && (mod.record ?? mod.default?.record);
|
|
4813
|
-
if (real) {
|
|
4814
|
-
try {
|
|
4815
|
-
calledReal = true;
|
|
4816
|
-
realStop = real(...args);
|
|
4817
|
-
// flush any queued calls that were waiting for rrweb
|
|
4818
|
-
while (_queuedCalls.length) {
|
|
4819
|
-
try {
|
|
4820
|
-
const fn = _queuedCalls.shift();
|
|
4821
|
-
fn();
|
|
4822
|
-
} catch (e) {
|
|
4823
|
-
// ignore
|
|
4824
|
-
}
|
|
4825
|
-
}
|
|
4826
|
-
} catch (e) {
|
|
4827
|
-
// ignore
|
|
4828
|
-
}
|
|
4829
|
-
}
|
|
4830
|
-
})();
|
|
4831
|
-
// return a stop function that will call the real stop when available
|
|
4832
|
-
return () => {
|
|
4833
|
-
if (realStop) {
|
|
4834
|
-
try {
|
|
4835
|
-
realStop();
|
|
4836
|
-
} catch (e) {
|
|
4837
|
-
// ignore
|
|
4838
|
-
}
|
|
4839
|
-
} else if (!calledReal) {
|
|
4840
|
-
// If rrweb hasn't been initialised yet, queue a stop request that will
|
|
4841
|
-
// call the real stop once available.
|
|
4842
|
-
_queuedCalls.push(() => {
|
|
4843
|
-
try {
|
|
4844
|
-
realStop?.();
|
|
4845
|
-
} catch (e) {
|
|
4846
|
-
// ignore
|
|
4847
|
-
}
|
|
4848
|
-
});
|
|
4849
|
-
}
|
|
4850
|
-
};
|
|
4851
|
-
};
|
|
4852
|
-
// methods that can be called on the rrweb.record object - queue until real module is available
|
|
4853
|
-
rrwebRecordProxy.addCustomEvent = function (tag, payload) {
|
|
4854
|
-
const call = () => {
|
|
4855
|
-
try {
|
|
4856
|
-
const real = _cachedRRWeb && (_cachedRRWeb.record ?? _cachedRRWeb.default?.record);
|
|
4857
|
-
real?.addCustomEvent?.(tag, payload);
|
|
4858
|
-
} catch (e) {
|
|
4859
|
-
// ignore
|
|
4860
|
-
}
|
|
4861
|
-
};
|
|
4862
|
-
if (_cachedRRWeb) call();else _queuedCalls.push(call);
|
|
4863
|
-
};
|
|
4864
|
-
rrwebRecordProxy.takeFullSnapshot = function () {
|
|
4865
|
-
const call = () => {
|
|
4866
|
-
try {
|
|
4867
|
-
const real = _cachedRRWeb && (_cachedRRWeb.record ?? _cachedRRWeb.default?.record);
|
|
4868
|
-
real?.takeFullSnapshot?.();
|
|
4869
|
-
} catch (e) {
|
|
4870
|
-
// ignore
|
|
4871
|
-
}
|
|
4872
|
-
};
|
|
4873
|
-
if (_cachedRRWeb) call();else _queuedCalls.push(call);
|
|
4874
|
-
};
|
|
4875
|
-
// Use a safe global target (prefer `win`, fallback to globalThis)
|
|
4876
4788
|
const _target = win ?? globalThis;
|
|
4877
4789
|
_target.__PosthogExtensions__ = _target.__PosthogExtensions__ || {};
|
|
4878
|
-
|
|
4879
|
-
// builds that execute this file don't require rrweb at module evaluation time.
|
|
4880
|
-
_target.__PosthogExtensions__.rrweb = _target.__PosthogExtensions__.rrweb || {
|
|
4881
|
-
record: rrwebRecordProxy
|
|
4882
|
-
};
|
|
4883
|
-
// Provide initSessionRecording if not present — return a new LazyLoadedSessionRecording when called
|
|
4790
|
+
_target.__PosthogExtensions__.rrweb = _target.__PosthogExtensions__.rrweb || {};
|
|
4884
4791
|
_target.__PosthogExtensions__.initSessionRecording = _target.__PosthogExtensions__.initSessionRecording || (instance => {
|
|
4885
4792
|
const factory = _target.__PosthogExtensions__._initSessionRecordingFactory;
|
|
4886
4793
|
if (factory) {
|
|
4887
4794
|
return factory(instance);
|
|
4888
4795
|
}
|
|
4889
|
-
// If no factory is registered yet, return undefined — callers should handle lazy-loading.
|
|
4890
4796
|
return undefined;
|
|
4891
4797
|
});
|
|
4892
|
-
// Provide a no-op loadExternalDependency that calls the callback immediately (since rrweb is bundled)
|
|
4893
4798
|
_target.__PosthogExtensions__.loadExternalDependency = _target.__PosthogExtensions__.loadExternalDependency || ((instance, scriptName, cb) => {
|
|
4894
4799
|
if (cb) cb(undefined);
|
|
4895
4800
|
});
|
|
4896
|
-
// Provide rrwebPlugins object with network plugin factory if not present
|
|
4897
4801
|
_target.__PosthogExtensions__.rrwebPlugins = _target.__PosthogExtensions__.rrwebPlugins || {};
|
|
4898
|
-
// Default to undefined; the lazy-loaded recorder will register the real factory when it initializes.
|
|
4899
4802
|
_target.__PosthogExtensions__.rrwebPlugins.getRecordNetworkPlugin = _target.__PosthogExtensions__.rrwebPlugins.getRecordNetworkPlugin || (() => undefined);
|
|
4900
4803
|
|
|
4901
4804
|
// Type definitions copied from @rrweb/types@2.0.0-alpha.17 and rrweb-snapshot@2.0.0-alpha.17
|
|
@@ -6150,10 +6053,8 @@ var leanbase = (function () {
|
|
|
6150
6053
|
return event;
|
|
6151
6054
|
}
|
|
6152
6055
|
};
|
|
6153
|
-
const configuredBucketSize = this._options.bucketSize ?? 100;
|
|
6154
|
-
const effectiveBucketSize = Math.max(configuredBucketSize - 1, 1);
|
|
6155
6056
|
this._rateLimiter = new BucketedRateLimiter({
|
|
6156
|
-
bucketSize:
|
|
6057
|
+
bucketSize: this._options.bucketSize ?? 100,
|
|
6157
6058
|
refillRate: this._options.refillRate ?? 10,
|
|
6158
6059
|
refillInterval: 1000,
|
|
6159
6060
|
// one second
|
|
@@ -6349,6 +6250,23 @@ var leanbase = (function () {
|
|
|
6349
6250
|
get sessionId() {
|
|
6350
6251
|
return this._sessionId;
|
|
6351
6252
|
}
|
|
6253
|
+
_disablePermanently(reason, error) {
|
|
6254
|
+
this._permanentlyDisabled = true;
|
|
6255
|
+
this._isFullyReady = false;
|
|
6256
|
+
this._mutationThrottler?.stop();
|
|
6257
|
+
this._mutationThrottler = undefined;
|
|
6258
|
+
this._queuedRRWebEvents = [];
|
|
6259
|
+
this._recording = undefined;
|
|
6260
|
+
this._stopRrweb = undefined;
|
|
6261
|
+
if (!this._loggedPermanentlyDisabled) {
|
|
6262
|
+
this._loggedPermanentlyDisabled = true;
|
|
6263
|
+
if (error) {
|
|
6264
|
+
logger.error(`replay disabled: ${reason}`, error);
|
|
6265
|
+
} else {
|
|
6266
|
+
logger.error(`replay disabled: ${reason}`);
|
|
6267
|
+
}
|
|
6268
|
+
}
|
|
6269
|
+
}
|
|
6352
6270
|
get _sessionManager() {
|
|
6353
6271
|
if (!this._instance.sessionManager) {
|
|
6354
6272
|
throw new Error(LOGGER_PREFIX$1 + ' must be started with a valid sessionManager.');
|
|
@@ -6380,6 +6298,9 @@ var leanbase = (function () {
|
|
|
6380
6298
|
*/
|
|
6381
6299
|
this._forceAllowLocalhostNetworkCapture = false;
|
|
6382
6300
|
this._stopRrweb = undefined;
|
|
6301
|
+
this._permanentlyDisabled = false;
|
|
6302
|
+
this._loggedPermanentlyDisabled = false;
|
|
6303
|
+
this._hasReportedRecordingInitialized = false;
|
|
6383
6304
|
this._lastActivityTimestamp = Date.now();
|
|
6384
6305
|
/**
|
|
6385
6306
|
* and a queue - that contains rrweb events that we want to send to rrweb, but rrweb wasn't able to accept them yet
|
|
@@ -6569,6 +6490,9 @@ var leanbase = (function () {
|
|
|
6569
6490
|
}
|
|
6570
6491
|
}
|
|
6571
6492
|
_tryAddCustomEvent(tag, payload) {
|
|
6493
|
+
if (!this.isStarted || !this._recording) {
|
|
6494
|
+
return false;
|
|
6495
|
+
}
|
|
6572
6496
|
const rrwebRecord = getRRWebRecord();
|
|
6573
6497
|
if (!rrwebRecord || typeof rrwebRecord.addCustomEvent !== 'function') {
|
|
6574
6498
|
return false;
|
|
@@ -6598,6 +6522,10 @@ var leanbase = (function () {
|
|
|
6598
6522
|
}
|
|
6599
6523
|
}
|
|
6600
6524
|
_processQueuedEvents() {
|
|
6525
|
+
if (!this.isStarted || !this._recording) {
|
|
6526
|
+
this._queuedRRWebEvents = [];
|
|
6527
|
+
return;
|
|
6528
|
+
}
|
|
6601
6529
|
if (this._queuedRRWebEvents.length) {
|
|
6602
6530
|
// if rrweb isn't ready to accept events earlier, then we queued them up.
|
|
6603
6531
|
// now that `emit` has been called rrweb should be ready to accept them.
|
|
@@ -6619,6 +6547,9 @@ var leanbase = (function () {
|
|
|
6619
6547
|
}
|
|
6620
6548
|
}
|
|
6621
6549
|
_tryTakeFullSnapshot() {
|
|
6550
|
+
if (!this.isStarted || !this._recording) {
|
|
6551
|
+
return false;
|
|
6552
|
+
}
|
|
6622
6553
|
const rrwebRecord = getRRWebRecord();
|
|
6623
6554
|
if (!rrwebRecord || typeof rrwebRecord.takeFullSnapshot !== 'function') {
|
|
6624
6555
|
return false;
|
|
@@ -6632,6 +6563,9 @@ var leanbase = (function () {
|
|
|
6632
6563
|
return this._instance.config.session_recording?.full_snapshot_interval_millis ?? FIVE_MINUTES;
|
|
6633
6564
|
}
|
|
6634
6565
|
_scheduleFullSnapshot() {
|
|
6566
|
+
if (!this.isStarted || !this._recording) {
|
|
6567
|
+
return;
|
|
6568
|
+
}
|
|
6635
6569
|
if (this._fullSnapshotTimer) {
|
|
6636
6570
|
clearInterval(this._fullSnapshotTimer);
|
|
6637
6571
|
}
|
|
@@ -6648,6 +6582,9 @@ var leanbase = (function () {
|
|
|
6648
6582
|
}, interval);
|
|
6649
6583
|
}
|
|
6650
6584
|
_pauseRecording() {
|
|
6585
|
+
if (!this.isStarted || !this._recording) {
|
|
6586
|
+
return;
|
|
6587
|
+
}
|
|
6651
6588
|
// we check _urlBlocked not status, since more than one thing can affect status
|
|
6652
6589
|
if (this._urlTriggerMatching.urlBlocked) {
|
|
6653
6590
|
return;
|
|
@@ -6665,6 +6602,9 @@ var leanbase = (function () {
|
|
|
6665
6602
|
});
|
|
6666
6603
|
}
|
|
6667
6604
|
_resumeRecording() {
|
|
6605
|
+
if (!this.isStarted || !this._recording) {
|
|
6606
|
+
return;
|
|
6607
|
+
}
|
|
6668
6608
|
// we check _urlBlocked not status, since more than one thing can affect status
|
|
6669
6609
|
if (!this._urlTriggerMatching.urlBlocked) {
|
|
6670
6610
|
return;
|
|
@@ -6678,6 +6618,9 @@ var leanbase = (function () {
|
|
|
6678
6618
|
logger.info('recording resumed');
|
|
6679
6619
|
}
|
|
6680
6620
|
_activateTrigger(triggerType) {
|
|
6621
|
+
if (!this.isStarted || !this._recording || !this._isFullyReady) {
|
|
6622
|
+
return;
|
|
6623
|
+
}
|
|
6681
6624
|
if (this._triggerMatching.triggerStatus(this.sessionId) === TRIGGER_PENDING) {
|
|
6682
6625
|
// status is stored separately for URL and event triggers
|
|
6683
6626
|
this._instance?.persistence?.register({
|
|
@@ -6688,7 +6631,7 @@ var leanbase = (function () {
|
|
|
6688
6631
|
}
|
|
6689
6632
|
}
|
|
6690
6633
|
get isStarted() {
|
|
6691
|
-
return !!this.
|
|
6634
|
+
return !!this._recording?.stop;
|
|
6692
6635
|
}
|
|
6693
6636
|
get _remoteConfig() {
|
|
6694
6637
|
const persistedConfig = this._instance.get_property(SESSION_RECORDING_REMOTE_CONFIG);
|
|
@@ -6699,6 +6642,9 @@ var leanbase = (function () {
|
|
|
6699
6642
|
return parsedConfig;
|
|
6700
6643
|
}
|
|
6701
6644
|
async start(startReason) {
|
|
6645
|
+
if (this._permanentlyDisabled) {
|
|
6646
|
+
return;
|
|
6647
|
+
}
|
|
6702
6648
|
this._isFullyReady = false;
|
|
6703
6649
|
const config = this._remoteConfig;
|
|
6704
6650
|
if (!config) {
|
|
@@ -6724,8 +6670,14 @@ var leanbase = (function () {
|
|
|
6724
6670
|
});
|
|
6725
6671
|
this._urlTriggerMatching.onConfig(config);
|
|
6726
6672
|
this._eventTriggerMatching.onConfig(config);
|
|
6727
|
-
|
|
6728
|
-
this.
|
|
6673
|
+
// Start rrweb first; only once we have a valid recorder do we install any listeners/timers.
|
|
6674
|
+
await this._startRecorder();
|
|
6675
|
+
// If rrweb failed to load/start, do not proceed further.
|
|
6676
|
+
// This prevents installing listeners that assume rrweb is active.
|
|
6677
|
+
if (!this.isStarted) {
|
|
6678
|
+
return;
|
|
6679
|
+
}
|
|
6680
|
+
// Now that rrweb has started, we can safely install replay side-effects.
|
|
6729
6681
|
this._linkedFlagMatching.onConfig(config, (flag, variant) => {
|
|
6730
6682
|
this._reportStarted('linked_flag_matched', {
|
|
6731
6683
|
flag,
|
|
@@ -6733,12 +6685,8 @@ var leanbase = (function () {
|
|
|
6733
6685
|
});
|
|
6734
6686
|
});
|
|
6735
6687
|
this._makeSamplingDecision(this.sessionId);
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
// This prevents installing listeners that assume rrweb is active.
|
|
6739
|
-
if (!this.isStarted) {
|
|
6740
|
-
return;
|
|
6741
|
-
}
|
|
6688
|
+
this._removeEventTriggerCaptureHook?.();
|
|
6689
|
+
this._addEventTriggerListener();
|
|
6742
6690
|
// Only start processing rrweb emits once the ingestion endpoint is available.
|
|
6743
6691
|
// If it isn't available, we must degrade to a no-op (never crash the host app).
|
|
6744
6692
|
this._isFullyReady = this._canCaptureSnapshots();
|
|
@@ -6788,7 +6736,13 @@ var leanbase = (function () {
|
|
|
6788
6736
|
});
|
|
6789
6737
|
}
|
|
6790
6738
|
if (this.status === ACTIVE) {
|
|
6791
|
-
|
|
6739
|
+
const reason = startReason || 'recording_initialized';
|
|
6740
|
+
if (reason !== 'recording_initialized' || !this._hasReportedRecordingInitialized) {
|
|
6741
|
+
if (reason === 'recording_initialized') {
|
|
6742
|
+
this._hasReportedRecordingInitialized = true;
|
|
6743
|
+
}
|
|
6744
|
+
this._reportStarted(reason);
|
|
6745
|
+
}
|
|
6792
6746
|
}
|
|
6793
6747
|
}
|
|
6794
6748
|
stop() {
|
|
@@ -6817,9 +6771,11 @@ var leanbase = (function () {
|
|
|
6817
6771
|
this._mutationThrottler?.stop();
|
|
6818
6772
|
// Clear any queued rrweb events to prevent memory leaks from closures
|
|
6819
6773
|
this._queuedRRWebEvents = [];
|
|
6820
|
-
this.
|
|
6774
|
+
this._recording?.stop?.();
|
|
6775
|
+
this._recording = undefined;
|
|
6821
6776
|
this._stopRrweb = undefined;
|
|
6822
6777
|
this._isFullyReady = false;
|
|
6778
|
+
this._hasReportedRecordingInitialized = false;
|
|
6823
6779
|
logger.info('stopped');
|
|
6824
6780
|
}
|
|
6825
6781
|
_snapshotIngestionUrl() {
|
|
@@ -6837,8 +6793,12 @@ var leanbase = (function () {
|
|
|
6837
6793
|
return !!this._snapshotIngestionUrl();
|
|
6838
6794
|
}
|
|
6839
6795
|
onRRwebEmit(rawEvent) {
|
|
6796
|
+
// First-line invariant gate: drop everything unless replay is truly started.
|
|
6797
|
+
if (!this.isStarted || !this._recording) {
|
|
6798
|
+
return;
|
|
6799
|
+
}
|
|
6840
6800
|
// Never process rrweb emits until we're fully ready.
|
|
6841
|
-
if (!this._isFullyReady
|
|
6801
|
+
if (!this._isFullyReady) {
|
|
6842
6802
|
return;
|
|
6843
6803
|
}
|
|
6844
6804
|
try {
|
|
@@ -6943,6 +6903,9 @@ var leanbase = (function () {
|
|
|
6943
6903
|
});
|
|
6944
6904
|
}
|
|
6945
6905
|
overrideLinkedFlag() {
|
|
6906
|
+
if (!this.isStarted || !this._recording || !this._isFullyReady) {
|
|
6907
|
+
return;
|
|
6908
|
+
}
|
|
6946
6909
|
this._linkedFlagMatching.linkedFlagSeen = true;
|
|
6947
6910
|
this._tryTakeFullSnapshot();
|
|
6948
6911
|
this._reportStarted('linked_flag_overridden');
|
|
@@ -6954,6 +6917,9 @@ var leanbase = (function () {
|
|
|
6954
6917
|
* instead call `posthog.startSessionRecording({sampling: true})`
|
|
6955
6918
|
* */
|
|
6956
6919
|
overrideSampling() {
|
|
6920
|
+
if (!this.isStarted || !this._recording || !this._isFullyReady) {
|
|
6921
|
+
return;
|
|
6922
|
+
}
|
|
6957
6923
|
this._instance.persistence?.register({
|
|
6958
6924
|
// short-circuits the `makeSamplingDecision` function in the session recording module
|
|
6959
6925
|
[SESSION_RECORDING_IS_SAMPLED]: this.sessionId
|
|
@@ -6968,6 +6934,9 @@ var leanbase = (function () {
|
|
|
6968
6934
|
* instead call `posthog.startSessionRecording({trigger: 'url' | 'event'})`
|
|
6969
6935
|
* */
|
|
6970
6936
|
overrideTrigger(triggerType) {
|
|
6937
|
+
if (!this.isStarted || !this._recording || !this._isFullyReady) {
|
|
6938
|
+
return;
|
|
6939
|
+
}
|
|
6971
6940
|
this._activateTrigger(triggerType);
|
|
6972
6941
|
}
|
|
6973
6942
|
_clearFlushBufferTimer() {
|
|
@@ -7100,10 +7069,18 @@ var leanbase = (function () {
|
|
|
7100
7069
|
return this._buffer;
|
|
7101
7070
|
}
|
|
7102
7071
|
_reportStarted(startReason, tagPayload) {
|
|
7072
|
+
if (!this.isStarted || !this._recording) {
|
|
7073
|
+
return;
|
|
7074
|
+
}
|
|
7103
7075
|
this._instance.registerForSession({
|
|
7104
7076
|
$session_recording_start_reason: startReason
|
|
7105
7077
|
});
|
|
7106
|
-
|
|
7078
|
+
const message = startReason.replace('_', ' ');
|
|
7079
|
+
if (typeof tagPayload === 'undefined') {
|
|
7080
|
+
logger.info(message);
|
|
7081
|
+
} else {
|
|
7082
|
+
logger.info(message, tagPayload);
|
|
7083
|
+
}
|
|
7107
7084
|
if (!includes(['recording_initialized', 'session_id_changed'], startReason)) {
|
|
7108
7085
|
this._tryAddCustomEvent(startReason, tagPayload);
|
|
7109
7086
|
}
|
|
@@ -7243,7 +7220,7 @@ var leanbase = (function () {
|
|
|
7243
7220
|
};
|
|
7244
7221
|
}
|
|
7245
7222
|
async _startRecorder() {
|
|
7246
|
-
if (this.
|
|
7223
|
+
if (this._permanentlyDisabled || this._recording) {
|
|
7247
7224
|
return;
|
|
7248
7225
|
}
|
|
7249
7226
|
// rrweb config info: https://github.com/rrweb-io/rrweb/blob/7d5d0033258d6c29599fb08412202d9a2c7b9413/src/record/index.ts#L28
|
|
@@ -7305,27 +7282,18 @@ var leanbase = (function () {
|
|
|
7305
7282
|
rrwebRecord = loaded ?? undefined;
|
|
7306
7283
|
}
|
|
7307
7284
|
if (!rrwebRecord) {
|
|
7308
|
-
|
|
7285
|
+
this._disablePermanently('rrweb record function unavailable');
|
|
7309
7286
|
return;
|
|
7310
7287
|
}
|
|
7311
|
-
this._mutationThrottler = this._mutationThrottler ?? new MutationThrottler(rrwebRecord, {
|
|
7312
|
-
refillRate: this._instance.config.session_recording?.__mutationThrottlerRefillRate,
|
|
7313
|
-
bucketSize: this._instance.config.session_recording?.__mutationThrottlerBucketSize,
|
|
7314
|
-
onBlockedNode: (id, node) => {
|
|
7315
|
-
const message = `Too many mutations on node '${id}'. Rate limiting. This could be due to SVG animations or something similar`;
|
|
7316
|
-
logger.info(message, {
|
|
7317
|
-
node: node
|
|
7318
|
-
});
|
|
7319
|
-
this.log(LOGGER_PREFIX$1 + ' ' + message, 'warn');
|
|
7320
|
-
}
|
|
7321
|
-
});
|
|
7322
7288
|
const activePlugins = this._gatherRRWebPlugins();
|
|
7289
|
+
let stopHandler;
|
|
7323
7290
|
try {
|
|
7324
|
-
|
|
7291
|
+
stopHandler = rrwebRecord({
|
|
7325
7292
|
emit: event => {
|
|
7326
7293
|
try {
|
|
7327
7294
|
this.onRRwebEmit(event);
|
|
7328
7295
|
} catch (e) {
|
|
7296
|
+
// never throw from rrweb emit handler
|
|
7329
7297
|
logger.error('error in rrweb emit handler', e);
|
|
7330
7298
|
}
|
|
7331
7299
|
},
|
|
@@ -7333,10 +7301,30 @@ var leanbase = (function () {
|
|
|
7333
7301
|
...sessionRecordingOptions
|
|
7334
7302
|
});
|
|
7335
7303
|
} catch (e) {
|
|
7336
|
-
|
|
7337
|
-
|
|
7304
|
+
this._disablePermanently('rrweb recorder threw during initialization', e);
|
|
7305
|
+
return;
|
|
7306
|
+
}
|
|
7307
|
+
if (typeof stopHandler !== 'function') {
|
|
7308
|
+
this._disablePermanently('rrweb recorder returned an invalid stop handler');
|
|
7338
7309
|
return;
|
|
7339
7310
|
}
|
|
7311
|
+
// Mark replay started only after rrweb has successfully returned a valid stop handler.
|
|
7312
|
+
this._recording = {
|
|
7313
|
+
stop: stopHandler
|
|
7314
|
+
};
|
|
7315
|
+
this._stopRrweb = stopHandler;
|
|
7316
|
+
// Only create mutation throttler once replay is truly started.
|
|
7317
|
+
this._mutationThrottler = this._mutationThrottler ?? new MutationThrottler(rrwebRecord, {
|
|
7318
|
+
refillRate: this._instance.config.session_recording?.__mutationThrottlerRefillRate,
|
|
7319
|
+
bucketSize: this._instance.config.session_recording?.__mutationThrottlerBucketSize,
|
|
7320
|
+
onBlockedNode: (id, node) => {
|
|
7321
|
+
const message = `Too many mutations on node '${id}'. Rate limiting. This could be due to SVG animations or something similar`;
|
|
7322
|
+
logger.info(message, {
|
|
7323
|
+
node: node
|
|
7324
|
+
});
|
|
7325
|
+
this.log(LOGGER_PREFIX$1 + ' ' + message, 'warn');
|
|
7326
|
+
}
|
|
7327
|
+
});
|
|
7340
7328
|
// We reset the last activity timestamp, resetting the idle timer
|
|
7341
7329
|
this._lastActivityTimestamp = Date.now();
|
|
7342
7330
|
// stay unknown if we're not sure if we're idle or not
|
|
@@ -7366,10 +7354,6 @@ var leanbase = (function () {
|
|
|
7366
7354
|
get started() {
|
|
7367
7355
|
return !!this._lazyLoadedSessionRecording?.isStarted;
|
|
7368
7356
|
}
|
|
7369
|
-
/**
|
|
7370
|
-
* defaults to buffering mode until a flags response is received
|
|
7371
|
-
* once a flags response is received status can be disabled, active or sampled
|
|
7372
|
-
*/
|
|
7373
7357
|
get status() {
|
|
7374
7358
|
if (this._lazyLoadedSessionRecording) {
|
|
7375
7359
|
return this._lazyLoadedSessionRecording.status;
|
|
@@ -7383,7 +7367,6 @@ var leanbase = (function () {
|
|
|
7383
7367
|
this._instance = _instance;
|
|
7384
7368
|
this._forceAllowLocalhostNetworkCapture = false;
|
|
7385
7369
|
this._receivedFlags = false;
|
|
7386
|
-
this._serverRecordingEnabled = false;
|
|
7387
7370
|
this._persistFlagsOnSessionListener = undefined;
|
|
7388
7371
|
if (!this._instance.sessionManager) {
|
|
7389
7372
|
log.error('started without valid sessionManager');
|
|
@@ -7406,26 +7389,14 @@ var leanbase = (function () {
|
|
|
7406
7389
|
const canRunReplay = !isUndefined(Object.assign) && !isUndefined(Array.from);
|
|
7407
7390
|
if (this._isRecordingEnabled && canRunReplay) {
|
|
7408
7391
|
this._lazyLoadAndStart(startReason);
|
|
7409
|
-
log.info('starting');
|
|
7410
7392
|
} else {
|
|
7411
7393
|
this.stopRecording();
|
|
7412
7394
|
}
|
|
7413
7395
|
}
|
|
7414
|
-
/**
|
|
7415
|
-
* session recording waits until it receives remote config before loading the script
|
|
7416
|
-
* this is to ensure we can control the script name remotely
|
|
7417
|
-
* and because we wait until we have local and remote config to determine if we should start at all
|
|
7418
|
-
* if start is called and there is no remote config then we wait until there is
|
|
7419
|
-
*/
|
|
7420
7396
|
_lazyLoadAndStart(startReason) {
|
|
7421
|
-
// by checking `_isRecordingEnabled` here we know that
|
|
7422
|
-
// we have stored remote config and client config to read
|
|
7423
|
-
// replay waits for both local and remote config before starting
|
|
7424
7397
|
if (!this._isRecordingEnabled) {
|
|
7425
7398
|
return;
|
|
7426
7399
|
}
|
|
7427
|
-
// If extensions provide a loader, use it. Otherwise fallback to the local _onScriptLoaded which
|
|
7428
|
-
// will create the local LazyLoadedSessionRecording (so tests that mock it work correctly).
|
|
7429
7400
|
const loader = assignableWindow.__PosthogExtensions__?.loadExternalDependency;
|
|
7430
7401
|
if (typeof loader === 'function') {
|
|
7431
7402
|
loader(this._instance, this._scriptName, err => {
|
|
@@ -7480,16 +7451,10 @@ var leanbase = (function () {
|
|
|
7480
7451
|
});
|
|
7481
7452
|
};
|
|
7482
7453
|
persistResponse();
|
|
7483
|
-
// in case we see multiple flags responses, we should only use the response from the most recent one
|
|
7484
7454
|
this._persistFlagsOnSessionListener?.();
|
|
7485
|
-
// we 100% know there is a session manager by this point
|
|
7486
7455
|
this._persistFlagsOnSessionListener = this._instance.sessionManager?.onSessionId(persistResponse);
|
|
7487
7456
|
}
|
|
7488
7457
|
}
|
|
7489
|
-
_clearRemoteConfig() {
|
|
7490
|
-
this._instance.persistence?.unregister(SESSION_RECORDING_REMOTE_CONFIG);
|
|
7491
|
-
this._resetSampling();
|
|
7492
|
-
}
|
|
7493
7458
|
onRemoteConfig(response) {
|
|
7494
7459
|
if (!('sessionRecording' in response)) {
|
|
7495
7460
|
// if sessionRecording is not in the response, we do nothing
|
|
@@ -7498,12 +7463,8 @@ var leanbase = (function () {
|
|
|
7498
7463
|
}
|
|
7499
7464
|
this._receivedFlags = true;
|
|
7500
7465
|
if (response.sessionRecording === false) {
|
|
7501
|
-
this._serverRecordingEnabled = false;
|
|
7502
|
-
this._clearRemoteConfig();
|
|
7503
|
-
this.stopRecording();
|
|
7504
7466
|
return;
|
|
7505
7467
|
}
|
|
7506
|
-
this._serverRecordingEnabled = true;
|
|
7507
7468
|
this._persistRemoteConfig(response);
|
|
7508
7469
|
this.startIfEnabledOrStop();
|
|
7509
7470
|
}
|
|
@@ -7564,59 +7525,61 @@ var leanbase = (function () {
|
|
|
7564
7525
|
onRRwebEmit(rawEvent) {
|
|
7565
7526
|
this._lazyLoadedSessionRecording?.onRRwebEmit?.(rawEvent);
|
|
7566
7527
|
}
|
|
7567
|
-
/**
|
|
7568
|
-
* this ignores the linked flag config and (if other conditions are met) causes capture to start
|
|
7569
|
-
*
|
|
7570
|
-
* It is not usual to call this directly,
|
|
7571
|
-
* instead call `posthog.startSessionRecording({linked_flag: true})`
|
|
7572
|
-
* */
|
|
7573
7528
|
overrideLinkedFlag() {
|
|
7574
7529
|
// TODO what if this gets called before lazy loading is done
|
|
7575
7530
|
this._lazyLoadedSessionRecording?.overrideLinkedFlag();
|
|
7576
7531
|
}
|
|
7577
|
-
/**
|
|
7578
|
-
* this ignores the sampling config and (if other conditions are met) causes capture to start
|
|
7579
|
-
*
|
|
7580
|
-
* It is not usual to call this directly,
|
|
7581
|
-
* instead call `posthog.startSessionRecording({sampling: true})`
|
|
7582
|
-
* */
|
|
7583
7532
|
overrideSampling() {
|
|
7584
7533
|
// TODO what if this gets called before lazy loading is done
|
|
7585
7534
|
this._lazyLoadedSessionRecording?.overrideSampling();
|
|
7586
7535
|
}
|
|
7587
|
-
/**
|
|
7588
|
-
* this ignores the URL/Event trigger config and (if other conditions are met) causes capture to start
|
|
7589
|
-
*
|
|
7590
|
-
* It is not usual to call this directly,
|
|
7591
|
-
* instead call `posthog.startSessionRecording({trigger: 'url' | 'event'})`
|
|
7592
|
-
* */
|
|
7593
7536
|
overrideTrigger(triggerType) {
|
|
7594
7537
|
// TODO what if this gets called before lazy loading is done
|
|
7595
7538
|
this._lazyLoadedSessionRecording?.overrideTrigger(triggerType);
|
|
7596
7539
|
}
|
|
7597
|
-
/*
|
|
7598
|
-
* whenever we capture an event, we add these properties to the event
|
|
7599
|
-
* these are used to debug issues with the session recording
|
|
7600
|
-
* when looking at the event feed for a session
|
|
7601
|
-
*/
|
|
7602
7540
|
get sdkDebugProperties() {
|
|
7603
7541
|
return this._lazyLoadedSessionRecording?.sdkDebugProperties || {
|
|
7604
7542
|
$recording_status: this.status
|
|
7605
7543
|
};
|
|
7606
7544
|
}
|
|
7607
|
-
/**
|
|
7608
|
-
* This adds a custom event to the session recording
|
|
7609
|
-
*
|
|
7610
|
-
* It is not intended for arbitrary public use - playback only displays known custom events
|
|
7611
|
-
* And is exposed on the public interface only so that other parts of the SDK are able to use it
|
|
7612
|
-
*
|
|
7613
|
-
* if you are calling this from client code, you're probably looking for `posthog.capture('$custom_event', {...})`
|
|
7614
|
-
*/
|
|
7615
7545
|
tryAddCustomEvent(tag, payload) {
|
|
7616
7546
|
return !!this._lazyLoadedSessionRecording?.tryAddCustomEvent(tag, payload);
|
|
7617
7547
|
}
|
|
7618
7548
|
}
|
|
7619
7549
|
|
|
7550
|
+
/**
|
|
7551
|
+
* Leanbase-local version of PostHog's RequestRouter.
|
|
7552
|
+
*
|
|
7553
|
+
* Browser SDK always has a requestRouter instance; Leanbase IIFE needs this too so
|
|
7554
|
+
* features like Session Replay can construct ingestion URLs.
|
|
7555
|
+
*/
|
|
7556
|
+
class RequestRouter {
|
|
7557
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
7558
|
+
constructor(instance) {
|
|
7559
|
+
this.instance = instance;
|
|
7560
|
+
}
|
|
7561
|
+
get apiHost() {
|
|
7562
|
+
const configured = (this.instance.config.api_host || this.instance.config.host || '').trim();
|
|
7563
|
+
return configured.replace(/\/$/, '');
|
|
7564
|
+
}
|
|
7565
|
+
get uiHost() {
|
|
7566
|
+
const configured = this.instance.config.ui_host?.trim().replace(/\/$/, '');
|
|
7567
|
+
return configured || undefined;
|
|
7568
|
+
}
|
|
7569
|
+
endpointFor(target, path = '') {
|
|
7570
|
+
if (path) {
|
|
7571
|
+
path = path[0] === '/' ? path : `/${path}`;
|
|
7572
|
+
}
|
|
7573
|
+
if (target === 'ui') {
|
|
7574
|
+
const host = this.uiHost || this.apiHost;
|
|
7575
|
+
return host + path;
|
|
7576
|
+
}
|
|
7577
|
+
// Leanbase doesn't currently do region-based routing; default to apiHost.
|
|
7578
|
+
// Browser's router has special handling for assets; we keep parity in interface, not domains.
|
|
7579
|
+
return this.apiHost + path;
|
|
7580
|
+
}
|
|
7581
|
+
}
|
|
7582
|
+
|
|
7620
7583
|
const defaultConfig = () => ({
|
|
7621
7584
|
host: 'https://i.leanbase.co',
|
|
7622
7585
|
token: '',
|
|
@@ -7674,6 +7637,8 @@ var leanbase = (function () {
|
|
|
7674
7637
|
}));
|
|
7675
7638
|
this.isLoaded = true;
|
|
7676
7639
|
this.persistence = new LeanbasePersistence(this.config);
|
|
7640
|
+
// Browser SDK always has a requestRouter; session replay relies on it for $snapshot ingestion URLs.
|
|
7641
|
+
this.requestRouter = new RequestRouter(this);
|
|
7677
7642
|
if (this.config.cookieless_mode !== 'always') {
|
|
7678
7643
|
this.sessionManager = new SessionIdManager(this);
|
|
7679
7644
|
this.sessionPropsManager = new SessionPropsManager(this, this.sessionManager, this.persistence);
|