@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/LICENSE ADDED
@@ -0,0 +1,37 @@
1
+ Copyright 2025 Leanflag Limited
2
+ This project is a fork of PostHog/posthog-js.
3
+
4
+ All rights reserved. This software is proprietary to Leanflag Limited and may only be used for Leanflag Limited products and internal purposes. Redistribution, modification, or commercial use outside of Leanflag Limited is strictly prohibited without explicit written permission from Leanflag Limited.
5
+
6
+
7
+ ---
8
+
9
+ Copyright 2020 Posthog / Hiberly, Inc.
10
+
11
+ Copyright 2015 Mixpanel, Inc.
12
+
13
+
14
+ Some files in this codebase contain code from getsentry/sentry-javascript by Software, Inc. dba Sentry.
15
+ In such cases it is explicitly stated in the file header. This license only applies to the relevant code in such cases.
16
+
17
+ MIT License
18
+
19
+ Copyright (c) 2012 Functional Software, Inc. dba Sentry
20
+
21
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
22
+ this software and associated documentation files (the "Software"), to deal in
23
+ the Software without restriction, including without limitation the rights to
24
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
25
+ of the Software, and to permit persons to whom the Software is furnished to do
26
+ so, subject to the following conditions:
27
+
28
+ The above copyright notice and this permission notice shall be included in all
29
+ copies or substantial portions of the Software.
30
+
31
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37
+ SOFTWARE.
package/dist/index.cjs CHANGED
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  var core = require('@posthog/core');
4
- var record = require('@rrweb/record');
5
4
  var fflate = require('fflate');
6
5
 
7
6
  const breaker = {};
@@ -17,6 +16,8 @@ global?.fetch;
17
16
  global?.XMLHttpRequest && 'withCredentials' in new global.XMLHttpRequest() ? global.XMLHttpRequest : undefined;
18
17
  global?.AbortController;
19
18
  const userAgent = navigator$1?.userAgent;
