Diff -- /var/www/vhosts/3dshawn.com/crm.3dshawn.com/assets/js/fingerprint.js

O Operator
Diff

/var/www/vhosts/3dshawn.com/crm.3dshawn.com/assets/js/fingerprint.js

added on local at 2026-07-02 17:01:12

Added
+0
lines
Removed
-0
lines
Context
135
unchanged
Blobs
from 578cec2be731
to 578cec2be731
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
11// fingerprint.js -- lightweight passive browser fingerprint.
22//
33// Computes a stable-per-browser SHA-256 hash from:
44// canvas rendering, WebGL renderer/vendor, screen dims, timezone,
55// language, platform, available fonts (probed via canvas measure),
66// hardware concurrency, deviceMemory, and touch capability.
77//
88// Injects the resulting 64-char hex hash into a hidden input named
99// `fp_hash` inside the first login/signup form on the page, so a normal
1010// form POST carries it through without any XHR.
1111//
1212// Absolutely no personally-identifying info. This is a device signature
1313// used for the blocklist -- when an admin blocks a fingerprint, that
1414// hash is compared against the signup/login attempt's hash.
1515(function () {
1616 'use strict';
1717 if (!window.crypto || !window.crypto.subtle) return; // very old browsers -- skip silently
1818 if (document.querySelector('form input[name="fp_hash"]')) return; // already injected
1919
2020 function safe(fn) { try { return fn(); } catch (e) { return ''; } }
2121
2222 function canvasHash() {
2323 return safe(function () {
2424 var c = document.createElement('canvas');
2525 c.width = 220; c.height = 40;
2626 var ctx = c.getContext('2d');
2727 if (!ctx) return '';
2828 ctx.textBaseline = 'top';
2929 ctx.font = "14px 'Arial'";
3030 ctx.fillStyle = '#f60';
3131 ctx.fillRect(0, 0, 200, 20);
3232 ctx.fillStyle = '#069';
3333 ctx.fillText('Cwm fjord bank glyphs vext quiz. \u{1F4A1}', 2, 15);
3434 ctx.strokeStyle = 'rgba(102,204,0,0.7)';
3535 ctx.beginPath();
3636 ctx.arc(30, 20, 12, 0, Math.PI * 2, true);
3737 ctx.stroke();
3838 return c.toDataURL();
3939 });
4040 }
4141
4242 function webglHash() {
4343 return safe(function () {
4444 var c = document.createElement('canvas');
4545 var gl = c.getContext('webgl') || c.getContext('experimental-webgl');
4646 if (!gl) return '';
4747 var dbg = gl.getExtension('WEBGL_debug_renderer_info');
4848 var vendor = dbg ? gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL) : '';
4949 var render = dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : '';
5050 return vendor + '||' + render + '||' + gl.getParameter(gl.VERSION);
5151 });
5252 }
5353
5454 function fontProbe() {
5555 // Detect installed fonts by measuring baseline widths and comparing
5656 // to a fallback. Not exhaustive -- just enough to add entropy.
5757 return safe(function () {
5858 var baseFonts = ['monospace', 'sans-serif', 'serif'];
5959 var testFonts = ['Arial','Courier New','Georgia','Helvetica','Impact','Tahoma','Times New Roman','Verdana','Trebuchet MS','Comic Sans MS'];
6060 var span = document.createElement('span');
6161 span.style.cssText = 'position:absolute;left:-9999px;top:-9999px;font-size:72px';
6262 span.textContent = 'mmmmmmmmmmlli';
6363 document.body.appendChild(span);
6464 var widths = {};
6565 baseFonts.forEach(function (bf) {
6666 span.style.fontFamily = bf;
6767 widths[bf] = { w: span.offsetWidth, h: span.offsetHeight };
6868 });
6969 var detected = testFonts.filter(function (tf) {
7070 return baseFonts.some(function (bf) {
7171 span.style.fontFamily = tf + ',' + bf;
7272 return span.offsetWidth !== widths[bf].w || span.offsetHeight !== widths[bf].h;
7373 });
7474 });
7575 document.body.removeChild(span);
7676 return detected.join(',');
7777 });
7878 }
7979
8080 function hexDigest(buf) {
8181 var arr = new Uint8Array(buf);
8282 var hex = '';
8383 for (var i = 0; i < arr.length; i++) {
8484 var h = arr[i].toString(16);
8585 hex += (h.length < 2 ? '0' + h : h);
8686 }
8787 return hex;
8888 }
8989
9090 function compute() {
9191 var parts = [
9292 'v1',
9393 canvasHash(),
9494 webglHash(),
9595 fontProbe(),
9696 navigator.userAgent || '',
9797 navigator.language || '',
9898 navigator.platform || '',
9999 (navigator.hardwareConcurrency || 0) + '',
100100 (navigator.deviceMemory || 0) + '',
101101 ('ontouchstart' in window) + '',
102102 (screen.width || 0) + 'x' + (screen.height || 0),
103103 (screen.colorDepth || 0) + '',
104104 (window.devicePixelRatio || 0) + '',
105105 safe(function () { return Intl.DateTimeFormat().resolvedOptions().timeZone; }) || '',
106106 (new Date().getTimezoneOffset()) + ''
107107 ];
108108 var input = parts.join('||');
109109 var enc = new TextEncoder().encode(input);
110110 return window.crypto.subtle.digest('SHA-256', enc).then(hexDigest);
111111 }
112112
113113 function injectInto(form, hash) {
114114 if (!form) return;
115115 if (form.querySelector('input[name="fp_hash"]')) return;
116116 var hidden = document.createElement('input');
117117 hidden.type = 'hidden';
118118 hidden.name = 'fp_hash';
119119 hidden.value = hash;
120120 form.appendChild(hidden);
121121 }
122122
123123 function run() {
124124 compute().then(function (hash) {
125125 // Attach to every form -- login, signup, and any others on the page.
126126 document.querySelectorAll('form').forEach(function (f) { injectInto(f, hash); });
127127 }).catch(function () { /* silent -- fingerprint is best-effort */ });
128128 }
129129
130130 if (document.readyState === 'loading') {
131131 document.addEventListener('DOMContentLoaded', run);
132132 } else {
133133 run();
134134 }
135135})();