@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 +37 -0
- package/dist/index.cjs +180 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.mjs +180 -10
- package/dist/index.mjs.map +1 -1
- package/dist/leanbase.iife.js +5329 -80
- package/dist/leanbase.iife.js.map +1 -1
- package/package.json +46 -47
- package/src/extensions/replay/extension-shim.ts +102 -3
- package/src/extensions/replay/external/lazy-loaded-session-recorder.ts +47 -6
- package/src/extensions/replay/session-recording.ts +52 -2
- package/src/utils/index.ts +3 -0
- package/src/version.ts +1 -1
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.
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|