@devskin/browser-sdk 1.0.40 → 1.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/devskin.cjs.js +205 -97
- package/dist/devskin.cjs.js.map +1 -1
- package/dist/devskin.esm.js +205 -97
- package/dist/devskin.esm.js.map +1 -1
- package/dist/devskin.umd.js +205 -97
- package/dist/devskin.umd.js.map +1 -1
- package/dist/devskin.umd.min.js +3 -3
- package/dist/devskin.umd.min.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/recorder/rrweb.d.ts +1 -1
- package/dist/recorder/rrweb.d.ts.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/devskin.esm.js
CHANGED
|
@@ -13469,11 +13469,12 @@ class RRWebRecorder {
|
|
|
13469
13469
|
},
|
|
13470
13470
|
// Configuration options
|
|
13471
13471
|
sampling: {
|
|
13472
|
-
// Mouse
|
|
13473
|
-
|
|
13474
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13472
|
+
// Mouse movement sampling - throttle to reduce bandwidth
|
|
13473
|
+
// e.g. 0.1 = capture 10% of mouse movements (Math.floor(1/0.1) = 10, meaning capture 1 every 10 events)
|
|
13474
|
+
mousemove: this.config.mouseMoveSampleRate !== undefined
|
|
13475
|
+
? Math.max(1, Math.floor(1 / this.config.mouseMoveSampleRate))
|
|
13476
|
+
: 10, // Default: capture 1 every 10 mouse movements (10%)
|
|
13477
|
+
// Mouse interactions (clicks, etc) - always capture
|
|
13477
13478
|
mouseInteraction: true,
|
|
13478
13479
|
// Scroll events
|
|
13479
13480
|
scroll: 150, // Throttle scroll events to every 150ms
|
|
@@ -13874,6 +13875,7 @@ class DevSkinSDK {
|
|
|
13874
13875
|
this.anonymousId = null;
|
|
13875
13876
|
this.sessionStartTime = 0;
|
|
13876
13877
|
this.initialized = false;
|
|
13878
|
+
this.initializing = false;
|
|
13877
13879
|
this.heartbeatInterval = null;
|
|
13878
13880
|
// Collectors
|
|
13879
13881
|
this.deviceCollector = null;
|
|
@@ -13887,14 +13889,83 @@ class DevSkinSDK {
|
|
|
13887
13889
|
// private sessionRecorder: SessionRecorder | null = null; // Replaced by RRWebRecorder
|
|
13888
13890
|
this.rrwebRecorder = null;
|
|
13889
13891
|
}
|
|
13892
|
+
/**
|
|
13893
|
+
* Detect if the current visitor is a bot/crawler
|
|
13894
|
+
*/
|
|
13895
|
+
isBot() {
|
|
13896
|
+
if (typeof navigator === 'undefined')
|
|
13897
|
+
return true;
|
|
13898
|
+
const botPatterns = [
|
|
13899
|
+
/bot/i,
|
|
13900
|
+
/crawler/i,
|
|
13901
|
+
/spider/i,
|
|
13902
|
+
/crawling/i,
|
|
13903
|
+
/google/i,
|
|
13904
|
+
/bing/i,
|
|
13905
|
+
/yahoo/i,
|
|
13906
|
+
/duckduckgo/i,
|
|
13907
|
+
/baiduspider/i,
|
|
13908
|
+
/yandex/i,
|
|
13909
|
+
/facebookexternalhit/i,
|
|
13910
|
+
/twitterbot/i,
|
|
13911
|
+
/rogerbot/i,
|
|
13912
|
+
/linkedinbot/i,
|
|
13913
|
+
/embedly/i,
|
|
13914
|
+
/quora link preview/i,
|
|
13915
|
+
/showyoubot/i,
|
|
13916
|
+
/outbrain/i,
|
|
13917
|
+
/pinterest/i,
|
|
13918
|
+
/slackbot/i,
|
|
13919
|
+
/vkShare/i,
|
|
13920
|
+
/W3C_Validator/i,
|
|
13921
|
+
/redditbot/i,
|
|
13922
|
+
/Applebot/i,
|
|
13923
|
+
/WhatsApp/i,
|
|
13924
|
+
/flipboard/i,
|
|
13925
|
+
/tumblr/i,
|
|
13926
|
+
/bitlybot/i,
|
|
13927
|
+
/SkypeUriPreview/i,
|
|
13928
|
+
/nuzzel/i,
|
|
13929
|
+
/Discordbot/i,
|
|
13930
|
+
/Google Page Speed/i,
|
|
13931
|
+
/Qwantify/i,
|
|
13932
|
+
/pinterestbot/i,
|
|
13933
|
+
/Bitrix link preview/i,
|
|
13934
|
+
/XING-contenttabreceiver/i,
|
|
13935
|
+
/Chrome-Lighthouse/i,
|
|
13936
|
+
/telegrambot/i,
|
|
13937
|
+
/Lighthouse/i,
|
|
13938
|
+
/GTmetrix/i,
|
|
13939
|
+
/PageSpeed/i,
|
|
13940
|
+
/HeadlessChrome/i,
|
|
13941
|
+
/PhantomJS/i,
|
|
13942
|
+
];
|
|
13943
|
+
const userAgent = navigator.userAgent;
|
|
13944
|
+
// Check user agent against bot patterns
|
|
13945
|
+
if (botPatterns.some(pattern => pattern.test(userAgent))) {
|
|
13946
|
+
return true;
|
|
13947
|
+
}
|
|
13948
|
+
// Check for headless browsers
|
|
13949
|
+
if ('webdriver' in navigator && navigator.webdriver === true) {
|
|
13950
|
+
return true;
|
|
13951
|
+
}
|
|
13952
|
+
// Check for headless Chrome
|
|
13953
|
+
if (userAgent.includes('HeadlessChrome')) {
|
|
13954
|
+
return true;
|
|
13955
|
+
}
|
|
13956
|
+
return false;
|
|
13957
|
+
}
|
|
13890
13958
|
/**
|
|
13891
13959
|
* Initialize the DevSkin SDK
|
|
13960
|
+
* Uses requestIdleCallback to defer heavy initialization without blocking the page
|
|
13892
13961
|
*/
|
|
13893
13962
|
init(config) {
|
|
13894
|
-
if (this.initialized) {
|
|
13895
|
-
console.warn('[DevSkin] SDK already initialized');
|
|
13963
|
+
if (this.initialized || this.initializing) {
|
|
13964
|
+
console.warn('[DevSkin] SDK already initialized or initializing');
|
|
13896
13965
|
return;
|
|
13897
13966
|
}
|
|
13967
|
+
// Mark as initializing to prevent duplicate init() calls
|
|
13968
|
+
this.initializing = true;
|
|
13898
13969
|
this.config = Object.assign({ debug: false, captureWebVitals: true, captureNetworkRequests: true, captureErrors: true, captureUserAgent: true, captureLocation: true, captureDevice: true, heatmapOptions: {
|
|
13899
13970
|
enabled: true,
|
|
13900
13971
|
trackClicks: true,
|
|
@@ -13904,99 +13975,135 @@ class DevSkinSDK {
|
|
|
13904
13975
|
if (this.config.debug) {
|
|
13905
13976
|
console.log('[DevSkin] Initializing SDK with config:', this.config);
|
|
13906
13977
|
}
|
|
13907
|
-
// Initialize
|
|
13978
|
+
// Initialize lightweight components immediately (needed for session context)
|
|
13908
13979
|
this.transport = new Transport(this.config);
|
|
13909
|
-
// Generate anonymous ID if not exists
|
|
13910
13980
|
this.anonymousId = this.getOrCreateAnonymousId();
|
|
13911
|
-
// Initialize collectors BEFORE starting session (so getContextData works)
|
|
13912
13981
|
this.deviceCollector = new DeviceCollector(this.config);
|
|
13913
13982
|
this.locationCollector = new LocationCollector(this.config);
|
|
13914
13983
|
this.browserCollector = new BrowserCollector(this.config);
|
|
13915
|
-
//
|
|
13916
|
-
|
|
13917
|
-
|
|
13918
|
-
//
|
|
13919
|
-
|
|
13920
|
-
|
|
13921
|
-
|
|
13922
|
-
this.
|
|
13923
|
-
|
|
13924
|
-
|
|
13925
|
-
|
|
13926
|
-
this.
|
|
13927
|
-
|
|
13928
|
-
|
|
13929
|
-
|
|
13930
|
-
this.
|
|
13931
|
-
|
|
13932
|
-
|
|
13933
|
-
|
|
13934
|
-
|
|
13935
|
-
|
|
13936
|
-
|
|
13937
|
-
|
|
13938
|
-
|
|
13939
|
-
|
|
13940
|
-
|
|
13941
|
-
|
|
13942
|
-
|
|
13943
|
-
|
|
13944
|
-
|
|
13945
|
-
if ((_a = this.config.sessionRecording) === null || _a === void 0 ? void 0 : _a.enabled) {
|
|
13946
|
-
// Use RRWebRecorder for complete DOM recording
|
|
13947
|
-
// Pass sessionStartTime to ensure timestamp continuity across page navigations
|
|
13948
|
-
this.rrwebRecorder = new RRWebRecorder(this.sessionId, {
|
|
13949
|
-
enabled: true,
|
|
13950
|
-
sampleRate: this.config.sessionRecording.sampling || 0.5,
|
|
13951
|
-
blockClass: 'rr-block',
|
|
13952
|
-
ignoreClass: this.config.sessionRecording.ignoreClass || 'rr-ignore',
|
|
13953
|
-
maskAllInputs: this.config.sessionRecording.maskAllInputs !== undefined
|
|
13954
|
-
? this.config.sessionRecording.maskAllInputs
|
|
13955
|
-
: true,
|
|
13956
|
-
maskInputOptions: {
|
|
13957
|
-
password: true,
|
|
13958
|
-
email: true,
|
|
13959
|
-
tel: true,
|
|
13960
|
-
},
|
|
13961
|
-
recordCanvas: this.config.sessionRecording.recordCanvas || false,
|
|
13962
|
-
collectFonts: true,
|
|
13963
|
-
inlineStylesheet: true,
|
|
13964
|
-
checkoutEveryNms: 5 * 60 * 1000, // Every 5 minutes
|
|
13965
|
-
checkoutEveryNth: 200, // Every 200 events
|
|
13966
|
-
}, (events) => {
|
|
13967
|
-
var _a;
|
|
13968
|
-
// Send rrweb events to backend
|
|
13969
|
-
(_a = this.transport) === null || _a === void 0 ? void 0 : _a.sendRecordingEvents(this.sessionId, events);
|
|
13970
|
-
}, this.sessionStartTime // Pass session start time for timestamp continuity
|
|
13971
|
-
);
|
|
13972
|
-
// Start recording immediately (session already created)
|
|
13973
|
-
this.rrwebRecorder.start();
|
|
13974
|
-
if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
|
|
13975
|
-
console.log('[DevSkin] RRWeb recording started for session:', this.sessionId);
|
|
13984
|
+
// Defer heavy initialization to avoid blocking page load/rendering
|
|
13985
|
+
const initHeavyCollectors = () => {
|
|
13986
|
+
// Start session (will now include device/browser/location data)
|
|
13987
|
+
// Wait for session creation to complete before starting collectors
|
|
13988
|
+
this.startSession().then(() => {
|
|
13989
|
+
// Session created, now safe to start collectors that send data
|
|
13990
|
+
var _a, _b, _c;
|
|
13991
|
+
if (this.config.captureWebVitals) {
|
|
13992
|
+
this.performanceCollector = new PerformanceCollector(this.config, this.transport);
|
|
13993
|
+
this.performanceCollector.start();
|
|
13994
|
+
}
|
|
13995
|
+
if (this.config.captureErrors) {
|
|
13996
|
+
this.errorCollector = new ErrorCollector(this.config, this.transport);
|
|
13997
|
+
this.errorCollector.start();
|
|
13998
|
+
}
|
|
13999
|
+
if (this.config.captureNetworkRequests) {
|
|
14000
|
+
this.networkCollector = new NetworkCollector(this.config, this.transport);
|
|
14001
|
+
this.networkCollector.start();
|
|
14002
|
+
}
|
|
14003
|
+
// Initialize heatmap collector - SEMPRE habilitado
|
|
14004
|
+
// Merge default heatmap config with user config
|
|
14005
|
+
const heatmapConfig = Object.assign({ enabled: true, trackClicks: true, trackScroll: true, trackMouseMovement: true, mouseMoveSampling: 0.1 }, this.config.heatmapOptions);
|
|
14006
|
+
this.config.heatmapOptions = heatmapConfig;
|
|
14007
|
+
this.heatmapCollector = new HeatmapCollector(this.config, this.transport, this.anonymousId, this.sessionId);
|
|
14008
|
+
this.heatmapCollector.start();
|
|
14009
|
+
// Initialize screenshot collector and capture page
|
|
14010
|
+
this.screenshotCollector = new ScreenshotCollector(this.config, this.transport);
|
|
14011
|
+
this.screenshotCollector.captureAndSend(this.sessionId, window.location.href);
|
|
14012
|
+
if (this.config.debug) {
|
|
14013
|
+
console.log('[DevSkin] Heatmap collection enabled (always on)');
|
|
13976
14014
|
}
|
|
14015
|
+
// Initialize session recording with rrweb
|
|
14016
|
+
if ((_a = this.config.sessionRecording) === null || _a === void 0 ? void 0 : _a.enabled) {
|
|
14017
|
+
// Skip recording for bots/crawlers to avoid impacting SEO
|
|
14018
|
+
const isBot = this.isBot();
|
|
14019
|
+
// Apply session sampling - decide if this session should be recorded
|
|
14020
|
+
const samplingRate = this.config.sessionRecording.sampling || 1.0;
|
|
14021
|
+
const shouldRecord = !isBot && Math.random() < samplingRate;
|
|
14022
|
+
if (shouldRecord) {
|
|
14023
|
+
// Use RRWebRecorder for complete DOM recording
|
|
14024
|
+
// Pass sessionStartTime to ensure timestamp continuity across page navigations
|
|
14025
|
+
this.rrwebRecorder = new RRWebRecorder(this.sessionId, {
|
|
14026
|
+
enabled: true,
|
|
14027
|
+
mouseMoveSampleRate: 0.5, // Sample 10% of mouse movements to reduce bandwidth
|
|
14028
|
+
blockClass: 'rr-block',
|
|
14029
|
+
ignoreClass: this.config.sessionRecording.ignoreClass || 'rr-ignore',
|
|
14030
|
+
maskAllInputs: this.config.sessionRecording.maskAllInputs !== undefined
|
|
14031
|
+
? this.config.sessionRecording.maskAllInputs
|
|
14032
|
+
: true,
|
|
14033
|
+
maskInputOptions: {
|
|
14034
|
+
password: true,
|
|
14035
|
+
email: true,
|
|
14036
|
+
tel: true,
|
|
14037
|
+
},
|
|
14038
|
+
recordCanvas: this.config.sessionRecording.recordCanvas || false,
|
|
14039
|
+
collectFonts: true,
|
|
14040
|
+
inlineStylesheet: true,
|
|
14041
|
+
checkoutEveryNms: 5 * 60 * 1000, // Every 5 minutes
|
|
14042
|
+
checkoutEveryNth: 200, // Every 200 events
|
|
14043
|
+
}, (events) => {
|
|
14044
|
+
var _a;
|
|
14045
|
+
// Send rrweb events to backend
|
|
14046
|
+
(_a = this.transport) === null || _a === void 0 ? void 0 : _a.sendRecordingEvents(this.sessionId, events);
|
|
14047
|
+
}, this.sessionStartTime // Pass session start time for timestamp continuity
|
|
14048
|
+
);
|
|
14049
|
+
// Start recording immediately (session already created)
|
|
14050
|
+
this.rrwebRecorder.start();
|
|
14051
|
+
if ((_b = this.config) === null || _b === void 0 ? void 0 : _b.debug) {
|
|
14052
|
+
console.log(`[DevSkin] RRWeb recording started for session: ${this.sessionId} (sampling: ${samplingRate * 100}%)`);
|
|
14053
|
+
}
|
|
14054
|
+
}
|
|
14055
|
+
else {
|
|
14056
|
+
if ((_c = this.config) === null || _c === void 0 ? void 0 : _c.debug) {
|
|
14057
|
+
if (isBot) {
|
|
14058
|
+
console.log(`[DevSkin] Session ${this.sessionId} skipped recording (bot/crawler detected for SEO optimization)`);
|
|
14059
|
+
}
|
|
14060
|
+
else {
|
|
14061
|
+
console.log(`[DevSkin] Session ${this.sessionId} not sampled for recording (sampling: ${samplingRate * 100}%)`);
|
|
14062
|
+
}
|
|
14063
|
+
}
|
|
14064
|
+
}
|
|
14065
|
+
}
|
|
14066
|
+
// Track initial page view
|
|
14067
|
+
this.trackPageView();
|
|
14068
|
+
// Start heartbeat to update session duration every 30 seconds
|
|
14069
|
+
this.startHeartbeat();
|
|
14070
|
+
}).catch((err) => {
|
|
14071
|
+
console.error('[DevSkin] Failed to create session:', err);
|
|
14072
|
+
});
|
|
14073
|
+
// Track page visibility changes
|
|
14074
|
+
this.setupVisibilityTracking();
|
|
14075
|
+
// Track page unload
|
|
14076
|
+
this.setupUnloadTracking();
|
|
14077
|
+
// Mark as fully initialized only after everything is loaded
|
|
14078
|
+
this.initialized = true;
|
|
14079
|
+
this.initializing = false;
|
|
14080
|
+
};
|
|
14081
|
+
// Use requestIdleCallback to defer heavy initialization (non-blocking)
|
|
14082
|
+
// Falls back to setTimeout for browsers that don't support it
|
|
14083
|
+
if (typeof window !== 'undefined') {
|
|
14084
|
+
if ('requestIdleCallback' in window) {
|
|
14085
|
+
window.requestIdleCallback(initHeavyCollectors, { timeout: 2000 });
|
|
13977
14086
|
}
|
|
13978
|
-
|
|
13979
|
-
|
|
13980
|
-
|
|
13981
|
-
|
|
13982
|
-
}
|
|
13983
|
-
|
|
13984
|
-
|
|
13985
|
-
|
|
13986
|
-
|
|
13987
|
-
// Track page unload
|
|
13988
|
-
this.setupUnloadTracking();
|
|
13989
|
-
this.initialized = true;
|
|
14087
|
+
else {
|
|
14088
|
+
// Fallback for older browsers
|
|
14089
|
+
setTimeout(initHeavyCollectors, 1);
|
|
14090
|
+
}
|
|
14091
|
+
}
|
|
14092
|
+
else {
|
|
14093
|
+
// Node.js environment (SSR)
|
|
14094
|
+
initHeavyCollectors();
|
|
14095
|
+
}
|
|
13990
14096
|
if (this.config.debug) {
|
|
13991
|
-
console.log('[DevSkin] SDK
|
|
14097
|
+
console.log('[DevSkin] SDK initialization started (heavy collectors loading in background)');
|
|
13992
14098
|
}
|
|
13993
14099
|
}
|
|
13994
14100
|
/**
|
|
13995
14101
|
* Track a custom event
|
|
14102
|
+
* Works immediately after init() even if heavy collectors are still loading
|
|
13996
14103
|
*/
|
|
13997
14104
|
track(eventName, properties) {
|
|
13998
|
-
var _a, _b;
|
|
13999
|
-
if (!this.
|
|
14105
|
+
var _a, _b, _c, _d;
|
|
14106
|
+
if (!this.transport) {
|
|
14000
14107
|
console.warn('[DevSkin] SDK not initialized. Call init() first.');
|
|
14001
14108
|
return;
|
|
14002
14109
|
}
|
|
@@ -14004,15 +14111,15 @@ class DevSkinSDK {
|
|
|
14004
14111
|
eventName: eventName,
|
|
14005
14112
|
eventType: 'track',
|
|
14006
14113
|
timestamp: new Date().toISOString(),
|
|
14007
|
-
sessionId: this.sessionId,
|
|
14008
|
-
userId: this.userId
|
|
14009
|
-
anonymousId: this.anonymousId
|
|
14114
|
+
sessionId: (_a = this.sessionId) !== null && _a !== void 0 ? _a : undefined,
|
|
14115
|
+
userId: (_b = this.userId) !== null && _b !== void 0 ? _b : undefined,
|
|
14116
|
+
anonymousId: (_c = this.anonymousId) !== null && _c !== void 0 ? _c : undefined,
|
|
14010
14117
|
properties: Object.assign(Object.assign({}, properties), this.getContextData()),
|
|
14011
14118
|
pageUrl: window.location.href,
|
|
14012
14119
|
pageTitle: document.title,
|
|
14013
14120
|
};
|
|
14014
|
-
|
|
14015
|
-
if ((
|
|
14121
|
+
this.transport.sendEvent(eventData);
|
|
14122
|
+
if ((_d = this.config) === null || _d === void 0 ? void 0 : _d.debug) {
|
|
14016
14123
|
console.log('[DevSkin] Event tracked:', eventData);
|
|
14017
14124
|
}
|
|
14018
14125
|
}
|
|
@@ -14041,23 +14148,24 @@ class DevSkinSDK {
|
|
|
14041
14148
|
}
|
|
14042
14149
|
/**
|
|
14043
14150
|
* Identify a user
|
|
14151
|
+
* Works immediately after init() even if heavy collectors are still loading
|
|
14044
14152
|
*/
|
|
14045
14153
|
identify(userId, traits) {
|
|
14046
|
-
var _a, _b;
|
|
14047
|
-
if (!this.
|
|
14154
|
+
var _a, _b, _c;
|
|
14155
|
+
if (!this.transport) {
|
|
14048
14156
|
console.warn('[DevSkin] SDK not initialized. Call init() first.');
|
|
14049
14157
|
return;
|
|
14050
14158
|
}
|
|
14051
14159
|
this.userId = userId;
|
|
14052
14160
|
const userData = {
|
|
14053
14161
|
userId: userId,
|
|
14054
|
-
anonymousId: this.anonymousId
|
|
14162
|
+
anonymousId: (_a = this.anonymousId) !== null && _a !== void 0 ? _a : undefined,
|
|
14055
14163
|
traits: Object.assign(Object.assign({}, traits), this.getContextData()),
|
|
14056
|
-
sessionId: this.sessionId,
|
|
14164
|
+
sessionId: (_b = this.sessionId) !== null && _b !== void 0 ? _b : undefined,
|
|
14057
14165
|
timestamp: new Date().toISOString(),
|
|
14058
14166
|
};
|
|
14059
|
-
|
|
14060
|
-
if ((
|
|
14167
|
+
this.transport.identifyUser(userData);
|
|
14168
|
+
if ((_c = this.config) === null || _c === void 0 ? void 0 : _c.debug) {
|
|
14061
14169
|
console.log('[DevSkin] User identified:', userData);
|
|
14062
14170
|
}
|
|
14063
14171
|
}
|