@mack1ch/fingerprint-js 0.1.3 → 0.1.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 CHANGED
@@ -946,6 +946,20 @@ var VOLATILE_DEVICE_KEYS = /* @__PURE__ */ new Set([
946
946
  "screenLeft",
947
947
  "screenTop"
948
948
  ]);
949
+ var HARDWARE_DEVICE_KEYS = /* @__PURE__ */ new Set([
950
+ "hardwareConcurrency",
951
+ "deviceMemory",
952
+ "platform",
953
+ "unmaskedVendor",
954
+ "unmaskedRenderer",
955
+ "width",
956
+ "height",
957
+ "colorDepth",
958
+ "pixelDepth",
959
+ "timeZone",
960
+ "sampleRate",
961
+ "maxTouchPoints"
962
+ ]);
949
963
  function getSignalStability(key) {
950
964
  if (STABLE_DEVICE_KEYS.has(key)) {
951
965
  return "stable";
@@ -1004,6 +1018,16 @@ function toCoreCanonicalPayload(components) {
1004
1018
  return false;
1005
1019
  }).map((component) => `${component.key}=${component.value}`).join("|");
1006
1020
  }
1021
+ function toHardwareCanonicalPayload(components) {
1022
+ return components.filter((component) => {
1023
+ if (component.kind === "required" && component.key === "required.merchantId") return true;
1024
+ if (component.kind === "device") {
1025
+ const key = component.key.replace(/^device\./, "");
1026
+ return HARDWARE_DEVICE_KEYS.has(key);
1027
+ }
1028
+ return false;
1029
+ }).map((component) => `${component.key}=${component.value}`).join("|");
1030
+ }
1007
1031
  function computeConfidenceScore(components, timedOutCollectors, failedCollectorsCount) {
1008
1032
  const weightedTotal = components.reduce((total, component) => total + component.weight, 0);
1009
1033
  if (weightedTotal === 0) {
@@ -1053,10 +1077,12 @@ async function getFingerprint(options) {
1053
1077
  deviceSignals: gathered.deviceSignals
1054
1078
  });
1055
1079
  const corePayload = toCoreCanonicalPayload(components);
1080
+ const hardwarePayload = toHardwareCanonicalPayload(components);
1056
1081
  const fullPayload = toCanonicalPayload(components);
1057
1082
  const digestAlgorithm = ((_a = options.hash) == null ? void 0 : _a.algorithm) ?? "SHA-256";
1058
- const [coreHash, fullHash] = await Promise.all([
1083
+ const [coreHash, hardwareCoreHash, fullHash] = await Promise.all([
1059
1084
  createDigest(corePayload, digestAlgorithm),
1085
+ createDigest(hardwarePayload, digestAlgorithm),
1060
1086
  createDigest(fullPayload, digestAlgorithm)
1061
1087
  ]);
1062
1088
  const completedAtMs = Date.now();
@@ -1064,6 +1090,7 @@ async function getFingerprint(options) {
1064
1090
  const maskedRaw = applyPrivacy(gathered.rawItems, ((_c = options.privacy) == null ? void 0 : _c.maskSensitive) ?? false);
1065
1091
  return {
1066
1092
  coreHash,
1093
+ hardwareCoreHash,
1067
1094
  fullHash,
1068
1095
  hash: fullHash,
1069
1096
  hashVersion: "fp_v1",
@@ -1104,10 +1131,12 @@ async function getFingerprintQuick(options) {
1104
1131
  deviceSignals: gathered.deviceSignals
1105
1132
  });
1106
1133
  const corePayload = toCoreCanonicalPayload(components);
1134
+ const hardwarePayload = toHardwareCanonicalPayload(components);
1107
1135
  const fullPayload = toCanonicalPayload(components);
1108
1136
  const digestAlgorithm = ((_a = options.hash) == null ? void 0 : _a.algorithm) ?? "SHA-256";
1109
- const [coreHash, fullHash] = await Promise.all([
1137
+ const [coreHash, hardwareCoreHash, fullHash] = await Promise.all([
1110
1138
  createDigest(corePayload, digestAlgorithm),
1139
+ createDigest(hardwarePayload, digestAlgorithm),
1111
1140
  createDigest(fullPayload, digestAlgorithm)
1112
1141
  ]);
1113
1142
  const completedAtMs = Date.now();
@@ -1115,6 +1144,7 @@ async function getFingerprintQuick(options) {
1115
1144
  const maskedRaw = applyPrivacy(gathered.rawItems, ((_c = options.privacy) == null ? void 0 : _c.maskSensitive) ?? false);
1116
1145
  return {
1117
1146
  coreHash,
1147
+ hardwareCoreHash,
1118
1148
  fullHash,
1119
1149
  hash: fullHash,
1120
1150
  hashVersion: "fp_v1",
@@ -1200,8 +1230,9 @@ function compareFingerprints(current, reference) {
1200
1230
  if (refComponent.stability === "volatile") changedVolatileKeys.push(refComponent.key);
1201
1231
  }
1202
1232
  const similarityScore = totalReferenceWeight === 0 ? 0 : round3(matchedWeight / totalReferenceWeight);
1233
+ const crossBrowserSimilarityScore = current.hardwareCoreHash === reference.hardwareCoreHash ? Math.max(similarityScore, 0.9) : similarityScore;
1203
1234
  const reasons = [];
1204
- let risk = 1 - similarityScore;
1235
+ let risk = 1 - crossBrowserSimilarityScore;
1205
1236
  if (changedStableKeys.length >= 3) {
1206
1237
  risk += 0.2;
1207
1238
  reasons.push("Multiple stable identifiers changed");
@@ -1244,13 +1275,15 @@ function compareFingerprints(current, reference) {
1244
1275
  const riskScore = round3(risk);
1245
1276
  let verdict = "different_device";
1246
1277
  if (similarityScore >= 0.85 && riskScore < 0.35) verdict = "same_device";
1278
+ else if (crossBrowserSimilarityScore >= 0.82 && riskScore < 0.45) verdict = "likely_same_cross_browser";
1247
1279
  else if (similarityScore >= 0.7 && riskScore < 0.55) verdict = "likely_same";
1248
- else if (similarityScore >= 0.45) verdict = "suspicious";
1280
+ else if (crossBrowserSimilarityScore >= 0.45) verdict = "suspicious";
1249
1281
  if (!reasons.length) {
1250
1282
  reasons.push("No meaningful drift between profiles");
1251
1283
  }
1252
1284
  return {
1253
1285
  similarityScore,
1286
+ crossBrowserSimilarityScore,
1254
1287
  riskScore,
1255
1288
  verdict,
1256
1289
  matchedWeight,
@@ -1398,7 +1431,10 @@ function assessPaymentRisk(input) {
1398
1431
  var _a, _b, _c, _d;
1399
1432
  const reasons = [...input.comparison.reasons];
1400
1433
  let score = input.comparison.riskScore;
1401
- const similarityScore = input.comparison.similarityScore;
1434
+ const effectiveSimilarity = Math.max(
1435
+ input.comparison.similarityScore,
1436
+ input.comparison.crossBrowserSimilarityScore
1437
+ );
1402
1438
  if (input.history) {
1403
1439
  if (input.history.aggregateRiskScore > 0.6) {
1404
1440
  score += 0.1;
@@ -1428,14 +1464,14 @@ function assessPaymentRisk(input) {
1428
1464
  }
1429
1465
  const finalScore = Math.max(0, Math.min(1, Number(score.toFixed(3))));
1430
1466
  let decision = "manual_review";
1431
- if (finalScore < 0.35 && similarityScore >= 0.82) decision = "allow";
1432
- else if (finalScore < 0.55 && similarityScore >= 0.65) decision = "challenge";
1433
- else if (finalScore >= 0.8 || similarityScore < 0.35) decision = "deny";
1467
+ if (finalScore < 0.35 && effectiveSimilarity >= 0.82) decision = "allow";
1468
+ else if (finalScore < 0.55 && effectiveSimilarity >= 0.65) decision = "challenge";
1469
+ else if (finalScore >= 0.8 || effectiveSimilarity < 0.35) decision = "deny";
1434
1470
  const output = {
1435
1471
  decision,
1436
1472
  score: finalScore,
1437
1473
  reasons: Array.from(new Set(reasons)),
1438
- similarityScore,
1474
+ similarityScore: effectiveSimilarity,
1439
1475
  riskScore: input.comparison.riskScore
1440
1476
  };
1441
1477
  fetch("http://127.0.0.1:7417/ingest/67545057-d930-4022-a3c5-818008410ad2", { method: "POST", headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "ce1cd9" }, body: JSON.stringify({ sessionId: "ce1cd9", runId: "payment-risk", hypothesisId: "H4", location: "src/fingerprint/payment.ts:assessPaymentRisk", message: "payment risk assessed", data: { decision: output.decision, score: output.score, similarity: output.similarityScore, risk: output.riskScore, reasonsCount: output.reasons.length }, timestamp: Date.now() }) }).catch(() => {
package/dist/index.d.cts CHANGED
@@ -59,6 +59,7 @@ type FingerprintMeta = {
59
59
  };
60
60
  type FingerprintResult = {
61
61
  coreHash: string;
62
+ hardwareCoreHash: string;
62
63
  fullHash: string;
63
64
  hash: string;
64
65
  hashVersion: 'fp_v1';
@@ -71,8 +72,9 @@ type FingerprintResult = {
71
72
  };
72
73
  type FingerprintComparison = {
73
74
  similarityScore: number;
75
+ crossBrowserSimilarityScore: number;
74
76
  riskScore: number;
75
- verdict: 'same_device' | 'likely_same' | 'suspicious' | 'different_device';
77
+ verdict: 'same_device' | 'likely_same' | 'likely_same_cross_browser' | 'suspicious' | 'different_device';
76
78
  matchedWeight: number;
77
79
  totalReferenceWeight: number;
78
80
  reasons: string[];
package/dist/index.d.ts CHANGED
@@ -59,6 +59,7 @@ type FingerprintMeta = {
59
59
  };
60
60
  type FingerprintResult = {
61
61
  coreHash: string;
62
+ hardwareCoreHash: string;
62
63
  fullHash: string;
63
64
  hash: string;
64
65
  hashVersion: 'fp_v1';
@@ -71,8 +72,9 @@ type FingerprintResult = {
71
72
  };
72
73
  type FingerprintComparison = {
73
74
  similarityScore: number;
75
+ crossBrowserSimilarityScore: number;
74
76
  riskScore: number;
75
- verdict: 'same_device' | 'likely_same' | 'suspicious' | 'different_device';
77
+ verdict: 'same_device' | 'likely_same' | 'likely_same_cross_browser' | 'suspicious' | 'different_device';
76
78
  matchedWeight: number;
77
79
  totalReferenceWeight: number;
78
80
  reasons: string[];
package/dist/index.js CHANGED
@@ -913,6 +913,20 @@ var VOLATILE_DEVICE_KEYS = /* @__PURE__ */ new Set([
913
913
  "screenLeft",
914
914
  "screenTop"
915
915
  ]);
916
+ var HARDWARE_DEVICE_KEYS = /* @__PURE__ */ new Set([
917
+ "hardwareConcurrency",
918
+ "deviceMemory",
919
+ "platform",
920
+ "unmaskedVendor",
921
+ "unmaskedRenderer",
922
+ "width",
923
+ "height",
924
+ "colorDepth",
925
+ "pixelDepth",
926
+ "timeZone",
927
+ "sampleRate",
928
+ "maxTouchPoints"
929
+ ]);
916
930
  function getSignalStability(key) {
917
931
  if (STABLE_DEVICE_KEYS.has(key)) {
918
932
  return "stable";
@@ -971,6 +985,16 @@ function toCoreCanonicalPayload(components) {
971
985
  return false;
972
986
  }).map((component) => `${component.key}=${component.value}`).join("|");
973
987
  }
988
+ function toHardwareCanonicalPayload(components) {
989
+ return components.filter((component) => {
990
+ if (component.kind === "required" && component.key === "required.merchantId") return true;
991
+ if (component.kind === "device") {
992
+ const key = component.key.replace(/^device\./, "");
993
+ return HARDWARE_DEVICE_KEYS.has(key);
994
+ }
995
+ return false;
996
+ }).map((component) => `${component.key}=${component.value}`).join("|");
997
+ }
974
998
  function computeConfidenceScore(components, timedOutCollectors, failedCollectorsCount) {
975
999
  const weightedTotal = components.reduce((total, component) => total + component.weight, 0);
976
1000
  if (weightedTotal === 0) {
@@ -1020,10 +1044,12 @@ async function getFingerprint(options) {
1020
1044
  deviceSignals: gathered.deviceSignals
1021
1045
  });
1022
1046
  const corePayload = toCoreCanonicalPayload(components);
1047
+ const hardwarePayload = toHardwareCanonicalPayload(components);
1023
1048
  const fullPayload = toCanonicalPayload(components);
1024
1049
  const digestAlgorithm = ((_a = options.hash) == null ? void 0 : _a.algorithm) ?? "SHA-256";
1025
- const [coreHash, fullHash] = await Promise.all([
1050
+ const [coreHash, hardwareCoreHash, fullHash] = await Promise.all([
1026
1051
  createDigest(corePayload, digestAlgorithm),
1052
+ createDigest(hardwarePayload, digestAlgorithm),
1027
1053
  createDigest(fullPayload, digestAlgorithm)
1028
1054
  ]);
1029
1055
  const completedAtMs = Date.now();
@@ -1031,6 +1057,7 @@ async function getFingerprint(options) {
1031
1057
  const maskedRaw = applyPrivacy(gathered.rawItems, ((_c = options.privacy) == null ? void 0 : _c.maskSensitive) ?? false);
1032
1058
  return {
1033
1059
  coreHash,
1060
+ hardwareCoreHash,
1034
1061
  fullHash,
1035
1062
  hash: fullHash,
1036
1063
  hashVersion: "fp_v1",
@@ -1071,10 +1098,12 @@ async function getFingerprintQuick(options) {
1071
1098
  deviceSignals: gathered.deviceSignals
1072
1099
  });
1073
1100
  const corePayload = toCoreCanonicalPayload(components);
1101
+ const hardwarePayload = toHardwareCanonicalPayload(components);
1074
1102
  const fullPayload = toCanonicalPayload(components);
1075
1103
  const digestAlgorithm = ((_a = options.hash) == null ? void 0 : _a.algorithm) ?? "SHA-256";
1076
- const [coreHash, fullHash] = await Promise.all([
1104
+ const [coreHash, hardwareCoreHash, fullHash] = await Promise.all([
1077
1105
  createDigest(corePayload, digestAlgorithm),
1106
+ createDigest(hardwarePayload, digestAlgorithm),
1078
1107
  createDigest(fullPayload, digestAlgorithm)
1079
1108
  ]);
1080
1109
  const completedAtMs = Date.now();
@@ -1082,6 +1111,7 @@ async function getFingerprintQuick(options) {
1082
1111
  const maskedRaw = applyPrivacy(gathered.rawItems, ((_c = options.privacy) == null ? void 0 : _c.maskSensitive) ?? false);
1083
1112
  return {
1084
1113
  coreHash,
1114
+ hardwareCoreHash,
1085
1115
  fullHash,
1086
1116
  hash: fullHash,
1087
1117
  hashVersion: "fp_v1",
@@ -1167,8 +1197,9 @@ function compareFingerprints(current, reference) {
1167
1197
  if (refComponent.stability === "volatile") changedVolatileKeys.push(refComponent.key);
1168
1198
  }
1169
1199
  const similarityScore = totalReferenceWeight === 0 ? 0 : round3(matchedWeight / totalReferenceWeight);
1200
+ const crossBrowserSimilarityScore = current.hardwareCoreHash === reference.hardwareCoreHash ? Math.max(similarityScore, 0.9) : similarityScore;
1170
1201
  const reasons = [];
1171
- let risk = 1 - similarityScore;
1202
+ let risk = 1 - crossBrowserSimilarityScore;
1172
1203
  if (changedStableKeys.length >= 3) {
1173
1204
  risk += 0.2;
1174
1205
  reasons.push("Multiple stable identifiers changed");
@@ -1211,13 +1242,15 @@ function compareFingerprints(current, reference) {
1211
1242
  const riskScore = round3(risk);
1212
1243
  let verdict = "different_device";
1213
1244
  if (similarityScore >= 0.85 && riskScore < 0.35) verdict = "same_device";
1245
+ else if (crossBrowserSimilarityScore >= 0.82 && riskScore < 0.45) verdict = "likely_same_cross_browser";
1214
1246
  else if (similarityScore >= 0.7 && riskScore < 0.55) verdict = "likely_same";
1215
- else if (similarityScore >= 0.45) verdict = "suspicious";
1247
+ else if (crossBrowserSimilarityScore >= 0.45) verdict = "suspicious";
1216
1248
  if (!reasons.length) {
1217
1249
  reasons.push("No meaningful drift between profiles");
1218
1250
  }
1219
1251
  return {
1220
1252
  similarityScore,
1253
+ crossBrowserSimilarityScore,
1221
1254
  riskScore,
1222
1255
  verdict,
1223
1256
  matchedWeight,
@@ -1365,7 +1398,10 @@ function assessPaymentRisk(input) {
1365
1398
  var _a, _b, _c, _d;
1366
1399
  const reasons = [...input.comparison.reasons];
1367
1400
  let score = input.comparison.riskScore;
1368
- const similarityScore = input.comparison.similarityScore;
1401
+ const effectiveSimilarity = Math.max(
1402
+ input.comparison.similarityScore,
1403
+ input.comparison.crossBrowserSimilarityScore
1404
+ );
1369
1405
  if (input.history) {
1370
1406
  if (input.history.aggregateRiskScore > 0.6) {
1371
1407
  score += 0.1;
@@ -1395,14 +1431,14 @@ function assessPaymentRisk(input) {
1395
1431
  }
1396
1432
  const finalScore = Math.max(0, Math.min(1, Number(score.toFixed(3))));
1397
1433
  let decision = "manual_review";
1398
- if (finalScore < 0.35 && similarityScore >= 0.82) decision = "allow";
1399
- else if (finalScore < 0.55 && similarityScore >= 0.65) decision = "challenge";
1400
- else if (finalScore >= 0.8 || similarityScore < 0.35) decision = "deny";
1434
+ if (finalScore < 0.35 && effectiveSimilarity >= 0.82) decision = "allow";
1435
+ else if (finalScore < 0.55 && effectiveSimilarity >= 0.65) decision = "challenge";
1436
+ else if (finalScore >= 0.8 || effectiveSimilarity < 0.35) decision = "deny";
1401
1437
  const output = {
1402
1438
  decision,
1403
1439
  score: finalScore,
1404
1440
  reasons: Array.from(new Set(reasons)),
1405
- similarityScore,
1441
+ similarityScore: effectiveSimilarity,
1406
1442
  riskScore: input.comparison.riskScore
1407
1443
  };
1408
1444
  fetch("http://127.0.0.1:7417/ingest/67545057-d930-4022-a3c5-818008410ad2", { method: "POST", headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "ce1cd9" }, body: JSON.stringify({ sessionId: "ce1cd9", runId: "payment-risk", hypothesisId: "H4", location: "src/fingerprint/payment.ts:assessPaymentRisk", message: "payment risk assessed", data: { decision: output.decision, score: output.score, similarity: output.similarityScore, risk: output.riskScore, reasonsCount: output.reasons.length }, timestamp: Date.now() }) }).catch(() => {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  "name": "@mack1ch/fingerprint-js",
4
4
 
5
- "version": "0.1.3",
5
+ "version": "0.1.4",
6
6
 
7
7
  "private": false,
8
8