@arsedizioni/ars-utils 20.4.7 → 20.4.9

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/core/index.d.ts CHANGED
@@ -409,32 +409,64 @@ declare class SystemUtils {
409
409
  */
410
410
  static isColorLight(color: string, minimumLuminance?: number): boolean;
411
411
  /**
412
- * Get a unique machine id
413
- * @returns : the machine id
414
- */
412
+ * Generates a unique Machine ID based on browser fingerprinting
413
+ * Purely deterministic - same device = same ID
414
+ * GUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
415
+ */
415
416
  static getMachineId(): string;
416
417
  /**
417
- * Format as guid
418
- */
418
+ * Generates a salt based on additional system characteristics
419
+ * to differentiate identical PCs
420
+ */
421
+ private static generateSalt;
422
+ /**
423
+ * Gets or creates a persistent salt in cookies
424
+ */
425
+ private static getCookieSalt;
426
+ /**
427
+ * Formats a hexadecimal hash as GUID
428
+ */
419
429
  private static formatAsGuid;
420
430
  /**
421
- * Browser fingerprint
431
+ * Collects browser fingerprint
432
+ * Focus on hardware characteristics + elements that vary between PCs
422
433
  */
423
434
  private static collectFingerprint;
424
435
  /**
425
- * Platform
436
+ * Normalizes platform to be consistent across browsers
426
437
  */
427
438
  private static normalizePlatform;
439
+ /**
440
+ * Lightweight canvas hash - uses only numerical hash of rendering
441
+ * More stable between browsers but varies by GPU/drivers
442
+ */
443
+ private static getCanvasHash;
428
444
  /**
429
445
  * WebGL fingerprinting
430
446
  */
431
447
  private static getWebGLFingerprint;
432
448
  /**
433
- * Get all fonts
449
+ * Additional WebGL parameters for greater entropy
450
+ */
451
+ private static getWebGLParameters;
452
+ /**
453
+ * Entropy from performance timing (varies by system load)
454
+ */
455
+ private static getPerformanceEntropy;
456
+ /**
457
+ * Audio context fingerprinting - REMOVED
458
+ * Audio can give different results between browsers
459
+ */
460
+ /**
461
+ * Detects available fonts
434
462
  */
435
463
  private static detectFonts;
436
464
  /**
437
- * Generate hash SHA-256-like
465
+ * Plugins - REMOVED
466
+ * Plugin list deprecated and different between browsers
467
+ */
468
+ /**
469
+ * Generates 128-bit hash (32 hex characters) from string
438
470
  */
439
471
  private static generateHash;
440
472
  }
@@ -1071,55 +1071,206 @@ class SystemUtils {
1071
1071
  return luminance > minimumLuminance;
1072
1072
  }
1073
1073
  /**
1074
- * Get a unique machine id
1075
- * @returns : the machine id
1076
- */
1074
+ * Generates a unique Machine ID based on browser fingerprinting
1075
+ * Purely deterministic - same device = same ID
1076
+ * GUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
1077
+ */
1077
1078
  static getMachineId() {
1078
1079
  const fingerprint = this.collectFingerprint();
1079
- const hash = this.generateHash(JSON.stringify(fingerprint));
1080
+ const salt = this.generateSalt();
1081
+ const combined = JSON.stringify(fingerprint) + salt;
1082
+ const hash = this.generateHash(combined);
1080
1083
  return this.formatAsGuid(hash);
1081
1084
  }
