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