19
+ // assignableWindow mirrors browser package's assignableWindow for extension loading shims
20
+ const assignableWindow = win ?? {};
20
21
  function eachArray(obj, iterator, thisArg) {
21
22
  if (core.isArray(obj)) {
22
23
  if (nativeForEach && obj.forEach === nativeForEach) {
@@ -1170,7 +1171,7 @@ const detectDeviceType = function (user_agent) {
1170
1171
  }
1171
1172
  };
1172
1173
 
1173
- var version = "0.1.5";
1174
+ var version = "0.2.2";
1174
1175
  var packageInfo = {
1175
1176
  version: version};
1176
1177
 
@@ -3108,12 +3109,101 @@ const isLikelyBot = function (navigator, customBlockedUserAgents) {
3108
3109
  // It would be very bad if posthog-js caused a permission prompt to appear on every page load.
3109
3110
  };
3110
3111
 
3112
+ /* eslint-disable @typescript-eslint/no-unused-vars */
3113
+ // We avoid importing '@rrweb/record' at module load time to prevent IIFE builds
3114
+ // from requiring a top-level global. Instead, expose a lazy proxy that will
3115
+ // dynamically import the module the first time it's used.
3116
+ let _cachedRRWeb = null;
3117
+ async function _loadRRWebModule() {
3118
+ if (_cachedRRWeb) return _cachedRRWeb;
3119
+ try {
3120
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3121
+ const mod = await import('@rrweb/record');
3122
+ _cachedRRWeb = mod;
3123
+ return _cachedRRWeb;
3124
+ } catch (e) {
3125
+ return null;
3126
+ }
3127
+ }
3128
+ // queue for method calls before rrweb loads
3129
+ const _queuedCalls = [];
3130
+ // Create a proxy function that delegates to the real rrweb.record when called
3131
+ const rrwebRecordProxy = function (...args) {
3132
+ let realStop;
3133
+ let calledReal = false;
3134
+ // Start loading asynchronously and call the real record when available
3135
+ void (async () => {
3136
+ const mod = await _loadRRWebModule();
3137
+ const real = mod && (mod.record ?? mod.default?.record);
3138
+ if (real) {
3139
+ try {
3140
+ calledReal = true;
3141
+ realStop = real(...args);
3142
+ // flush any queued calls that were waiting for rrweb
3143
+ while (_queuedCalls.length) {
3144
+ try {
3145
+ const fn = _queuedCalls.shift();
3146
+ fn();
3147
+ } catch (e) {
3148
+ // ignore
3149
+ }
3150
+ }
3151
+ } catch (e) {
3152
+ // ignore
3153
+ }
3154
+ }
3155
+ })();
3156
+ // return a stop function that will call the real stop when available
3157
+ return () => {
3158
+ if (realStop) {
3159
+ try {
3160
+ realStop();
3161
+ } catch (e) {
3162
+ // ignore
3163
+ }
3164
+ } else if (!calledReal) {
3165
+ // If rrweb hasn't been initialised yet, queue a stop request that will
3166
+ // call the real stop once available.
3167
+ _queuedCalls.push(() => {
3168
+ try {
3169
+ realStop?.();
3170
+ } catch (e) {
3171
+ // ignore
3172
+ }
3173
+ });
3174
+ }
3175
+ };
3176
+ };
3177
+ // methods that can be called on the rrweb.record object - queue until real module is available
3178
+ rrwebRecordProxy.addCustomEvent = function (tag, payload) {
3179
+ const call = () => {
3180
+ try {
3181
+ const real = _cachedRRWeb && (_cachedRRWeb.record ?? _cachedRRWeb.default?.record);
3182
+ real?.addCustomEvent?.(tag, payload);
3183
+ } catch (e) {
3184
+ // ignore
3185
+ }
3186
+ };
3187
+ if (_cachedRRWeb) call();else _queuedCalls.push(call);
3188
+ };
3189
+ rrwebRecordProxy.takeFullSnapshot = function () {
3190
+ const call = () => {
3191
+ try {
3192
+ const real = _cachedRRWeb && (_cachedRRWeb.record ?? _cachedRRWeb.default?.record);
3193
+ real?.takeFullSnapshot?.();
3194
+ } catch (e) {
3195
+ // ignore
3196
+ }
3197
+ };
3198
+ if (_cachedRRWeb) call();else _queuedCalls.push(call);
3199
+ };
3111
3200
  // Use a safe global target (prefer `win`, fallback to globalThis)
3112
3201
  const _target = win ?? globalThis;
3113
3202
  _target.__PosthogExtensions__ = _target.__PosthogExtensions__ || {};
3114
- // Expose rrweb.record under the same contract
3203
+ // Expose rrweb.record under the same contract. We provide a lazy proxy so
3204
+ // builds that execute this file don't require rrweb at module evaluation time.
3115
3205
  _target.__PosthogExtensions__.rrweb = _target.__PosthogExtensions__.rrweb || {
3116
- record: record.record
3206
+ record: rrwebRecordProxy
3117
3207
  };
3118
3208
  // Provide initSessionRecording if not present — return a new LazyLoadedSessionRecording when called
3119
3209
  _target.__PosthogExtensions__.initSessionRecording = _target.__PosthogExtensions__.initSessionRecording || (instance => {
@@ -3866,7 +3956,41 @@ function getRRWebRecord() {
3866
3956
  } catch {
3867
3957
  // ignore
3868
3958
  }
3869
- return record.record;
3959
+ // If we've previously loaded rrweb via dynamic import, return the cached reference
3960
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3961
+ const cached = getRRWebRecord._cachedRRWebRecord;
3962
+ return cached;
3963
+ }
3964
+ async function loadRRWeb() {
3965
+ try {
3966
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3967
+ const ext = globalThis.__PosthogExtensions__;
3968
+ if (ext && ext.rrweb && ext.rrweb.record) {
3969
+ ;
3970
+ getRRWebRecord._cachedRRWebRecord = ext.rrweb.record;
3971
+ return ext.rrweb.record;
3972
+ }
3973
+ // If already cached, return it
3974
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3975
+ const already = getRRWebRecord._cachedRRWebRecord;
3976
+ if (already) {
3977
+ return already;
3978
+ }
3979
+ // Dynamic import - let the bundler (IIFE build) include rrweb in the bundle or allow lazy-load
3980
+ // Note: we intentionally use a dynamic import so rrweb is not referenced at the module top-level
3981
+ // which would cause IIFE builds to assume a global is present at script execution.
3982
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3983
+ const mod = await import('@rrweb/record');
3984
+ const rr = mod && (mod.record ?? (mod.default && mod.default.record));
3985
+ if (rr) {
3986
+ ;
3987
+ getRRWebRecord._cachedRRWebRecord = rr;
3988
+ return rr;
3989
+ }
3990
+ } catch (e) {
3991
+ logger.error('could not dynamically load rrweb', e);
3992
+ }
3993
+ return null;
3870
3994
  }
3871
3995
  function gzipToString(data) {
3872
3996
  return fflate.strFromU8(fflate.gzipSync(fflate.strToU8(JSON.stringify(data))), true);
@@ -4289,7 +4413,7 @@ class LazyLoadedSessionRecording {
4289
4413
  const parsedConfig = core.isObject(persistedConfig) ? persistedConfig : JSON.parse(persistedConfig);
4290
4414
  return parsedConfig;
4291
4415
  }
4292
- start(startReason) {
4416
+ async start(startReason) {
4293
4417
  const config = this._remoteConfig;
4294
4418
  if (!config) {
4295
4419
  logger.info('remote config must be stored in persistence before recording can start');
@@ -4323,7 +4447,7 @@ class LazyLoadedSessionRecording {
4323
4447
  });
4324
4448
  });
4325
4449
  this._makeSamplingDecision(this.sessionId);
4326
- this._startRecorder();
4450
+ await this._startRecorder();
4327
4451
  // calling addEventListener multiple times is safe and will not add duplicates
4328
4452
  addEventListener(win, 'beforeunload', this._onBeforeUnload);
4329
4453
  addEventListener(win, 'offline', this._onOffline);
@@ -4779,7 +4903,7 @@ class LazyLoadedSessionRecording {
4779
4903
  $sdk_debug_session_start: sessionStartTimestamp
4780
4904
  };
4781
4905
  }
4782
- _startRecorder() {
4906
+ async _startRecorder() {
4783
4907
  if (this._stopRrweb) {
4784
4908
  return;
4785
4909
  }
@@ -4835,7 +4959,12 @@ class LazyLoadedSessionRecording {
4835
4959
  sessionRecordingOptions.maskTextSelector = this._masking.maskTextSelector ?? undefined;
4836
4960
  sessionRecordingOptions.blockSelector = this._masking.blockSelector ?? undefined;
4837
4961
  }
4838
- const rrwebRecord = getRRWebRecord();
4962
+ // Ensure rrweb is loaded (either via global extension or dynamic import)
4963
+ let rrwebRecord = getRRWebRecord();
4964
+ if (!rrwebRecord) {
4965
+ const loaded = await loadRRWeb();
4966
+ rrwebRecord = loaded ?? undefined;
4967
+ }
4839
4968
  if (!rrwebRecord) {
4840
4969
  logger.error('_startRecorder was called but rrwebRecord is not available. This indicates something has gone wrong.');
4841
4970
  return;
@@ -4877,6 +5006,7 @@ class LazyLoadedSessionRecording {
4877
5006
  }
4878
5007
  }
4879
5008
 
5009
+ /* eslint-disable posthog-js/no-direct-function-check */
4880
5010
  const LOGGER_PREFIX = '[SessionRecording]';
4881
5011
  const log = {
4882
5012
  info: (...args) => logger$2.info(LOGGER_PREFIX, ...args),
@@ -4945,6 +5075,18 @@ class SessionRecording {
4945
5075
  if (!this._isRecordingEnabled) {
4946
5076
  return;
4947
5077
  }
5078
+ // If extensions provide a loader, use it. Otherwise fallback to the local _onScriptLoaded which
5079
+ // will create the local LazyLoadedSessionRecording (so tests that mock it work correctly).
5080
+ const loader = assignableWindow.__PosthogExtensions__?.loadExternalDependency;
5081
+ if (typeof loader === 'function') {
5082
+ loader(this._instance, this._scriptName, err => {
5083
+ if (err) {
5084
+ return log.error('could not load recorder', err);
5085
+ }
5086
+ this._onScriptLoaded(startReason);
5087
+ });
5088
+ return;
5089
+ }
4948
5090
  this._onScriptLoaded(startReason);
4949
5091
  }
4950
5092
  stopRecording() {
@@ -5023,12 +5165,40 @@ class SessionRecording {
5023
5165
  logger$2.warn('log called before recorder was ready');
5024
5166
  }
5025
5167
  }
5168
+ get _scriptName() {
5169
+ const remoteConfig = this._instance?.persistence?.get_property(SESSION_RECORDING_REMOTE_CONFIG);
5170
+ return remoteConfig?.scriptConfig?.script || 'lazy-recorder';
5171
+ }
5026
5172
  _onScriptLoaded(startReason) {
5173
+ // If extensions provide an init function, use it. Otherwise, fall back to the local LazyLoadedSessionRecording
5174
+ if (assignableWindow.__PosthogExtensions__?.initSessionRecording) {
5175
+ if (!this._lazyLoadedSessionRecording) {
5176
+ this._lazyLoadedSessionRecording = assignableWindow.__PosthogExtensions__?.initSessionRecording(this._instance);
5177
+ this._lazyLoadedSessionRecording._forceAllowLocalhostNetworkCapture = this._forceAllowLocalhostNetworkCapture;
5178
+ }
5179
+ try {
5180
+ const maybePromise = this._lazyLoadedSessionRecording.start(startReason);
5181
+ if (maybePromise && typeof maybePromise.catch === 'function') {
5182
+ maybePromise.catch(e => logger$2.error('error starting session recording', e));
5183
+ }
5184
+ } catch (e) {
5185
+ logger$2.error('error starting session recording', e);
5186
+ }
5187
+ return;
5188
+ }
5027
5189
  if (!this._lazyLoadedSessionRecording) {
5028
5190
  this._lazyLoadedSessionRecording = new LazyLoadedSessionRecording(this._instance);
5029
5191
  this._lazyLoadedSessionRecording._forceAllowLocalhostNetworkCapture = this._forceAllowLocalhostNetworkCapture;
5030
5192
  }
5031
- this._lazyLoadedSessionRecording.start(startReason);
5193
+ // start may perform a dynamic import; handle both sync and Promise returns
5194
+ try {
5195
+ const maybePromise = this._lazyLoadedSessionRecording.start(startReason);
5196
+ if (maybePromise && typeof maybePromise.catch === 'function') {
5197
+ maybePromise.catch(e => logger$2.error('error starting session recording', e));
5198
+ }
5199
+ } catch (e) {
5200
+ logger$2.error('error starting session recording', e);
5201
+ }
5032
5202
  }
5033
5203
  /**
5034
5204
  * this is maintained on the public API only because it has always been on the public API