@deviceidio/deviceid
Version:
deviceID client
1,537 lines (1,485 loc) • 93.1 kB
JavaScript
const UAParser = require("ua-parser-js");
const CryptoJS = require("crypto-js");
class DeviceIDError extends Error {
constructor(message) {
super(message);
this.type = message.type;
this.val = message.val;
this.code = message.code;
}
}
function checkOS(res) {
if (res.os == null || res.os == undefined) return "-";
const Desktop = [
"AIX",
"Amiga OS",
"Arch",
"BeOS",
"CentOS",
"Chromium OS",
"Contiki",
"Fedora",
"FreeBSD",
"Debian",
"Deepin",
"DragonFly",
"elementary OS",
"Gentoo",
"GhostBSD",
"GNU",
"Haiku",
"HP-UX",
"Hurd",
"Joli",
"Linpus",
"Linux",
"Linspire",
"Mageia",
"Mandriva",
"Manjaro",
"MeeGo",
"Minix",
"Mint",
"Morph OS",
"NetBSD",
"OpenBSD",
"OpenVMS",
"OS/2",
"PC-BSD",
"PCLinuxOS",
"Plan9",
"RedHat",
"RISC OS",
"Sabayon",
"SerenityOS",
"Slackware",
"Solaris",
"SUSE",
"Ubuntu",
"VectorLinux",
"Zenwalk",
];
const mobile = [
"Android",
"Android-x86",
"Bada",
"BlackBerry",
"Firefox OS",
"Fuchsia",
"HarmonyOS",
"iOS",
"KaiOS",
"Maemo",
"QNX",
"RIM Tablet OS",
"Sailfish",
"Series40",
"Symbian",
"Tizen",
"WebOS",
"Windows Phone",
"Windows Mobile",
];
if (Desktop.includes(res.os.name)) {
return 0;
} else if (mobile.includes(res.os.name)) {
return 6;
} else {
return 7;
}
}
const {
Identification,
Print,
Audio,
AudioData,
Private,
FragmentShader,
Rasterizer,
FrameBuffer,
WebGLData,
VertexShader,
WebGLContextInfo,
Response,
} = require("./bundle.js");
var __awaiter =
(this && this.__awaiter) ||
function (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());
});
};
var __generator =
(this && this.__generator) ||
function (thisArg, body) {
var _ = {
label: 0,
sent: function () {
if (t[0] & 1) throw t[1];
return t[1];
},
trys: [],
ops: [],
},
f,
y,
t,
g;
return (
(g = { next: verb(0), throw: verb(1), 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 };
}
};
function detectIncognito() {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
return [
4 /*yield*/,
new Promise(function (resolve, reject) {
var browserName = "Unknown";
function __callback(isPrivate) {
resolve({
isPrivate: isPrivate,
browserName: browserName,
});
}
function identifyChromium() {
var ua = navigator.userAgent;
if (ua.match(/Chrome/)) {
if (navigator.brave !== undefined) {
return "Brave";
} else if (ua.match(/Edg/)) {
return "Edge";
} else if (ua.match(/OPR/)) {
return "Opera";
}
return "Chrome";
} else {
return "Chromium";
}
}
function assertEvalToString(value) {
return value === eval.toString().length;
}
function isSafari() {
var v = navigator.vendor;
return (
v !== undefined &&
v.indexOf("Apple") === 0 &&
assertEvalToString(37)
);
}
function isChrome() {
var v = navigator.vendor;
return (
v !== undefined &&
v.indexOf("Google") === 0 &&
assertEvalToString(33)
);
}
function isFirefox() {
return (
document.documentElement !== undefined &&
document.documentElement.style.MozAppearance !== undefined &&
assertEvalToString(37)
);
}
function isMSIE() {
return (
navigator.msSaveBlob !== undefined && assertEvalToString(39)
);
}
/**
* Safari (Safari for iOS & macOS)
**/
function newSafariTest() {
var tmp_name = String(Math.random());
try {
var db = window.indexedDB.open(tmp_name, 1);
db.onupgradeneeded = function (i) {
var _a, _b;
var res =
(_a = i.target) === null || _a === void 0
? void 0
: _a.result;
try {
res
.createObjectStore("test", {
autoIncrement: true,
})
.put(new Blob());
__callback(false);
} catch (e) {
var message = e;
if (e instanceof Error) {
message =
(_b = e.message) !== null && _b !== void 0 ? _b : e;
}
if (typeof message !== "string") {
__callback(false);
return;
}
var matchesExpectedError = message.includes(
"BlobURLs are not yet supported",
);
__callback(matchesExpectedError);
return;
} finally {
res.close();
window.indexedDB.deleteDatabase(tmp_name);
}
};
} catch (e) {
__callback(false);
}
}
function oldSafariTest() {
var openDB = window.openDatabase;
var storage = window.localStorage;
try {
openDB(null, null, null, null);
} catch (e) {
__callback(true);
return;
}
try {
storage.setItem("test", "1");
storage.removeItem("test");
} catch (e) {
__callback(true);
return;
}
__callback(false);
}
function safariPrivateTest() {
if (navigator.maxTouchPoints !== undefined) {
newSafariTest();
} else {
oldSafariTest();
}
}
/**
* Chrome
**/
function getQuotaLimit() {
var w = window;
if (
w.performance !== undefined &&
w.performance.memory !== undefined &&
w.performance.memory.jsHeapSizeLimit !== undefined
) {
return performance.memory.jsHeapSizeLimit;
}
return 1073741824;
}
// >= 76
function storageQuotaChromePrivateTest() {
navigator.webkitTemporaryStorage.queryUsageAndQuota(
function (_, quota) {
var quotaInMib = Math.round(quota / (1024 * 1024));
var quotaLimitInMib =
Math.round(getQuotaLimit() / (1024 * 1024)) * 2;
__callback(quotaInMib < quotaLimitInMib);
},
function (e) {
reject(
new Error(
"detectIncognito somehow failed to query storage quota: " +
e.message,
),
);
},
);
}
// 50 to 75
function oldChromePrivateTest() {
var fs = window.webkitRequestFileSystem;
var success = function () {
__callback(false);
};
var error = function () {
__callback(true);
};
fs(0, 1, success, error);
}
function chromePrivateTest() {
if (Promise !== undefined && Promise.allSettled !== undefined) {
storageQuotaChromePrivateTest();
} else {
oldChromePrivateTest();
}
}
/**
* Firefox
**/
function firefoxPrivateTest() {
__callback(navigator.serviceWorker === undefined);
}
/**
* MSIE
**/
function msiePrivateTest() {
__callback(window.indexedDB === undefined);
}
function main() {
if (isSafari()) {
browserName = "Safari";
safariPrivateTest();
} else if (isChrome()) {
browserName = identifyChromium();
chromePrivateTest();
} else if (isFirefox()) {
browserName = "Firefox";
firefoxPrivateTest();
} else if (isMSIE()) {
browserName = "Internet Explorer";
msiePrivateTest();
} else {
reject(
new Error("detectIncognito cannot determine the browser"),
);
}
}
main();
}),
];
case 1:
return [2 /*return*/, _a.sent()];
}
});
});
}
(function (name, context, definition) {
"use strict";
if (typeof module !== "undefined" && module.exports) {
module.exports = definition();
} else if (typeof define === "function" && define.amd) {
define(definition);
} else {
context[name] = definition();
}
})("DeviceID", this, function () {
"use strict";
// for IE8 and older
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement, fromIndex) {
var k;
if (this == null) {
throw new TypeError("'this' is null or undefined");
}
var O = Object(this);
var len = O.length >>> 0;
if (len === 0) {
return -1;
}
var n = +fromIndex || 0;
if (Math.abs(n) === Infinity) {
n = 0;
}
if (n >= len) {
return -1;
}
k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
while (k < len) {
if (k in O && O[k] === searchElement) {
return k;
}
k++;
}
return -1;
};
}
var DeviceID = function (options) {
this.loaded = ""; // loaded token for communication
this.stored_id = ""; // visit identifier for TLS
this.old = null; // stored localStorage ID
this.cookie = null; // stored cookie ID
this.url = "https://api.deviceid.io";
//this.url = 'https://api.deviceid.io';
this.options = options;
this.uap = new UAParser();
this.tokenErrorCount = 0;
this.iv = CryptoJS.enc.Utf8.parse("c7VEVapazCwNVcWgi1Ej");
};
DeviceID.prototype = {
load: async function (done) {
try {
this.stored_id = localStorage.getItem("deviceID_identifier");
if (
this.stored_id == null ||
this.stored_id == undefined ||
this.stored_id.length != 20
) {
this.stored_id = this.makeid(20);
localStorage.setItem("deviceID_identifier", this.stored_id);
}
// return done(new Promise(async (resolve, reject) => {
if (typeof this.options === "object") {
if (!("apiKey" in this.options)) {
return done(
false,
new DeviceIDError({
type: "LOAD ERROR",
code: 0,
val: "NO API KEY PROVIDED",
}),
);
// return done(false);
} else if (!("secret" in this.options)) {
return done(
false,
new DeviceIDError({
type: "LOAD ERROR",
code: 1,
val: "NO SECRET KEY PROVIDED",
}),
);
// return done(false);
}
} else {
return done(
false,
new DeviceIDError({
type: "LOAD ERROR",
code: 2,
val: "NO DATA PROVIDED",
}),
);
// return done(false);
}
const xhr1 = new XMLHttpRequest();
xhr1.open("POST", "https://api.deviceid.io:3005/index.json");
xhr1.setRequestHeader("Content-Type", "text/plain");
xhr1.send(JSON.stringify({ id: this.stored_id }));
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
try {
this.loaded = CryptoJS.AES.decrypt(
xhr.responseText,
this.options.secret,
).toString(CryptoJS.enc.Utf8);
} catch (e) {
return done(
false,
new DeviceIDError({
code: 10,
val: "SECRET KEY DECRYPTION FAILED",
type: "LOAD ERROR",
}),
);
}
// this.key = this.options.secret;
if (typeof Storage !== "undefined") {
this.old = localStorage.getItem("c:GkK?_5eVdQdiQT0Fb?");
}
if (navigator.cookieEnabled) {
this.cookieStored = this.getCookie("-BAL4_z*-wQ=6TYqCA!U");
}
return done(true, null);
} else {
try {
const dt = xhr.responseText.split(":");
return done(
false,
new DeviceIDError({
type: "LOAD ERROR",
code: dt[1],
val: dt[0],
}),
);
} catch (err) {
return done(
false,
new DeviceIDError({
type: "LOAD ERROR",
code: xhr.status,
val: xhr.responseText,
}),
);
}
}
}
};
xhr.open(
"GET",
"https://api.deviceid.io/load?key=" +
encodeURIComponent(this.options.apiKey),
);
xhr.send();
try {
detectIncognito().then((res) => {
this.prv = res;
});
} catch (e) {
this.prv = {
isPrivate: false,
browserName: "-",
};
}
this.device = this.device();
} catch (e) {
return done(
false,
new DeviceIDError({ code: 99, val: e, type: "Load Error" }),
);
}
},
id: async function (done) {
try {
var obj = undefined;
var str =
(typeof this.old === "string" && this.old.length > 0) ||
(typeof this.cookieStored === "string" &&
this.cookieStored.length > 0);
const start = performance.now();
if (!str) {
const audioData = await this.getAudio(this);
obj = {
a: this.fonts(),
b: this.cryptoSupport(),
d: this.blending(),
i: this.osCpu(),
j: this.getLanguages(),
k: window.screen.colorDepth,
l: navigator.deviceMemory,
m: window.screen.width + "x" + window.screen.height,
c: [window.screen.availHeight, window.screen.availWidth],
n: this.getHardwareConcurrency(),
o: new Date().getTimezoneOffset(),
t: this.getNavigatorCpuClass(),
v: this.getMimeTypes(),
w: this.getCanvas(),
x: this.getTouchSupport(),
bb: this.colorGamut(),
cc: this.invertedColors(),
dd: this.forcedColors(),
ee: this.monochrome(),
ff: this.contrast(),
gg: this.reducedMotion(),
hh: this.hdr(),
ii: this.getMathsConstants(),
jj: await this.webGLParameters(),
ll: this.arch(),
b0: this.reducedTransparency(),
b2: this.webGL(),
};
obj["zz"] = this.x64hash128(JSON.stringify(obj), 31);
/*
const mini_print = {
a: obj.a,
d: obj.g,
f: obj.ii,
h: obj.b2,
};
obj["b1"] = this.x64hash128(JSON.stringify(mini_print), 31);
*/
obj["b1"] = obj.jj[0].gpuHash + "&" + obj.jj[1].gpuHash;
obj["a2"] = navigator.appCodeName;
obj["a3"] = navigator.appName;
obj["a4"] = navigator.appVersion;
obj["a5"] = navigator.product; // 1, 1
obj["a6"] = navigator.productSub; // 1, 2
obj["a7"] = this.getNavigatorPrototype(); // 1, 4
obj["a9"] = navigator.buildID;
obj["kk"] = navigator.pdfViewerEnabled;
obj["aa"] = navigator.cookieEnabled;
obj["p"] = this.sessionStorage();
obj["q"] = this.localStorage();
obj["r"] = this.indexedDB();
obj["s"] = Boolean(window.openDatabase);
obj["ua"] = navigator.userAgent.split(" ").join("");
obj["y"] = navigator.vendor;
obj["z"] = navigator.vendorSub;
audioData.vc = AudioData.fromObject({
a: audioData.vc["ac-baseLatency"],
b: audioData.vc["ac-channelCount"],
c: audioData.vc["ac-channelCountMode"],
d: audioData.vc["ac-channelInterpretation"],
e: audioData.vc["ac-maxChannelCount"],
f: audioData.vc["ac-numberOfInputs"],
g: audioData.vc["ac-numberOfOutputs"],
h: audioData.vc["ac-outputLatency"],
i: audioData.vc["ac-sampleRate"],
j: audioData.vc["ac-sinkId"],
k: audioData.vc["ac-state"],
l: audioData.vc["an-channelCount"],
m: audioData.vc["an-channelCountMode"],
n: audioData.vc["an-channelInterpretation"],
o: audioData.vc["an-fftSize"],
p: audioData.vc["an-frequencyBinCount"],
q: audioData.vc["an-maxDecibels"],
r: audioData.vc["an-minDecibels"],
s: audioData.vc["an-numberOfInputs"],
t: audioData.vc["an-numberOfOutputs"],
u: audioData.vc["an-smoothingTimeConstant"],
});
const msg = Audio.fromObject(audioData);
obj["g"] = msg;
}
const end = performance.now();
var res = !str
? {
tls: this.stored_id,
dev: this.device,
url: window.location.href,
platform: {},
private: this.prv,
print: Print.fromObject(obj),
clientTiming: end - start,
local: obj.q,
}
: {
tls: this.stored_id,
dev: this.device,
url: window.location.href,
platform: {},
private: this.prv,
print: null,
clientTiming: end - start,
local: this.localStorage(),
old: this.old,
cookie: this.cookieStored,
};
if (this.options != undefined) {
if (this.options.request_id != undefined) {
res["id"] = params.request_id;
}
if (this.options.data != undefined) {
res["tag"] = JSON.stringify(params.data);
}
}
const message = Identification.fromObject(res);
const buffer = Identification.encode(message).finish();
const resp = await fetch(this.url + "/id", {
method: "POST",
body: buffer,
headers: { Authorization: "Bearer " + this.loaded },
});
if (resp.status === 444) {
if (this.tokenErrorCount > 2) {
return new DeviceIDError({
code: 201,
type: "ID Error",
val: "AUTH TOKEN PRESISTED AFTER MULTIPLE FAILURES",
});
} else this.tokenErrorCount++;
localStorage.removeItem("c:GkK?_5eVdQdiQT0Fb?");
this.setCookie("-BAL4_z*-wQ=6TYqCA!U", "", {
secure: true,
expires: 3600,
});
this.old = null;
this.cookieStored = null;
this.id(function (res) {
return done(res, null);
});
} else if (resp.status !== 200)
return done(
null,
new DeviceIDError({
code: resp.status,
val: resp.responseText,
type: "ID Error",
}),
);
const timing = performance.now() - end;
const msg1 = Response.decode(new Uint8Array(await resp.arrayBuffer()));
const parsed = Response.toObject(msg1);
if (parsed.j != null) {
localStorage.setItem("c:GkK?_5eVdQdiQT0Fb?", parsed["j"]);
this.setCookie("-BAL4_z*-wQ=6TYqCA!U", parsed["j"], {
secure: true,
expires: 3600,
});
}
parsed.visit_id = parsed.a;
parsed.device_id = parsed.b;
parsed.device_found = parsed.c;
parsed.threat_level = parsed.e;
parsed.confidence = parsed.f;
parsed.tempered = parsed.g;
parsed.blocked = parsed.h;
parsed.ip = parsed.i;
delete parsed.j;
delete parsed.a;
delete parsed.b;
delete parsed.c;
delete parsed.e;
delete parsed.f;
delete parsed.g;
delete parsed.h;
delete parsed.i;
parsed["private"] = this.prv;
parsed["platform"] = this.uapRes;
parsed["adblock"] = !str ? obj.c : false;
parsed["dev"] = this.device;
setTimeout(() => {
const xhr1 = new XMLHttpRequest();
xhr1.open("POST", this.url + "/updateTime");
xhr1.setRequestHeader("Content-Type", "text/plain");
xhr1.send(JSON.stringify({ timing, visit_id: parsed["visit_id"] }));
}, 1);
return done(parsed, null);
//xhr.send(buffer);
} catch (e) {
return done(
null,
new DeviceIDError({ code: 20, val: e, type: "ID Error" }),
);
}
},
device: function () {
this.uapRes = this.uap.getResult();
var device = 0;
const arch = this.uapRes.cpu.architecture;
if (
(this.uapRes.os != undefined &&
this.uapRes.os != null &&
this.uapRes.os.name == "macOS") ||
this.uapRes.device.model == "Macintosh"
) {
device = 1;
} else if (
this.uapRes.device != null &&
this.uapRes.device != undefined &&
this.uapRes.device.vendor == "Apple"
) {
if (this.uapRes.os.name == "iOS") {
device = 3;
} else if (uap.os.name == "watchOS") {
device = 2;
} else {
device = 1;
}
} else if (
arch == "amd64" ||
arch == "ia32" ||
arch == "ia64" ||
arch == "pa-risc" ||
arch == "sparc" ||
arch == "sparch64"
) {
// Desktop
device = 0;
} else if (arch == "68k") {
// mobile
device = 6;
} else if (arch == "arm64") {
// ipad / iphone
device = 3;
} else if (arch == "ppc") {
// mac
device = 1;
} else if (
arch == "avr" ||
arch == "armhf" ||
arch == "irix" ||
arch == "irix64" ||
arch == "mips" ||
arch == "mips64"
) {
// something weird
device = 7;
} else {
// cpu arch undefined
if (this.uapRes.device.type == undefined) {
if (this.uapRes.os.name == undefined) {
device = 7;
} else {
device = checkOS(this.uapRes);
}
} else {
if (this.uapRes.device == null || this.uapRes.device == undefined) {
device = 0;
} else {
const type = this.uapRes.device.type;
if (type == "mobile") {
device = 6;
} else if (type == "tablet") {
device = 5;
} else if (type == "werable") {
device = 2;
} else if (this.uapRes.os != null && this.uapRes.os != undefined) {
if (
this.uapRes.os == null ||
this.uapRes.os == undefined ||
this.uapRes.os.name == undefined
) {
device = 7;
} else {
device = checkOS(this.uapRes);
}
}
}
}
}
return device;
},
indexedDB: function () {
try {
return Boolean(window.indexedDB);
} catch (e) {
return true;
}
},
localStorage: function () {
try {
return Boolean(window.localStorage);
} catch (e) {
return true;
}
},
sessionStorage: function () {
try {
return Boolean(window.sessionStorage);
} catch (e) {
return true;
}
},
getNavigatorPrototype: function () {
try {
var obj = window.navigator;
var protoNavigator = [];
do
Object.getOwnPropertyNames(obj).forEach(function (name) {
protoNavigator.push(name);
});
while ((obj = Object.getPrototypeOf(obj)));
var res;
var finalProto = [];
protoNavigator.forEach(function (prop) {
var objDesc = Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(navigator),
prop,
);
if (objDesc != undefined) {
if (objDesc.value != undefined) {
res = objDesc.value.toString();
} else if (objDesc.get != undefined) {
res = objDesc.get.toString();
}
} else {
res = "";
}
finalProto.push(prop + "~~~" + res);
});
return finalProto.join(";;;");
} catch (e) {
return "";
}
},
webGL: function () {
var canvas,
ctx,
width = 256,
height = 128;
canvas = document.createElement("canvas");
(canvas.width = width),
(canvas.height = height),
(ctx =
canvas.getContext("webgl2") ||
canvas.getContext("experimental-webgl2") ||
canvas.getContext("webgl") ||
canvas.getContext("experimental-webgl") ||
canvas.getContext("moz-webgl"));
if (ctx == null || ctx == undefined) return "";
try {
var f =
"attribute vec2 attrVertex;varying vec2 varyinTexCoordinate;uniform vec2 uniformOffset;void main(){varyinTexCoordinate=attrVertex+uniformOffset;gl_Position=vec4(attrVertex,0,1);}";
var g =
"precision mediump float;varying vec2 varyinTexCoordinate;void main() {gl_FragColor=vec4(varyinTexCoordinate,0,1);}";
var h = ctx.createBuffer();
ctx.bindBuffer(ctx.ARRAY_BUFFER, h);
var i = new Float32Array([-0.2, -0.9, 0, 0.4, -0.26, 0, 0, 0.7321, 0]);
ctx.bufferData(ctx.ARRAY_BUFFER, i, ctx.STATIC_DRAW),
(h.itemSize = 3),
(h.numItems = 3);
var j = ctx.createProgram();
var k = ctx.createShader(ctx.VERTEX_SHADER);
ctx.shaderSource(k, f);
ctx.compileShader(k);
var l = ctx.createShader(ctx.FRAGMENT_SHADER);
ctx.shaderSource(l, g);
ctx.compileShader(l);
ctx.attachShader(j, k);
ctx.attachShader(j, l);
ctx.linkProgram(j);
ctx.useProgram(j);
j.vertexPosAttrib = ctx.getAttribLocation(j, "attrVertex");
j.offsetUniform = ctx.getUniformLocation(j, "uniformOffset");
ctx.enableVertexAttribArray(j.vertexPosArray);
ctx.vertexAttribPointer(
j.vertexPosAttrib,
h.itemSize,
ctx.FLOAT,
!1,
0,
0,
);
ctx.uniform2f(j.offsetUniform, 1, 1);
ctx.drawArrays(ctx.TRIANGLE_STRIP, 0, h.numItems);
} catch (e) {}
var m = "";
var n = new Uint8Array(width * height * 4);
ctx.readPixels(0, 0, width, height, ctx.RGBA, ctx.UNSIGNED_BYTE, n);
// m = JSON.stringify(n).replace(/,?"[0-9]+":/g, "");
m = JSON.stringify(n);
// console.log(m);
return this.x64hash128(m, 31);
},
arch: function () {
const f = new Float32Array(1);
const u = new Uint8Array(f.buffer);
f[0] = Infinity / Infinity;
return u[3];
},
webGLParameters: async function () {
// Based on and inspired by https://github.com/CesiumGS/webglreport
// https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Constants
const WebGLConstants = [
"ALIASED_LINE_WIDTH_RANGE",
"ALIASED_POINT_SIZE_RANGE",
"ALPHA_BITS",
"BLUE_BITS",
"DEPTH_BITS",
"GREEN_BITS",
"MAX_COMBINED_TEXTURE_IMAGE_UNITS",
"MAX_CUBE_MAP_TEXTURE_SIZE",
"MAX_FRAGMENT_UNIFORM_VECTORS",
"MAX_RENDERBUFFER_SIZE",
"MAX_TEXTURE_IMAGE_UNITS",
"MAX_TEXTURE_SIZE",
"MAX_VARYING_VECTORS",
"MAX_VERTEX_ATTRIBS",
"MAX_VERTEX_TEXTURE_IMAGE_UNITS",
"MAX_VERTEX_UNIFORM_VECTORS",
"MAX_VIEWPORT_DIMS",
"RED_BITS",
"RENDERER",
"SHADING_LANGUAGE_VERSION",
"STENCIL_BITS",
"VERSION",
];
const WebGL2Constants = [
"MAX_VARYING_COMPONENTS",
"MAX_VERTEX_UNIFORM_COMPONENTS",
"MAX_VERTEX_UNIFORM_BLOCKS",
"MAX_VERTEX_OUTPUT_COMPONENTS",
"MAX_PROGRAM_TEXEL_OFFSET",
"MAX_3D_TEXTURE_SIZE",
"MAX_ARRAY_TEXTURE_LAYERS",
"MAX_COLOR_ATTACHMENTS",
"MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS",
"MAX_COMBINED_UNIFORM_BLOCKS",
"MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS",
"MAX_DRAW_BUFFERS",
"MAX_ELEMENT_INDEX",
"MAX_FRAGMENT_INPUT_COMPONENTS",
"MAX_FRAGMENT_UNIFORM_COMPONENTS",
"MAX_FRAGMENT_UNIFORM_BLOCKS",
"MAX_SAMPLES",
"MAX_SERVER_WAIT_TIMEOUT",
"MAX_TEXTURE_LOD_BIAS",
"MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS",
"MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS",
"MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS",
"MAX_UNIFORM_BLOCK_SIZE",
"MAX_UNIFORM_BUFFER_BINDINGS",
"MIN_PROGRAM_TEXEL_OFFSET",
"UNIFORM_BUFFER_OFFSET_ALIGNMENT",
];
const Categories = {
uniformBuffers: [
"MAX_UNIFORM_BUFFER_BINDINGS",
"MAX_UNIFORM_BLOCK_SIZE",
"UNIFORM_BUFFER_OFFSET_ALIGNMENT",
"MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS",
"MAX_COMBINED_UNIFORM_BLOCKS",
"MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS",
],
debugRendererInfo: ["UNMASKED_VENDOR_WEBGL", "UNMASKED_RENDERER_WEBGL"],
fragmentShader: [
"MAX_FRAGMENT_UNIFORM_VECTORS",
"MAX_TEXTURE_IMAGE_UNITS",
"MAX_FRAGMENT_INPUT_COMPONENTS",
"MAX_FRAGMENT_UNIFORM_COMPONENTS",
"MAX_FRAGMENT_UNIFORM_BLOCKS",
"FRAGMENT_SHADER_BEST_FLOAT_PRECISION",
"MIN_PROGRAM_TEXEL_OFFSET",
"MAX_PROGRAM_TEXEL_OFFSET",
],
frameBuffer: [
"MAX_DRAW_BUFFERS",
"MAX_COLOR_ATTACHMENTS",
"MAX_SAMPLES",
"RGBA_BITS",
"DEPTH_STENCIL_BITS",
"MAX_RENDERBUFFER_SIZE",
"MAX_VIEWPORT_DIMS",
],
rasterizer: ["ALIASED_LINE_WIDTH_RANGE", "ALIASED_POINT_SIZE_RANGE"],
textures: [
"MAX_TEXTURE_SIZE",
"MAX_CUBE_MAP_TEXTURE_SIZE",
"MAX_COMBINED_TEXTURE_IMAGE_UNITS",
"MAX_TEXTURE_MAX_ANISOTROPY_EXT",
"MAX_3D_TEXTURE_SIZE",
"MAX_ARRAY_TEXTURE_LAYERS",
"MAX_TEXTURE_LOD_BIAS",
],
transformFeedback: [
"MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS",
"MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS",
"MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS",
],
vertexShader: [
"MAX_VARYING_VECTORS",
"MAX_VERTEX_ATTRIBS",
"MAX_VERTEX_TEXTURE_IMAGE_UNITS",
"MAX_VERTEX_UNIFORM_VECTORS",
"MAX_VERTEX_UNIFORM_COMPONENTS",
"MAX_VERTEX_UNIFORM_BLOCKS",
"MAX_VERTEX_OUTPUT_COMPONENTS",
"MAX_VARYING_COMPONENTS",
"VERTEX_SHADER_BEST_FLOAT_PRECISION",
],
webGLContextInfo: [
"CONTEXT",
"ANTIALIAS",
"DIRECT_3D",
"MAJOR_PERFORMANCE_CAVEAT",
"RENDERER",
"SHADING_LANGUAGE_VERSION",
"VERSION",
],
};
/* parameter helpers */
// https://developer.mozilla.org/en-US/docs/Web/API/EXT_texture_filter_anisotropic
const getMaxAnisotropy = (context) => {
try {
const extension =
context.getExtension("EXT_texture_filter_anisotropic") ||
context.getExtension("WEBKIT_EXT_texture_filter_anisotropic") ||
context.getExtension("MOZ_EXT_texture_filter_anisotropic");
return context.getParameter(extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
} catch (error) {
console.error(error);
return undefined;
}
};
// https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_draw_buffers
const getMaxDrawBuffers = (context) => {
try {
const extension =
context.getExtension("WEBGL_draw_buffers") ||
context.getExtension("WEBKIT_WEBGL_draw_buffers") ||
context.getExtension("MOZ_WEBGL_draw_buffers");
return context.getParameter(extension.MAX_DRAW_BUFFERS_WEBGL);
} catch (error) {
return undefined;
}
};
// https://developer.mozilla.org/en-US/docs/Web/API/WebGLShaderPrecisionFormat/precision
// https://developer.mozilla.org/en-US/docs/Web/API/WebGLShaderPrecisionFormat/rangeMax
// https://developer.mozilla.org/en-US/docs/Web/API/WebGLShaderPrecisionFormat/rangeMin
const getShaderData = (shader) => {
const shaderData = {};
try {
for (const prop in shader) {
const shaderPrecisionFormat = shader[prop];
shaderData[prop] = {
precision: shaderPrecisionFormat.precision,
rangeMax: shaderPrecisionFormat.rangeMax,
rangeMin: shaderPrecisionFormat.rangeMin,
};
}
return shaderData;
} catch (error) {
return undefined;
}
};
// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getShaderPrecisionFormat
const getShaderPrecisionFormat = (context, shaderType) => {
const props = ["LOW_FLOAT", "MEDIUM_FLOAT", "HIGH_FLOAT"];
const precisionFormat = {};
try {
props.forEach((prop) => {
precisionFormat[prop] = context.getShaderPrecisionFormat(
context[shaderType],
context[prop],
);
return;
});
return precisionFormat;
} catch (error) {
return undefined;
}
};
// https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_debug_renderer_info
const getUnmasked = (context, constant) => {
try {
const extension = context.getExtension("WEBGL_debug_renderer_info");
const unmasked = context.getParameter(extension[constant]);
return unmasked;
} catch (error) {
return undefined;
}
};
// Takes the parameter object and generate a fingerprint of sorted numeric values
function getNumericValues(parameters) {
if (!parameters) return;
return [
...new Set(
Object.values(parameters)
.filter((val) => val && typeof val != "string")
.flat()
.map((val) => Number(val) || 0),
),
].sort((a, b) => a - b);
}
// Highlight common GPU brands
function getGpuBrand(gpu) {
if (!gpu) return;
const gpuBrandMatcher =
/(adreno|amd|apple|intel|llvm|mali|microsoft|nvidia|parallels|powervr|samsung|swiftshader|virtualbox|vmware)/i;
const brand = /radeon/i.test(gpu)
? "AMD"
: /geforce/i.test(gpu)
? "NVIDIA"
: (gpuBrandMatcher.exec(gpu) || [])[0] || "Other";
return brand;
}
/* get WebGLRenderingContext or WebGL2RenderingContext */
// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext
// https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext
function getWebGL(contextType) {
const errors = [];
let data = {};
const isWebGL = /^(experimental-)?webgl$/;
const isWebGL2 = /^(experimental-)?webgl2$/;
const supportsWebGL =
isWebGL.test(contextType) && "WebGLRenderingContext" in window;
const supportsWebGL2 =
isWebGL2.test(contextType) && "WebGLRenderingContext" in window;
// detect support
if (!supportsWebGL && !supportsWebGL2) {
errors.push("not supported");
return [data, errors];
}
// get canvas context
let canvas;
let context;
let hasMajorPerformanceCaveat;
try {
canvas = document.createElement("canvas");
context = canvas.getContext(contextType, {
failIfMajorPerformanceCaveat: true,
});
if (!context) {
hasMajorPerformanceCaveat = true;
context = canvas.getContext(contextType);
if (!context) {
throw new Error(`context of type ${typeof context}`);
}
}
} catch (err) {
console.error(err);
errors.push("context blocked");
return [data, errors];
}
// get supported extensions
// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getSupportedExtensions
// https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Using_Extensions
let webGLExtensions;
try {
webGLExtensions = context.getSupportedExtensions();
} catch (error) {
console.error(error);
errors.push("extensions blocked");
}
// get parameters
let parameters;
try {
const VERTEX_SHADER = getShaderData(
getShaderPrecisionFormat(context, "VERTEX_SHADER"),
);
const FRAGMENT_SHADER = getShaderData(
getShaderPrecisionFormat(context, "FRAGMENT_SHADER"),
);
parameters = {
ANTIALIAS: context.getContextAttributes().antialias,
CONTEXT: contextType,
MAJOR_PERFORMANCE_CAVEAT: hasMajorPerformanceCaveat,
MAX_TEXTURE_MAX_ANISOTROPY_EXT: getMaxAnisotropy(context),
MAX_DRAW_BUFFERS_WEBGL: getMaxDrawBuffers(context),
VERTEX_SHADER,
VERTEX_SHADER_BEST_FLOAT_PRECISION: Object.values(
VERTEX_SHADER.HIGH_FLOAT,
),
FRAGMENT_SHADER,
FRAGMENT_SHADER_BEST_FLOAT_PRECISION: Object.values(
FRAGMENT_SHADER.HIGH_FLOAT,
),
UNMASKED_VENDOR_WEBGL: getUnmasked(
context,
"UNMASKED_VENDOR_WEBGL",
),
UNMASKED_RENDERER_WEBGL: getUnmasked(
context,
"UNMASKED_RENDERER_WEBGL",
),
};
const glConstants = [
...WebGLConstants,
...(supportsWebGL2 ? WebGL2Constants : []),
];
glConstants.forEach((key) => {
const result = context.getParameter(context[key]);
const typedArray =
result &&
(result.constructor === Float32Array ||
result.constructor === Int32Array);
parameters[key] = typedArray ? [...result] : result;
});
parameters.RGBA_BITS = [
parameters.RED_BITS,
parameters.GREEN_BITS,
parameters.BLUE_BITS,
parameters.ALPHA_BITS,
];
parameters.DEPTH_STENCIL_BITS = [
parameters.DEPTH_BITS,
parameters.STENCIL_BITS,
];
parameters.DIRECT_3D = /Direct3D|D3D(\d+)/.test(
parameters.UNMASKED_RENDERER_WEBGL,
);
} catch (error) {
console.error(error);
errors.push("parameters blocked");
}
const gpu = String([
parameters.UNMASKED_VENDOR_WEBGL,
parameters.UNMASKED_RENDERER_WEBGL,
]);
const gpuBrand = getGpuBrand(gpu);
// Structure parameter data
let components = {};
if (parameters) {
Object.keys(Categories).forEach((name) => {
const componentData = Categories[name].reduce((acc, key) => {
if (parameters[key] !== undefined) {
acc[key] = parameters[key];
}
return acc;
}, {});
// Only compile if the data exists
if (Object.keys(componentData).length) {
components[name] = componentData;
}
});
}
data = {
gpuHash: !parameters
? undefined
: [gpuBrand, ...getNumericValues(parameters)].join(":"),
gpu,
gpuBrand,
...components,
webGLExtensions,
};
return [data, errors];
}
const value = await Promise.all([
getWebGL("webgl"),
getWebGL("webgl2"),
getWebGL("experimental-webgl"),
])
.then((response) => {
const [webGL, webGL2, experimentalWebGL] = response;
// Extract both data and errors
const [webGLData, webGLErrors] = webGL;
const [webGL2Data, webGL2Errors] = webGL2;
const [experimentalWebGLData, experimentalWebGLErrors] =
experimentalWebGL;
// Show the data
/*
console.log('WebGLRenderingContext: ', webGLData)
console.log('WebGL2RenderingContext: ', webGL2Data)
console.log('Experimental: ', experimentalWebGLData)
*/
// return(XXH64(JSON.stringify([webGLData, webGL2Data, experimentalWebGLData]), 0xA3FC ).toString(16));
webGLData.fragmentShader = FragmentShader.fromObject({
a: webGLData.fragmentShader.FRAGMENT_SHADER_BEST_FLOAT_PRECISION,
b: webGLData.fragmentShader.MAX_FRAGMENT_UNIFORM_VECTORS,
c: webGLData.fragmentShader.MAX_TEXTURE_IMAGE_UNITS,
});
webGLData.frameBuffer = FrameBuffer.fromObject({
a: webGLData.frameBuffer.DEPTH_STENCIL_BITS,
b: webGLData.frameBuffer.MAX_RENDERBUFFER_SIZE,
c: webGLData.frameBuffer.MAX_VIEWPORT_DIMS,
d: webGLData.frameBuffer.RGBA_BITS,
});
webGLData.rasterizer = Rasterizer.fromObject({
a: webGLData.rasterizer.ALIASED_LINE_WIDTH_RANGE,
b: webGLData.rasterizer.ALIASED_POINT_SIZE_RANGE,
});
webGLData.vertexShader = VertexShader.fromObject({
a: webGLData.vertexShader.MAX_VARYING_VECTORS,
b: webGLData.vertexShader.MAX_VERTEX_ATTRIBS,
c: webGLData.vertexShader.MAX_VERTEX_TEXTURE_IMAGE_UNITS,
d: webGLData.vertexShader.MAX_VERTEX_UNIFORM_VECTORS,
e: webGLData.vertexShader.VERTEX_SHADER_BEST_FLOAT_PRECISION,
});
webGLData.webGLContextInfo = WebGLContextInfo.fromObject({
a: webGLData.webGLContextInfo.ANTIALIAS,
b: webGLData.webGLContextInfo.CONTEXT,
c: webGLData.webGLContextInfo.DIRECT_3D,
d: webGLData.webGLContextInfo.RENDERER,
e: webGLData.webGLContextInfo.SHADING_LANGUAGE_VERSION,
f: webGLData.webGLContextInfo.VERSION,
});
const msg = WebGLData.fromObject(webGLData);
webGL2Data.fragmentShader = FragmentShader.fromObject({
a: webGL2Data.fragmentShader.FRAGMENT_SHADER_BEST_FLOAT_PRECISION,
b: webGL2Data.fragmentShader.MAX_FRAGMENT_UNIFORM_VECTORS,
c: webGL2Data.fragmentShader.MAX_TEXTURE_IMAGE_UNITS,
d: webGL2Data.fragmentShader.MAX_FRAGMENT_INPUT_COMPONENTS,
e: webGL2Data.fragmentShader.MAX_FRAGMENT_UNIFORM_BLOCKS,
f: webGL2Data.fragmentShader.MAX_FRAGMENT_UNIFORM_COMPONENTS,
g: webGL2Data.fragmentShader.MAX_PROGRAM_TEXEL_OFFSET,
h: webGL2Data.fragmentShader.MIN_PROGRAM_TEXEL_OFFSET,
});
webGL2Data.frameBuffer = FrameBuffer.fromObject({
a: webGL2Data.frameBuffer.DEPTH_STENCIL_BITS,
b: webGL2Data.frameBuffer.MAX_RENDERBUFFER_SIZE,
c: webGL2Data.frameBuffer.MAX_VIEWPORT_DIMS,
d: webGL2Data.frameBuffer.RGBA_BITS,
e: webGL2Data.frameBuffer.MAX_COLOR_ATTACHMENTS,
f: webGL2Data.frameBuffer.MAX_DRAW_BUFFERS,
g: webGL2Data.frameBuffer.MAX_SAMPLES,
});
webGL2Data.rasterizer = Rasterizer.fromObject({
a: webGL2Data.rasterizer.ALIASED_LINE_WIDTH_RANGE,
b: webGL2Data.rasterizer.ALIASED_POINT_SIZE_RANGE,
});
webGL2Data.vertexShader = VertexShader.fromObject({
a: webGL2Data.vertexShader.MAX_VARYING_VECTORS,
b: webGL2Data.vertexShader.MAX_VERTEX_ATTRIBS,
c: webGL2Data.vertexShader.MAX_VERTEX_TEXTURE_IMAGE_UNITS,
d: webGL2Data.vertexShader.MAX_VERTEX_UNIFORM_VECTORS,
e: webGL2Data.vertexShader.VERTEX_SHADER_BEST_FLOAT_PRECISION,
f: webGL2Data.vertexShader.MAX_VARYING_COMPONENTS,
g: webGL2Data.vertexShader.MAX_VERTEX_OUTPUT_COMPONENTS,
h: webGL2Data.vertexShader.MAX_VERTEX_TEXTURE_IMAGE_UNITS,
i: webGL2Data.vertexShader.MAX_VERTEX_UNIFORM_BLOCKS,
j: webGL2Data.vertexShader.MAX_VE