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