Skip to content

Commit e59050e

Browse files
committed
refactor: enhance analysis report structure and content
- Updated `createAnalysisReport` to include compact confidence, totals, aggregate weights, and per-component weight map. - Introduced `summarizeConfidence`, `summarizeTotals`, and `summarizeResults` functions for better data handling. - Modified risk summary to include compacted risk details. - Adjusted report outputs in examples and documentation to reflect new report structure. - Improved tests to validate new report features and ensure consistency across outputs.
1 parent 6e6056d commit e59050e

11 files changed

Lines changed: 350 additions & 133 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,11 @@ Collection confidence and identity confidence are both exposed. `confidence.scor
204204
The browser demo in [examples/browser.html](examples/browser.html) renders three reports side by side and tracks repeated runs:
205205
206206
- Compact report: concise identity, risk, quality, calculations, and every capability with status, role, hashability, weight, duration, value summary, and error state.
207-
- ID analysis report: shortest dense format for backend scoring. It contains `id`, request metadata, confidence, aggregate weights, hash checks, risk verdicts, and every component's role, status, weight, raw result, and error.
207+
- ID analysis report: shortest dense format for backend scoring. It is rendered as readable JSON and contains `id`, request metadata, compact confidence, totals, aggregate weights, `weights.byComponent`, hash checks, risk verdicts, identity/report-only lists, and short `results` summaries keyed by component ID.
208208
- Full report: raw SDK result, recalculated hash, all-signals hash, derived calculations, stability data, explainable report, ID analysis report, and every component value/error.
209209
- Stability view: baseline visitor ID, current visitor ID, identity input count, report-only count, changed identity/report-only components, and recent run history.
210210
211-
All demo outputs are generated from the same `IdentifyResult`. The compact and ID analysis formats include every collected capability; the full format additionally embeds the raw result and explainable report. Use the `extended` profile in the demo to exercise the full collector pack and confirm that report-only changes do not move the stable visitor ID.
211+
All demo outputs are generated from the same `IdentifyResult` and are ordered by verbosity: ID analysis, compact report, then full report. The compact format includes every collected capability with readable summaries. The ID analysis format keeps every component represented through weight and result maps without dumping raw component objects. The full format additionally embeds the raw result and explainable report. Use the `extended` profile in the demo to exercise the full collector pack and confirm that report-only changes do not move the stable visitor ID.
212212
213213
The debug inspector in [examples/inspector.html](examples/inspector.html) accepts an `IdentifyResult`, full demo report JSON, or ID analysis report JSON and explains identity components, report-only components, tamper, bot, and private-mode evidence.
214214

dist/browser/fingerprintjs-botblocker.js

Lines changed: 67 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3261,24 +3261,24 @@ var FingerprintJSBotBlocker = (() => {
32613261
function createAnalysisReport(result, options = {}) {
32623262
assertResult2(result);
32633263
const identityComponents = new Set(result.meta && Array.isArray(result.meta.identityComponents) ? result.meta.identityComponents : []);
3264-
const components = Object.freeze(result.components.map((component) => analysisComponent(component, identityComponents)));
32653264
const risk = buildRiskSummary(result.components);
32663265
return Object.freeze({
32673266
id: result.visitorId || null,
32683267
requestId: result.requestId || null,
32693268
namespace: result.namespace || "default",
32703269
profile: result.meta && result.meta.profile ? result.meta.profile : null,
3271-
confidence: result.confidence || null,
3270+
confidence: summarizeConfidence(result.confidence || null),
3271+
totals: summarizeTotals(result.components, identityComponents),
32723272
weights: summarizeWeights(result.components, result.confidence || {}, identityComponents),
3273-
totals: Object.freeze({
3274-
total: components.length,
3275-
ok: countStatus(result.components, "ok"),
3276-
identity: components.filter((component) => component.role === "identity").length,
3277-
reportOnly: components.filter((component) => component.role === "report-only").length
3278-
}),
32793273
hash: buildHashSummary(result, options),
3280-
risk,
3281-
components
3274+
risk: Object.freeze({
3275+
bot: compactRisk(risk.bot),
3276+
privateMode: compactRisk(risk.privateMode),
3277+
tamper: compactRisk(risk.tamper)
3278+
}),
3279+
identityComponents: Object.freeze(Array.from(identityComponents)),
3280+
reportOnlyComponents: Object.freeze(result.meta && Array.isArray(result.meta.reportOnlyComponents) ? result.meta.reportOnlyComponents.slice() : []),
3281+
results: summarizeResults(result.components)
32823282
});
32833283
}
32843284
function explainComponent(component, identityComponents = [], options = {}) {
@@ -3313,20 +3313,24 @@ var FingerprintJSBotBlocker = (() => {
33133313
}
33143314
return component.hashable === false ? "report_only_collector" : "excluded_by_identity_policy";
33153315
}
3316-
function analysisComponent(component, identityComponents) {
3316+
function summarizeConfidence(confidence) {
3317+
if (!confidence) {
3318+
return null;
3319+
}
33173320
return Object.freeze({
3318-
id: component.id,
3319-
role: identityComponents.has(component.id) ? "identity" : "report-only",
3320-
status: component.status,
3321-
weight: component.weight,
3322-
category: component.category,
3323-
sensitivity: component.sensitivity,
3324-
mode: component.mode,
3325-
stability: component.stability,
3326-
hashable: component.hashable,
3327-
durationMs: component.durationMs,
3328-
result: component.status === "ok" ? component.value : null,
3329-
error: component.error || null
3321+
score: confidence.score,
3322+
level: confidence.level,
3323+
qualityScore: confidence.collectionQuality ? confidence.collectionQuality.score : null,
3324+
qualityLevel: confidence.collectionQuality ? confidence.collectionQuality.level : null
3325+
});
3326+
}
3327+
function summarizeTotals(components, identityComponents) {
3328+
return Object.freeze({
3329+
total: components.length,
3330+
ok: countStatus(components, "ok"),
3331+
identity: components.filter((component) => identityComponents.has(component.id)).length,
3332+
reportOnly: components.filter((component) => !identityComponents.has(component.id)).length,
3333+
failed: components.filter((component) => component.status === "error" || component.status === "timeout").length
33303334
});
33313335
}
33323336
function summarizeWeights(components, confidence, identityComponents) {
@@ -3341,20 +3345,57 @@ var FingerprintJSBotBlocker = (() => {
33413345
collected: Number.isFinite(confidence.collectedWeight) ? confidence.collectedWeight : null,
33423346
possible: Number.isFinite(confidence.possibleWeight) ? confidence.possibleWeight : null,
33433347
qualityCollected: confidence.collectionQuality && Number.isFinite(confidence.collectionQuality.collectedWeight) ? confidence.collectionQuality.collectedWeight : null,
3344-
qualityPossible: confidence.collectionQuality && Number.isFinite(confidence.collectionQuality.possibleWeight) ? confidence.collectionQuality.possibleWeight : null
3348+
qualityPossible: confidence.collectionQuality && Number.isFinite(confidence.collectionQuality.possibleWeight) ? confidence.collectionQuality.possibleWeight : null,
3349+
byComponent: freezeRecord(Object.fromEntries(components.map((component) => [component.id, Number.isFinite(component.weight) ? component.weight : 0])))
33453350
});
33463351
}
33473352
function buildHashSummary(result, options) {
33483353
const recalculated = options.recalculatedHash || null;
33493354
const allSignals = options.allSignalsHash || null;
33503355
return Object.freeze({
33513356
algorithm: result.meta && result.meta.hashAlgorithm ? result.meta.hashAlgorithm : null,
3352-
recalculatedVisitorId: recalculated ? recalculated.visitorId : null,
33533357
recalculatedMatches: recalculated ? recalculated.visitorId === result.visitorId : null,
3354-
allSignalsVisitorId: allSignals ? allSignals.visitorId : null,
33553358
allSignalsDiffers: allSignals ? allSignals.visitorId !== result.visitorId : null
33563359
});
33573360
}
3361+
function compactRisk(value) {
3362+
return Object.freeze({
3363+
verdict: value.verdict,
3364+
score: value.score,
3365+
confidence: value.confidence,
3366+
evidence: value.evidence.length
3367+
});
3368+
}
3369+
function summarizeResults(components) {
3370+
return freezeRecord(Object.fromEntries(components.map((component) => [component.id, summarizeAnalysisValue(component)])));
3371+
}
3372+
function summarizeAnalysisValue(component) {
3373+
if (component.status !== "ok") {
3374+
return component.error && component.error.code ? `${component.status}:${component.error.code}` : component.status;
3375+
}
3376+
const value = component.value;
3377+
if (value && typeof value === "object" && !Array.isArray(value) && value.verdict) {
3378+
return compactRisk({
3379+
verdict: value.verdict,
3380+
score: Number.isFinite(value.score) ? value.score : null,
3381+
confidence: value.confidence || "none",
3382+
evidence: Array.isArray(value.evidence) ? value.evidence : []
3383+
});
3384+
}
3385+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
3386+
return value;
3387+
}
3388+
if (Array.isArray(value)) {
3389+
return `array:${value.length}`;
3390+
}
3391+
if (typeof value === "object") {
3392+
return `object:${Object.keys(value).length}`;
3393+
}
3394+
return typeof value;
3395+
}
3396+
function freezeRecord(record) {
3397+
return Object.freeze(record);
3398+
}
33583399
function sumWeights(components) {
33593400
return components.reduce((total, component) => total + (Number.isFinite(component.weight) ? Number(component.weight) : 0), 0);
33603401
}

dist/browser/fingerprintjs-botblocker.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.cjs

Lines changed: 67 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3255,24 +3255,24 @@ function createExplainableReport(result, options = {}) {
32553255
function createAnalysisReport(result, options = {}) {
32563256
assertResult2(result);
32573257
const identityComponents = new Set(result.meta && Array.isArray(result.meta.identityComponents) ? result.meta.identityComponents : []);
3258-
const components = Object.freeze(result.components.map((component) => analysisComponent(component, identityComponents)));
32593258
const risk = buildRiskSummary(result.components);
32603259
return Object.freeze({
32613260
id: result.visitorId || null,
32623261
requestId: result.requestId || null,
32633262
namespace: result.namespace || "default",
32643263
profile: result.meta && result.meta.profile ? result.meta.profile : null,
3265-
confidence: result.confidence || null,
3264+
confidence: summarizeConfidence(result.confidence || null),
3265+
totals: summarizeTotals(result.components, identityComponents),
32663266
weights: summarizeWeights(result.components, result.confidence || {}, identityComponents),
3267-
totals: Object.freeze({
3268-
total: components.length,
3269-
ok: countStatus(result.components, "ok"),
3270-
identity: components.filter((component) => component.role === "identity").length,
3271-
reportOnly: components.filter((component) => component.role === "report-only").length
3272-
}),
32733267
hash: buildHashSummary(result, options),
3274-
risk,
3275-
components
3268+
risk: Object.freeze({
3269+
bot: compactRisk(risk.bot),
3270+
privateMode: compactRisk(risk.privateMode),
3271+
tamper: compactRisk(risk.tamper)
3272+
}),
3273+
identityComponents: Object.freeze(Array.from(identityComponents)),
3274+
reportOnlyComponents: Object.freeze(result.meta && Array.isArray(result.meta.reportOnlyComponents) ? result.meta.reportOnlyComponents.slice() : []),
3275+
results: summarizeResults(result.components)
32763276
});
32773277
}
32783278
function explainComponent(component, identityComponents = [], options = {}) {
@@ -3307,20 +3307,24 @@ function explainReason(component, role) {
33073307
}
33083308
return component.hashable === false ? "report_only_collector" : "excluded_by_identity_policy";
33093309
}
3310-
function analysisComponent(component, identityComponents) {
3310+
function summarizeConfidence(confidence) {
3311+
if (!confidence) {
3312+
return null;
3313+
}
33113314
return Object.freeze({
3312-
id: component.id,
3313-
role: identityComponents.has(component.id) ? "identity" : "report-only",
3314-
status: component.status,
3315-
weight: component.weight,
3316-
category: component.category,
3317-
sensitivity: component.sensitivity,
3318-
mode: component.mode,
3319-
stability: component.stability,
3320-
hashable: component.hashable,
3321-
durationMs: component.durationMs,
3322-
result: component.status === "ok" ? component.value : null,
3323-
error: component.error || null
3315+
score: confidence.score,
3316+
level: confidence.level,
3317+
qualityScore: confidence.collectionQuality ? confidence.collectionQuality.score : null,
3318+
qualityLevel: confidence.collectionQuality ? confidence.collectionQuality.level : null
3319+
});
3320+
}
3321+
function summarizeTotals(components, identityComponents) {
3322+
return Object.freeze({
3323+
total: components.length,
3324+
ok: countStatus(components, "ok"),
3325+
identity: components.filter((component) => identityComponents.has(component.id)).length,
3326+
reportOnly: components.filter((component) => !identityComponents.has(component.id)).length,
3327+
failed: components.filter((component) => component.status === "error" || component.status === "timeout").length
33243328
});
33253329
}
33263330
function summarizeWeights(components, confidence, identityComponents) {
@@ -3335,20 +3339,57 @@ function summarizeWeights(components, confidence, identityComponents) {
33353339
collected: Number.isFinite(confidence.collectedWeight) ? confidence.collectedWeight : null,
33363340
possible: Number.isFinite(confidence.possibleWeight) ? confidence.possibleWeight : null,
33373341
qualityCollected: confidence.collectionQuality && Number.isFinite(confidence.collectionQuality.collectedWeight) ? confidence.collectionQuality.collectedWeight : null,
3338-
qualityPossible: confidence.collectionQuality && Number.isFinite(confidence.collectionQuality.possibleWeight) ? confidence.collectionQuality.possibleWeight : null
3342+
qualityPossible: confidence.collectionQuality && Number.isFinite(confidence.collectionQuality.possibleWeight) ? confidence.collectionQuality.possibleWeight : null,
3343+
byComponent: freezeRecord(Object.fromEntries(components.map((component) => [component.id, Number.isFinite(component.weight) ? component.weight : 0])))
33393344
});
33403345
}
33413346
function buildHashSummary(result, options) {
33423347
const recalculated = options.recalculatedHash || null;
33433348
const allSignals = options.allSignalsHash || null;
33443349
return Object.freeze({
33453350
algorithm: result.meta && result.meta.hashAlgorithm ? result.meta.hashAlgorithm : null,
3346-
recalculatedVisitorId: recalculated ? recalculated.visitorId : null,
33473351
recalculatedMatches: recalculated ? recalculated.visitorId === result.visitorId : null,
3348-
allSignalsVisitorId: allSignals ? allSignals.visitorId : null,
33493352
allSignalsDiffers: allSignals ? allSignals.visitorId !== result.visitorId : null
33503353
});
33513354
}
3355+
function compactRisk(value) {
3356+
return Object.freeze({
3357+
verdict: value.verdict,
3358+
score: value.score,
3359+
confidence: value.confidence,
3360+
evidence: value.evidence.length
3361+
});
3362+
}
3363+
function summarizeResults(components) {
3364+
return freezeRecord(Object.fromEntries(components.map((component) => [component.id, summarizeAnalysisValue(component)])));
3365+
}
3366+
function summarizeAnalysisValue(component) {
3367+
if (component.status !== "ok") {
3368+
return component.error && component.error.code ? `${component.status}:${component.error.code}` : component.status;
3369+
}
3370+
const value = component.value;
3371+
if (value && typeof value === "object" && !Array.isArray(value) && value.verdict) {
3372+
return compactRisk({
3373+
verdict: value.verdict,
3374+
score: Number.isFinite(value.score) ? value.score : null,
3375+
confidence: value.confidence || "none",
3376+
evidence: Array.isArray(value.evidence) ? value.evidence : []
3377+
});
3378+
}
3379+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
3380+
return value;
3381+
}
3382+
if (Array.isArray(value)) {
3383+
return `array:${value.length}`;
3384+
}
3385+
if (typeof value === "object") {
3386+
return `object:${Object.keys(value).length}`;
3387+
}
3388+
return typeof value;
3389+
}
3390+
function freezeRecord(record) {
3391+
return Object.freeze(record);
3392+
}
33523393
function sumWeights(components) {
33533394
return components.reduce((total, component) => total + (Number.isFinite(component.weight) ? Number(component.weight) : 0), 0);
33543395
}

0 commit comments

Comments
 (0)