1082
1085
  /**
1083
- * Format as guid
1084
- */
1086
+ * Generates a salt based on additional system characteristics
1087
+ * to differentiate identical PCs
1088
+ */
1089
+ static generateSalt() {
1090
+ const saltComponents = [];
1091
+ // 1. Battery API (if available - varies by battery state)
1092
+ try {
1093
+ const nav = navigator;
1094
+ if (nav.getBattery) {
1095
+ // Note: it's async but we only use API presence
1096
+ saltComponents.push('battery-available');
1097
+ }
1098
+ }
1099
+ catch (e) { }
1100
+ // 2. Connection API (connection type, varies by network)
1101
+ try {
1102
+ const conn = navigator.connection ||
1103
+ navigator.mozConnection ||
1104
+ navigator.webkitConnection;
1105
+ if (conn) {
1106
+ saltComponents.push(conn.effectiveType || '');
1107
+ saltComponents.push(conn.downlink?.toString() || '');
1108
+ saltComponents.push(conn.rtt?.toString() || '');
1109
+ }
1110
+ }
1111
+ catch (e) { }
1112
+ // 3. Media Devices (number and type of I/O devices)
1113
+ try {
1114
+ if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
1115
+ // Placeholder - will be populated async, but keep synchronicity
1116
+ saltComponents.push('media-devices-available');
1117
+ }
1118
+ }
1119
+ catch (e) { }
1120
+ // 4. Permissions (permissions state varies by user/installation)
1121
+ try {
1122
+ if (navigator.permissions) {
1123
+ saltComponents.push('permissions-available');
1124
+ }
1125
+ }
1126
+ catch (e) { }
1127
+ // 5. First access timestamp (stored in cookie/sessionStorage compatible)
1128
+ try {
1129
+ // Use a cookie to persist salt across sessions
1130
+ const cookieSalt = this.getCookieSalt();
1131
+ if (cookieSalt) {
1132
+ saltComponents.push(cookieSalt);
1133
+ }
1134
+ }
1135
+ catch (e) { }
1136
+ // 6. Entropy from Math.random() seeded with performance.now()
1137
+ try {
1138
+ const seed = performance.now() % 1000;
1139
+ const randomFactor = Math.sin(seed) * 10000;
1140
+ saltComponents.push(Math.floor(randomFactor).toString(36));
1141
+ }
1142
+ catch (e) { }
1143
+ // 7. Specific DOM characteristics
1144
+ try {
1145
+ const docElement = document.documentElement;
1146
+ saltComponents.push(docElement.clientHeight.toString());
1147
+ saltComponents.push(docElement.clientWidth.toString());
1148
+ saltComponents.push(window.innerHeight.toString());
1149
+ saltComponents.push(window.innerWidth.toString());
1150
+ saltComponents.push(window.outerHeight.toString());
1151
+ saltComponents.push(window.outerWidth.toString());
1152
+ }
1153
+ catch (e) { }
1154
+ // 8. Additional navigator properties
1155
+ try {
1156
+ const nav = navigator;
1157
+ saltComponents.push(nav.vendor || '');
1158
+ saltComponents.push(nav.productSub || '');
1159
+ saltComponents.push(nav.oscpu || '');
1160
+ }
1161
+ catch (e) { }
1162
+ return saltComponents.filter(s => s).join('|');
1163
+ }
1164
+ /**
1165
+ * Gets or creates a persistent salt in cookies
1166
+ */
1167
+ static getCookieSalt() {
1168
+ const cookieName = '_machine_salt';
1169
+ // Read existing cookie
1170
+ const cookies = document.cookie.split(';');
1171
+ for (const cookie of cookies) {
1172
+ const [name, value] = cookie.trim().split('=');
1173
+ if (name === cookieName) {
1174
+ return value;
1175
+ }
1176
+ }
1177
+ // Create new persistent salt
1178
+ const newSalt = Math.random().toString(36).substring(2, 15) +
1179
+ Date.now().toString(36);
1180
+ // Save cookie with 10 year expiration
1181
+ const expires = new Date();
1182
+ expires.setFullYear(expires.getFullYear() + 10);
1183
+ document.cookie = `${cookieName}=${newSalt}; expires=${expires.toUTCString()}; path=/; SameSite=Strict`;
1184
+ return newSalt;
1185
+ }
1186
+ /**
1187
+ * Formats a hexadecimal hash as GUID
1188
+ */
1085
1189
  static formatAsGuid(hash) {
1190
+ // Ensure hash is at least 32 characters long
1086
1191
  const paddedHash = hash.padEnd(32, '0');
1192
+ // GUID format: 8-4-4-4-12 characters
1087
1193
  return `${paddedHash.substring(0, 8)}-${paddedHash.substring(8, 12)}-${paddedHash.substring(12, 16)}-${paddedHash.substring(16, 20)}-${paddedHash.substring(20, 32)}`;
1088
1194
  }
