browser-imprint
Version:
Modern browser fingerprinting library for visitor identification. Generates unique device IDs using canvas, WebGL, audio and hardware signals. Zero dependencies, TypeScript-first, cross-browser compatible.
310 lines (295 loc) • 13.9 kB
JavaScript
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
var getScreenResolution = function () {
try {
var screen_1 = window.screen;
var width = (screen_1 === null || screen_1 === void 0 ? void 0 : screen_1.width) ||
window.innerWidth ||
document.documentElement.clientWidth;
var height = (screen_1 === null || screen_1 === void 0 ? void 0 : screen_1.height) ||
window.innerHeight ||
document.documentElement.clientHeight;
return "".concat(width, "x").concat(height);
}
catch (_a) {
return "";
}
};
var getPlatform = function () {
var platform = navigator.platform, userAgent = navigator.userAgent;
if (platform)
return platform;
if (/Win/.test(userAgent))
return "Win";
if (/Mac/.test(userAgent))
return "Mac";
if (/Linux/.test(userAgent))
return "Linux";
if (/Android/.test(userAgent))
return "Android";
if (/iOS/.test(userAgent))
return "iOS";
return "Unknown";
};
var generateHash = function (str) { return __awaiter(void 0, void 0, void 0, function () {
var buffer, hashBuffer, hash, i, char;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_b.trys.push([0, 2, , 3]);
buffer = new TextEncoder().encode(str);
return [4 /*yield*/, crypto.subtle.digest("SHA-256", buffer)];
case 1:
hashBuffer = _b.sent();
return [2 /*return*/, Array.from(new Uint8Array(hashBuffer))
.map(function (b) { return b.toString(16).padStart(2, "0"); })
.join("")];
case 2:
_b.sent();
hash = 0;
for (i = 0; i < str.length; i++) {
char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash;
}
return [2 /*return*/, Math.abs(hash).toString(16)];
case 3: return [2 /*return*/];
}
});
}); };
var getCanvasFingerprint = function () { return __awaiter(void 0, void 0, void 0, function () {
var canvas, ctx, gradient;
return __generator(this, function (_a) {
try {
canvas = document.createElement("canvas");
ctx = canvas.getContext("2d");
if (!ctx)
return [2 /*return*/, ""];
canvas.width = 200;
canvas.height = 50;
ctx.textBaseline = "top";
ctx.font = "14px 'Arial'";
ctx.fillStyle = "#F60";
ctx.fillRect(125, 1, 62, 20);
gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
gradient.addColorStop(0, "#FF0000");
gradient.addColorStop(1, "#0000FF");
ctx.fillStyle = gradient;
ctx.fillRect(0, 25, canvas.width, 25);
ctx.fillStyle = "#069";
ctx.fillText("Browser Fingerprint", 2, 15);
return [2 /*return*/, canvas.toDataURL().slice(-50)];
}
catch (_b) {
return [2 /*return*/, ""];
}
return [2 /*return*/];
});
}); };
var getWebGLFingerprint = function () { return __awaiter(void 0, void 0, void 0, function () {
var canvas, gl, debugInfo, vendor, renderer, version, fingerprint;
return __generator(this, function (_a) {
try {
canvas = document.createElement("canvas");
gl = (canvas.getContext("webgl") ||
canvas.getContext("experimental-webgl") ||
canvas.getContext("webgl2"));
if (!gl) {
return [2 /*return*/, canvas.toDataURL().slice(-32)]; // fallback for browsers without WebGL support
}
debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) ||
gl.getParameter(gl.VENDOR) ||
"";
renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) ||
gl.getParameter(gl.RENDERER) ||
"";
version = gl.getParameter(gl.VERSION) || "";
fingerprint = "".concat(vendor, "~").concat(renderer, "~").concat(version);
if (!fingerprint) {
return [2 /*return*/, canvas.toDataURL("image/png").slice(-32)]; // fall back to canvas data URL
}
return [2 /*return*/, fingerprint];
}
catch (error) {
return [2 /*return*/, ""];
}
return [2 /*return*/];
});
}); };
var getAudioFingerprint = function () { return __awaiter(void 0, void 0, void 0, function () {
var AudioContext_1, context, oscillator, analyser, gainNode, dataArray;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_b.trys.push([0, 2, , 3]);
AudioContext_1 = window.AudioContext || window.webkitAudioContext;
if (!AudioContext_1)
return [2 /*return*/, ""];
context = new AudioContext_1();
oscillator = context.createOscillator();
analyser = context.createAnalyser();
gainNode = context.createGain();
gainNode.gain.value = 0;
oscillator.connect(analyser);
analyser.connect(gainNode);
gainNode.connect(context.destination);
oscillator.start(0);
analyser.fftSize = 256;
dataArray = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(dataArray);
oscillator.stop();
return [4 /*yield*/, context.close()];
case 1:
_b.sent();
return [2 /*return*/, dataArray.slice(0, 16).join(",")];
case 2:
_b.sent();
return [2 /*return*/, ""];
case 3: return [2 /*return*/];
}
});
}); };
var getStorageAvailability = function () {
var checkStorage = function (type) {
try {
var storage = window[type];
var x = "__storage_test__";
storage.setItem(x, x);
storage.removeItem(x);
return true;
}
catch (_a) {
return false;
}
};
return {
localStorage: checkStorage("localStorage"),
sessionStorage: checkStorage("sessionStorage"),
indexedDB: !!window.indexedDB,
};
};
var getDeviceFingerprint = function () { return __awaiter(void 0, void 0, void 0, function () {
var fingerprint, validFingerprints, fingerprintString, error_1;
var _a;
var _b, _c;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
_d.trys.push([0, 5, , 6]);
_a = {
userAgent: navigator.userAgent || "",
language: navigator.language || navigator.userLanguage || "",
colorDepth: ((_b = window.screen) === null || _b === void 0 ? void 0 : _b.colorDepth) || "",
screenResolution: getScreenResolution(),
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
storageAvailable: getStorageAvailability(),
hardwareConcurrency: navigator.hardwareConcurrency || "",
deviceMemory: navigator.deviceMemory || "",
platform: getPlatform(),
doNotTrack: navigator.doNotTrack || window.doNotTrack || "",
cookiesEnabled: navigator.cookieEnabled || false
};
return [4 /*yield*/, getCanvasFingerprint()];
case 1:
_a.canvas = _d.sent();
return [4 /*yield*/, getWebGLFingerprint()];
case 2:
_a.webgl = _d.sent();
return [4 /*yield*/, getAudioFingerprint()];
case 3:
fingerprint = (_a.audio = _d.sent(),
_a.screenOrientation = ((_c = screen.orientation) === null || _c === void 0 ? void 0 : _c.type) || "",
_a.mimeTypes = Array.from(navigator.mimeTypes)
.map(function (m) { return m.type; })
.join(","),
_a.plugins = Array.from(navigator.plugins)
.map(function (p) { return p.name; })
.join(","),
_a.maxTouchPoints = navigator.maxTouchPoints || 0,
_a);
validFingerprints = Object.entries(fingerprint)
.filter(function (_a) {
_a[0]; var value = _a[1];
return value !== null && value !== undefined;
})
.map(function (_a) {
_a[0]; var value = _a[1];
return String(value);
});
fingerprintString = validFingerprints.join("|||");
return [4 /*yield*/, generateHash(fingerprintString)];
case 4: return [2 /*return*/, _d.sent()];
case 5:
error_1 = _d.sent();
console.error("Error generating, trying basic mode:", error_1);
return [2 /*return*/, generateBasicFingerprint()];
case 6: return [2 /*return*/];
}
});
}); };
var generateBasicFingerprint = function () { return __awaiter(void 0, void 0, void 0, function () {
var basic;
return __generator(this, function (_a) {
basic = {
userAgent: navigator.userAgent,
language: navigator.language,
colorDepth: screen.colorDepth,
resolution: getScreenResolution(),
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
};
return [2 /*return*/, generateHash(Object.values(basic).join("|||"))];
});
}); };
export { getDeviceFingerprint };
//# sourceMappingURL=index.esm.js.map