@zaplier/sdk 1.0.3 → 1.0.5

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/sdk.js CHANGED
@@ -179,8 +179,10 @@
179
179
  * Hash fingerprint components into a stable identifier
180
180
  */
181
181
  function hashFingerprint(components) {
182
- // Convert components to canonical string representation
183
- const canonical = JSON.stringify(components, Object.keys(components).sort());
182
+ // Convert components to canonical string representation using deep sorting
183
+ // CRITICAL: Do NOT use JSON.stringify(obj, keys) as it filters out nested properties!
184
+ // Use canonicalizeStable instead which handles deep sorting and normalization.
185
+ const canonical = JSON.stringify(canonicalizeStable(components));
184
186
  // Use MurmurHash3 x64 for consistent hashing
185
187
  return x64hash128(canonical);
186
188
  }
@@ -1033,7 +1035,13 @@
1033
1035
  // Quick stability check
1034
1036
  const webglContext = gl;
1035
1037
  const renderer = webglContext.getParameter(webglContext.RENDERER);
1036
- return Boolean(renderer && typeof renderer === "string");
1038
+ const isSupported = Boolean(renderer && typeof renderer === "string");
1039
+ // Cleanup context to avoid "Too many active WebGL contexts" warning
1040
+ const ext = webglContext.getExtension("WEBGL_lose_context");
1041
+ if (ext) {
1042
+ ext.loseContext();
1043
+ }
1044
+ return isSupported;
1037
1045
  }
