@grainql/analytics-web 3.2.1 → 3.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/dist/cjs/heatmap-tracking.d.ts +14 -0
- package/dist/cjs/heatmap-tracking.d.ts.map +1 -1
- package/dist/cjs/heatmap-tracking.js +92 -0
- package/dist/cjs/heatmap-tracking.js.map +1 -1
- package/dist/esm/heatmap-tracking.d.ts +14 -0
- package/dist/esm/heatmap-tracking.d.ts.map +1 -1
- package/dist/esm/heatmap-tracking.js +92 -0
- package/dist/esm/heatmap-tracking.js.map +1 -1
- package/dist/heatmap-tracking.d.ts +14 -0
- package/dist/heatmap-tracking.d.ts.map +1 -1
- package/dist/heatmap-tracking.js +92 -0
- package/dist/index.global.dev.js +80 -1
- package/dist/index.global.dev.js.map +2 -2
- package/dist/index.global.js +8 -8
- package/dist/index.global.js.map +3 -3
- package/package.json +1 -1
package/dist/heatmap-tracking.js
CHANGED
|
@@ -179,6 +179,12 @@ class HeatmapTrackingManager {
|
|
|
179
179
|
async captureSnapshot() {
|
|
180
180
|
if (this.snapshotCaptured || !this.snapshotEnabled)
|
|
181
181
|
return;
|
|
182
|
+
// Check daily snapshot limits before capturing
|
|
183
|
+
if (!this.canUploadSnapshot()) {
|
|
184
|
+
this.log('Snapshot upload limit reached or URL already captured today');
|
|
185
|
+
this.snapshotCaptured = true; // Prevent retry
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
182
188
|
try {
|
|
183
189
|
this.log('Capturing DOM snapshot...');
|
|
184
190
|
// Dynamically import rrweb-snapshot (only if enabled)
|
|
@@ -246,11 +252,97 @@ class HeatmapTrackingManager {
|
|
|
246
252
|
}
|
|
247
253
|
const result = await response.json();
|
|
248
254
|
this.log('Snapshot uploaded successfully:', result);
|
|
255
|
+
// Record successful upload
|
|
256
|
+
this.recordSnapshotUpload(pageUrl);
|
|
249
257
|
}
|
|
250
258
|
catch (error) {
|
|
251
259
|
this.log('Failed to upload snapshot:', error);
|
|
252
260
|
}
|
|
253
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Check if snapshot can be uploaded based on daily limits
|
|
264
|
+
* - Max 5 snapshots per user per day
|
|
265
|
+
* - Same URL (without query params) can't be uploaded twice in same day
|
|
266
|
+
*/
|
|
267
|
+
canUploadSnapshot() {
|
|
268
|
+
if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
|
|
269
|
+
return true; // Allow in SSR/non-browser environments
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
const pageUrl = this.normalizeUrl(window.location.href);
|
|
273
|
+
const urlWithoutQuery = this.stripQueryParams(pageUrl);
|
|
274
|
+
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
|
275
|
+
const storageKey = '_grain_snapshots';
|
|
276
|
+
// Get existing snapshot records
|
|
277
|
+
const stored = localStorage.getItem(storageKey);
|
|
278
|
+
let snapshots = stored ? JSON.parse(stored) : [];
|
|
279
|
+
// Clean up old entries (older than 2 days)
|
|
280
|
+
const twoDaysAgo = Date.now() - (2 * 24 * 60 * 60 * 1000);
|
|
281
|
+
snapshots = snapshots.filter(s => s.timestamp > twoDaysAgo);
|
|
282
|
+
// Get today's snapshots
|
|
283
|
+
const todaySnapshots = snapshots.filter(s => s.date === today);
|
|
284
|
+
// Check daily limit (5 per day)
|
|
285
|
+
if (todaySnapshots.length >= 5) {
|
|
286
|
+
this.log('Daily snapshot limit reached (5/5)');
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
// Check if this URL (without query params) was already uploaded today
|
|
290
|
+
const urlAlreadyUploaded = todaySnapshots.some(s => this.stripQueryParams(s.url) === urlWithoutQuery);
|
|
291
|
+
if (urlAlreadyUploaded) {
|
|
292
|
+
this.log(`Snapshot for ${urlWithoutQuery} already uploaded today`);
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
this.log(`Snapshot upload allowed (${todaySnapshots.length}/5 today)`);
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
this.log('Error checking snapshot limits:', error);
|
|
300
|
+
return true; // Allow on error to not break functionality
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Record a successful snapshot upload
|
|
305
|
+
*/
|
|
306
|
+
recordSnapshotUpload(pageUrl) {
|
|
307
|
+
if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
const today = new Date().toISOString().split('T')[0];
|
|
312
|
+
const storageKey = '_grain_snapshots';
|
|
313
|
+
// Get existing records
|
|
314
|
+
const stored = localStorage.getItem(storageKey);
|
|
315
|
+
let snapshots = stored ? JSON.parse(stored) : [];
|
|
316
|
+
// Add new record
|
|
317
|
+
snapshots.push({
|
|
318
|
+
url: pageUrl,
|
|
319
|
+
date: today,
|
|
320
|
+
timestamp: Date.now()
|
|
321
|
+
});
|
|
322
|
+
// Clean up old entries (older than 2 days)
|
|
323
|
+
const twoDaysAgo = Date.now() - (2 * 24 * 60 * 60 * 1000);
|
|
324
|
+
snapshots = snapshots.filter(s => s.timestamp > twoDaysAgo);
|
|
325
|
+
// Save back to localStorage
|
|
326
|
+
localStorage.setItem(storageKey, JSON.stringify(snapshots));
|
|
327
|
+
this.log(`Snapshot upload recorded: ${snapshots.filter(s => s.date === today).length}/5 today`);
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
this.log('Error recording snapshot upload:', error);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Strip query parameters from URL for comparison
|
|
335
|
+
*/
|
|
336
|
+
stripQueryParams(url) {
|
|
337
|
+
try {
|
|
338
|
+
const urlObj = new URL(url);
|
|
339
|
+
return `${urlObj.origin}${urlObj.pathname}`;
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
// If URL parsing fails, remove everything after ?
|
|
343
|
+
return url.split('?')[0];
|
|
344
|
+
}
|
|
345
|
+
}
|
|
254
346
|
/**
|
|
255
347
|
* Get API URL from tracker configuration
|
|
256
348
|
*/
|
package/dist/index.global.dev.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Grain Analytics Web SDK v3.2.
|
|
1
|
+
/* Grain Analytics Web SDK v3.2.2 | MIT License | Development Build */
|
|
2
2
|
"use strict";
|
|
3
3
|
var Grain = (() => {
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -5955,6 +5955,11 @@ var Grain = (() => {
|
|
|
5955
5955
|
async captureSnapshot() {
|
|
5956
5956
|
if (this.snapshotCaptured || !this.snapshotEnabled)
|
|
5957
5957
|
return;
|
|
5958
|
+
if (!this.canUploadSnapshot()) {
|
|
5959
|
+
this.log("Snapshot upload limit reached or URL already captured today");
|
|
5960
|
+
this.snapshotCaptured = true;
|
|
5961
|
+
return;
|
|
5962
|
+
}
|
|
5958
5963
|
try {
|
|
5959
5964
|
this.log("Capturing DOM snapshot...");
|
|
5960
5965
|
const rrwebSnapshot = await Promise.resolve().then(() => (init_rrweb_snapshot(), rrweb_snapshot_exports));
|
|
@@ -6014,10 +6019,84 @@ var Grain = (() => {
|
|
|
6014
6019
|
}
|
|
6015
6020
|
const result2 = await response.json();
|
|
6016
6021
|
this.log("Snapshot uploaded successfully:", result2);
|
|
6022
|
+
this.recordSnapshotUpload(pageUrl);
|
|
6017
6023
|
} catch (error) {
|
|
6018
6024
|
this.log("Failed to upload snapshot:", error);
|
|
6019
6025
|
}
|
|
6020
6026
|
}
|
|
6027
|
+
/**
|
|
6028
|
+
* Check if snapshot can be uploaded based on daily limits
|
|
6029
|
+
* - Max 5 snapshots per user per day
|
|
6030
|
+
* - Same URL (without query params) can't be uploaded twice in same day
|
|
6031
|
+
*/
|
|
6032
|
+
canUploadSnapshot() {
|
|
6033
|
+
if (typeof window === "undefined" || typeof localStorage === "undefined") {
|
|
6034
|
+
return true;
|
|
6035
|
+
}
|
|
6036
|
+
try {
|
|
6037
|
+
const pageUrl = this.normalizeUrl(window.location.href);
|
|
6038
|
+
const urlWithoutQuery = this.stripQueryParams(pageUrl);
|
|
6039
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6040
|
+
const storageKey = "_grain_snapshots";
|
|
6041
|
+
const stored = localStorage.getItem(storageKey);
|
|
6042
|
+
let snapshots = stored ? JSON.parse(stored) : [];
|
|
6043
|
+
const twoDaysAgo = Date.now() - 2 * 24 * 60 * 60 * 1e3;
|
|
6044
|
+
snapshots = snapshots.filter((s) => s.timestamp > twoDaysAgo);
|
|
6045
|
+
const todaySnapshots = snapshots.filter((s) => s.date === today);
|
|
6046
|
+
if (todaySnapshots.length >= 5) {
|
|
6047
|
+
this.log("Daily snapshot limit reached (5/5)");
|
|
6048
|
+
return false;
|
|
6049
|
+
}
|
|
6050
|
+
const urlAlreadyUploaded = todaySnapshots.some(
|
|
6051
|
+
(s) => this.stripQueryParams(s.url) === urlWithoutQuery
|
|
6052
|
+
);
|
|
6053
|
+
if (urlAlreadyUploaded) {
|
|
6054
|
+
this.log(`Snapshot for ${urlWithoutQuery} already uploaded today`);
|
|
6055
|
+
return false;
|
|
6056
|
+
}
|
|
6057
|
+
this.log(`Snapshot upload allowed (${todaySnapshots.length}/5 today)`);
|
|
6058
|
+
return true;
|
|
6059
|
+
} catch (error) {
|
|
6060
|
+
this.log("Error checking snapshot limits:", error);
|
|
6061
|
+
return true;
|
|
6062
|
+
}
|
|
6063
|
+
}
|
|
6064
|
+
/**
|
|
6065
|
+
* Record a successful snapshot upload
|
|
6066
|
+
*/
|
|
6067
|
+
recordSnapshotUpload(pageUrl) {
|
|
6068
|
+
if (typeof window === "undefined" || typeof localStorage === "undefined") {
|
|
6069
|
+
return;
|
|
6070
|
+
}
|
|
6071
|
+
try {
|
|
6072
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
6073
|
+
const storageKey = "_grain_snapshots";
|
|
6074
|
+
const stored = localStorage.getItem(storageKey);
|
|
6075
|
+
let snapshots = stored ? JSON.parse(stored) : [];
|
|
6076
|
+
snapshots.push({
|
|
6077
|
+
url: pageUrl,
|
|
6078
|
+
date: today,
|
|
6079
|
+
timestamp: Date.now()
|
|
6080
|
+
});
|
|
6081
|
+
const twoDaysAgo = Date.now() - 2 * 24 * 60 * 60 * 1e3;
|
|
6082
|
+
snapshots = snapshots.filter((s) => s.timestamp > twoDaysAgo);
|
|
6083
|
+
localStorage.setItem(storageKey, JSON.stringify(snapshots));
|
|
6084
|
+
this.log(`Snapshot upload recorded: ${snapshots.filter((s) => s.date === today).length}/5 today`);
|
|
6085
|
+
} catch (error) {
|
|
6086
|
+
this.log("Error recording snapshot upload:", error);
|
|
6087
|
+
}
|
|
6088
|
+
}
|
|
6089
|
+
/**
|
|
6090
|
+
* Strip query parameters from URL for comparison
|
|
6091
|
+
*/
|
|
6092
|
+
stripQueryParams(url) {
|
|
6093
|
+
try {
|
|
6094
|
+
const urlObj = new URL(url);
|
|
6095
|
+
return `${urlObj.origin}${urlObj.pathname}`;
|
|
6096
|
+
} catch {
|
|
6097
|
+
return url.split("?")[0];
|
|
6098
|
+
}
|
|
6099
|
+
}
|
|
6021
6100
|
/**
|
|
6022
6101
|
* Get API URL from tracker configuration
|
|
6023
6102
|
*/
|