@leanbase-giangnd/js 0.1.2 → 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.mjs CHANGED
@@ -1,5 +1,4 @@
1
- import { isArray, isNullish, isFormData, hasOwnProperty, isString, isNull, isNumber, PostHogPersistedProperty, isUndefined, isFile, isFunction, includes, stripLeadingDollar, isObject, isEmptyObject, trim, isBoolean, clampToRange, BucketedRateLimiter, PostHogCore, getFetch, isEmptyString } from '@posthog/core';
2
- import { record } from '@rrweb/record';
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';
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) {
@@ -228,7 +229,7 @@ PostHogPersistedProperty.Queue, PostHogPersistedProperty.FeatureFlagDetails, Pos
228
229
 
229
230
  /* eslint-disable no-console */
230
231
  const PREFIX = '[Leanbase]';
231
- const logger$3 = {
232
+ const logger$2 = {
232
233
  info: (...args) => {
233
234
  if (typeof console !== 'undefined') {
234
235
  console.log(PREFIX, ...args);
@@ -526,7 +527,7 @@ function chooseCookieDomain(hostname, cross_subdomain) {
526
527
  if (!matchedSubDomain) {
527
528
  const originalMatch = originalCookieDomainFn(hostname);
528
529
  if (originalMatch !== matchedSubDomain) {
529
- logger$3.info('Warning: cookie subdomain discovery mismatch', originalMatch, matchedSubDomain);
530
+ logger$2.info('Warning: cookie subdomain discovery mismatch', originalMatch, matchedSubDomain);
530
531
  }
531
532
  matchedSubDomain = originalMatch;
532
533
  }
@@ -538,7 +539,7 @@ function chooseCookieDomain(hostname, cross_subdomain) {
538
539
  const cookieStore = {
539
540
  _is_supported: () => !!document,
540
541
  _error: function (msg) {
541
- logger$3.error('cookieStore error: ' + msg);
542
+ logger$2.error('cookieStore error: ' + msg);
542
543
  },
543
544
  _get: function (name) {
544
545
  if (!document) {
@@ -587,7 +588,7 @@ const cookieStore = {
587
588
  const new_cookie_val = name + '=' + encodeURIComponent(JSON.stringify(value)) + expires + '; SameSite=Lax; path=/' + cdomain + secure;
588
589
  // 4096 bytes is the size at which some browsers (e.g. firefox) will not store a cookie, warn slightly before that
589
590
  if (new_cookie_val.length > 4096 * 0.9) {
590
- logger$3.warn('cookieStore warning: large cookie, len=' + new_cookie_val.length);
591
+ logger$2.warn('cookieStore warning: large cookie, len=' + new_cookie_val.length);
591
592
  }
592
593
  document.cookie = new_cookie_val;
593
594
  return new_cookie_val;
@@ -629,13 +630,13 @@ const localStore = {
629
630
  supported = false;
630
631
  }
631
632
  if (!supported) {
632
- logger$3.error('localStorage unsupported; falling back to cookie store');
633
+ logger$2.error('localStorage unsupported; falling back to cookie store');
633
634
  }
634
635
  _localStorage_supported = supported;
635
636
  return supported;
636
637
  },
637
638
  _error: function (msg) {
638
- logger$3.error('localStorage error: ' + msg);
639
+ logger$2.error('localStorage error: ' + msg);
639
640
  },
640
641
  _get: function (name) {
641
642
  try {
@@ -721,7 +722,7 @@ const memoryStore = {
721
722
  return true;
722
723
  },
723
724
  _error: function (msg) {
724
- logger$3.error('memoryStorage error: ' + msg);
725
+ logger$2.error('memoryStorage error: ' + msg);
725
726
  },
726
727
  _get: function (name) {
727
728
  return memoryStorage[name] || null;
@@ -762,7 +763,7 @@ const sessionStore = {
762
763
  return sessionStorageSupported;
763
764
  },
764
765
  _error: function (msg) {
765
- logger$3.error('sessionStorage error: ', msg);
766
+ logger$2.error('sessionStorage error: ', msg);
766
767
  },
767
768
  _get: function (name) {
768
769
  try {
@@ -811,21 +812,6 @@ const convertToURL = url => {
811
812
  location.href = url;
812
813
  return location;
813
814
  };
814
- const formDataToQuery = function (formdata, arg_separator = '&') {
815
- let use_val;
816
- let use_key;
817
- const tph_arr = [];
818
- each(formdata, function (val, key) {
819
- // the key might be literally the string undefined for e.g. if {undefined: 'something'}
820
- if (isUndefined(val) || isUndefined(key) || key === 'undefined') {
821
- return;
822
- }
823
- use_val = encodeURIComponent(isFile(val) ? val.name : val.toString());
824
- use_key = encodeURIComponent(key);
825
- tph_arr[tph_arr.length] = use_key + '=' + use_val;
826
- });
827
- return tph_arr.join(arg_separator);
828
- };
829
815
  // NOTE: Once we get rid of IE11/op_mini we can start using URLSearchParams
830
816
  const getQueryParam = function (url, param) {
831
817
  const withoutHash = url.split('#')[0] || '';
@@ -849,7 +835,7 @@ const getQueryParam = function (url, param) {
849
835
  try {
850
836
  result = decodeURIComponent(result);
851
837
  } catch {
852
- logger$3.error('Skipping decoding for malformed query param: ' + result);
838
+ logger$2.error('Skipping decoding for malformed query param: ' + result);
853
839
  }
854
840
  return result.replace(/\+/g, ' ');
855
841
  }
@@ -1183,7 +1169,7 @@ const detectDeviceType = function (user_agent) {
1183
1169
  }
1184
1170
  };
1185
1171
 
1186
- var version = "0.1.2";
1172
+ var version = "0.2.2";
1187
1173
  var packageInfo = {
1188
1174
  version: version};
1189
1175
 
@@ -1448,7 +1434,7 @@ class LeanbasePersistence {
1448
1434
  this._storage = this._buildStorage(config);
1449
1435
  this.load();
1450
1436
  if (config.debug) {
1451
- logger$3.info('Persistence loaded', config['persistence'], {
1437
+ logger$2.info('Persistence loaded', config['persistence'], {
1452
1438
  ...this.props
1453
1439
  });
1454
1440
  }
@@ -1464,7 +1450,7 @@ class LeanbasePersistence {
1464
1450
  }
1465
1451
  _buildStorage(config) {
1466
1452
  if (CASE_INSENSITIVE_PERSISTENCE_TYPES.indexOf(config['persistence'].toLowerCase()) === -1) {
1467
- logger$3.info('Unknown persistence type ' + config['persistence'] + '; falling back to localStorage+cookie');
1453
+ logger$2.info('Unknown persistence type ' + config['persistence'] + '; falling back to localStorage+cookie');
1468
1454
  config['persistence'] = 'localStorage+cookie';
1469
1455
  }
1470
1456
  let store;
@@ -2100,7 +2086,7 @@ function getNestedSpanText(target) {
2100
2086
  text = `${text} ${getNestedSpanText(child)}`.trim();
2101
2087
  }
2102
2088
  } catch (e) {
2103
- logger$3.error('[AutoCapture]', e);
2089
+ logger$2.error('[AutoCapture]', e);
2104
2090
  }
2105
2091
  }
2106
2092
  });
@@ -2402,7 +2388,7 @@ class Autocapture {
2402
2388
  }
2403
2389
  _addDomEventHandlers() {
2404
2390
  if (!this.isBrowserSupported()) {
2405
- logger$3.info('Disabling Automatic Event Collection because this browser is not supported');
2391
+ logger$2.info('Disabling Automatic Event Collection because this browser is not supported');
2406
2392
  return;
2407
2393
  }
2408
2394
  if (!win || !document) {
@@ -2413,7 +2399,7 @@ class Autocapture {
2413
2399
  try {
2414
2400
  this._captureEvent(e);
2415
2401
  } catch (error) {
2416
- logger$3.error('Failed to capture event', error);
2402
+ logger$2.error('Failed to capture event', error);
2417
2403
  }
2418
2404
  };
2419
2405
  addEventListener(document, 'submit', handler, {
@@ -2598,7 +2584,7 @@ class SessionIdManager {
2598
2584
  this._windowIdGenerator = windowIdGenerator || uuidv7;
2599
2585
  const persistenceName = this._config['persistence_name'] || this._config['token'];
2600
2586
  const desiredTimeout = this._config['session_idle_timeout_seconds'] || DEFAULT_SESSION_IDLE_TIMEOUT_SECONDS;
2601
- this._sessionTimeoutMs = clampToRange(desiredTimeout, MIN_SESSION_IDLE_TIMEOUT_SECONDS, MAX_SESSION_IDLE_TIMEOUT_SECONDS, logger$3, DEFAULT_SESSION_IDLE_TIMEOUT_SECONDS) * 1000;
2587
+ this._sessionTimeoutMs = clampToRange(desiredTimeout, MIN_SESSION_IDLE_TIMEOUT_SECONDS, MAX_SESSION_IDLE_TIMEOUT_SECONDS, logger$2, DEFAULT_SESSION_IDLE_TIMEOUT_SECONDS) * 1000;
2602
2588
  instance.register({
2603
2589
  $configured_session_timeout_ms: this._sessionTimeoutMs
2604
2590
  });
@@ -2625,7 +2611,7 @@ class SessionIdManager {
2625
2611
  const sessionStartTimestamp = uuid7ToTimestampMs(this._config.bootstrap.sessionID);
2626
2612
  this._setSessionId(this._config.bootstrap.sessionID, new Date().getTime(), sessionStartTimestamp);
2627
2613
  } catch (e) {
2628
- logger$3.error('Invalid sessionID in bootstrap', e);
2614
+ logger$2.error('Invalid sessionID in bootstrap', e);
2629
2615
  }
2630
2616
  }
2631
2617
  this._listenToReloadWindow();
@@ -2766,7 +2752,7 @@ class SessionIdManager {
2766
2752
  if (noSessionId || activityTimeout || sessionPastMaximumLength) {
2767
2753
  sessionId = this._sessionIdGenerator();
2768
2754
  windowId = this._windowIdGenerator();
2769
- logger$3.info('new session ID generated', {
2755
+ logger$2.info('new session ID generated', {
2770
2756
  sessionId,
2771
2757
  windowId,
2772
2758
  changeReason: {
@@ -2955,10 +2941,10 @@ class PageViewManager {
2955
2941
  lastContentY = Math.ceil(lastContentY);
2956
2942
  maxContentY = Math.ceil(maxContentY);
2957
2943
  // if the maximum scroll height is near 0, then the percentage is 1
2958
- const lastScrollPercentage = maxScrollHeight <= 1 ? 1 : clampToRange(lastScrollY / maxScrollHeight, 0, 1, logger$3);
2959
- const maxScrollPercentage = maxScrollHeight <= 1 ? 1 : clampToRange(maxScrollY / maxScrollHeight, 0, 1, logger$3);
2960
- const lastContentPercentage = maxContentHeight <= 1 ? 1 : clampToRange(lastContentY / maxContentHeight, 0, 1, logger$3);
2961
- const maxContentPercentage = maxContentHeight <= 1 ? 1 : clampToRange(maxContentY / maxContentHeight, 0, 1, logger$3);
2944
+ const lastScrollPercentage = maxScrollHeight <= 1 ? 1 : clampToRange(lastScrollY / maxScrollHeight, 0, 1, logger$2);
2945
+ const maxScrollPercentage = maxScrollHeight <= 1 ? 1 : clampToRange(maxScrollY / maxScrollHeight, 0, 1, logger$2);
2946
+ const lastContentPercentage = maxContentHeight <= 1 ? 1 : clampToRange(lastContentY / maxContentHeight, 0, 1, logger$2);
2947
+ const maxContentPercentage = maxContentHeight <= 1 ? 1 : clampToRange(maxContentY / maxContentHeight, 0, 1, logger$2);
2962
2948
  properties = extend(properties, {
2963
2949
  $prev_pageview_last_scroll: lastScrollY,
2964
2950
  $prev_pageview_last_scroll_percentage: lastScrollPercentage,
@@ -3121,83 +3107,217 @@ const isLikelyBot = function (navigator, customBlockedUserAgents) {
3121
3107
  // It would be very bad if posthog-js caused a permission prompt to appear on every page load.
3122
3108
  };
3123
3109
 
3124
- const _createLogger = prefix => {
3125
- return {
3126
- info: (...args) => logger$3.info(prefix, ...args),
3127
- warn: (...args) => logger$3.warn(prefix, ...args),
3128
- error: (...args) => logger$3.error(prefix, ...args),
3129
- critical: (...args) => logger$3.critical(prefix, ...args),
3130
- uninitializedWarning: methodName => {
3131
- logger$3.error(prefix, `You must initialize Leanbase before calling ${methodName}`);
3132
- },
3133
- createLogger: additionalPrefix => _createLogger(`${prefix} ${additionalPrefix}`)
3134
- };
3135
- };
3136
- const logger$2 = _createLogger('[Leanbase]');
3137
- const createLogger = _createLogger;
3138
-
3139
- function patch(source, name, replacement) {
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;
3140
3117
  try {
3141
- if (!(name in source)) {
3142
- return () => {
3143
- //
3144
- };
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
+ }
3145
3152
  }
3146
- const original = source[name];
3147
- const wrapped = replacement(original);
3148
- if (isFunction(wrapped)) {
3149
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
3150
- wrapped.prototype = wrapped.prototype || {};
3151
- Object.defineProperties(wrapped, {
3152
- __posthog_wrapped__: {
3153
- enumerable: false,
3154
- value: true
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
3155
3170
  }
3156
3171
  });
3157
3172
  }
3158
- source[name] = wrapped;
3159
- return () => {
3160
- source[name] = original;
3161
- };
3162
- } catch {
3163
- return () => {
3164
- //
3165
- };
3166
- }
3167
- }
3168
-
3169
- function hostnameFromURL(url) {
3170
- try {
3171
- if (typeof url === 'string') {
3172
- return new URL(url).hostname;
3173
- }
3174
- if ('url' in url) {
3175
- return new URL(url.url).hostname;
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
3176
3183
  }
3177
- return url.hostname;
3178
- } catch {
3179
- return null;
3180
- }
3181
- }
3182
- function isHostOnDenyList(url, options) {
3183
- const hostname = hostnameFromURL(url);
3184
- const defaultNotDenied = {
3185
- hostname,
3186
- isHostDenied: false
3187
3184
  };
3188
- if (!options.payloadHostDenyList?.length || !hostname?.trim().length) {
3189
- return defaultNotDenied;
3190
- }
3191
- for (const deny of options.payloadHostDenyList) {
3192
- if (hostname.endsWith(deny)) {
3193
- return {
3194
- hostname,
3195
- isHostDenied: true
3196
- };
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
3197
3194
  }
3195
+ };
3196
+ if (_cachedRRWeb) call();else _queuedCalls.push(call);
3197
+ };
3198
+ // Use a safe global target (prefer `win`, fallback to globalThis)
3199
+ const _target = win ?? globalThis;
3200
+ _target.__PosthogExtensions__ = _target.__PosthogExtensions__ || {};
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.
3203
+ _target.__PosthogExtensions__.rrweb = _target.__PosthogExtensions__.rrweb || {
3204
+ record: rrwebRecordProxy
3205
+ };
3206
+ // Provide initSessionRecording if not present — return a new LazyLoadedSessionRecording when called
3207
+ _target.__PosthogExtensions__.initSessionRecording = _target.__PosthogExtensions__.initSessionRecording || (instance => {
3208
+ const factory = _target.__PosthogExtensions__._initSessionRecordingFactory;
3209
+ if (factory) {
3210
+ return factory(instance);
3198
3211
  }
3199
- return defaultNotDenied;
3200
- }
3212
+ // If no factory is registered yet, return undefined — callers should handle lazy-loading.
3213
+ return undefined;
3214
+ });
3215
+ // Provide a no-op loadExternalDependency that calls the callback immediately (since rrweb is bundled)
3216
+ _target.__PosthogExtensions__.loadExternalDependency = _target.__PosthogExtensions__.loadExternalDependency || ((instance, scriptName, cb) => {
3217
+ if (cb) cb(undefined);
3218
+ });
3219
+ // Provide rrwebPlugins object with network plugin factory if not present
3220
+ _target.__PosthogExtensions__.rrwebPlugins = _target.__PosthogExtensions__.rrwebPlugins || {};
3221
+ // Default to undefined; the lazy-loaded recorder will register the real factory when it initializes.
3222
+ _target.__PosthogExtensions__.rrwebPlugins.getRecordNetworkPlugin = _target.__PosthogExtensions__.rrwebPlugins.getRecordNetworkPlugin || (() => undefined);
3223
+
3224
+ // Type definitions copied from @rrweb/types@2.0.0-alpha.17 and rrweb-snapshot@2.0.0-alpha.17
3225
+ // Both packages are MIT licensed: https://github.com/rrweb-io/rrweb
3226
+ //
3227
+ // These types are copied here to avoid requiring users to install peer dependencies
3228
+ // solely for TypeScript type information.
3229
+ //
3230
+ // Original sources:
3231
+ // - @rrweb/types: https://github.com/rrweb-io/rrweb/tree/main/packages/@rrweb/types
3232
+ // - rrweb-snapshot: https://github.com/rrweb-io/rrweb/tree/main/packages/rrweb-snapshot
3233
+ var NodeType;
3234
+ (function (NodeType) {
3235
+ NodeType[NodeType["Document"] = 0] = "Document";
3236
+ NodeType[NodeType["DocumentType"] = 1] = "DocumentType";
3237
+ NodeType[NodeType["Element"] = 2] = "Element";
3238
+ NodeType[NodeType["Text"] = 3] = "Text";
3239
+ NodeType[NodeType["CDATA"] = 4] = "CDATA";
3240
+ NodeType[NodeType["Comment"] = 5] = "Comment";
3241
+ })(NodeType || (NodeType = {}));
3242
+ var EventType;
3243
+ (function (EventType) {
3244
+ EventType[EventType["DomContentLoaded"] = 0] = "DomContentLoaded";
3245
+ EventType[EventType["Load"] = 1] = "Load";
3246
+ EventType[EventType["FullSnapshot"] = 2] = "FullSnapshot";
3247
+ EventType[EventType["IncrementalSnapshot"] = 3] = "IncrementalSnapshot";
3248
+ EventType[EventType["Meta"] = 4] = "Meta";
3249
+ EventType[EventType["Custom"] = 5] = "Custom";
3250
+ EventType[EventType["Plugin"] = 6] = "Plugin";
3251
+ })(EventType || (EventType = {}));
3252
+ var IncrementalSource;
3253
+ (function (IncrementalSource) {
3254
+ IncrementalSource[IncrementalSource["Mutation"] = 0] = "Mutation";
3255
+ IncrementalSource[IncrementalSource["MouseMove"] = 1] = "MouseMove";
3256
+ IncrementalSource[IncrementalSource["MouseInteraction"] = 2] = "MouseInteraction";
3257
+ IncrementalSource[IncrementalSource["Scroll"] = 3] = "Scroll";
3258
+ IncrementalSource[IncrementalSource["ViewportResize"] = 4] = "ViewportResize";
3259
+ IncrementalSource[IncrementalSource["Input"] = 5] = "Input";
3260
+ IncrementalSource[IncrementalSource["TouchMove"] = 6] = "TouchMove";
3261
+ IncrementalSource[IncrementalSource["MediaInteraction"] = 7] = "MediaInteraction";
3262
+ IncrementalSource[IncrementalSource["StyleSheetRule"] = 8] = "StyleSheetRule";
3263
+ IncrementalSource[IncrementalSource["CanvasMutation"] = 9] = "CanvasMutation";
3264
+ IncrementalSource[IncrementalSource["Font"] = 10] = "Font";
3265
+ IncrementalSource[IncrementalSource["Log"] = 11] = "Log";
3266
+ IncrementalSource[IncrementalSource["Drag"] = 12] = "Drag";
3267
+ IncrementalSource[IncrementalSource["StyleDeclaration"] = 13] = "StyleDeclaration";
3268
+ IncrementalSource[IncrementalSource["Selection"] = 14] = "Selection";
3269
+ IncrementalSource[IncrementalSource["AdoptedStyleSheet"] = 15] = "AdoptedStyleSheet";
3270
+ IncrementalSource[IncrementalSource["CustomElement"] = 16] = "CustomElement";
3271
+ })(IncrementalSource || (IncrementalSource = {}));
3272
+ var MouseInteractions;
3273
+ (function (MouseInteractions) {
3274
+ MouseInteractions[MouseInteractions["MouseUp"] = 0] = "MouseUp";
3275
+ MouseInteractions[MouseInteractions["MouseDown"] = 1] = "MouseDown";
3276
+ MouseInteractions[MouseInteractions["Click"] = 2] = "Click";
3277
+ MouseInteractions[MouseInteractions["ContextMenu"] = 3] = "ContextMenu";
3278
+ MouseInteractions[MouseInteractions["DblClick"] = 4] = "DblClick";
3279
+ MouseInteractions[MouseInteractions["Focus"] = 5] = "Focus";
3280
+ MouseInteractions[MouseInteractions["Blur"] = 6] = "Blur";
3281
+ MouseInteractions[MouseInteractions["TouchStart"] = 7] = "TouchStart";
3282
+ MouseInteractions[MouseInteractions["TouchMove_Departed"] = 8] = "TouchMove_Departed";
3283
+ MouseInteractions[MouseInteractions["TouchEnd"] = 9] = "TouchEnd";
3284
+ MouseInteractions[MouseInteractions["TouchCancel"] = 10] = "TouchCancel";
3285
+ })(MouseInteractions || (MouseInteractions = {}));
3286
+ var PointerTypes;
3287
+ (function (PointerTypes) {
3288
+ PointerTypes[PointerTypes["Mouse"] = 0] = "Mouse";
3289
+ PointerTypes[PointerTypes["Pen"] = 1] = "Pen";
3290
+ PointerTypes[PointerTypes["Touch"] = 2] = "Touch";
3291
+ })(PointerTypes || (PointerTypes = {}));
3292
+ var MediaInteractions;
3293
+ (function (MediaInteractions) {
3294
+ MediaInteractions[MediaInteractions["Play"] = 0] = "Play";
3295
+ MediaInteractions[MediaInteractions["Pause"] = 1] = "Pause";
3296
+ MediaInteractions[MediaInteractions["Seeked"] = 2] = "Seeked";
3297
+ MediaInteractions[MediaInteractions["VolumeChange"] = 3] = "VolumeChange";
3298
+ MediaInteractions[MediaInteractions["RateChange"] = 4] = "RateChange";
3299
+ })(MediaInteractions || (MediaInteractions = {}));
3300
+ var CanvasContext;
3301
+ (function (CanvasContext) {
3302
+ CanvasContext[CanvasContext["2D"] = 0] = "2D";
3303
+ CanvasContext[CanvasContext["WebGL"] = 1] = "WebGL";
3304
+ CanvasContext[CanvasContext["WebGL2"] = 2] = "WebGL2";
3305
+ })(CanvasContext || (CanvasContext = {}));
3306
+
3307
+ const _createLogger = prefix => {
3308
+ return {
3309
+ info: (...args) => logger$2.info(prefix, ...args),
3310
+ warn: (...args) => logger$2.warn(prefix, ...args),
3311
+ error: (...args) => logger$2.error(prefix, ...args),
3312
+ critical: (...args) => logger$2.critical(prefix, ...args),
3313
+ uninitializedWarning: methodName => {
3314
+ logger$2.error(prefix, `You must initialize Leanbase before calling ${methodName}`);
3315
+ },
3316
+ createLogger: additionalPrefix => _createLogger(`${prefix} ${additionalPrefix}`)
3317
+ };
3318
+ };
3319
+ const logger$1 = _createLogger('[Leanbase]');
3320
+ const createLogger = _createLogger;
3201
3321
 
3202
3322
  const LOGGER_PREFIX$2 = '[SessionRecording]';
3203
3323
  const REDACTED = 'redacted';
@@ -3270,7 +3390,7 @@ function enforcePayloadSizeLimit(payload, headers, limit, description) {
3270
3390
  // people can have arbitrarily large payloads on their site, but we don't want to ingest them
3271
3391
  const limitPayloadSize = options => {
3272
3392
  // the smallest of 1MB or the specified limit if there is one
3273
- const limit = Math.min(1000000, options.payloadSizeLimitBytes ?? 1000000);
3393
+ const limit = Math.min(1000000, options.payloadSizeLimitBytes);
3274
3394
  return data => {
3275
3395
  if (data?.requestBody) {
3276
3396
  data.requestBody = enforcePayloadSizeLimit(data.requestBody, data.requestHeaders, limit, 'Request');
@@ -3328,7 +3448,7 @@ const buildNetworkRequestOptions = (instanceConfig, remoteNetworkOptions = {}) =
3328
3448
  const enforcedCleaningFn = d => payloadLimiter(ignorePostHogPaths(removeAuthorizationHeader(d), instanceConfig.host || ''));
3329
3449
  const hasDeprecatedMaskFunction = isFunction(sessionRecordingConfig.maskNetworkRequestFn);
3330
3450
  if (hasDeprecatedMaskFunction && isFunction(sessionRecordingConfig.maskCapturedNetworkRequestFn)) {
3331
- logger$2.warn('Both `maskNetworkRequestFn` and `maskCapturedNetworkRequestFn` are defined. `maskNetworkRequestFn` will be ignored.');
3451
+ logger$1.warn('Both `maskNetworkRequestFn` and `maskCapturedNetworkRequestFn` are defined. `maskNetworkRequestFn` will be ignored.');
3332
3452
  }
3333
3453
  if (hasDeprecatedMaskFunction) {
3334
3454
  sessionRecordingConfig.maskCapturedNetworkRequestFn = data => {
@@ -3355,666 +3475,6 @@ const buildNetworkRequestOptions = (instanceConfig, remoteNetworkOptions = {}) =
3355
3475
  };
3356
3476
  };
3357
3477
 
3358
- /// <reference lib="dom" />
3359
- const logger$1 = createLogger('[Recorder]');
3360
- const isNavigationTiming = entry => entry.entryType === 'navigation';
3361
- const isResourceTiming = entry => entry.entryType === 'resource';
3362
- function findLast(array, predicate) {
3363
- const length = array.length;
3364
- for (let i = length - 1; i >= 0; i -= 1) {
3365
- if (predicate(array[i])) {
3366
- return array[i];
3367
- }
3368
- }
3369
- return undefined;
3370
- }
3371
- function isDocument(value) {
3372
- return !!value && typeof value === 'object' && 'nodeType' in value && value.nodeType === 9;
3373
- }
3374
- function initPerformanceObserver(cb, win, options) {
3375
- // if we are only observing timings then we could have a single observer for all types, with buffer true,
3376
- // but we are going to filter by initiatorType _if we are wrapping fetch and xhr as the wrapped functions
3377
- // will deal with those.
3378
- // so we have a block which captures requests from before fetch/xhr is wrapped
3379
- // these are marked `isInitial` so playback can display them differently if needed
3380
- // they will never have method/status/headers/body because they are pre-wrapping that provides that
3381
- if (options.recordInitialRequests) {
3382
- const initialPerformanceEntries = win.performance.getEntries().filter(entry => isNavigationTiming(entry) || isResourceTiming(entry) && options.initiatorTypes.includes(entry.initiatorType));
3383
- cb({
3384
- requests: initialPerformanceEntries.flatMap(entry => prepareRequest({
3385
- entry,
3386
- method: undefined,
3387
- status: undefined,
3388
- networkRequest: {},
3389
- isInitial: true
3390
- })),
3391
- isInitial: true
3392
- });
3393
- }
3394
- const observer = new win.PerformanceObserver(entries => {
3395
- // if recordBody or recordHeaders is true then we don't want to record fetch or xhr here
3396
- // as the wrapped functions will do that. Otherwise, this filter becomes a noop
3397
- // because we do want to record them here
3398
- const wrappedInitiatorFilter = entry => options.recordBody || options.recordHeaders ? entry.initiatorType !== 'xmlhttprequest' && entry.initiatorType !== 'fetch' : true;
3399
- const performanceEntries = entries.getEntries().filter(entry => isNavigationTiming(entry) || isResourceTiming(entry) && options.initiatorTypes.includes(entry.initiatorType) &&
3400
- // TODO if we are _only_ capturing timing we don't want to filter initiator here
3401
- wrappedInitiatorFilter(entry));
3402
- cb({
3403
- requests: performanceEntries.flatMap(entry => prepareRequest({
3404
- entry,
3405
- method: undefined,
3406
- status: undefined,
3407
- networkRequest: {}
3408
- }))
3409
- });
3410
- });
3411
- // compat checked earlier
3412
- // eslint-disable-next-line compat/compat
3413
- const entryTypes = PerformanceObserver.supportedEntryTypes.filter(x => options.performanceEntryTypeToObserve.includes(x));
3414
- // initial records are gathered above, so we don't need to observe and buffer each type separately
3415
- observer.observe({
3416
- entryTypes
3417
- });
3418
- return () => {
3419
- observer.disconnect();
3420
- };
3421
- }
3422
- function shouldRecordHeaders(type, recordHeaders) {
3423
- return !!recordHeaders && (isBoolean(recordHeaders) || recordHeaders[type]);
3424
- }
3425
- function shouldRecordBody({
3426
- type,
3427
- recordBody,
3428
- headers,
3429
- url
3430
- }) {
3431
- function matchesContentType(contentTypes) {
3432
- const contentTypeHeader = Object.keys(headers).find(key => key.toLowerCase() === 'content-type');
3433
- const contentType = contentTypeHeader && headers[contentTypeHeader];
3434
- return contentTypes.some(ct => contentType?.includes(ct));
3435
- }
3436
- /**
3437
- * particularly in canvas applications we see many requests to blob URLs
3438
- * e.g. blob:https://video_url
3439
- * these blob/object URLs are local to the browser, we can never capture that body
3440
- * so we can just return false here
3441
- */
3442
- function isBlobURL(url) {
3443
- try {
3444
- if (typeof url === 'string') {
3445
- return url.startsWith('blob:');
3446
- }
3447
- if (url instanceof URL) {
3448
- return url.protocol === 'blob:';
3449
- }
3450
- if (url instanceof Request) {
3451
- return isBlobURL(url.url);
3452
- }
3453
- return false;
3454
- } catch {
3455
- return false;
3456
- }
3457
- }
3458
- if (!recordBody) return false;
3459
- if (isBlobURL(url)) return false;
3460
- if (isBoolean(recordBody)) return true;
3461
- if (isArray(recordBody)) return matchesContentType(recordBody);
3462
- const recordBodyType = recordBody[type];
3463
- if (isBoolean(recordBodyType)) return recordBodyType;
3464
- return matchesContentType(recordBodyType);
3465
- }
3466
- async function getRequestPerformanceEntry(win, initiatorType, url, start, end, attempt = 0) {
3467
- if (attempt > 10) {
3468
- logger$1.warn('Failed to get performance entry for request', {
3469
- url,
3470
- initiatorType
3471
- });
3472
- return null;
3473
- }
3474
- const urlPerformanceEntries = win.performance.getEntriesByName(url);
3475
- const performanceEntry = findLast(urlPerformanceEntries, entry => isResourceTiming(entry) && entry.initiatorType === initiatorType && (isUndefined(start) || entry.startTime >= start) && (isUndefined(end) || entry.startTime <= end));
3476
- if (!performanceEntry) {
3477
- await new Promise(resolve => setTimeout(resolve, 50 * attempt));
3478
- return getRequestPerformanceEntry(win, initiatorType, url, start, end, attempt + 1);
3479
- }
3480
- return performanceEntry;
3481
- }
3482
- /**
3483
- * According to MDN https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/response
3484
- * xhr response is typed as any but can be an ArrayBuffer, a Blob, a Document, a JavaScript object,
3485
- * or a string, depending on the value of XMLHttpRequest.responseType, that contains the response entity body.
3486
- *
3487
- * XHR request body is Document | XMLHttpRequestBodyInit | null | undefined
3488
- */
3489
- function _tryReadXHRBody({
3490
- body,
3491
- options,
3492
- url
3493
- }) {
3494
- if (isNullish(body)) {
3495
- return null;
3496
- }
3497
- const {
3498
- hostname,
3499
- isHostDenied
3500
- } = isHostOnDenyList(url, options);
3501
- if (isHostDenied) {
3502
- return hostname + ' is in deny list';
3503
- }
3504
- if (isString(body)) {
3505
- return body;
3506
- }
3507
- if (isDocument(body)) {
3508
- return body.textContent;
3509
- }
3510
- if (isFormData(body)) {
3511
- return formDataToQuery(body);
3512
- }
3513
- if (isObject(body)) {
3514
- try {
3515
- return JSON.stringify(body);
3516
- } catch {
3517
- return '[SessionReplay] Failed to stringify response object';
3518
- }
3519
- }
3520
- return '[SessionReplay] Cannot read body of type ' + toString.call(body);
3521
- }
3522
- function initXhrObserver(cb, win, options) {
3523
- if (!options.initiatorTypes.includes('xmlhttprequest')) {
3524
- return () => {
3525
- //
3526
- };
3527
- }
3528
- const recordRequestHeaders = shouldRecordHeaders('request', options.recordHeaders);
3529
- const recordResponseHeaders = shouldRecordHeaders('response', options.recordHeaders);
3530
- const restorePatch = patch(win.XMLHttpRequest.prototype, 'open',
3531
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
3532
- // @ts-ignore
3533
- originalOpen => {
3534
- return function (method, url, async = true, username, password) {
3535
- // because this function is returned in its actual context `this` _is_ an XMLHttpRequest
3536
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
3537
- // @ts-ignore
3538
- const xhr = this;
3539
- // check IE earlier than this, we only initialize if Request is present
3540
- // eslint-disable-next-line compat/compat
3541
- const req = new Request(url);
3542
- const networkRequest = {};
3543
- let start;
3544
- let end;
3545
- const requestHeaders = {};
3546
- const originalSetRequestHeader = xhr.setRequestHeader.bind(xhr);
3547
- xhr.setRequestHeader = (header, value) => {
3548
- requestHeaders[header] = value;
3549
- return originalSetRequestHeader(header, value);
3550
- };
3551
- if (recordRequestHeaders) {
3552
- networkRequest.requestHeaders = requestHeaders;
3553
- }
3554
- const originalSend = xhr.send.bind(xhr);
3555
- xhr.send = body => {
3556
- if (shouldRecordBody({
3557
- type: 'request',
3558
- headers: requestHeaders,
3559
- url,
3560
- recordBody: options.recordBody
3561
- })) {
3562
- networkRequest.requestBody = _tryReadXHRBody({
3563
- body,
3564
- options,
3565
- url
3566
- });
3567
- }
3568
- start = win.performance.now();
3569
- return originalSend(body);
3570
- };
3571
- const readyStateListener = () => {
3572
- if (xhr.readyState !== xhr.DONE) {
3573
- return;
3574
- }
3575
- // Clean up the listener immediately when done to prevent memory leaks
3576
- xhr.removeEventListener('readystatechange', readyStateListener);
3577
- end = win.performance.now();
3578
- const responseHeaders = {};
3579
- const rawHeaders = xhr.getAllResponseHeaders();
3580
- const headers = rawHeaders.trim().split(/[\r\n]+/);
3581
- headers.forEach(line => {
3582
- const parts = line.split(': ');
3583
- const header = parts.shift();
3584
- const value = parts.join(': ');
3585
- if (header) {
3586
- responseHeaders[header] = value;
3587
- }
3588
- });
3589
- if (recordResponseHeaders) {
3590
- networkRequest.responseHeaders = responseHeaders;
3591
- }
3592
- if (shouldRecordBody({
3593
- type: 'response',
3594
- headers: responseHeaders,
3595
- url,
3596
- recordBody: options.recordBody
3597
- })) {
3598
- networkRequest.responseBody = _tryReadXHRBody({
3599
- body: xhr.response,
3600
- options,
3601
- url
3602
- });
3603
- }
3604
- getRequestPerformanceEntry(win, 'xmlhttprequest', req.url, start, end).then(entry => {
3605
- const requests = prepareRequest({
3606
- entry,
3607
- method: method,
3608
- status: xhr?.status,
3609
- networkRequest,
3610
- start,
3611
- end,
3612
- url: url.toString(),
3613
- initiatorType: 'xmlhttprequest'
3614
- });
3615
- cb({
3616
- requests
3617
- });
3618
- }).catch(() => {
3619
- //
3620
- });
3621
- };
3622
- // This is very tricky code, and making it passive won't bring many performance benefits,
3623
- // so let's ignore the rule here.
3624
- // eslint-disable-next-line posthog-js/no-add-event-listener
3625
- xhr.addEventListener('readystatechange', readyStateListener);
3626
- originalOpen.call(xhr, method, url, async, username, password);
3627
- };
3628
- });
3629
- return () => {
3630
- restorePatch();
3631
- };
3632
- }
3633
- /**
3634
- * Check if this PerformanceEntry is either a PerformanceResourceTiming or a PerformanceNavigationTiming
3635
- * NB PerformanceNavigationTiming extends PerformanceResourceTiming
3636
- * Here we don't care which interface it implements as both expose `serverTimings`
3637
- */
3638
- const exposesServerTiming = event => !isNull(event) && (event.entryType === 'navigation' || event.entryType === 'resource');
3639
- function prepareRequest({
3640
- entry,
3641
- method,
3642
- status,
3643
- networkRequest,
3644
- isInitial,
3645
- start,
3646
- end,
3647
- url,
3648
- initiatorType
3649
- }) {
3650
- start = entry ? entry.startTime : start;
3651
- end = entry ? entry.responseEnd : end;
3652
- // kudos to sentry javascript sdk for excellent background on why to use Date.now() here
3653
- // https://github.com/getsentry/sentry-javascript/blob/e856e40b6e71a73252e788cd42b5260f81c9c88e/packages/utils/src/time.ts#L70
3654
- // can't start observer if performance.now() is not available
3655
- // eslint-disable-next-line compat/compat
3656
- const timeOrigin = Math.floor(Date.now() - performance.now());
3657
- // clickhouse can't ingest timestamps that are floats
3658
- // (in this case representing fractions of a millisecond we don't care about anyway)
3659
- // use timeOrigin if we really can't gather a start time
3660
- const timestamp = Math.floor(timeOrigin + (start || 0));
3661
- const entryJSON = entry ? entry.toJSON() : {
3662
- name: url
3663
- };
3664
- const requests = [{
3665
- ...entryJSON,
3666
- startTime: isUndefined(start) ? undefined : Math.round(start),
3667
- endTime: isUndefined(end) ? undefined : Math.round(end),
3668
- timeOrigin,
3669
- timestamp,
3670
- method: method,
3671
- initiatorType: initiatorType ? initiatorType : entry ? entry.initiatorType : undefined,
3672
- status,
3673
- requestHeaders: networkRequest.requestHeaders,
3674
- requestBody: networkRequest.requestBody,
3675
- responseHeaders: networkRequest.responseHeaders,
3676
- responseBody: networkRequest.responseBody,
3677
- isInitial
3678
- }];
3679
- if (exposesServerTiming(entry)) {
3680
- for (const timing of entry.serverTiming || []) {
3681
- requests.push({
3682
- timeOrigin,
3683
- timestamp,
3684
- startTime: Math.round(entry.startTime),
3685
- name: timing.name,
3686
- duration: timing.duration,
3687
- // the spec has a closed list of possible types
3688
- // https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEntry/entryType
3689
- // but, we need to know this was a server timing so that we know to
3690
- // match it to the appropriate navigation or resource timing
3691
- // that matching will have to be on timestamp and $current_url
3692
- entryType: 'serverTiming'
3693
- });
3694
- }
3695
- }
3696
- return requests;
3697
- }
3698
- const contentTypePrefixDenyList = ['video/', 'audio/'];
3699
- function _checkForCannotReadResponseBody({
3700
- r,
3701
- options,
3702
- url
3703
- }) {
3704
- if (r.headers.get('Transfer-Encoding') === 'chunked') {
3705
- return 'Chunked Transfer-Encoding is not supported';
3706
- }
3707
- // `get` and `has` are case-insensitive
3708
- // but return the header value with the casing that was supplied
3709
- const contentType = r.headers.get('Content-Type')?.toLowerCase();
3710
- const contentTypeIsDenied = contentTypePrefixDenyList.some(prefix => contentType?.startsWith(prefix));
3711
- if (contentType && contentTypeIsDenied) {
3712
- return `Content-Type ${contentType} is not supported`;
3713
- }
3714
- const {
3715
- hostname,
3716
- isHostDenied
3717
- } = isHostOnDenyList(url, options);
3718
- if (isHostDenied) {
3719
- return hostname + ' is in deny list';
3720
- }
3721
- return null;
3722
- }
3723
- function _tryReadBody(r) {
3724
- // there are now already multiple places where we're using Promise...
3725
- // eslint-disable-next-line compat/compat
3726
- return new Promise((resolve, reject) => {
3727
- const timeout = setTimeout(() => resolve('[SessionReplay] Timeout while trying to read body'), 500);
3728
- try {
3729
- r.clone().text().then(txt => resolve(txt), reason => reject(reason)).finally(() => clearTimeout(timeout));
3730
- } catch {
3731
- clearTimeout(timeout);
3732
- resolve('[SessionReplay] Failed to read body');
3733
- }
3734
- });
3735
- }
3736
- async function _tryReadRequestBody({
3737
- r,
3738
- options,
3739
- url
3740
- }) {
3741
- const {
3742
- hostname,
3743
- isHostDenied
3744
- } = isHostOnDenyList(url, options);
3745
- if (isHostDenied) {
3746
- return Promise.resolve(hostname + ' is in deny list');
3747
- }
3748
- return _tryReadBody(r);
3749
- }
3750
- async function _tryReadResponseBody({
3751
- r,
3752
- options,
3753
- url
3754
- }) {
3755
- const cannotReadBodyReason = _checkForCannotReadResponseBody({
3756
- r,
3757
- options,
3758
- url
3759
- });
3760
- if (!isNull(cannotReadBodyReason)) {
3761
- return Promise.resolve(cannotReadBodyReason);
3762
- }
3763
- return _tryReadBody(r);
3764
- }
3765
- function initFetchObserver(cb, win, options) {
3766
- if (!options.initiatorTypes.includes('fetch')) {
3767
- return () => {
3768
- //
3769
- };
3770
- }
3771
- const recordRequestHeaders = shouldRecordHeaders('request', options.recordHeaders);
3772
- const recordResponseHeaders = shouldRecordHeaders('response', options.recordHeaders);
3773
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
3774
- // @ts-ignore
3775
- const restorePatch = patch(win, 'fetch', originalFetch => {
3776
- return async function (url, init) {
3777
- // check IE earlier than this, we only initialize if Request is present
3778
- // eslint-disable-next-line compat/compat
3779
- const req = new Request(url, init);
3780
- let res;
3781
- const networkRequest = {};
3782
- let start;
3783
- let end;
3784
- try {
3785
- const requestHeaders = {};
3786
- req.headers.forEach((value, header) => {
3787
- requestHeaders[header] = value;
3788
- });
3789
- if (recordRequestHeaders) {
3790
- networkRequest.requestHeaders = requestHeaders;
3791
- }
3792
- if (shouldRecordBody({
3793
- type: 'request',
3794
- headers: requestHeaders,
3795
- url,
3796
- recordBody: options.recordBody
3797
- })) {
3798
- networkRequest.requestBody = await _tryReadRequestBody({
3799
- r: req,
3800
- options,
3801
- url
3802
- });
3803
- }
3804
- start = win.performance.now();
3805
- res = await originalFetch(req);
3806
- end = win.performance.now();
3807
- const responseHeaders = {};
3808
- res.headers.forEach((value, header) => {
3809
- responseHeaders[header] = value;
3810
- });
3811
- if (recordResponseHeaders) {
3812
- networkRequest.responseHeaders = responseHeaders;
3813
- }
3814
- if (shouldRecordBody({
3815
- type: 'response',
3816
- headers: responseHeaders,
3817
- url,
3818
- recordBody: options.recordBody
3819
- })) {
3820
- networkRequest.responseBody = await _tryReadResponseBody({
3821
- r: res,
3822
- options,
3823
- url
3824
- });
3825
- }
3826
- return res;
3827
- } finally {
3828
- getRequestPerformanceEntry(win, 'fetch', req.url, start, end).then(entry => {
3829
- const requests = prepareRequest({
3830
- entry,
3831
- method: req.method,
3832
- status: res?.status,
3833
- networkRequest,
3834
- start,
3835
- end,
3836
- url: req.url,
3837
- initiatorType: 'fetch'
3838
- });
3839
- cb({
3840
- requests
3841
- });
3842
- }).catch(() => {
3843
- //
3844
- });
3845
- }
3846
- };
3847
- });
3848
- return () => {
3849
- restorePatch();
3850
- };
3851
- }
3852
- let initialisedHandler = null;
3853
- function initNetworkObserver(callback, win,
3854
- // top window or in an iframe
3855
- options) {
3856
- if (!('performance' in win)) {
3857
- return () => {
3858
- //
3859
- };
3860
- }
3861
- if (initialisedHandler) {
3862
- logger$1.warn('Network observer already initialised, doing nothing');
3863
- return () => {
3864
- // the first caller should already have this handler and will be responsible for teardown
3865
- };
3866
- }
3867
- const networkOptions = options ? Object.assign({}, defaultNetworkOptions, options) : defaultNetworkOptions;
3868
- const cb = data => {
3869
- const requests = [];
3870
- data.requests.forEach(request => {
3871
- const maskedRequest = networkOptions.maskRequestFn(request);
3872
- if (maskedRequest) {
3873
- requests.push(maskedRequest);
3874
- }
3875
- });
3876
- if (requests.length > 0) {
3877
- callback({
3878
- ...data,
3879
- requests
3880
- });
3881
- }
3882
- };
3883
- const performanceObserver = initPerformanceObserver(cb, win, networkOptions);
3884
- // only wrap fetch and xhr if headers or body are being recorded
3885
- let xhrObserver = () => {};
3886
- let fetchObserver = () => {};
3887
- if (networkOptions.recordHeaders || networkOptions.recordBody) {
3888
- xhrObserver = initXhrObserver(cb, win, networkOptions);
3889
- fetchObserver = initFetchObserver(cb, win, networkOptions);
3890
- }
3891
- const teardown = () => {
3892
- performanceObserver();
3893
- xhrObserver();
3894
- fetchObserver();
3895
- // allow future observers to initialize after cleanup
3896
- initialisedHandler = null;
3897
- };
3898
- initialisedHandler = teardown;
3899
- return teardown;
3900
- }
3901
- // use the plugin name so that when this functionality is adopted into rrweb
3902
- // we can remove this plugin and use the core functionality with the same data
3903
- const NETWORK_PLUGIN_NAME = 'rrweb/network@1';
3904
- // TODO how should this be typed?
3905
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
3906
- // @ts-ignore
3907
- const getRecordNetworkPlugin = options => {
3908
- return {
3909
- name: NETWORK_PLUGIN_NAME,
3910
- observer: initNetworkObserver,
3911
- options: options
3912
- };
3913
- };
3914
- // rrweb/networ@1 ends
3915
-
3916
- // Use a safe global target (prefer `win`, fallback to globalThis)
3917
- const _target = win ?? globalThis;
3918
- _target.__PosthogExtensions__ = _target.__PosthogExtensions__ || {};
3919
- // Expose rrweb.record under the same contract
3920
- _target.__PosthogExtensions__.rrweb = _target.__PosthogExtensions__.rrweb || {
3921
- record: record
3922
- };
3923
- // Provide initSessionRecording if not present — return a new LazyLoadedSessionRecording when called
3924
- _target.__PosthogExtensions__.initSessionRecording = _target.__PosthogExtensions__.initSessionRecording || (instance => {
3925
- return new LazyLoadedSessionRecording(instance);
3926
- });
3927
- // Provide a no-op loadExternalDependency that calls the callback immediately (since rrweb is bundled)
3928
- _target.__PosthogExtensions__.loadExternalDependency = _target.__PosthogExtensions__.loadExternalDependency || ((instance, scriptName, cb) => {
3929
- if (cb) cb(undefined);
3930
- });
3931
- // Provide rrwebPlugins object with network plugin factory if not present
3932
- _target.__PosthogExtensions__.rrwebPlugins = _target.__PosthogExtensions__.rrwebPlugins || {};
3933
- _target.__PosthogExtensions__.rrwebPlugins.getRecordNetworkPlugin = _target.__PosthogExtensions__.rrwebPlugins.getRecordNetworkPlugin || (() => getRecordNetworkPlugin);
3934
-
3935
- // Type definitions copied from @rrweb/types@2.0.0-alpha.17 and rrweb-snapshot@2.0.0-alpha.17
3936
- // Both packages are MIT licensed: https://github.com/rrweb-io/rrweb
3937
- //
3938
- // These types are copied here to avoid requiring users to install peer dependencies
3939
- // solely for TypeScript type information.
3940
- //
3941
- // Original sources:
3942
- // - @rrweb/types: https://github.com/rrweb-io/rrweb/tree/main/packages/@rrweb/types
3943
- // - rrweb-snapshot: https://github.com/rrweb-io/rrweb/tree/main/packages/rrweb-snapshot
3944
- var NodeType;
3945
- (function (NodeType) {
3946
- NodeType[NodeType["Document"] = 0] = "Document";
3947
- NodeType[NodeType["DocumentType"] = 1] = "DocumentType";
3948
- NodeType[NodeType["Element"] = 2] = "Element";
3949
- NodeType[NodeType["Text"] = 3] = "Text";
3950
- NodeType[NodeType["CDATA"] = 4] = "CDATA";
3951
- NodeType[NodeType["Comment"] = 5] = "Comment";
3952
- })(NodeType || (NodeType = {}));
3953
- var EventType;
3954
- (function (EventType) {
3955
- EventType[EventType["DomContentLoaded"] = 0] = "DomContentLoaded";
3956
- EventType[EventType["Load"] = 1] = "Load";
3957
- EventType[EventType["FullSnapshot"] = 2] = "FullSnapshot";
3958
- EventType[EventType["IncrementalSnapshot"] = 3] = "IncrementalSnapshot";
3959
- EventType[EventType["Meta"] = 4] = "Meta";
3960
- EventType[EventType["Custom"] = 5] = "Custom";
3961
- EventType[EventType["Plugin"] = 6] = "Plugin";
3962
- })(EventType || (EventType = {}));
3963
- var IncrementalSource;
3964
- (function (IncrementalSource) {
3965
- IncrementalSource[IncrementalSource["Mutation"] = 0] = "Mutation";
3966
- IncrementalSource[IncrementalSource["MouseMove"] = 1] = "MouseMove";
3967
- IncrementalSource[IncrementalSource["MouseInteraction"] = 2] = "MouseInteraction";
3968
- IncrementalSource[IncrementalSource["Scroll"] = 3] = "Scroll";
3969
- IncrementalSource[IncrementalSource["ViewportResize"] = 4] = "ViewportResize";
3970
- IncrementalSource[IncrementalSource["Input"] = 5] = "Input";
3971
- IncrementalSource[IncrementalSource["TouchMove"] = 6] = "TouchMove";
3972
- IncrementalSource[IncrementalSource["MediaInteraction"] = 7] = "MediaInteraction";
3973
- IncrementalSource[IncrementalSource["StyleSheetRule"] = 8] = "StyleSheetRule";
3974
- IncrementalSource[IncrementalSource["CanvasMutation"] = 9] = "CanvasMutation";
3975
- IncrementalSource[IncrementalSource["Font"] = 10] = "Font";
3976
- IncrementalSource[IncrementalSource["Log"] = 11] = "Log";
3977
- IncrementalSource[IncrementalSource["Drag"] = 12] = "Drag";
3978
- IncrementalSource[IncrementalSource["StyleDeclaration"] = 13] = "StyleDeclaration";
3979
- IncrementalSource[IncrementalSource["Selection"] = 14] = "Selection";
3980
- IncrementalSource[IncrementalSource["AdoptedStyleSheet"] = 15] = "AdoptedStyleSheet";
3981
- IncrementalSource[IncrementalSource["CustomElement"] = 16] = "CustomElement";
3982
- })(IncrementalSource || (IncrementalSource = {}));
3983
- var MouseInteractions;
3984
- (function (MouseInteractions) {
3985
- MouseInteractions[MouseInteractions["MouseUp"] = 0] = "MouseUp";
3986
- MouseInteractions[MouseInteractions["MouseDown"] = 1] = "MouseDown";
3987
- MouseInteractions[MouseInteractions["Click"] = 2] = "Click";
3988
- MouseInteractions[MouseInteractions["ContextMenu"] = 3] = "ContextMenu";
3989
- MouseInteractions[MouseInteractions["DblClick"] = 4] = "DblClick";
3990
- MouseInteractions[MouseInteractions["Focus"] = 5] = "Focus";
3991
- MouseInteractions[MouseInteractions["Blur"] = 6] = "Blur";
3992
- MouseInteractions[MouseInteractions["TouchStart"] = 7] = "TouchStart";
3993
- MouseInteractions[MouseInteractions["TouchMove_Departed"] = 8] = "TouchMove_Departed";
3994
- MouseInteractions[MouseInteractions["TouchEnd"] = 9] = "TouchEnd";
3995
- MouseInteractions[MouseInteractions["TouchCancel"] = 10] = "TouchCancel";
3996
- })(MouseInteractions || (MouseInteractions = {}));
3997
- var PointerTypes;
3998
- (function (PointerTypes) {
3999
- PointerTypes[PointerTypes["Mouse"] = 0] = "Mouse";
4000
- PointerTypes[PointerTypes["Pen"] = 1] = "Pen";
4001
- PointerTypes[PointerTypes["Touch"] = 2] = "Touch";
4002
- })(PointerTypes || (PointerTypes = {}));
4003
- var MediaInteractions;
4004
- (function (MediaInteractions) {
4005
- MediaInteractions[MediaInteractions["Play"] = 0] = "Play";
4006
- MediaInteractions[MediaInteractions["Pause"] = 1] = "Pause";
4007
- MediaInteractions[MediaInteractions["Seeked"] = 2] = "Seeked";
4008
- MediaInteractions[MediaInteractions["VolumeChange"] = 3] = "VolumeChange";
4009
- MediaInteractions[MediaInteractions["RateChange"] = 4] = "RateChange";
4010
- })(MediaInteractions || (MediaInteractions = {}));
4011
- var CanvasContext;
4012
- (function (CanvasContext) {
4013
- CanvasContext[CanvasContext["2D"] = 0] = "2D";
4014
- CanvasContext[CanvasContext["WebGL"] = 1] = "WebGL";
4015
- CanvasContext[CanvasContext["WebGL2"] = 2] = "WebGL2";
4016
- })(CanvasContext || (CanvasContext = {}));
4017
-
4018
3478
  const DISABLED = 'disabled';
4019
3479
  const SAMPLED = 'sampled';
4020
3480
  const ACTIVE = 'active';
@@ -4422,7 +3882,7 @@ class MutationThrottler {
4422
3882
  refillInterval: 1000,
4423
3883
  // one second
4424
3884
  _onBucketRateLimited: this._onNodeRateLimited,
4425
- _logger: logger$2
3885
+ _logger: logger$1
4426
3886
  });
4427
3887
  }
4428
3888
  reset() {
@@ -4446,9 +3906,10 @@ function simpleHash(str) {
4446
3906
  * receives percent as a number between 0 and 1
4447
3907
  */
4448
3908
  function sampleOnProperty(prop, percent) {
4449
- return simpleHash(prop) % 100 < clampToRange(percent * 100, 0, 100, logger$2);
3909
+ return simpleHash(prop) % 100 < clampToRange(percent * 100, 0, 100, logger$1);
4450
3910
  }
4451
3911
 
3912
+ /* eslint-disable posthog-js/no-direct-function-check */
4452
3913
  const BASE_ENDPOINT = '/s/';
4453
3914
  const DEFAULT_CANVAS_QUALITY = 0.4;
4454
3915
  const DEFAULT_CANVAS_FPS = 4;
@@ -4459,6 +3920,19 @@ const ONE_KB = 1024;
4459
3920
  const ONE_MINUTE = 1000 * 60;
4460
3921
  const FIVE_MINUTES = ONE_MINUTE * 5;
4461
3922
  const RECORDING_IDLE_THRESHOLD_MS = FIVE_MINUTES;
3923
+ // Register a factory on the global extensions object so the extension shim can
3924
+ // instantiate a LazyLoadedSessionRecording without importing this module directly.
3925
+ try {
3926
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3927
+ const ext = globalThis.__PosthogExtensions__;
3928
+ if (ext) {
3929
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3930
+ ext._initSessionRecordingFactory = ext._initSessionRecordingFactory || (instance => new LazyLoadedSessionRecording(instance));
3931
+ }
3932
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
3933
+ } catch (e) {
3934
+ // ignore
3935
+ }
4462
3936
  const RECORDING_MAX_EVENT_SIZE = ONE_KB * ONE_KB * 0.9; // ~1mb (with some wiggle room)
4463
3937
  const RECORDING_BUFFER_TIMEOUT = 2000; // 2 seconds
4464
3938
  const SESSION_RECORDING_BATCH_KEY = 'recordings';
@@ -4480,7 +3954,41 @@ function getRRWebRecord() {
4480
3954
  } catch {
4481
3955
  // ignore
4482
3956
  }
4483
- 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;
4484
3992
  }
4485
3993
  function gzipToString(data) {
4486
3994
  return strFromU8(gzipSync(strToU8(JSON.stringify(data))), true);
@@ -4737,7 +4245,13 @@ class LazyLoadedSessionRecording {
4737
4245
  if (this._networkPayloadCapture) {
4738
4246
  const canRecordNetwork = !isLocalhost() || this._forceAllowLocalhostNetworkCapture;
4739
4247
  if (canRecordNetwork) {
4740
- plugins.push(getRecordNetworkPlugin(buildNetworkRequestOptions(this._instance.config, this._networkPayloadCapture)));
4248
+ const assignableWindow = globalThis;
4249
+ const networkFactory = assignableWindow.__PosthogExtensions__?.rrwebPlugins?.getRecordNetworkPlugin?.();
4250
+ if (typeof networkFactory === 'function') {
4251
+ plugins.push(networkFactory(buildNetworkRequestOptions(this._instance.config, this._networkPayloadCapture)));
4252
+ } else {
4253
+ logger.info('Network plugin factory not available yet; skipping network plugin');
4254
+ }
4741
4255
  } else {
4742
4256
  logger.info('NetworkCapture not started because we are on localhost.');
4743
4257
  }
@@ -4897,7 +4411,7 @@ class LazyLoadedSessionRecording {
4897
4411
  const parsedConfig = isObject(persistedConfig) ? persistedConfig : JSON.parse(persistedConfig);
4898
4412
  return parsedConfig;
4899
4413
  }
4900
- start(startReason) {
4414
+ async start(startReason) {
4901
4415
  const config = this._remoteConfig;
4902
4416
  if (!config) {
4903
4417
  logger.info('remote config must be stored in persistence before recording can start');
@@ -4931,7 +4445,7 @@ class LazyLoadedSessionRecording {
4931
4445
  });
4932
4446
  });
4933
4447
  this._makeSamplingDecision(this.sessionId);
4934
- this._startRecorder();
4448
+ await this._startRecorder();
4935
4449
  // calling addEventListener multiple times is safe and will not add duplicates
4936
4450
  addEventListener(win, 'beforeunload', this._onBeforeUnload);
4937
4451
  addEventListener(win, 'offline', this._onOffline);
@@ -5387,7 +4901,7 @@ class LazyLoadedSessionRecording {
5387
4901
  $sdk_debug_session_start: sessionStartTimestamp
5388
4902
  };
5389
4903
  }
5390
- _startRecorder() {
4904
+ async _startRecorder() {
5391
4905
  if (this._stopRrweb) {
5392
4906
  return;
5393
4907
  }
@@ -5443,7 +4957,12 @@ class LazyLoadedSessionRecording {
5443
4957
  sessionRecordingOptions.maskTextSelector = this._masking.maskTextSelector ?? undefined;
5444
4958
  sessionRecordingOptions.blockSelector = this._masking.blockSelector ?? undefined;
5445
4959
  }
5446
- 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
+ }
5447
4966
  if (!rrwebRecord) {
5448
4967
  logger.error('_startRecorder was called but rrwebRecord is not available. This indicates something has gone wrong.');
5449
4968
  return;
@@ -5485,11 +5004,12 @@ class LazyLoadedSessionRecording {
5485
5004
  }
5486
5005
  }
5487
5006
 
5007
+ /* eslint-disable posthog-js/no-direct-function-check */
5488
5008
  const LOGGER_PREFIX = '[SessionRecording]';
5489
5009
  const log = {
5490
- info: (...args) => logger$3.info(LOGGER_PREFIX, ...args),
5491
- warn: (...args) => logger$3.warn(LOGGER_PREFIX, ...args),
5492
- error: (...args) => logger$3.error(LOGGER_PREFIX, ...args)
5010
+ info: (...args) => logger$2.info(LOGGER_PREFIX, ...args),
5011
+ warn: (...args) => logger$2.warn(LOGGER_PREFIX, ...args),
5012
+ error: (...args) => logger$2.error(LOGGER_PREFIX, ...args)
5493
5013
  };
5494
5014
  class SessionRecording {
5495
5015
  get started() {
@@ -5553,6 +5073,18 @@ class SessionRecording {
5553
5073
  if (!this._isRecordingEnabled) {
5554
5074
  return;
5555
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
+ }
5556
5088
  this._onScriptLoaded(startReason);
5557
5089
  }
5558
5090
  stopRecording() {
@@ -5628,15 +5160,43 @@ class SessionRecording {
5628
5160
  if (this._lazyLoadedSessionRecording?.log) {
5629
5161
  this._lazyLoadedSessionRecording.log(message, level);
5630
5162
  } else {
5631
- logger$3.warn('log called before recorder was ready');
5163
+ logger$2.warn('log called before recorder was ready');
5632
5164
  }
5633
5165
  }
5166
+ get _scriptName() {
5167
+ const remoteConfig = this._instance?.persistence?.get_property(SESSION_RECORDING_REMOTE_CONFIG);
5168
+ return remoteConfig?.scriptConfig?.script || 'lazy-recorder';
5169
+ }
5634
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
+ }
5635
5187
  if (!this._lazyLoadedSessionRecording) {
5636
5188
  this._lazyLoadedSessionRecording = new LazyLoadedSessionRecording(this._instance);
5637
5189
  this._lazyLoadedSessionRecording._forceAllowLocalhostNetworkCapture = this._forceAllowLocalhostNetworkCapture;
5638
5190
  }
5639
- 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
+ }
5640
5200
  }
5641
5201
  /**
5642
5202
  * this is maintained on the public API only because it has always been on the public API
@@ -5893,7 +5453,7 @@ class Leanbase extends PostHogCore {
5893
5453
  };
5894
5454
  properties['distinct_id'] = persistenceProps.distinct_id;
5895
5455
  if (!(isString(properties['distinct_id']) || isNumber(properties['distinct_id'])) || isEmptyString(properties['distinct_id'])) {
5896
- logger$3.error('Invalid distinct_id for replay event. This indicates a bug in your implementation');
5456
+ logger$2.error('Invalid distinct_id for replay event. This indicates a bug in your implementation');
5897
5457
  }
5898
5458
  return properties;
5899
5459
  }
@@ -5968,11 +5528,11 @@ class Leanbase extends PostHogCore {
5968
5528
  return;
5969
5529
  }
5970
5530
  if (isUndefined(event) || !isString(event)) {
5971
- logger$3.error('No event name provided to posthog.capture');
5531
+ logger$2.error('No event name provided to posthog.capture');
5972
5532
  return;
5973
5533
  }
5974
5534
  if (properties?.$current_url && !isString(properties?.$current_url)) {
5975
- logger$3.error('Invalid `$current_url` property provided to `posthog.capture`. Input must be a string. Ignoring provided value.');
5535
+ logger$2.error('Invalid `$current_url` property provided to `posthog.capture`. Input must be a string. Ignoring provided value.');
5976
5536
  delete properties?.$current_url;
5977
5537
  }
5978
5538
  this.sessionPersistence.update_search_keyword();