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