cryptique-sdk 1.1.6 → 1.1.8
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/lib/cjs/index.js +119 -18
- package/lib/esm/index.js +119 -18
- package/lib/types/index.d.ts +7 -1
- package/lib/umd/index.js +119 -18
- package/package.json +1 -1
package/lib/cjs/index.js
CHANGED
|
@@ -164,6 +164,21 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
164
164
|
eip6963Providers: [] // EIP-6963 discovered wallet providers
|
|
165
165
|
};
|
|
166
166
|
|
|
167
|
+
// Ready promise - resolves when SDK is fully initialized (allows consumers to await init)
|
|
168
|
+
let _readyPromiseResolve;
|
|
169
|
+
const _readyPromise = new Promise((resolve) => {
|
|
170
|
+
_readyPromiseResolve = resolve;
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Helper to wait for SDK ready with timeout (avoids hanging if init fails)
|
|
174
|
+
const _waitForReady = (timeoutMs = 5000) => {
|
|
175
|
+
if (runtimeState.isInitialized) return Promise.resolve();
|
|
176
|
+
return Promise.race([
|
|
177
|
+
_readyPromise,
|
|
178
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('SDK init timeout')), timeoutMs))
|
|
179
|
+
]);
|
|
180
|
+
};
|
|
181
|
+
|
|
167
182
|
// Initialize EIP-6963 wallet provider detection (if enabled)
|
|
168
183
|
{
|
|
169
184
|
// Listen for providers being announced
|
|
@@ -4299,6 +4314,10 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
4299
4314
|
// - Proper use of sendBeacon vs fetch
|
|
4300
4315
|
// ============================================================================
|
|
4301
4316
|
|
|
4317
|
+
// Throttle network error logs (e.g. "Failed to fetch" from ad blockers) - log at most once per 30s
|
|
4318
|
+
let _lastNetworkErrorLog = 0;
|
|
4319
|
+
const _NETWORK_ERROR_THROTTLE_MS = 30000;
|
|
4320
|
+
|
|
4302
4321
|
/**
|
|
4303
4322
|
* APIClient - Unified API communication
|
|
4304
4323
|
*
|
|
@@ -4479,8 +4498,14 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
4479
4498
|
// Don't retry aborted requests - they were intentionally cancelled
|
|
4480
4499
|
return; // Silently return, don't throw
|
|
4481
4500
|
}
|
|
4482
|
-
|
|
4483
|
-
|
|
4501
|
+
|
|
4502
|
+
// Throttle "Failed to fetch" logs (common with ad blockers, CORS, network issues)
|
|
4503
|
+
const isNetworkError = error.name === 'TypeError' && (error.message === 'Failed to fetch' || error.message?.includes('fetch'));
|
|
4504
|
+
const now = Date.now();
|
|
4505
|
+
if (isNetworkError && (now - _lastNetworkErrorLog) < _NETWORK_ERROR_THROTTLE_MS) ; else {
|
|
4506
|
+
if (isNetworkError) _lastNetworkErrorLog = now;
|
|
4507
|
+
console.error('❌ Error sending data:', error);
|
|
4508
|
+
}
|
|
4484
4509
|
|
|
4485
4510
|
// Retry if retries > 0 (but not for abort errors)
|
|
4486
4511
|
if (retries > 0) {
|
|
@@ -5887,6 +5912,12 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5887
5912
|
|
|
5888
5913
|
} catch (error) {
|
|
5889
5914
|
console.error('Error initializing Cryptique SDK:', error);
|
|
5915
|
+
} finally {
|
|
5916
|
+
// Always resolve ready promise so awaiters don't hang (even if init failed)
|
|
5917
|
+
if (typeof _readyPromiseResolve === 'function') {
|
|
5918
|
+
_readyPromiseResolve();
|
|
5919
|
+
_readyPromiseResolve = null; // Prevent multiple resolves
|
|
5920
|
+
}
|
|
5890
5921
|
}
|
|
5891
5922
|
}
|
|
5892
5923
|
};
|
|
@@ -6099,9 +6130,17 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
6099
6130
|
}
|
|
6100
6131
|
}
|
|
6101
6132
|
|
|
6133
|
+
// Wait for SDK init if not ready (handles race conditions)
|
|
6102
6134
|
if (!runtimeState.isInitialized) {
|
|
6103
|
-
|
|
6104
|
-
|
|
6135
|
+
try {
|
|
6136
|
+
await _waitForReady();
|
|
6137
|
+
} catch (e) {
|
|
6138
|
+
// Init timeout or failed - silently skip
|
|
6139
|
+
return;
|
|
6140
|
+
}
|
|
6141
|
+
if (!runtimeState.isInitialized) {
|
|
6142
|
+
return;
|
|
6143
|
+
}
|
|
6105
6144
|
}
|
|
6106
6145
|
|
|
6107
6146
|
// Get session from storage - it returns { id, userId, ... }
|
|
@@ -6676,8 +6715,9 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
6676
6715
|
|
|
6677
6716
|
/**
|
|
6678
6717
|
* Setup auto events tracking
|
|
6718
|
+
* Waits for SDK initialization before proceeding (handles SPA navigation race)
|
|
6679
6719
|
*/
|
|
6680
|
-
setup() {
|
|
6720
|
+
async setup() {
|
|
6681
6721
|
// Check if auto events are enabled
|
|
6682
6722
|
if (!CONFIG.AUTO_EVENTS.enabled) {
|
|
6683
6723
|
return;
|
|
@@ -6693,6 +6733,17 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
6693
6733
|
}
|
|
6694
6734
|
|
|
6695
6735
|
try {
|
|
6736
|
+
// Wait for SDK to be fully initialized (handles SPA nav before init completes)
|
|
6737
|
+
await _waitForReady().catch(() => {
|
|
6738
|
+
// Init failed or timed out - silently skip setup
|
|
6739
|
+
return;
|
|
6740
|
+
});
|
|
6741
|
+
|
|
6742
|
+
// Double-check after wait (init may have failed)
|
|
6743
|
+
if (!runtimeState.isInitialized) {
|
|
6744
|
+
return;
|
|
6745
|
+
}
|
|
6746
|
+
|
|
6696
6747
|
// Track page views
|
|
6697
6748
|
this.trackPageView();
|
|
6698
6749
|
|
|
@@ -6852,11 +6903,15 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
6852
6903
|
const isInteractive = isInteractiveElement(element);
|
|
6853
6904
|
|
|
6854
6905
|
if (!isInteractive) {
|
|
6855
|
-
// Capture coordinates before setTimeout
|
|
6906
|
+
// Capture coordinates and page context before setTimeout (for heatmaps)
|
|
6856
6907
|
const clickX = event.clientX;
|
|
6857
6908
|
const clickY = event.clientY;
|
|
6858
6909
|
const clickElement = element;
|
|
6859
|
-
|
|
6910
|
+
const scrollX = window.scrollX != null ? window.scrollX : window.pageXOffset;
|
|
6911
|
+
const scrollY = window.scrollY != null ? window.scrollY : window.pageYOffset;
|
|
6912
|
+
const docHeight = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
|
|
6913
|
+
const docWidth = Math.max(document.body.scrollWidth, document.documentElement.scrollWidth);
|
|
6914
|
+
|
|
6860
6915
|
// Mark this click as potentially dead
|
|
6861
6916
|
const clickId = `${now}_${Math.random().toString(36).substr(2, 9)}`;
|
|
6862
6917
|
pendingDeadClicks.set(clickId, {
|
|
@@ -6866,7 +6921,13 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
6866
6921
|
timestamp: now,
|
|
6867
6922
|
url: window.location.href,
|
|
6868
6923
|
clickX,
|
|
6869
|
-
clickY
|
|
6924
|
+
clickY,
|
|
6925
|
+
page_x: event.pageX,
|
|
6926
|
+
page_y: event.pageY,
|
|
6927
|
+
scroll_x: scrollX,
|
|
6928
|
+
scroll_y: scrollY,
|
|
6929
|
+
document_height: docHeight,
|
|
6930
|
+
document_width: docWidth
|
|
6870
6931
|
});
|
|
6871
6932
|
|
|
6872
6933
|
// Check after 1 second if navigation occurred or if it's still a dead click
|
|
@@ -6880,6 +6941,12 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
6880
6941
|
|
|
6881
6942
|
EventsManager.trackAutoEvent('dead_click', {
|
|
6882
6943
|
click_coordinates: { x: pendingClick.clickX, y: pendingClick.clickY },
|
|
6944
|
+
page_x: pendingClick.page_x,
|
|
6945
|
+
page_y: pendingClick.page_y,
|
|
6946
|
+
scroll_x: pendingClick.scroll_x,
|
|
6947
|
+
scroll_y: pendingClick.scroll_y,
|
|
6948
|
+
document_height: pendingClick.document_height,
|
|
6949
|
+
document_width: pendingClick.document_width,
|
|
6883
6950
|
element_area: clickElement.offsetWidth * clickElement.offsetHeight,
|
|
6884
6951
|
element_category: pendingClick.elementCategory,
|
|
6885
6952
|
element_has_onclick: !!clickElement.onclick,
|
|
@@ -6895,9 +6962,19 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
6895
6962
|
}, 1000);
|
|
6896
6963
|
}
|
|
6897
6964
|
|
|
6898
|
-
// Track regular click with enhanced data
|
|
6965
|
+
// Track regular click with enhanced data (viewport + page-relative for heatmaps)
|
|
6966
|
+
const scrollX = window.scrollX != null ? window.scrollX : window.pageXOffset;
|
|
6967
|
+
const scrollY = window.scrollY != null ? window.scrollY : window.pageYOffset;
|
|
6968
|
+
const docHeight = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
|
|
6969
|
+
const docWidth = Math.max(document.body.scrollWidth, document.documentElement.scrollWidth);
|
|
6899
6970
|
EventsManager.trackAutoEvent('element_click', {
|
|
6900
6971
|
click_coordinates: { x: event.clientX, y: event.clientY },
|
|
6972
|
+
page_x: event.pageX,
|
|
6973
|
+
page_y: event.pageY,
|
|
6974
|
+
scroll_x: scrollX,
|
|
6975
|
+
scroll_y: scrollY,
|
|
6976
|
+
document_height: docHeight,
|
|
6977
|
+
document_width: docWidth,
|
|
6901
6978
|
double_click: event.detail === 2,
|
|
6902
6979
|
element_category: elementCategory
|
|
6903
6980
|
}, elementData).catch(err => {
|
|
@@ -6922,11 +6999,12 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
6922
6999
|
clearTimeout(scrollTimeout);
|
|
6923
7000
|
|
|
6924
7001
|
scrollTimeout = setTimeout(() => {
|
|
6925
|
-
const
|
|
6926
|
-
|
|
7002
|
+
const maxScroll = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight) - window.innerHeight;
|
|
7003
|
+
const scrollDepth = maxScroll <= 0 ? 100 : Math.round((window.scrollY / maxScroll) * 100);
|
|
7004
|
+
|
|
6927
7005
|
if (scrollDepth > maxScrollDepth) {
|
|
6928
7006
|
maxScrollDepth = scrollDepth;
|
|
6929
|
-
|
|
7007
|
+
|
|
6930
7008
|
EventsManager.trackAutoEvent('page_scroll', {
|
|
6931
7009
|
scroll_depth: scrollDepth,
|
|
6932
7010
|
max_scroll_reached: maxScrollDepth,
|
|
@@ -7675,6 +7753,9 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
7675
7753
|
...window.Cryptique,
|
|
7676
7754
|
version: CONFIG.VERSION,
|
|
7677
7755
|
|
|
7756
|
+
// Wait for SDK to be fully initialized (returns Promise)
|
|
7757
|
+
ready: () => _readyPromise,
|
|
7758
|
+
|
|
7678
7759
|
// Events System
|
|
7679
7760
|
track: EventsManager.trackEvent.bind(EventsManager),
|
|
7680
7761
|
trackEvent: EventsManager.trackEvent.bind(EventsManager),
|
|
@@ -7899,7 +7980,7 @@ const CryptiqueSDK = {
|
|
|
7899
7980
|
init(options = {}) {
|
|
7900
7981
|
if (typeof window === 'undefined') {
|
|
7901
7982
|
console.warn('Cryptique SDK requires a browser environment');
|
|
7902
|
-
return null;
|
|
7983
|
+
return Promise.resolve(null);
|
|
7903
7984
|
}
|
|
7904
7985
|
|
|
7905
7986
|
if (!options.siteId) {
|
|
@@ -7940,21 +8021,28 @@ const CryptiqueSDK = {
|
|
|
7940
8021
|
return null;
|
|
7941
8022
|
};
|
|
7942
8023
|
|
|
7943
|
-
// If SDK is already loaded, configure
|
|
8024
|
+
// If SDK is already loaded, configure and return ready promise
|
|
7944
8025
|
if (window.Cryptique) {
|
|
7945
|
-
|
|
8026
|
+
checkSDK();
|
|
8027
|
+
// Return ready() so await Cryptique.init() waits for full initialization
|
|
8028
|
+
return window.Cryptique.ready ? window.Cryptique.ready() : Promise.resolve(window.Cryptique);
|
|
7946
8029
|
}
|
|
7947
8030
|
|
|
7948
|
-
// Otherwise, wait
|
|
8031
|
+
// Otherwise, wait for SDK to load then configure and wait for ready
|
|
7949
8032
|
return new Promise((resolve) => {
|
|
7950
8033
|
const maxAttempts = 50;
|
|
7951
8034
|
let attempts = 0;
|
|
7952
8035
|
const interval = setInterval(() => {
|
|
7953
8036
|
attempts++;
|
|
7954
8037
|
const sdk = checkSDK();
|
|
7955
|
-
if (sdk
|
|
8038
|
+
if (sdk) {
|
|
8039
|
+
clearInterval(interval);
|
|
8040
|
+
// Wait for full initialization
|
|
8041
|
+
const readyPromise = sdk.ready ? sdk.ready() : Promise.resolve(sdk);
|
|
8042
|
+
resolve(readyPromise);
|
|
8043
|
+
} else if (attempts >= maxAttempts) {
|
|
7956
8044
|
clearInterval(interval);
|
|
7957
|
-
resolve(
|
|
8045
|
+
resolve(null);
|
|
7958
8046
|
}
|
|
7959
8047
|
}, 100);
|
|
7960
8048
|
});
|
|
@@ -7971,6 +8059,19 @@ const CryptiqueSDK = {
|
|
|
7971
8059
|
return null;
|
|
7972
8060
|
},
|
|
7973
8061
|
|
|
8062
|
+
/**
|
|
8063
|
+
* Wait for SDK to be fully initialized
|
|
8064
|
+
* Returns a Promise that resolves when initialization is complete
|
|
8065
|
+
* @returns {Promise<void>}
|
|
8066
|
+
*/
|
|
8067
|
+
ready() {
|
|
8068
|
+
const instance = this.getInstance();
|
|
8069
|
+
if (instance && instance.ready) {
|
|
8070
|
+
return instance.ready();
|
|
8071
|
+
}
|
|
8072
|
+
return Promise.resolve();
|
|
8073
|
+
},
|
|
8074
|
+
|
|
7974
8075
|
/**
|
|
7975
8076
|
* Identify a user with a unique identifier
|
|
7976
8077
|
*
|
package/lib/esm/index.js
CHANGED
|
@@ -162,6 +162,21 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
162
162
|
eip6963Providers: [] // EIP-6963 discovered wallet providers
|
|
163
163
|
};
|
|
164
164
|
|
|
165
|
+
// Ready promise - resolves when SDK is fully initialized (allows consumers to await init)
|
|
166
|
+
let _readyPromiseResolve;
|
|
167
|
+
const _readyPromise = new Promise((resolve) => {
|
|
168
|
+
_readyPromiseResolve = resolve;
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Helper to wait for SDK ready with timeout (avoids hanging if init fails)
|
|
172
|
+
const _waitForReady = (timeoutMs = 5000) => {
|
|
173
|
+
if (runtimeState.isInitialized) return Promise.resolve();
|
|
174
|
+
return Promise.race([
|
|
175
|
+
_readyPromise,
|
|
176
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('SDK init timeout')), timeoutMs))
|
|
177
|
+
]);
|
|
178
|
+
};
|
|
179
|
+
|
|
165
180
|
// Initialize EIP-6963 wallet provider detection (if enabled)
|
|
166
181
|
{
|
|
167
182
|
// Listen for providers being announced
|
|
@@ -4297,6 +4312,10 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
4297
4312
|
// - Proper use of sendBeacon vs fetch
|
|
4298
4313
|
// ============================================================================
|
|
4299
4314
|
|
|
4315
|
+
// Throttle network error logs (e.g. "Failed to fetch" from ad blockers) - log at most once per 30s
|
|
4316
|
+
let _lastNetworkErrorLog = 0;
|
|
4317
|
+
const _NETWORK_ERROR_THROTTLE_MS = 30000;
|
|
4318
|
+
|
|
4300
4319
|
/**
|
|
4301
4320
|
* APIClient - Unified API communication
|
|
4302
4321
|
*
|
|
@@ -4477,8 +4496,14 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
4477
4496
|
// Don't retry aborted requests - they were intentionally cancelled
|
|
4478
4497
|
return; // Silently return, don't throw
|
|
4479
4498
|
}
|
|
4480
|
-
|
|
4481
|
-
|
|
4499
|
+
|
|
4500
|
+
// Throttle "Failed to fetch" logs (common with ad blockers, CORS, network issues)
|
|
4501
|
+
const isNetworkError = error.name === 'TypeError' && (error.message === 'Failed to fetch' || error.message?.includes('fetch'));
|
|
4502
|
+
const now = Date.now();
|
|
4503
|
+
if (isNetworkError && (now - _lastNetworkErrorLog) < _NETWORK_ERROR_THROTTLE_MS) ; else {
|
|
4504
|
+
if (isNetworkError) _lastNetworkErrorLog = now;
|
|
4505
|
+
console.error('❌ Error sending data:', error);
|
|
4506
|
+
}
|
|
4482
4507
|
|
|
4483
4508
|
// Retry if retries > 0 (but not for abort errors)
|
|
4484
4509
|
if (retries > 0) {
|
|
@@ -5885,6 +5910,12 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
5885
5910
|
|
|
5886
5911
|
} catch (error) {
|
|
5887
5912
|
console.error('Error initializing Cryptique SDK:', error);
|
|
5913
|
+
} finally {
|
|
5914
|
+
// Always resolve ready promise so awaiters don't hang (even if init failed)
|
|
5915
|
+
if (typeof _readyPromiseResolve === 'function') {
|
|
5916
|
+
_readyPromiseResolve();
|
|
5917
|
+
_readyPromiseResolve = null; // Prevent multiple resolves
|
|
5918
|
+
}
|
|
5888
5919
|
}
|
|
5889
5920
|
}
|
|
5890
5921
|
};
|
|
@@ -6097,9 +6128,17 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
6097
6128
|
}
|
|
6098
6129
|
}
|
|
6099
6130
|
|
|
6131
|
+
// Wait for SDK init if not ready (handles race conditions)
|
|
6100
6132
|
if (!runtimeState.isInitialized) {
|
|
6101
|
-
|
|
6102
|
-
|
|
6133
|
+
try {
|
|
6134
|
+
await _waitForReady();
|
|
6135
|
+
} catch (e) {
|
|
6136
|
+
// Init timeout or failed - silently skip
|
|
6137
|
+
return;
|
|
6138
|
+
}
|
|
6139
|
+
if (!runtimeState.isInitialized) {
|
|
6140
|
+
return;
|
|
6141
|
+
}
|
|
6103
6142
|
}
|
|
6104
6143
|
|
|
6105
6144
|
// Get session from storage - it returns { id, userId, ... }
|
|
@@ -6674,8 +6713,9 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
6674
6713
|
|
|
6675
6714
|
/**
|
|
6676
6715
|
* Setup auto events tracking
|
|
6716
|
+
* Waits for SDK initialization before proceeding (handles SPA navigation race)
|
|
6677
6717
|
*/
|
|
6678
|
-
setup() {
|
|
6718
|
+
async setup() {
|
|
6679
6719
|
// Check if auto events are enabled
|
|
6680
6720
|
if (!CONFIG.AUTO_EVENTS.enabled) {
|
|
6681
6721
|
return;
|
|
@@ -6691,6 +6731,17 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
6691
6731
|
}
|
|
6692
6732
|
|
|
6693
6733
|
try {
|
|
6734
|
+
// Wait for SDK to be fully initialized (handles SPA nav before init completes)
|
|
6735
|
+
await _waitForReady().catch(() => {
|
|
6736
|
+
// Init failed or timed out - silently skip setup
|
|
6737
|
+
return;
|
|
6738
|
+
});
|
|
6739
|
+
|
|
6740
|
+
// Double-check after wait (init may have failed)
|
|
6741
|
+
if (!runtimeState.isInitialized) {
|
|
6742
|
+
return;
|
|
6743
|
+
}
|
|
6744
|
+
|
|
6694
6745
|
// Track page views
|
|
6695
6746
|
this.trackPageView();
|
|
6696
6747
|
|
|
@@ -6850,11 +6901,15 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
6850
6901
|
const isInteractive = isInteractiveElement(element);
|
|
6851
6902
|
|
|
6852
6903
|
if (!isInteractive) {
|
|
6853
|
-
// Capture coordinates before setTimeout
|
|
6904
|
+
// Capture coordinates and page context before setTimeout (for heatmaps)
|
|
6854
6905
|
const clickX = event.clientX;
|
|
6855
6906
|
const clickY = event.clientY;
|
|
6856
6907
|
const clickElement = element;
|
|
6857
|
-
|
|
6908
|
+
const scrollX = window.scrollX != null ? window.scrollX : window.pageXOffset;
|
|
6909
|
+
const scrollY = window.scrollY != null ? window.scrollY : window.pageYOffset;
|
|
6910
|
+
const docHeight = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
|
|
6911
|
+
const docWidth = Math.max(document.body.scrollWidth, document.documentElement.scrollWidth);
|
|
6912
|
+
|
|
6858
6913
|
// Mark this click as potentially dead
|
|
6859
6914
|
const clickId = `${now}_${Math.random().toString(36).substr(2, 9)}`;
|
|
6860
6915
|
pendingDeadClicks.set(clickId, {
|
|
@@ -6864,7 +6919,13 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
6864
6919
|
timestamp: now,
|
|
6865
6920
|
url: window.location.href,
|
|
6866
6921
|
clickX,
|
|
6867
|
-
clickY
|
|
6922
|
+
clickY,
|
|
6923
|
+
page_x: event.pageX,
|
|
6924
|
+
page_y: event.pageY,
|
|
6925
|
+
scroll_x: scrollX,
|
|
6926
|
+
scroll_y: scrollY,
|
|
6927
|
+
document_height: docHeight,
|
|
6928
|
+
document_width: docWidth
|
|
6868
6929
|
});
|
|
6869
6930
|
|
|
6870
6931
|
// Check after 1 second if navigation occurred or if it's still a dead click
|
|
@@ -6878,6 +6939,12 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
6878
6939
|
|
|
6879
6940
|
EventsManager.trackAutoEvent('dead_click', {
|
|
6880
6941
|
click_coordinates: { x: pendingClick.clickX, y: pendingClick.clickY },
|
|
6942
|
+
page_x: pendingClick.page_x,
|
|
6943
|
+
page_y: pendingClick.page_y,
|
|
6944
|
+
scroll_x: pendingClick.scroll_x,
|
|
6945
|
+
scroll_y: pendingClick.scroll_y,
|
|
6946
|
+
document_height: pendingClick.document_height,
|
|
6947
|
+
document_width: pendingClick.document_width,
|
|
6881
6948
|
element_area: clickElement.offsetWidth * clickElement.offsetHeight,
|
|
6882
6949
|
element_category: pendingClick.elementCategory,
|
|
6883
6950
|
element_has_onclick: !!clickElement.onclick,
|
|
@@ -6893,9 +6960,19 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
6893
6960
|
}, 1000);
|
|
6894
6961
|
}
|
|
6895
6962
|
|
|
6896
|
-
// Track regular click with enhanced data
|
|
6963
|
+
// Track regular click with enhanced data (viewport + page-relative for heatmaps)
|
|
6964
|
+
const scrollX = window.scrollX != null ? window.scrollX : window.pageXOffset;
|
|
6965
|
+
const scrollY = window.scrollY != null ? window.scrollY : window.pageYOffset;
|
|
6966
|
+
const docHeight = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
|
|
6967
|
+
const docWidth = Math.max(document.body.scrollWidth, document.documentElement.scrollWidth);
|
|
6897
6968
|
EventsManager.trackAutoEvent('element_click', {
|
|
6898
6969
|
click_coordinates: { x: event.clientX, y: event.clientY },
|
|
6970
|
+
page_x: event.pageX,
|
|
6971
|
+
page_y: event.pageY,
|
|
6972
|
+
scroll_x: scrollX,
|
|
6973
|
+
scroll_y: scrollY,
|
|
6974
|
+
document_height: docHeight,
|
|
6975
|
+
document_width: docWidth,
|
|
6899
6976
|
double_click: event.detail === 2,
|
|
6900
6977
|
element_category: elementCategory
|
|
6901
6978
|
}, elementData).catch(err => {
|
|
@@ -6920,11 +6997,12 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
6920
6997
|
clearTimeout(scrollTimeout);
|
|
6921
6998
|
|
|
6922
6999
|
scrollTimeout = setTimeout(() => {
|
|
6923
|
-
const
|
|
6924
|
-
|
|
7000
|
+
const maxScroll = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight) - window.innerHeight;
|
|
7001
|
+
const scrollDepth = maxScroll <= 0 ? 100 : Math.round((window.scrollY / maxScroll) * 100);
|
|
7002
|
+
|
|
6925
7003
|
if (scrollDepth > maxScrollDepth) {
|
|
6926
7004
|
maxScrollDepth = scrollDepth;
|
|
6927
|
-
|
|
7005
|
+
|
|
6928
7006
|
EventsManager.trackAutoEvent('page_scroll', {
|
|
6929
7007
|
scroll_depth: scrollDepth,
|
|
6930
7008
|
max_scroll_reached: maxScrollDepth,
|
|
@@ -7673,6 +7751,9 @@ if (window.Cryptique && window.Cryptique.initialized) ; else {
|
|
|
7673
7751
|
...window.Cryptique,
|
|
7674
7752
|
version: CONFIG.VERSION,
|
|
7675
7753
|
|
|
7754
|
+
// Wait for SDK to be fully initialized (returns Promise)
|
|
7755
|
+
ready: () => _readyPromise,
|
|
7756
|
+
|
|
7676
7757
|
// Events System
|
|
7677
7758
|
track: EventsManager.trackEvent.bind(EventsManager),
|
|
7678
7759
|
trackEvent: EventsManager.trackEvent.bind(EventsManager),
|
|
@@ -7897,7 +7978,7 @@ const CryptiqueSDK = {
|
|
|
7897
7978
|
init(options = {}) {
|
|
7898
7979
|
if (typeof window === 'undefined') {
|
|
7899
7980
|
console.warn('Cryptique SDK requires a browser environment');
|
|
7900
|
-
return null;
|
|
7981
|
+
return Promise.resolve(null);
|
|
7901
7982
|
}
|
|
7902
7983
|
|
|
7903
7984
|
if (!options.siteId) {
|
|
@@ -7938,21 +8019,28 @@ const CryptiqueSDK = {
|
|
|
7938
8019
|
return null;
|
|
7939
8020
|
};
|
|
7940
8021
|
|
|
7941
|
-
// If SDK is already loaded, configure
|
|
8022
|
+
// If SDK is already loaded, configure and return ready promise
|
|
7942
8023
|
if (window.Cryptique) {
|
|
7943
|
-
|
|
8024
|
+
checkSDK();
|
|
8025
|
+
// Return ready() so await Cryptique.init() waits for full initialization
|
|
8026
|
+
return window.Cryptique.ready ? window.Cryptique.ready() : Promise.resolve(window.Cryptique);
|
|
7944
8027
|
}
|
|
7945
8028
|
|
|
7946
|
-
// Otherwise, wait
|
|
8029
|
+
// Otherwise, wait for SDK to load then configure and wait for ready
|
|
7947
8030
|
return new Promise((resolve) => {
|
|
7948
8031
|
const maxAttempts = 50;
|
|
7949
8032
|
let attempts = 0;
|
|
7950
8033
|
const interval = setInterval(() => {
|
|
7951
8034
|
attempts++;
|
|
7952
8035
|
const sdk = checkSDK();
|
|
7953
|
-
if (sdk
|
|
8036
|
+
if (sdk) {
|
|
8037
|
+
clearInterval(interval);
|
|
8038
|
+
// Wait for full initialization
|
|
8039
|
+
const readyPromise = sdk.ready ? sdk.ready() : Promise.resolve(sdk);
|
|
8040
|
+
resolve(readyPromise);
|
|
8041
|
+
} else if (attempts >= maxAttempts) {
|
|
7954
8042
|
clearInterval(interval);
|
|
7955
|
-
resolve(
|
|
8043
|
+
resolve(null);
|
|
7956
8044
|
}
|
|
7957
8045
|
}, 100);
|
|
7958
8046
|
});
|
|
@@ -7969,6 +8057,19 @@ const CryptiqueSDK = {
|
|
|
7969
8057
|
return null;
|
|
7970
8058
|
},
|
|
7971
8059
|
|
|
8060
|
+
/**
|
|
8061
|
+
* Wait for SDK to be fully initialized
|
|
8062
|
+
* Returns a Promise that resolves when initialization is complete
|
|
8063
|
+
* @returns {Promise<void>}
|
|
8064
|
+
*/
|
|
8065
|
+
ready() {
|
|
8066
|
+
const instance = this.getInstance();
|
|
8067
|
+
if (instance && instance.ready) {
|
|
8068
|
+
return instance.ready();
|
|
8069
|
+
}
|
|
8070
|
+
return Promise.resolve();
|
|
8071
|
+
},
|
|
8072
|
+
|
|
7972
8073
|
/**
|
|
7973
8074
|
* Identify a user with a unique identifier
|
|
7974
8075
|
*
|
package/lib/types/index.d.ts
CHANGED
|
@@ -99,14 +99,20 @@ export interface PeopleManager {
|
|
|
99
99
|
export default class CryptiqueSDK {
|
|
100
100
|
/**
|
|
101
101
|
* Initialize the SDK programmatically
|
|
102
|
+
* Returns a Promise that resolves when the SDK is fully initialized
|
|
102
103
|
*/
|
|
103
|
-
init(options: CryptiqueOptions): Promise<any
|
|
104
|
+
init(options: CryptiqueOptions): Promise<any>;
|
|
104
105
|
|
|
105
106
|
/**
|
|
106
107
|
* Get the SDK instance
|
|
107
108
|
*/
|
|
108
109
|
getInstance(): any | null;
|
|
109
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Wait for SDK to be fully initialized
|
|
113
|
+
*/
|
|
114
|
+
ready(): Promise<void>;
|
|
115
|
+
|
|
110
116
|
/**
|
|
111
117
|
* Track a custom event
|
|
112
118
|
*/
|
package/lib/umd/index.js
CHANGED
|
@@ -168,6 +168,21 @@
|
|
|
168
168
|
eip6963Providers: [] // EIP-6963 discovered wallet providers
|
|
169
169
|
};
|
|
170
170
|
|
|
171
|
+
// Ready promise - resolves when SDK is fully initialized (allows consumers to await init)
|
|
172
|
+
let _readyPromiseResolve;
|
|
173
|
+
const _readyPromise = new Promise((resolve) => {
|
|
174
|
+
_readyPromiseResolve = resolve;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Helper to wait for SDK ready with timeout (avoids hanging if init fails)
|
|
178
|
+
const _waitForReady = (timeoutMs = 5000) => {
|
|
179
|
+
if (runtimeState.isInitialized) return Promise.resolve();
|
|
180
|
+
return Promise.race([
|
|
181
|
+
_readyPromise,
|
|
182
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('SDK init timeout')), timeoutMs))
|
|
183
|
+
]);
|
|
184
|
+
};
|
|
185
|
+
|
|
171
186
|
// Initialize EIP-6963 wallet provider detection (if enabled)
|
|
172
187
|
{
|
|
173
188
|
// Listen for providers being announced
|
|
@@ -4303,6 +4318,10 @@
|
|
|
4303
4318
|
// - Proper use of sendBeacon vs fetch
|
|
4304
4319
|
// ============================================================================
|
|
4305
4320
|
|
|
4321
|
+
// Throttle network error logs (e.g. "Failed to fetch" from ad blockers) - log at most once per 30s
|
|
4322
|
+
let _lastNetworkErrorLog = 0;
|
|
4323
|
+
const _NETWORK_ERROR_THROTTLE_MS = 30000;
|
|
4324
|
+
|
|
4306
4325
|
/**
|
|
4307
4326
|
* APIClient - Unified API communication
|
|
4308
4327
|
*
|
|
@@ -4483,8 +4502,14 @@
|
|
|
4483
4502
|
// Don't retry aborted requests - they were intentionally cancelled
|
|
4484
4503
|
return; // Silently return, don't throw
|
|
4485
4504
|
}
|
|
4486
|
-
|
|
4487
|
-
|
|
4505
|
+
|
|
4506
|
+
// Throttle "Failed to fetch" logs (common with ad blockers, CORS, network issues)
|
|
4507
|
+
const isNetworkError = error.name === 'TypeError' && (error.message === 'Failed to fetch' || error.message?.includes('fetch'));
|
|
4508
|
+
const now = Date.now();
|
|
4509
|
+
if (isNetworkError && (now - _lastNetworkErrorLog) < _NETWORK_ERROR_THROTTLE_MS) ; else {
|
|
4510
|
+
if (isNetworkError) _lastNetworkErrorLog = now;
|
|
4511
|
+
console.error('❌ Error sending data:', error);
|
|
4512
|
+
}
|
|
4488
4513
|
|
|
4489
4514
|
// Retry if retries > 0 (but not for abort errors)
|
|
4490
4515
|
if (retries > 0) {
|
|
@@ -5891,6 +5916,12 @@
|
|
|
5891
5916
|
|
|
5892
5917
|
} catch (error) {
|
|
5893
5918
|
console.error('Error initializing Cryptique SDK:', error);
|
|
5919
|
+
} finally {
|
|
5920
|
+
// Always resolve ready promise so awaiters don't hang (even if init failed)
|
|
5921
|
+
if (typeof _readyPromiseResolve === 'function') {
|
|
5922
|
+
_readyPromiseResolve();
|
|
5923
|
+
_readyPromiseResolve = null; // Prevent multiple resolves
|
|
5924
|
+
}
|
|
5894
5925
|
}
|
|
5895
5926
|
}
|
|
5896
5927
|
};
|
|
@@ -6103,9 +6134,17 @@
|
|
|
6103
6134
|
}
|
|
6104
6135
|
}
|
|
6105
6136
|
|
|
6137
|
+
// Wait for SDK init if not ready (handles race conditions)
|
|
6106
6138
|
if (!runtimeState.isInitialized) {
|
|
6107
|
-
|
|
6108
|
-
|
|
6139
|
+
try {
|
|
6140
|
+
await _waitForReady();
|
|
6141
|
+
} catch (e) {
|
|
6142
|
+
// Init timeout or failed - silently skip
|
|
6143
|
+
return;
|
|
6144
|
+
}
|
|
6145
|
+
if (!runtimeState.isInitialized) {
|
|
6146
|
+
return;
|
|
6147
|
+
}
|
|
6109
6148
|
}
|
|
6110
6149
|
|
|
6111
6150
|
// Get session from storage - it returns { id, userId, ... }
|
|
@@ -6680,8 +6719,9 @@
|
|
|
6680
6719
|
|
|
6681
6720
|
/**
|
|
6682
6721
|
* Setup auto events tracking
|
|
6722
|
+
* Waits for SDK initialization before proceeding (handles SPA navigation race)
|
|
6683
6723
|
*/
|
|
6684
|
-
setup() {
|
|
6724
|
+
async setup() {
|
|
6685
6725
|
// Check if auto events are enabled
|
|
6686
6726
|
if (!CONFIG.AUTO_EVENTS.enabled) {
|
|
6687
6727
|
return;
|
|
@@ -6697,6 +6737,17 @@
|
|
|
6697
6737
|
}
|
|
6698
6738
|
|
|
6699
6739
|
try {
|
|
6740
|
+
// Wait for SDK to be fully initialized (handles SPA nav before init completes)
|
|
6741
|
+
await _waitForReady().catch(() => {
|
|
6742
|
+
// Init failed or timed out - silently skip setup
|
|
6743
|
+
return;
|
|
6744
|
+
});
|
|
6745
|
+
|
|
6746
|
+
// Double-check after wait (init may have failed)
|
|
6747
|
+
if (!runtimeState.isInitialized) {
|
|
6748
|
+
return;
|
|
6749
|
+
}
|
|
6750
|
+
|
|
6700
6751
|
// Track page views
|
|
6701
6752
|
this.trackPageView();
|
|
6702
6753
|
|
|
@@ -6856,11 +6907,15 @@
|
|
|
6856
6907
|
const isInteractive = isInteractiveElement(element);
|
|
6857
6908
|
|
|
6858
6909
|
if (!isInteractive) {
|
|
6859
|
-
// Capture coordinates before setTimeout
|
|
6910
|
+
// Capture coordinates and page context before setTimeout (for heatmaps)
|
|
6860
6911
|
const clickX = event.clientX;
|
|
6861
6912
|
const clickY = event.clientY;
|
|
6862
6913
|
const clickElement = element;
|
|
6863
|
-
|
|
6914
|
+
const scrollX = window.scrollX != null ? window.scrollX : window.pageXOffset;
|
|
6915
|
+
const scrollY = window.scrollY != null ? window.scrollY : window.pageYOffset;
|
|
6916
|
+
const docHeight = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
|
|
6917
|
+
const docWidth = Math.max(document.body.scrollWidth, document.documentElement.scrollWidth);
|
|
6918
|
+
|
|
6864
6919
|
// Mark this click as potentially dead
|
|
6865
6920
|
const clickId = `${now}_${Math.random().toString(36).substr(2, 9)}`;
|
|
6866
6921
|
pendingDeadClicks.set(clickId, {
|
|
@@ -6870,7 +6925,13 @@
|
|
|
6870
6925
|
timestamp: now,
|
|
6871
6926
|
url: window.location.href,
|
|
6872
6927
|
clickX,
|
|
6873
|
-
clickY
|
|
6928
|
+
clickY,
|
|
6929
|
+
page_x: event.pageX,
|
|
6930
|
+
page_y: event.pageY,
|
|
6931
|
+
scroll_x: scrollX,
|
|
6932
|
+
scroll_y: scrollY,
|
|
6933
|
+
document_height: docHeight,
|
|
6934
|
+
document_width: docWidth
|
|
6874
6935
|
});
|
|
6875
6936
|
|
|
6876
6937
|
// Check after 1 second if navigation occurred or if it's still a dead click
|
|
@@ -6884,6 +6945,12 @@
|
|
|
6884
6945
|
|
|
6885
6946
|
EventsManager.trackAutoEvent('dead_click', {
|
|
6886
6947
|
click_coordinates: { x: pendingClick.clickX, y: pendingClick.clickY },
|
|
6948
|
+
page_x: pendingClick.page_x,
|
|
6949
|
+
page_y: pendingClick.page_y,
|
|
6950
|
+
scroll_x: pendingClick.scroll_x,
|
|
6951
|
+
scroll_y: pendingClick.scroll_y,
|
|
6952
|
+
document_height: pendingClick.document_height,
|
|
6953
|
+
document_width: pendingClick.document_width,
|
|
6887
6954
|
element_area: clickElement.offsetWidth * clickElement.offsetHeight,
|
|
6888
6955
|
element_category: pendingClick.elementCategory,
|
|
6889
6956
|
element_has_onclick: !!clickElement.onclick,
|
|
@@ -6899,9 +6966,19 @@
|
|
|
6899
6966
|
}, 1000);
|
|
6900
6967
|
}
|
|
6901
6968
|
|
|
6902
|
-
// Track regular click with enhanced data
|
|
6969
|
+
// Track regular click with enhanced data (viewport + page-relative for heatmaps)
|
|
6970
|
+
const scrollX = window.scrollX != null ? window.scrollX : window.pageXOffset;
|
|
6971
|
+
const scrollY = window.scrollY != null ? window.scrollY : window.pageYOffset;
|
|
6972
|
+
const docHeight = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
|
|
6973
|
+
const docWidth = Math.max(document.body.scrollWidth, document.documentElement.scrollWidth);
|
|
6903
6974
|
EventsManager.trackAutoEvent('element_click', {
|
|
6904
6975
|
click_coordinates: { x: event.clientX, y: event.clientY },
|
|
6976
|
+
page_x: event.pageX,
|
|
6977
|
+
page_y: event.pageY,
|
|
6978
|
+
scroll_x: scrollX,
|
|
6979
|
+
scroll_y: scrollY,
|
|
6980
|
+
document_height: docHeight,
|
|
6981
|
+
document_width: docWidth,
|
|
6905
6982
|
double_click: event.detail === 2,
|
|
6906
6983
|
element_category: elementCategory
|
|
6907
6984
|
}, elementData).catch(err => {
|
|
@@ -6926,11 +7003,12 @@
|
|
|
6926
7003
|
clearTimeout(scrollTimeout);
|
|
6927
7004
|
|
|
6928
7005
|
scrollTimeout = setTimeout(() => {
|
|
6929
|
-
const
|
|
6930
|
-
|
|
7006
|
+
const maxScroll = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight) - window.innerHeight;
|
|
7007
|
+
const scrollDepth = maxScroll <= 0 ? 100 : Math.round((window.scrollY / maxScroll) * 100);
|
|
7008
|
+
|
|
6931
7009
|
if (scrollDepth > maxScrollDepth) {
|
|
6932
7010
|
maxScrollDepth = scrollDepth;
|
|
6933
|
-
|
|
7011
|
+
|
|
6934
7012
|
EventsManager.trackAutoEvent('page_scroll', {
|
|
6935
7013
|
scroll_depth: scrollDepth,
|
|
6936
7014
|
max_scroll_reached: maxScrollDepth,
|
|
@@ -7679,6 +7757,9 @@
|
|
|
7679
7757
|
...window.Cryptique,
|
|
7680
7758
|
version: CONFIG.VERSION,
|
|
7681
7759
|
|
|
7760
|
+
// Wait for SDK to be fully initialized (returns Promise)
|
|
7761
|
+
ready: () => _readyPromise,
|
|
7762
|
+
|
|
7682
7763
|
// Events System
|
|
7683
7764
|
track: EventsManager.trackEvent.bind(EventsManager),
|
|
7684
7765
|
trackEvent: EventsManager.trackEvent.bind(EventsManager),
|
|
@@ -7903,7 +7984,7 @@
|
|
|
7903
7984
|
init(options = {}) {
|
|
7904
7985
|
if (typeof window === 'undefined') {
|
|
7905
7986
|
console.warn('Cryptique SDK requires a browser environment');
|
|
7906
|
-
return null;
|
|
7987
|
+
return Promise.resolve(null);
|
|
7907
7988
|
}
|
|
7908
7989
|
|
|
7909
7990
|
if (!options.siteId) {
|
|
@@ -7944,21 +8025,28 @@
|
|
|
7944
8025
|
return null;
|
|
7945
8026
|
};
|
|
7946
8027
|
|
|
7947
|
-
// If SDK is already loaded, configure
|
|
8028
|
+
// If SDK is already loaded, configure and return ready promise
|
|
7948
8029
|
if (window.Cryptique) {
|
|
7949
|
-
|
|
8030
|
+
checkSDK();
|
|
8031
|
+
// Return ready() so await Cryptique.init() waits for full initialization
|
|
8032
|
+
return window.Cryptique.ready ? window.Cryptique.ready() : Promise.resolve(window.Cryptique);
|
|
7950
8033
|
}
|
|
7951
8034
|
|
|
7952
|
-
// Otherwise, wait
|
|
8035
|
+
// Otherwise, wait for SDK to load then configure and wait for ready
|
|
7953
8036
|
return new Promise((resolve) => {
|
|
7954
8037
|
const maxAttempts = 50;
|
|
7955
8038
|
let attempts = 0;
|
|
7956
8039
|
const interval = setInterval(() => {
|
|
7957
8040
|
attempts++;
|
|
7958
8041
|
const sdk = checkSDK();
|
|
7959
|
-
if (sdk
|
|
8042
|
+
if (sdk) {
|
|
8043
|
+
clearInterval(interval);
|
|
8044
|
+
// Wait for full initialization
|
|
8045
|
+
const readyPromise = sdk.ready ? sdk.ready() : Promise.resolve(sdk);
|
|
8046
|
+
resolve(readyPromise);
|
|
8047
|
+
} else if (attempts >= maxAttempts) {
|
|
7960
8048
|
clearInterval(interval);
|
|
7961
|
-
resolve(
|
|
8049
|
+
resolve(null);
|
|
7962
8050
|
}
|
|
7963
8051
|
}, 100);
|
|
7964
8052
|
});
|
|
@@ -7975,6 +8063,19 @@
|
|
|
7975
8063
|
return null;
|
|
7976
8064
|
},
|
|
7977
8065
|
|
|
8066
|
+
/**
|
|
8067
|
+
* Wait for SDK to be fully initialized
|
|
8068
|
+
* Returns a Promise that resolves when initialization is complete
|
|
8069
|
+
* @returns {Promise<void>}
|
|
8070
|
+
*/
|
|
8071
|
+
ready() {
|
|
8072
|
+
const instance = this.getInstance();
|
|
8073
|
+
if (instance && instance.ready) {
|
|
8074
|
+
return instance.ready();
|
|
8075
|
+
}
|
|
8076
|
+
return Promise.resolve();
|
|
8077
|
+
},
|
|
8078
|
+
|
|
7978
8079
|
/**
|
|
7979
8080
|
* Identify a user with a unique identifier
|
|
7980
8081
|
*
|
package/package.json
CHANGED