1089
1195
  /**
1090
- * Browser fingerprint
1196
+ * Collects browser fingerprint
1197
+ * Focus on hardware characteristics + elements that vary between PCs
1091
1198
  */
1092
1199
  static collectFingerprint() {
1093
1200
  const nav = navigator;
1094
1201
  return {
1095
- // Only hardware specs
1202
+ // HARDWARE characteristics
1096
1203
  hardwareConcurrency: nav.hardwareConcurrency || 0,
1097
1204
  deviceMemory: nav.deviceMemory || 0,
1098
1205
  maxTouchPoints: nav.maxTouchPoints || 0,
1206
+ // Screen
1099
1207
  screenResolution: `${screen.width}x${screen.height}`,
1100
1208
  colorDepth: screen.colorDepth,
1101
1209
  pixelDepth: screen.pixelDepth,
1102
1210
  availScreen: `${screen.availWidth}x${screen.availHeight}`,
1211
+ // Timezone (can vary between PCs)
1103
1212
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
1104
1213
  timezoneOffset: new Date().getTimezoneOffset(),
1214
+ // WebGL (GPU - identifies specific graphics hardware)
1105
1215
  webgl: this.getWebGLFingerprint(),
1216
+ webglParams: this.getWebGLParameters(),
1217
+ // Normalized platform
1106
1218
  platform: this.normalizePlatform(nav.platform || ''),
1107
- fonts: this.detectFonts()
1219
+ // Fonts (installed on system - often vary between PCs)
1220
+ fonts: this.detectFonts(),
1221
+ // Lightweight canvas fingerprint (varies by GPU/specific drivers)
1222
+ canvasHash: this.getCanvasHash(),
1223
+ // Additional entropy from performance
1224
+ performanceEntropy: this.getPerformanceEntropy()
1108
1225
  };
1109
1226
  }
1110
1227
  /**
1111
- * Platform
1228
+ * Normalizes platform to be consistent across browsers
1112
1229
  */
1113
1230
  static normalizePlatform(platform) {
1114
1231
  platform = platform.toLowerCase();
1232
+ // Normalize Windows variants
1115
1233
  if (platform.includes('win'))
1116
1234
  return 'windows';
1235
+ // Normalize Mac variants
1117
1236
  if (platform.includes('mac'))
1118
1237
  return 'macos';
1238
+ // Normalize Linux variants
1119
1239
  if (platform.includes('linux'))
1120
1240
  return 'linux';
1121
1241
  return platform;
1122
1242
  }
1243
+ /**
1244
+ * Lightweight canvas hash - uses only numerical hash of rendering
1245
+ * More stable between browsers but varies by GPU/drivers
1246
+ */
1247
+ static getCanvasHash() {
1248
+ try {
1249
+ const canvas = document.createElement('canvas');
1250
+ canvas.width = 200;
1251
+ canvas.height = 50;
1252
+ const ctx = canvas.getContext('2d');
1253
+ if (!ctx)
1254
+ return '';
1255
+ ctx.textBaseline = 'alphabetic';
1256
+ ctx.fillStyle = '#f60';
1257
+ ctx.fillRect(125, 1, 62, 20);
1258
+ ctx.fillStyle = '#069';
1259
+ ctx.font = '11pt Arial';
1260
+ ctx.fillText('FP', 2, 15);
1261
+ // Get only a numerical hash instead of complete dataURL
1262
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
1263
+ let hash = 0;
1264
+ for (let i = 0; i < imageData.data.length; i += 4) {
1265
+ hash = ((hash << 5) - hash) + imageData.data[i];
1266
+ hash = hash & hash;
1267
+ }
1268
+ return hash.toString(16);
1269
+ }
1270
+ catch (e) {
1271
+ return '';
1272
+ }
1273
+ }
1123
1274
  /**
1124
1275
  * WebGL fingerprinting
1125
1276
  */
