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