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