@leanbase-giangnd/js 0.1.5 → 0.2.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.d.ts CHANGED
@@ -1406,6 +1406,7 @@ declare class SessionRecording$1 {
1406
1406
  private _clearRemoteConfig;
1407
1407
  onRemoteConfig(response: RemoteConfig$1): void;
1408
1408
  log(message: string, level?: 'log' | 'warn' | 'error'): void;
1409
+ private get _scriptName();
1409
1410
  private _onScriptLoaded;
1410
1411
  /**
1411
1412
  * this is maintained on the public API only because it has always been on the public API
@@ -3824,7 +3825,7 @@ declare class SessionIdManager {
3824
3825
  changeReason: {
3825
3826
  noSessionId: boolean;
3826
3827
  activityTimeout: boolean;
3827
- sessionPastMaximumLength: any;
3828
+ sessionPastMaximumLength: boolean;
3828
3829
  } | undefined;
3829
3830
  lastActivityTimestamp: number;
3830
3831
  };
@@ -4292,7 +4293,7 @@ declare class PostHogSurveys {
4292
4293
  private _isInitializingSurveys;
4293
4294
  private _surveyCallbacks;
4294
4295
  constructor(_instance: PostHog);
4295
- onRemoteConfig(response: RemoteConfig): any;
4296
+ onRemoteConfig(response: RemoteConfig): void;
4296
4297
  reset(): void;
4297
4298
  loadIfEnabled(): void;
4298
4299
  /** Helper to finalize survey initialization */
package/dist/index.mjs CHANGED
@@ -1,5 +1,4 @@
1
1
  import { isArray, isNullish, isFormData, hasOwnProperty, isString, isNull, isNumber, PostHogPersistedProperty, isUndefined, isFunction, includes, stripLeadingDollar, isObject, isEmptyObject, trim, isBoolean, clampToRange, BucketedRateLimiter, PostHogCore, getFetch, isEmptyString } from '@posthog/core';
2
- import { record } from '@rrweb/record';
3
2
  import { strFromU8, gzipSync, strToU8 } from 'fflate';
4
3
 
5
4
  const breaker = {};
@@ -15,6 +14,8 @@ global?.fetch;
15
14
  global?.XMLHttpRequest && 'withCredentials' in new global.XMLHttpRequest() ? global.XMLHttpRequest : undefined;
16
15
  global?.AbortController;
17
16
  const userAgent = navigator$1?.userAgent;
17
+ // assignableWindow mirrors browser package's assignableWindow for extension loading shims
18
+ const assignableWindow = win ?? {};
18
19
  function eachArray(obj, iterator, thisArg) {
19
20
  if (isArray(obj)) {
20
21
  if (nativeForEach && obj.forEach === nativeForEach) {
@@ -1168,7 +1169,7 @@ const detectDeviceType = function (user_agent) {
1168
1169
  }
1169
1170
  };
1170
1171
 
1171
- var version = "0.1.5";
1172
+ var version = "0.2.2";
1172
1173
  var packageInfo = {
1173
1174
  version: version};
1174
1175
 
@@ -3106,12 +3107,101 @@ const isLikelyBot = function (navigator, customBlockedUserAgents) {
3106
3107
  // It would be very bad if posthog-js caused a permission prompt to appear on every page load.
3107
3108
  };
3108
3109
 
3110
+ /* eslint-disable @typescript-eslint/no-unused-vars */
3111
+ // We avoid importing '@rrweb/record' at module load time to prevent IIFE builds
3112
+ // from requiring a top-level global. Instead, expose a lazy proxy that will
3113
+ // dynamically import the module the first time it's used.
3114
+ let _cachedRRWeb = null;
3115
+ async function _loadRRWebModule() {
3116
+ if (_cachedRRWeb) return _cachedRRWeb;
3117
+ try {
3118
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3119
+ const mod = await import('@rrweb/record');
3120
+ _cachedRRWeb = mod;
3121
+ return _cachedRRWeb;
3122
+ } catch (e) {
3123
+ return null;
3124
+ }
3125
+ }
3126
+ // queue for method calls before rrweb loads
3127
+ const _queuedCalls = [];
3128
+ // Create a proxy function that delegates to the real rrweb.record when called
3129
+ const rrwebRecordProxy = function (...args) {
3130
+ let realStop;
3131
+ let calledReal = false;
3132
+ // Start loading asynchronously and call the real record when available
3133
+ void (async () => {
3134
+ const mod = await _loadRRWebModule();
3135
+ const real = mod && (mod.record ?? mod.default?.record);
3136
+ if (real) {
3137
+ try {
3138
+ calledReal = true;
3139
+ realStop = real(...args);
3140
+ // flush any queued calls that were waiting for rrweb
3141
+ while (_queuedCalls.length) {
3142
+ try {
3143
+ const fn = _queuedCalls.shift();
3144
+ fn();
3145
+ } catch (e) {
3146
+ // ignore
3147
+ }
3148
+ }
3149
+ } catch (e) {
3150
+ // ignore
3151
+ }
3152
+ }
3153
+ })();
3154
+ // return a stop function that will call the real stop when available
3155
+ return () => {
3156
+ if (realStop) {
3157
+ try {
3158
+ realStop();
3159
+ } catch (e) {
3160
+ // ignore
3161
+ }
3162
+ } else if (!calledReal) {
3163
+ // If rrweb hasn't been initialised yet, queue a stop request that will
3164
+ // call the real stop once available.
3165
+ _queuedCalls.push(() => {
3166
+ try {
3167
+ realStop?.();
3168
+ } catch (e) {
3169
+ // ignore
3170
+ }
3171
+ });
3172
+ }
3173
+ };
3174
+ };
3175
+ // methods that can be called on the rrweb.record object - queue until real module is available
3176
+ rrwebRecordProxy.addCustomEvent = function (tag, payload) {
3177
+ const call = () => {
3178
+ try {
3179
+ const real = _cachedRRWeb && (_cachedRRWeb.record ?? _cachedRRWeb.default?.record);
3180
+ real?.addCustomEvent?.(tag, payload);
3181
+ } catch (e) {
3182
+ // ignore
3183
+ }
3184
+ };
3185
+ if (_cachedRRWeb) call();else _queuedCalls.push(call);
3186
+ };
3187
+ rrwebRecordProxy.takeFullSnapshot = function () {
3188
+ const call = () => {
3189
+ try {
3190
+ const real = _cachedRRWeb && (_cachedRRWeb.record ?? _cachedRRWeb.default?.record);
3191
+ real?.takeFullSnapshot?.();
3192
+ } catch (e) {
3193
+ // ignore
3194
+ }
3195
+ };
3196
+ if (_cachedRRWeb) call();else _queuedCalls.push(call);
3197
+ };
3109
3198
  // Use a safe global target (prefer `win`, fallback to globalThis)
3110
3199
  const _target = win ?? globalThis;
3111
3200
  _target.__PosthogExtensions__ = _target.__PosthogExtensions__ || {};
3112
- // Expose rrweb.record under the same contract
3201
+ // Expose rrweb.record under the same contract. We provide a lazy proxy so
3202
+ // builds that execute this file don't require rrweb at module evaluation time.
3113
3203
  _target.__PosthogExtensions__.rrweb = _target.__PosthogExtensions__.rrweb || {
3114
- record: record
3204
+ record: rrwebRecordProxy
3115
3205
  };
3116
3206
  // Provide initSessionRecording if not present — return a new LazyLoadedSessionRecording when called
3117
3207
  _target.__PosthogExtensions__.initSessionRecording = _target.__PosthogExtensions__.initSessionRecording || (instance => {
@@ -3864,7 +3954,41 @@ function getRRWebRecord() {
3864
3954
  } catch {
3865
3955
  // ignore
3866
3956
  }
3867
- return record;
3957
+ // If we've previously loaded rrweb via dynamic import, return the cached reference
3958
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3959
+ const cached = getRRWebRecord._cachedRRWebRecord;
3960
+ return cached;
3961
+ }
3962
+ async function loadRRWeb() {
3963
+ try {
3964
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3965
+ const ext = globalThis.__PosthogExtensions__;
3966
+ if (ext && ext.rrweb && ext.rrweb.record) {
3967
+ ;
3968
+ getRRWebRecord._cachedRRWebRecord = ext.rrweb.record;
3969
+ return ext.rrweb.record;
3970
+ }
3971
+ // If already cached, return it
3972
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3973
+ const already = getRRWebRecord._cachedRRWebRecord;
3974
+ if (already) {
3975
+ return already;
3976
+ }
3977
+ // Dynamic import - let the bundler (IIFE build) include rrweb in the bundle or allow lazy-load
3978
+ // Note: we intentionally use a dynamic import so rrweb is not referenced at the module top-level
3979
+ // which would cause IIFE builds to assume a global is present at script execution.
3980
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3981
+ const mod = await import('@rrweb/record');
3982
+ const rr = mod && (mod.record ?? (mod.default && mod.default.record));
3983
+ if (rr) {
3984
+ ;
3985
+ getRRWebRecord._cachedRRWebRecord = rr;
3986
+ return rr;
3987
+ }
3988
+ } catch (e) {
3989
+ logger.error('could not dynamically load rrweb', e);
3990
+ }
3991
+ return null;
3868
3992
  }
3869
3993
  function gzipToString(data) {
3870
3994
  return strFromU8(gzipSync(strToU8(JSON.stringify(data))), true);
@@ -4287,7 +4411,7 @@ class LazyLoadedSessionRecording {
4287
4411
  const parsedConfig = isObject(persistedConfig) ? persistedConfig : JSON.parse(persistedConfig);
4288
4412
  return parsedConfig;
4289
4413
  }
4290
- start(startReason) {
4414
+ async start(startReason) {
4291
4415
  const config = this._remoteConfig;
4292
4416
  if (!config) {
4293
4417
  logger.info('remote config must be stored in persistence before recording can start');
@@ -4321,7 +4445,7 @@ class LazyLoadedSessionRecording {
4321
4445
  });
4322
4446
  });
4323
4447
  this._makeSamplingDecision(this.sessionId);
4324
- this._startRecorder();
4448
+ await this._startRecorder();
4325
4449
  // calling addEventListener multiple times is safe and will not add duplicates
4326
4450
  addEventListener(win, 'beforeunload', this._onBeforeUnload);
4327
4451
  addEventListener(win, 'offline', this._onOffline);
@@ -4777,7 +4901,7 @@ class LazyLoadedSessionRecording {
4777
4901
  $sdk_debug_session_start: sessionStartTimestamp
4778
4902
  };
4779
4903
  }
4780
- _startRecorder() {
4904
+ async _startRecorder() {
4781
4905
  if (this._stopRrweb) {
4782
4906
  return;
4783
4907
  }
@@ -4833,7 +4957,12 @@ class LazyLoadedSessionRecording {
4833
4957
  sessionRecordingOptions.maskTextSelector = this._masking.maskTextSelector ?? undefined;
4834
4958
  sessionRecordingOptions.blockSelector = this._masking.blockSelector ?? undefined;
4835
4959
  }
4836
- const rrwebRecord = getRRWebRecord();
4960
+ // Ensure rrweb is loaded (either via global extension or dynamic import)
4961
+ let rrwebRecord = getRRWebRecord();
4962
+ if (!rrwebRecord) {
4963
+ const loaded = await loadRRWeb();
4964
+ rrwebRecord = loaded ?? undefined;
4965
+ }
4837
4966
  if (!rrwebRecord) {
4838
4967
  logger.error('_startRecorder was called but rrwebRecord is not available. This indicates something has gone wrong.');
4839
4968
  return;
@@ -4875,6 +5004,7 @@ class LazyLoadedSessionRecording {
4875
5004
  }
4876
5005
  }
4877
5006
 
5007
+ /* eslint-disable posthog-js/no-direct-function-check */
4878
5008
  const LOGGER_PREFIX = '[SessionRecording]';
4879
5009
  const log = {
4880
5010
  info: (...args) => logger$2.info(LOGGER_PREFIX, ...args),
@@ -4943,6 +5073,18 @@ class SessionRecording {
4943
5073
  if (!this._isRecordingEnabled) {
4944
5074
  return;
4945
5075
  }
5076
+ // If extensions provide a loader, use it. Otherwise fallback to the local _onScriptLoaded which
5077
+ // will create the local LazyLoadedSessionRecording (so tests that mock it work correctly).
5078
+ const loader = assignableWindow.__PosthogExtensions__?.loadExternalDependency;
5079
+ if (typeof loader === 'function') {
5080
+ loader(this._instance, this._scriptName, err => {
5081
+ if (err) {
5082
+ return log.error('could not load recorder', err);
5083
+ }
5084
+ this._onScriptLoaded(startReason);
5085
+ });
5086
+ return;
5087
+ }
4946
5088
  this._onScriptLoaded(startReason);
4947
5089
  }
4948
5090
  stopRecording() {
@@ -5021,12 +5163,40 @@ class SessionRecording {
5021
5163
  logger$2.warn('log called before recorder was ready');
5022
5164
  }
5023
5165
  }
5166
+ get _scriptName() {
5167
+ const remoteConfig = this._instance?.persistence?.get_property(SESSION_RECORDING_REMOTE_CONFIG);
5168
+ return remoteConfig?.scriptConfig?.script || 'lazy-recorder';
5169
+ }
5024
5170
  _onScriptLoaded(startReason) {
5171
+ // If extensions provide an init function, use it. Otherwise, fall back to the local LazyLoadedSessionRecording
5172
+ if (assignableWindow.__PosthogExtensions__?.initSessionRecording) {
5173
+ if (!this._lazyLoadedSessionRecording) {
5174
+ this._lazyLoadedSessionRecording = assignableWindow.__PosthogExtensions__?.initSessionRecording(this._instance);
5175
+ this._lazyLoadedSessionRecording._forceAllowLocalhostNetworkCapture = this._forceAllowLocalhostNetworkCapture;
5176
+ }
5177
+ try {
5178
+ const maybePromise = this._lazyLoadedSessionRecording.start(startReason);
5179
+ if (maybePromise && typeof maybePromise.catch === 'function') {
5180
+ maybePromise.catch(e => logger$2.error('error starting session recording', e));
5181
+ }
5182
+ } catch (e) {
5183
+ logger$2.error('error starting session recording', e);
5184
+ }
5185
+ return;
5186
+ }
5025
5187
  if (!this._lazyLoadedSessionRecording) {
5026
5188
  this._lazyLoadedSessionRecording = new LazyLoadedSessionRecording(this._instance);
5027
5189
  this._lazyLoadedSessionRecording._forceAllowLocalhostNetworkCapture = this._forceAllowLocalhostNetworkCapture;
5028
5190
  }
5029
- this._lazyLoadedSessionRecording.start(startReason);
5191
+ // start may perform a dynamic import; handle both sync and Promise returns
5192
+ try {
5193
+ const maybePromise = this._lazyLoadedSessionRecording.start(startReason);
5194
+ if (maybePromise && typeof maybePromise.catch === 'function') {
5195
+ maybePromise.catch(e => logger$2.error('error starting session recording', e));
5196
+ }
5197
+ } catch (e) {
5198
+ logger$2.error('error starting session recording', e);
5199
+ }
5030
5200
  }
5031
5201
  /**
5032
5202
  * this is maintained on the public API only because it has always been on the public API