1038
1046
  catch {
1039
1047
  return false;
@@ -1347,11 +1355,11 @@
1347
1355
  * Text to render for canvas fingerprinting with maximum differentiation
1348
1356
  */
1349
1357
  const CANVAS_TEXTS = [
1350
- 'RabbitTracker Canvas 🎨🔒2024',
1351
- 'Żółć gęślą jaźń €$¢£¥',
1352
- 'αβγδεζηθικλμνξο',
1353
- '中文测试字体渲染',
1354
- '🌟🎯🚀💎🌊🎨'
1358
+ "Zap Canvas 🎨🔒2024",
1359
+ "Żółć gęślą jaźń €$¢£¥",
1360
+ "αβγδεζηθικλμνξο",
1361
+ "中文测试字体渲染",
1362
+ "🌟🎯🚀💎🌊🎨",
1355
1363
  ];
1356
1364
  /**
1357
1365
  * Enhanced geometric shapes for canvas fingerprinting with sub-pixel precision
@@ -1359,19 +1367,19 @@
1359
1367
  function drawAdvancedGeometry(ctx) {
1360
1368
  // Enable high-quality rendering for sub-pixel differences
1361
1369
  ctx.imageSmoothingEnabled = true;
1362
- ctx.imageSmoothingQuality = 'high';
1370
+ ctx.imageSmoothingQuality = "high";
1363
1371
  // Complex gradient with multiple stops
1364
1372
  const radialGradient = ctx.createRadialGradient(75, 75, 0, 75, 75, 50);
1365
- radialGradient.addColorStop(0, 'rgba(255, 0, 0, 0.8)');
1366
- radialGradient.addColorStop(0.3, 'rgba(0, 255, 0, 0.6)');
1367
- radialGradient.addColorStop(0.7, 'rgba(0, 0, 255, 0.4)');
1368
- radialGradient.addColorStop(1, 'rgba(255, 255, 0, 0.2)');
1373
+ radialGradient.addColorStop(0, "rgba(255, 0, 0, 0.8)");
1374
+ radialGradient.addColorStop(0.3, "rgba(0, 255, 0, 0.6)");
1375
+ radialGradient.addColorStop(0.7, "rgba(0, 0, 255, 0.4)");
1376
+ radialGradient.addColorStop(1, "rgba(255, 255, 0, 0.2)");
1369
1377
  ctx.fillStyle = radialGradient;
1370
1378
  ctx.fillRect(10, 10, 130, 100);
1371
1379
  // Sub-pixel positioned shapes for GPU/anti-aliasing differentiation
1372
- ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
1380
+ ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
1373
1381
  ctx.fillRect(15.5, 15.3, 49.7, 49.2);
1374
- ctx.fillStyle = '#f60';
1382
+ ctx.fillStyle = "#f60";
1375
1383
  ctx.fillRect(70.3, 10.7, 50.1, 50.9);
1376
1384
  // Complex bezier curves that stress different rendering engines
1377
1385
  ctx.beginPath();
@@ -1379,21 +1387,21 @@
1379
1387
  ctx.bezierCurveTo(25.2, 120.1, 75.8, 90.3, 125.5, 120.7);
1380
1388
  ctx.bezierCurveTo(125.5, 120.7, 90.1, 150.2, 60.8, 140.9);
1381
1389
  ctx.closePath();
1382
- ctx.fillStyle = 'rgba(200, 100, 50, 0.6)';
1390
+ ctx.fillStyle = "rgba(200, 100, 50, 0.6)";
1383
1391
  ctx.fill();
1384
1392
  // Overlapping circles with complex blending
1385
- ctx.globalCompositeOperation = 'multiply';
1393
+ ctx.globalCompositeOperation = "multiply";
1386
1394
  ctx.beginPath();
1387
1395
  ctx.arc(50.7, 80.3, 20.1, 0, Math.PI * 2);
1388
- ctx.fillStyle = 'rgba(255, 0, 100, 0.5)';
1396
+ ctx.fillStyle = "rgba(255, 0, 100, 0.5)";
1389
1397
  ctx.fill();
1390
1398
  ctx.beginPath();
1391
1399
  ctx.arc(70.3, 80.7, 20.9, 0, Math.PI * 2);
1392
- ctx.fillStyle = 'rgba(0, 255, 100, 0.5)';
1400
+ ctx.fillStyle = "rgba(0, 255, 100, 0.5)";
1393
1401
  ctx.fill();
1394
- ctx.globalCompositeOperation = 'source-over';
1402
+ ctx.globalCompositeOperation = "source-over";
1395
1403
  // Thin lines to test anti-aliasing
1396
- ctx.strokeStyle = 'rgba(50, 50, 50, 0.8)';
1404
+ ctx.strokeStyle = "rgba(50, 50, 50, 0.8)";
1397
1405
  ctx.lineWidth = 0.5;
1398
1406
  ctx.setLineDash([2.3, 1.7]);
1399
1407
  for (let i = 0; i < 10; i++) {
@@ -1405,29 +1413,29 @@
1405
1413
  // Reset line dash
1406
1414
  ctx.setLineDash([]);
1407
1415
  // Pattern with complex repeating elements
1408
- const patternCanvas = document.createElement('canvas');
1416
+ const patternCanvas = document.createElement("canvas");
1409
1417
  patternCanvas.width = 20;
1410
1418
  patternCanvas.height = 20;
1411
- const patternCtx = patternCanvas.getContext('2d');
1419
+ const patternCtx = patternCanvas.getContext("2d");
1412
1420
  if (patternCtx) {
1413
- patternCtx.fillStyle = 'rgba(150, 75, 200, 0.3)';
1421
+ patternCtx.fillStyle = "rgba(150, 75, 200, 0.3)";
1414
1422
  patternCtx.fillRect(0, 0, 10, 10);
1415
1423
  patternCtx.fillRect(10, 10, 10, 10);
1416
- const pattern = ctx.createPattern(patternCanvas, 'repeat');
1424
+ const pattern = ctx.createPattern(patternCanvas, "repeat");
1417
1425
  if (pattern) {
1418
1426
  ctx.fillStyle = pattern;
1419
1427
  ctx.fillRect(150, 120, 80, 60);
1420
1428
  }
1421
1429
  }
1422
1430
  // Shadow effects for additional GPU differentiation
1423
- ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
1431
+ ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
1424
1432
  ctx.shadowBlur = 3.2;
1425
1433
  ctx.shadowOffsetX = 2.1;
1426
1434
  ctx.shadowOffsetY = 2.7;
1427
- ctx.fillStyle = 'rgba(100, 200, 150, 0.8)';
1435
+ ctx.fillStyle = "rgba(100, 200, 150, 0.8)";
1428
1436
  ctx.fillRect(160, 30, 60, 40);
1429
1437
  // Reset shadow
1430
- ctx.shadowColor = 'transparent';
1438
+ ctx.shadowColor = "transparent";
1431
1439
  ctx.shadowBlur = 0;
1432
1440
  ctx.shadowOffsetX = 0;
1433
1441
  ctx.shadowOffsetY = 0;
@@ -1437,23 +1445,34 @@
1437
1445
  */
1438
1446
  function drawAdvancedText(ctx) {
1439
1447
  const fonts = [
1440
- '14px Arial, sans-serif',
1448
+ "14px Arial, sans-serif",
1441
1449
  '13px "Times New Roman", serif',
1442
- '12px Georgia, serif',
1443
- '15px Helvetica, Arial, sans-serif',
1450
+ "12px Georgia, serif",
1451
+ "15px Helvetica, Arial, sans-serif",
1444
1452
  '11px "Courier New", monospace',
1445
- '13px Verdana, sans-serif',
1446
- '16px Impact, fantasy',
1447
- '12px "Comic Sans MS", cursive'
1453
+ "13px Verdana, sans-serif",
1454
+ "16px Impact, fantasy",
1455
+ '12px "Comic Sans MS", cursive',
1448
1456
  ];
1449
1457
  const colors = [
1450
- '#000', '#333', '#666', '#999',
1451
- 'rgba(255, 0, 0, 0.8)', 'rgba(0, 255, 0, 0.7)',
1452
- 'rgba(0, 0, 255, 0.6)', 'rgba(128, 64, 192, 0.9)'
1458
+ "#000",
1459
+ "#333",
1460
+ "#666",
1461
+ "#999",
1462
+ "rgba(255, 0, 0, 0.8)",
1463
+ "rgba(0, 255, 0, 0.7)",
1464
+ "rgba(0, 0, 255, 0.6)",
1465
+ "rgba(128, 64, 192, 0.9)",
1453
1466
  ];
1454
1467
  // Test different text baselines and alignments
1455
- const baselines = ['top', 'hanging', 'middle', 'alphabetic', 'bottom'];
1456
- const alignments = ['left', 'center', 'right'];
1468
+ const baselines = [
1469
+ "top",
1470
+ "hanging",
1471
+ "middle",
1472
+ "alphabetic",
1473
+ "bottom",
1474
+ ];
1475
+ const alignments = ["left", "center", "right"];
1457
1476
  let y = 250;
1458
1477
  CANVAS_TEXTS.forEach((text, textIndex) => {
1459
1478
  fonts.forEach((font, fontIndex) => {
@@ -1462,11 +1481,11 @@
1462
1481
  const alignIndex = fontIndex % alignments.length;
1463
1482
  ctx.font = font;
1464
1483
  ctx.fillStyle = colors[colorIndex] || colors[0];
1465
- ctx.textBaseline = baselines[baselineIndex] || 'top';
1466
- ctx.textAlign = alignments[alignIndex] || 'left';
1484
+ ctx.textBaseline = baselines[baselineIndex] || "top";
1485
+ ctx.textAlign = alignments[alignIndex] || "left";
1467
1486
  // Sub-pixel positioning for enhanced differentiation
1468
- const x = 10 + (fontIndex * 0.7) % 200;
1469
- const yPos = y + (fontIndex * 18.3) % 100;
1487
+ const x = 10 + ((fontIndex * 0.7) % 200);
1488
+ const yPos = y + ((fontIndex * 18.3) % 100);
1470
1489
  ctx.fillText(text, x, yPos);
1471
1490
  // Add stroke text for additional GPU stress testing
1472
1491
  if (fontIndex % 2 === 0) {
@@ -1482,20 +1501,20 @@
1482
1501
  ctx.translate(100, 400);
1483
1502
  ctx.rotate(Math.PI / 6);
1484
1503
  ctx.scale(1.2, 0.8);
1485
- ctx.font = 'italic 14px Arial';
1486
- ctx.fillStyle = 'rgba(200, 100, 50, 0.8)';
1487
- ctx.fillText('Transformed & Rotated Text', 0, 0);
1504
+ ctx.font = "italic 14px Arial";
1505
+ ctx.fillStyle = "rgba(200, 100, 50, 0.8)";
1506
+ ctx.fillText("Transformed & Rotated Text", 0, 0);
1488
1507
  ctx.restore();
1489
1508
  // Test very small and very large text
1490
- ctx.font = '6px Arial';
1491
- ctx.fillStyle = '#000';
1492
- ctx.fillText('Tiny text rendering test', 10, 500);
1493
- ctx.font = '32px Arial';
1494
- ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
1495
- ctx.fillText('Large', 10, 540);
1509
+ ctx.font = "6px Arial";
1510
+ ctx.fillStyle = "#000";
1511
+ ctx.fillText("Tiny text rendering test", 10, 500);
1512
+ ctx.font = "32px Arial";
1513
+ ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
1514
+ ctx.fillText("Large", 10, 540);
1496
1515
  // Reset text properties
1497
- ctx.textAlign = 'left';
1498
- ctx.textBaseline = 'top';
1516
+ ctx.textAlign = "left";
1517
+ ctx.textBaseline = "top";
1499
1518
  }
1500
1519
  /**
1501
1520
  * Analyze sub-pixel differences for enhanced GPU/anti-aliasing detection
@@ -1505,9 +1524,9 @@
1505
1524
  // Create test pattern for sub-pixel analysis
1506
1525
  ctx.clearRect(0, 0, canvas.width, canvas.height);
1507
1526
  // Draw shapes at sub-pixel coordinates to stress anti-aliasing
1508
- ctx.fillStyle = 'rgb(100, 150, 200)';
1527
+ ctx.fillStyle = "rgb(100, 150, 200)";
1509
1528
  ctx.fillRect(10.3, 10.7, 20.1, 20.9);
1510
- ctx.strokeStyle = 'rgb(200, 100, 50)';
1529
+ ctx.strokeStyle = "rgb(200, 100, 50)";
1511
1530
  ctx.lineWidth = 1.3;
1512
1531
  ctx.beginPath();
1513
1532
  ctx.moveTo(15.2, 35.8);
@@ -1538,7 +1557,7 @@
1538
1557
  return `${avgRed}-${avgGreen}-${avgBlue}-${avgVariance}`;
1539
1558
  }
1540
1559
  catch (error) {
1541
- return 'subpixel-error';
1560
+ return "subpixel-error";
1542
1561
  }
1543
1562
  }
1544
1563
  /**
@@ -1551,47 +1570,52 @@
1551
1570
  // Test 1: Check if getImageData works consistently
1552
1571
  const imageData = ctx.getImageData(0, 0, 1, 1);
1553
1572
  if (!imageData || imageData.data.length === 0) {
1554
- reasons.push('getImageData-blocked');
1573
+ reasons.push("getImageData-blocked");
1555
1574
  suspiciousSignals += 3;
1556
1575
  }
1557
1576
  // Test 2: Check hash lengths (blocked canvas often returns default values)
1558
1577
  if (textHash.length < 8 || geometryHash.length < 8) {
1559
- reasons.push('short-hash');
1578
+ reasons.push("short-hash");
1560
1579
  suspiciousSignals += 2;
1561
1580
  }
1562
1581
  // Test 3: Check for known blocked patterns
1563
1582
  const knownBlockedHashes = [
1564
- 'error', 'blocked', 'disabled',
1565
- '00000000', 'ffffffff', '12345678'
1583
+ "error",
1584
+ "blocked",
1585
+ "disabled",
1586
+ "00000000",
1587
+ "ffffffff",
1588
+ "12345678",
1566
1589
  ];
1567
- if (knownBlockedHashes.includes(textHash) || knownBlockedHashes.includes(geometryHash)) {
1568
- reasons.push('known-blocked-hash');
1590
+ if (knownBlockedHashes.includes(textHash) ||
1591
+ knownBlockedHashes.includes(geometryHash)) {
1592
+ reasons.push("known-blocked-hash");
1569
1593
  suspiciousSignals += 3;
1570
1594
  }
1571
1595
  // Test 4: Sub-pixel analysis indicates blocking
1572
- if (subPixelData === 'subpixel-error' || subPixelData.includes('0-0-0')) {
1573
- reasons.push('subpixel-anomaly');
1596
+ if (subPixelData === "subpixel-error" || subPixelData.includes("0-0-0")) {
1597
+ reasons.push("subpixel-anomaly");
1574
1598
  suspiciousSignals += 2;
1575
1599
  }
1576
1600
  // Test 5: Same hash for different operations (indicates fake canvas)
1577
- if (textHash === geometryHash && textHash !== 'error') {
1578
- reasons.push('identical-hashes');
1601
+ if (textHash === geometryHash && textHash !== "error") {
1602
+ reasons.push("identical-hashes");
1579
1603
  suspiciousSignals += 2;
1580
1604
  }
1581
1605
  // Test 6: toDataURL performance (some privacy tools slow it down)
1582
- const canvas = document.createElement('canvas');
1606
+ const canvas = document.createElement("canvas");
1583
1607
  canvas.width = 10;
1584
1608
  canvas.height = 10;
1585
- const testCtx = canvas.getContext('2d');
1609
+ const testCtx = canvas.getContext("2d");
1586
1610
  if (testCtx) {
1587
1611
  const start = performance.now();
1588
- testCtx.fillStyle = 'red';
1612
+ testCtx.fillStyle = "red";
1589
1613
  testCtx.fillRect(0, 0, 10, 10);
1590
1614
  canvas.toDataURL();
1591
1615
  const duration = performance.now() - start;
1592
1616
  // Suspiciously slow canvas operations
1593
1617
  if (duration > 50) {
1594
- reasons.push('slow-canvas');
1618
+ reasons.push("slow-canvas");
1595
1619
  suspiciousSignals += 1;
1596
1620
  }
1597
1621
  }
@@ -1603,7 +1627,7 @@
1603
1627
  return {
1604
1628
  isInconsistent: true,
1605
1629
  confidence: 1,
1606
- reasons: ['detection-error']
1630
+ reasons: ["detection-error"],
1607
1631
  };
1608
1632
  }
1609
1633
  }
@@ -1614,29 +1638,30 @@
1614
1638
  const startTime = performance.now();
1615
1639
  try {
1616
1640
  // Create larger canvas for more detailed fingerprinting
1617
- const canvas = document.createElement('canvas');
1641
+ const canvas = document.createElement("canvas");
1618
1642
  canvas.width = 300;
1619
1643
  canvas.height = 600;
1620
- const ctx = canvas.getContext('2d', {
1644
+ const ctx = canvas.getContext("2d", {
1621
1645
  alpha: true,
1622
1646
  desynchronized: false,
1623
- colorSpace: 'srgb'
1647
+ colorSpace: "srgb",
1648
+ willReadFrequently: true,
1624
1649
  });
1625
1650
  if (!ctx) {
1626
- throw new Error('Canvas 2D context not available');
1651
+ throw new Error("Canvas 2D context not available");
1627
1652
  }
1628
1653
  // Test canvas winding (different behavior across browsers/GPUs)
1629
1654
  ctx.rect(0, 0, 10, 10);
1630
1655
  ctx.rect(2, 2, 6, 6);
1631
- const isClockwise = ctx.isPointInPath(5, 5, 'evenodd');
1656
+ const isClockwise = ctx.isPointInPath(5, 5, "evenodd");
1632
1657
  // ENHANCED: Draw advanced text with multiple fonts and effects
1633
1658
  drawAdvancedText(ctx);
1634
- const textData = canvas.toDataURL('image/png');
1659
+ const textData = canvas.toDataURL("image/png");
1635
1660
  const textHash = hash32(textData);
1636
1661
  // Clear and draw advanced geometry
1637
1662
  ctx.clearRect(0, 0, canvas.width, canvas.height);
1638
1663
  drawAdvancedGeometry(ctx);
1639
- const geometryData = canvas.toDataURL('image/png');
1664
+ const geometryData = canvas.toDataURL("image/png");
1640
1665
  const geometryHash = hash32(geometryData);
1641
1666
  // ENHANCED: Analyze sub-pixels for GPU/anti-aliasing differentiation
1642
1667
  const subPixelAnalysis = analyzeSubPixels(ctx, canvas);
@@ -1646,10 +1671,10 @@
1646
1671
  ctx.clearRect(0, 0, canvas.width, canvas.height);
1647
1672
  // Draw both text and geometry together
1648
1673
  drawAdvancedText(ctx);
1649
- ctx.globalCompositeOperation = 'multiply';
1674
+ ctx.globalCompositeOperation = "multiply";
1650
1675
  drawAdvancedGeometry(ctx);
1651
- ctx.globalCompositeOperation = 'source-over';
1652
- const compositeData = canvas.toDataURL('image/png');
1676
+ ctx.globalCompositeOperation = "source-over";
1677
+ const compositeData = canvas.toDataURL("image/png");
1653
1678
  const compositeHash = hash32(compositeData);
1654
1679
  const endTime = performance.now();
1655
1680
  const result = {
@@ -1661,27 +1686,27 @@
1661
1686
  subPixelAnalysis, // Sub-pixel characteristics for GPU differentiation
1662
1687
  compositeHash, // Combined text+geometry hash
1663
1688
  inconsistencyConfidence: inconsistencyAnalysis.confidence,
1664
- blockingReasons: inconsistencyAnalysis.reasons
1689
+ blockingReasons: inconsistencyAnalysis.reasons,
1665
1690
  };
1666
1691
  return {
1667
1692
  value: result,
1668
- duration: endTime - startTime
1693
+ duration: endTime - startTime,
1669
1694
  };
1670
1695
  }
1671
1696
  catch (error) {
1672
1697
  return {
1673
1698
  value: {
1674
- text: 'error',
1675
- geometry: 'error',
1699
+ text: "error",
1700
+ geometry: "error",
1676
1701
  winding: false,
1677
1702
  isInconsistent: true,
1678
- subPixelAnalysis: 'error',
1679
- compositeHash: 'error',
1703
+ subPixelAnalysis: "error",
1704
+ compositeHash: "error",
1680
1705
  inconsistencyConfidence: 1,
1681
- blockingReasons: ['canvas-error']
1706
+ blockingReasons: ["canvas-error"],
1682
1707
  },
1683
1708
  duration: performance.now() - startTime,
1684
- error: error instanceof Error ? error.message : 'Canvas fingerprinting failed'
1709
+ error: error instanceof Error ? error.message : "Canvas fingerprinting failed",
1685
1710
  };
1686
1711
  }
1687
1712
  }
@@ -1690,9 +1715,9 @@
1690
1715
  */
1691
1716
  function isCanvasAvailable$1() {
1692
1717
  try {
1693
- const canvas = document.createElement('canvas');
1694
- const ctx = canvas.getContext('2d');
1695
- return ctx !== null && typeof ctx.fillText === 'function';
1718
+ const canvas = document.createElement("canvas");
1719
+ const ctx = canvas.getContext("2d");
1720
+ return ctx !== null && typeof ctx.fillText === "function";
1696
1721
  }
1697
1722
  catch {
1698
1723
  return false;
@@ -3790,7 +3815,7 @@
3790
3815
  const canvas = iframeDoc.createElement("canvas");
3791
3816
  canvas.width = 100;
3792
3817
  canvas.height = 50;
3793
- const ctx = canvas.getContext("2d");
3818
+ const ctx = canvas.getContext("2d", { willReadFrequently: true });
3794
3819
  if (!ctx)
3795
3820
  return { quality: 0, antiAliasing: false, smoothing: 0 };
3796
3821
  // Draw text at sub-pixel position to trigger anti-aliasing
@@ -3843,7 +3868,7 @@
3843
3868
  const canvas = iframeDoc.createElement("canvas");
3844
3869
  canvas.width = 100;
3845
3870
  canvas.height = 50;
3846
- const ctx = canvas.getContext("2d");
3871
+ const ctx = canvas.getContext("2d", { willReadFrequently: true });
3847
3872
  if (!ctx) {
3848
3873
  return { clearType: false, hintingLevel: 0, subpixelRendering: false };
3849
3874
  }