@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.
@@ -2846,7 +2846,7 @@ var leanbase = (function () {
2846
2846
  }
2847
2847
  };
2848
2848
 
2849
- var version = "0.3.0";
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
- // Expose rrweb.record under the same contract. We provide a lazy proxy so
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: effectiveBucketSize,
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._stopRrweb;
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
- this._removeEventTriggerCaptureHook?.();
6728
- this._addEventTriggerListener();
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
- 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
- }
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
- this._reportStarted(startReason || 'recording_initialized');
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._stopRrweb?.();
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 || !this.isStarted) {
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
- logger.info(startReason.replace('_', ' '), tagPayload);
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._stopRrweb) {
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
- logger.error('_startRecorder was called but rrwebRecord is not available. This indicates something has gone wrong.');
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
- this._stopRrweb = rrwebRecord({
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
- logger.error('failed to start rrweb recorder', e);
7337
- this._stopRrweb = undefined;
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);