@@ -1142,7 +1293,58 @@ class SystemUtils {
1142
1293
  }
1143
1294
  }
1144
1295
  /**
1145
- * Get all fonts
1296
+ * Additional WebGL parameters for greater entropy
1297
+ */
1298
+ static getWebGLParameters() {
1299
+ try {
1300
+ const canvas = document.createElement('canvas');
1301
+ const gl = canvas.getContext('webgl') ||
1302
+ canvas.getContext('experimental-webgl');
1303
+ if (!gl)
1304
+ return '';
1305
+ const params = [
1306
+ gl.getParameter(gl.MAX_TEXTURE_SIZE),
1307
+ gl.getParameter(gl.MAX_VIEWPORT_DIMS),
1308
+ gl.getParameter(gl.MAX_VERTEX_ATTRIBS),
1309
+ gl.getParameter(gl.MAX_VARYING_VECTORS),
1310
+ gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS),
1311
+ gl.getParameter(gl.SHADING_LANGUAGE_VERSION),
1312
+ gl.getSupportedExtensions()?.length || 0
1313
+ ];
1314
+ return params.join('|');
1315
+ }
1316
+ catch (e) {
1317
+ return '';
1318
+ }
1319
+ }
1320
+ /**
1321
+ * Entropy from performance timing (varies by system load)
1322
+ */
1323
+ static getPerformanceEntropy() {
1324
+ try {
1325
+ // Use performance.now() variation as entropy
1326
+ const now = performance.now();
1327
+ const seed = Math.floor(now * 1000) % 10000;
1328
+ // Get navigation timing if available
1329
+ let timingEntropy = '';
1330
+ if (performance.timing) {
1331
+ const timing = performance.timing;
1332
+ const loadTime = timing.loadEventEnd - timing.navigationStart;
1333
+ const domReady = timing.domContentLoadedEventEnd - timing.navigationStart;
1334
+ timingEntropy = `${Math.floor(loadTime / 100)}-${Math.floor(domReady / 100)}`;
1335
+ }
1336
+ return `${seed}-${timingEntropy}`;
1337
+ }
1338
+ catch (e) {
1339
+ return '';
1340
+ }
1341
+ }
1342
+ /**
1343
+ * Audio context fingerprinting - REMOVED
1344
+ * Audio can give different results between browsers
1345
+ */
1346
+ /**
1347
+ * Detects available fonts
1146
1348
  */
1147
1349
  static detectFonts() {
1148
1350
  const baseFonts = ['monospace', 'sans-serif', 'serif'];
@@ -1179,9 +1381,14 @@ class SystemUtils {
1179
1381
  return detectedFonts.join(',');
1180
1382
  }
1181
1383
  /**
1182
- * Generate hash SHA-256-like
1384
+ * Plugins - REMOVED
1385
+ * Plugin list deprecated and different between browsers
1386
+ */
1387
+ /**
1388
+ * Generates 128-bit hash (32 hex characters) from string
1183
1389
  */
1184
1390
  static generateHash(str) {
1391
+ // Generate 4 32-bit hashes to get 128-bit total (like a GUID)
1185
1392
  let h1 = 0xdeadbeef;
1186
1393
  let h2 = 0x41c6ce57;
1187
1394
  let h3 = 0x12345678;
@@ -1197,6 +1404,7 @@ class SystemUtils {
1197
1404
  h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
1198
1405
  h3 = Math.imul(h3 ^ (h3 >>> 16), 2246822507) ^ Math.imul(h4 ^ (h4 >>> 13), 3266489909);
1199
1406
  h4 = Math.imul(h4 ^ (h4 >>> 16), 2246822507) ^ Math.imul(h3 ^ (h3 >>> 13), 3266489909);
1407
+ // Combine the 4 hashes into a single 128-bit hash
1200
1408
  return (h1 >>> 0).toString(16).padStart(8, '0') +
1201
1409
  (h2 >>> 0).toString(16).padStart(8, '0') +
1202
1410
  (h3 >>> 0).toString(16).padStart(8, '0') +