cyborg-hunter 0.3.0 → 0.4.0
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/README.md +17 -4
- package/dist/cyborg-hunter.esm.js +28 -35
- package/dist/cyborg-hunter.min.js +3 -3
- package/dist/jspsych-cyborg-hunter.js +1 -1
- package/package.json +1 -1
- package/src/cli/analyzers/triage.js +36 -24
- package/src/cli/config.js +2 -2
- package/src/cli/ingest.js +57 -56
- package/src/cli/renderers/html-index.js +988 -263
- package/src/cli/renderers/trajectories.js +146 -104
- package/src/cli/report.js +15 -1
- package/src/core/monitor.js +47 -30
- package/src/core/signals/dom-protection.js +1 -1
- package/src/core/state-machine.js +1 -1
- package/src/jspsych/extension.js +67 -81
package/README.md
CHANGED
|
@@ -28,13 +28,26 @@ Two pieces:
|
|
|
28
28
|
|
|
29
29
|
## Install
|
|
30
30
|
|
|
31
|
-
For analysis (CLI)
|
|
31
|
+
For analysis (CLI):
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
npm install -g cyborg-hunter
|
|
34
|
+
npm install -g cyborg-hunter
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
For the experiment page (library),
|
|
37
|
+
For the experiment page (library), three options:
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<!-- Option 1: load via unpkg (always serves latest published version) -->
|
|
41
|
+
<script src="https://unpkg.com/cyborg-hunter/dist/cyborg-hunter.min.js"></script>
|
|
42
|
+
<script src="https://unpkg.com/cyborg-hunter/dist/jspsych-cyborg-hunter.js"></script>
|
|
43
|
+
|
|
44
|
+
<!-- Option 2: pin a specific version (recommended for production studies) -->
|
|
45
|
+
<script src="https://unpkg.com/cyborg-hunter@0.3.0/dist/cyborg-hunter.min.js"></script>
|
|
46
|
+
<script src="https://unpkg.com/cyborg-hunter@0.3.0/dist/jspsych-cyborg-hunter.js"></script>
|
|
47
|
+
|
|
48
|
+
<!-- Option 3: copy dist/cyborg-hunter.min.js + dist/jspsych-cyborg-hunter.js
|
|
49
|
+
into your project for fully offline / no-CDN-dependency setups. -->
|
|
50
|
+
```
|
|
38
51
|
|
|
39
52
|
## Plug into a jsPsych experiment
|
|
40
53
|
|
|
@@ -122,7 +135,7 @@ At the end of each trial, an "integrity" object is attached to that trial's data
|
|
|
122
135
|
- [`docs/signals-reference.md`](docs/signals-reference.md) — every signal with thresholds per preset
|
|
123
136
|
- [`docs/configuration.md`](docs/configuration.md) — config file fields and CLI flags
|
|
124
137
|
- [`docs/cli-reference.md`](docs/cli-reference.md) — commands and output structure
|
|
125
|
-
- [`docs/
|
|
138
|
+
- [`docs/design-decisions.md`](docs/design-decisions.md) — why the architecture looks the way it does (two-tier scoring, dual lifecycle, `finalize()`, zoom robustness, etc.)
|
|
126
139
|
|
|
127
140
|
## Citation
|
|
128
141
|
|
|
@@ -17,7 +17,7 @@ function createStateMachine() {
|
|
|
17
17
|
state = to;
|
|
18
18
|
return true;
|
|
19
19
|
}
|
|
20
|
-
console.warn("[
|
|
20
|
+
console.warn("[cyborg-hunter] invalid transition: " + state + " \u2192 " + to);
|
|
21
21
|
return false;
|
|
22
22
|
}
|
|
23
23
|
};
|
|
@@ -1084,7 +1084,7 @@ function injectDecoy(opts, expectedTrialId, config, trialData, getTrialData) {
|
|
|
1084
1084
|
};
|
|
1085
1085
|
} else if (config.decoyMap) {
|
|
1086
1086
|
console.warn(
|
|
1087
|
-
'
|
|
1087
|
+
'[cyborg-hunter] no decoyMap entry for trialId "' + expectedTrialId + '". Falling back to Level 3/4.'
|
|
1088
1088
|
);
|
|
1089
1089
|
var fallbackText = buildDecoyText(null, "calibration-metadata", null, _decoyTrialIndex);
|
|
1090
1090
|
insertDecoyElement(fallbackText, config.decoyVisibility);
|
|
@@ -1170,7 +1170,7 @@ function init(userConfig) {
|
|
|
1170
1170
|
var presetName = userConfig.preset || "standard";
|
|
1171
1171
|
var preset = PRESETS[presetName];
|
|
1172
1172
|
if (!preset) {
|
|
1173
|
-
throw new Error("
|
|
1173
|
+
throw new Error("[cyborg-hunter] unknown preset '" + presetName + "'. Use: permissive, standard, strict.");
|
|
1174
1174
|
}
|
|
1175
1175
|
var userScoring = userConfig.scoring || {};
|
|
1176
1176
|
var presetScoring = preset.scoring;
|
|
@@ -1205,8 +1205,7 @@ function init(userConfig) {
|
|
|
1205
1205
|
decoyMap: userConfig.decoyMap || null,
|
|
1206
1206
|
decoyVisibility: userConfig.decoyVisibility || "offscreen",
|
|
1207
1207
|
decoyFraming: userConfig.decoyFraming || "answer-key",
|
|
1208
|
-
decoyExcludeButtons: userConfig.decoyExcludeButtons || [".jspsych-btn"]
|
|
1209
|
-
_debug: userConfig._debug || false
|
|
1208
|
+
decoyExcludeButtons: userConfig.decoyExcludeButtons || [".jspsych-btn"]
|
|
1210
1209
|
};
|
|
1211
1210
|
var KNOWN_KEYS = [
|
|
1212
1211
|
"participantId",
|
|
@@ -1224,12 +1223,11 @@ function init(userConfig) {
|
|
|
1224
1223
|
"decoyExcludeButtons",
|
|
1225
1224
|
"onSignal",
|
|
1226
1225
|
"experimentContainer",
|
|
1227
|
-
"knownInputs"
|
|
1228
|
-
"_debug"
|
|
1226
|
+
"knownInputs"
|
|
1229
1227
|
];
|
|
1230
1228
|
var warnings = validateConfig(userConfig, KNOWN_KEYS);
|
|
1231
1229
|
warnings.forEach(function(w) {
|
|
1232
|
-
console.warn("
|
|
1230
|
+
console.warn("[cyborg-hunter] " + w);
|
|
1233
1231
|
});
|
|
1234
1232
|
Object.freeze(config);
|
|
1235
1233
|
Object.freeze(config.signals);
|
|
@@ -1303,7 +1301,7 @@ function init(userConfig) {
|
|
|
1303
1301
|
function transition(to) {
|
|
1304
1302
|
if (!sm.transition(to)) {
|
|
1305
1303
|
throw new Error(
|
|
1306
|
-
"
|
|
1304
|
+
"[cyborg-hunter] invalid lifecycle call: cannot transition from '" + sm.current + "' to '" + to + "'. Expected order: init() \u2192 startSession() \u2192 (startTrial \u2192 endTrial)* \u2192 destroy(). Common cause: a startTrial() without a preceding endTrial(), or a call after destroy()."
|
|
1307
1305
|
);
|
|
1308
1306
|
}
|
|
1309
1307
|
}
|
|
@@ -1385,38 +1383,33 @@ function init(userConfig) {
|
|
|
1385
1383
|
syntheticInsertions: []
|
|
1386
1384
|
};
|
|
1387
1385
|
removeDecoyElement();
|
|
1388
|
-
if (config.decoyAnswers) {
|
|
1386
|
+
if (!config.decoyAnswers) {
|
|
1387
|
+
if (opts.decoyAnswer != null) {
|
|
1388
|
+
console.warn(
|
|
1389
|
+
"[cyborg-hunter] decoyAnswer provided but decoyAnswers is not enabled. Add decoyAnswers: true to init() config."
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1392
|
+
} else if (opts.decoyAnswer === false) {
|
|
1393
|
+
trialData.decoy = { level: 0, source: "skipped" };
|
|
1394
|
+
} else {
|
|
1389
1395
|
var currentTrialId = trialData.trialId;
|
|
1390
|
-
|
|
1391
|
-
trialData.decoy = { level: 0, source: "skipped" };
|
|
1392
|
-
} else if (typeof opts.decoyAnswer === "string" && opts.decoyAnswer.length > 0) {
|
|
1393
|
-
injectDecoy(opts, currentTrialId, config, trialData, function() {
|
|
1394
|
-
return trialData;
|
|
1395
|
-
});
|
|
1396
|
-
} else if (config.decoyMap && config.decoyMap[currentTrialId]) {
|
|
1396
|
+
var inject = function() {
|
|
1397
1397
|
injectDecoy(opts, currentTrialId, config, trialData, function() {
|
|
1398
1398
|
return trialData;
|
|
1399
1399
|
});
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
injectDecoy(opts, currentTrialId, config, trialData, function() {
|
|
1406
|
-
return trialData;
|
|
1407
|
-
});
|
|
1408
|
-
}, 0);
|
|
1400
|
+
};
|
|
1401
|
+
var hasExplicitString = typeof opts.decoyAnswer === "string" && opts.decoyAnswer.length > 0;
|
|
1402
|
+
var hasMapEntry = config.decoyMap && config.decoyMap[currentTrialId];
|
|
1403
|
+
if (hasExplicitString || hasMapEntry) {
|
|
1404
|
+
inject();
|
|
1409
1405
|
} else {
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
}
|
|
1406
|
+
if (opts.decoyAnswer != null) {
|
|
1407
|
+
console.warn(
|
|
1408
|
+
"[cyborg-hunter] decoyAnswer must be a non-empty string, got: " + typeof opts.decoyAnswer + ". Falling through to auto-detection."
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
setTimeout(inject, 0);
|
|
1415
1412
|
}
|
|
1416
|
-
} else if (opts.decoyAnswer != null) {
|
|
1417
|
-
console.warn(
|
|
1418
|
-
"IntegrityMonitor: decoyAnswer provided but decoyAnswers is not enabled. Add decoyAnswers: true to init() config."
|
|
1419
|
-
);
|
|
1420
1413
|
}
|
|
1421
1414
|
var ctx = buildTrialCtx();
|
|
1422
1415
|
attachClipboardSignals(ctx);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
var CyborgHunter=(()=>{var V=Object.defineProperty;var fe=Object.getOwnPropertyDescriptor;var he=Object.getOwnPropertyNames;var ge=Object.prototype.hasOwnProperty;var ve=(e,n)=>{for(var t in n)V(e,t,{get:n[t],enumerable:!0})},me=(e,n,t,o)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of he(n))!ge.call(e,r)&&r!==t&&V(e,r,{get:()=>n[r],enumerable:!(o=fe(n,r))||o.enumerable});return e};var ye=e=>me(V({},"__esModule",{value:!0}),e);var Me={};ve(Me,{VERSION:()=>N,addHoneypot:()=>de,init:()=>pe,preventTextSelection:()=>ce,setAltText:()=>ue});var B={created:["session","destroyed"],session:["trial","destroyed"],trial:["session","destroyed"],destroyed:[]};function z(){let e="created";return{get current(){return e},transition(n){return B[e]&&B[e].indexOf(n)!==-1?(e=n,!0):(console.warn("[CyborgHunter] Invalid transition: "+e+" \u2192 "+n),!1)}}}function F(e){if(e===null||typeof e!="object")return e;try{return JSON.parse(JSON.stringify(e))}catch{return e}}var N="0.3.0",H={pasteMinChars:0,dropMinChars:0,sidebarGapPx:100,layoutCompressionPx:20,syntheticGapMs:100,idleGapMs:1e4,idleCheckIntervalMs:5e3,mouseThrottleMs:50,mouseMaxEvents:2e3,mouseBotMinEvents:3,sidebarPollMs:2e3,extensionScanMs:3e4,windowPositionPollMs:2e3,elementTraceHz:500,tabAwayDurationMs:3e3,typingSpeedCps:10},U={permissive:{signals:{paste:!0,copy:!0,tabAway:!0,typingSpeed:!0,devTools:!0,aiExtensions:!0,sidebarGap:!0,keyboardShortcuts:!0,mouseTracking:!0,idleGaps:!0,windowPosition:!0,clipboardManager:!0,keystrokeDynamics:!1},thresholds:{typingSpeedCps:15},scoring:{hard:{paste:{countThreshold:3},drop:{countThreshold:3}},soft:{copy:{weight:1,maxPerTrial:2},tabAway:{weight:1,maxPerTrial:2},typingSpeed:{weight:1},sidebarEvent:{weight:1},devTools:{weight:1},foreignInput:{weight:1}},softScoreThreshold:10},screenout:{enabled:!1,gracePeriodTrials:5}},standard:{signals:{paste:!0,copy:!0,tabAway:!0,typingSpeed:!0,devTools:!0,aiExtensions:!0,sidebarGap:!0,keyboardShortcuts:!0,mouseTracking:!0,idleGaps:!0,windowPosition:!0,clipboardManager:!0,keystrokeDynamics:!1},thresholds:{},scoring:{hard:{paste:{countThreshold:2},drop:{countThreshold:2}},soft:{copy:{weight:2,maxPerTrial:2},tabAway:{weight:1,maxPerTrial:2},typingSpeed:{weight:2},sidebarEvent:{weight:3},devTools:{weight:1},foreignInput:{weight:2}},softScoreThreshold:6},screenout:{enabled:!0,gracePeriodTrials:3}},strict:{signals:{paste:!0,copy:!0,tabAway:!0,typingSpeed:!0,devTools:!0,aiExtensions:!0,sidebarGap:!0,keyboardShortcuts:!0,mouseTracking:!0,idleGaps:!0,windowPosition:!0,clipboardManager:!0,keystrokeDynamics:!0},thresholds:{typingSpeedCps:8,tabAwayDurationMs:5e3,mouseMaxEvents:5e3},scoring:{hard:{paste:{countThreshold:1},copy:{countThreshold:2},drop:{countThreshold:1}},soft:{tabAway:{weight:2,maxPerTrial:1},typingSpeed:{weight:3},sidebarEvent:{weight:2},devTools:{weight:2},foreignInput:{weight:3}},softScoreThreshold:4},screenout:{enabled:!0,gracePeriodTrials:2}}},W=[{name:"ChatGPT sidebar",sel:"chatgpt-sidebar"},{name:"ChatGPT extension",sel:"[data-chatgpt]"},{name:"Copilot extension",sel:"copilot-suggestions"},{name:"Copilot extension",sel:"[data-copilot]"},{name:"Gemini extension",sel:"gemini-panel"},{name:"Claude extension",sel:"[data-claude-extension]"},{name:"Monica AI",sel:"monica-root"},{name:"Merlin AI",sel:"merlin-root"},{name:"Sider AI",sel:"[data-sider-extension]"},{name:"MaxAI",sel:"[data-maxai]"},{name:"AI assistant",sel:"[data-ai-assistant]"},{name:"AI iframe (OpenAI)",sel:'iframe[src*="chat.openai.com"]'},{name:"AI iframe (Copilot)",sel:'iframe[src*="copilot.microsoft.com"]'},{name:"AI iframe (Gemini)",sel:'iframe[src*="gemini.google.com"]'},{name:"AI iframe (Claude)",sel:'iframe[src*="claude.ai"]'},{name:"AI iframe (Perplexity)",sel:'iframe[src*="perplexity.ai"]'}],J=["grammarly","lastpass","1password","bitwarden","dashlane","roboform","honey","ublock","adblock"];var X={dataDir:"./data",filePattern:"*.json",participantIdField:"participantId",trialIdField:"trialId",trialOrderField:"trialIndex",integrityField:"integrity",trialsPerParticipant:null,platformIdField:null,conditionField:null,groupField:null,typingSpeedThreshold_cps:10,tabAwayMinDuration_ms:3e3,idleGapThreshold_ms:1e4,suspiciouslyFastRT_ms:2e3,scoring:null,signals:null,trajectoryGrid:"auto",trajectoryTrialLabel:"trialId",trajectoryResponseField:null,outputDir:"./cyborg-hunter-report",outputFormat:"html"};var we=[...Object.keys(X),...Object.keys(H),"preset","participantId","signals","thresholds","scoring","domProtection","collectForPostHoc","onSignal","screenout","experimentContainer","knownInputs","decoyAnswers","decoyMap","decoyVisibility","decoyFraming","decoyExcludeButtons","autoMonitor","excludeTrialTypes"];function Y(e,n=we){let t=[];return Object.keys(e).forEach(o=>{if(!n.includes(o)){let r=Se(o,n),s=r?`Unknown config key "${o}" \u2014 did you mean "${r}"?`:`Unknown config key "${o}"`;t.push(s)}}),t}function Se(e,n){let t=null,o=1/0;for(let r of n){let s=Te(e.toLowerCase(),r.toLowerCase());s<o&&s<=3&&(o=s,t=r)}return t}function Te(e,n){let t=e.length,o=n.length,r=Array.from({length:t+1},()=>Array(o+1).fill(0));for(let s=0;s<=t;s++)r[s][0]=s;for(let s=0;s<=o;s++)r[0][s]=s;for(let s=1;s<=t;s++)for(let a=1;a<=o;a++)r[s][a]=e[s-1]===n[a-1]?r[s-1][a-1]:1+Math.min(r[s-1][a],r[s][a-1],r[s-1][a-1]);return r[t][o]}function $(e,n,t,o){var r=t.scoring,s={hard:{},soft:{}},a=0;if(r.hard.paste){var c=(e.pasteEvents||[]).length;s.hard.paste={trialHits:c,sessionTotal:n.pasteCount,countThreshold:r.hard.paste.countThreshold}}if(r.hard.copy){var g=(e.copyEvents||[]).length;s.hard.copy={trialHits:g,sessionTotal:n.copyCount,countThreshold:r.hard.copy.countThreshold}}if(r.hard.drop){var p=(e.dropEvents||[]).length;s.hard.drop={trialHits:p,sessionTotal:n.dropCount,countThreshold:r.hard.drop.countThreshold}}if(r.soft.copy){var h=(e.copyEvents||[]).length,y=r.soft.copy.maxPerTrial!=null?Math.min(h,r.soft.copy.maxPerTrial):h,T=y*r.soft.copy.weight;a+=T,s.soft.copy={hits:h,capped:y,score:T}}if(r.soft.tabAway){var w=(e.tabAwayEvents||[]).filter(function(i){return(i.duration_ms||0)>t.thresholds.tabAwayDurationMs}).length,x=r.soft.tabAway.maxPerTrial!=null?Math.min(w,r.soft.tabAway.maxPerTrial):w,I=x*r.soft.tabAway.weight;a+=I,s.soft.tabAway={hits:w,capped:x,score:I}}if(r.soft.typingSpeed&&o.charsPerSec!=null){var b=t.thresholds.typingSpeedCps,u=o.charsPerSec>b?1:0,l=u*r.soft.typingSpeed.weight;a+=l,s.soft.typingSpeed={charsPerSec:o.charsPerSec,threshold:b,hit:u,score:l}}if(r.soft.sidebarEvent){var f=n.sidebarEvents.filter(function(i){return i.t>=e.startTime&&i.t<=performance.now()}).length;if(f>0){var d=r.soft.sidebarEvent.weight;a+=d,s.soft.sidebarEvent={hits:f,score:d}}}if(r.soft.devTools){var v=n.keyboardShortcuts.filter(function(i){return i.t>=e.startTime&&i.t<=performance.now()}).length;if(v>0){var E=r.soft.devTools.weight;a+=E,s.soft.devTools={hits:v,score:E}}}if(r.soft.foreignInput&&e.foreignInputEvents.length>0){var M=e.foreignInputEvents.length,C=r.soft.foreignInput.weight;a+=C,s.soft.foreignInput={hits:M,score:C}}for(var k={},P=Object.keys(r.hard),q=0;q<P.length;q++){var L=P[q],O=L==="paste"?n.pasteCount:L==="copy"?n.copyCount:L==="drop"?n.dropCount:0;k[L]={count:O,threshold:r.hard[L].countThreshold,triggered:O>=r.hard[L].countThreshold}}return{trialSoftScore:a,trialSignals:s,updatedHardScore:k}}function Q(e,n){if(!n.screenout.enabled)return!1;var t=Object.keys(e.hardScore).some(function(o){return e.hardScore[o].triggered});return t?!0:e.trialsCompleted<n.screenout.gracePeriodTrials?!1:e.softScore>=n.scoring.softScoreThreshold}function Z(e){var n=e.config,t=e.trialData;n.signals.paste&&e.addTrialListener(document,"paste",function(o){var r="";try{r=(o.clipboardData||window.clipboardData).getData("text")}catch{}var s=r.length;if(s>=n.thresholds.pasteMinChars){var a=e.isKnownInput(o.target),c={type:"paste",t:performance.now(),pastedLength:s,isKnownInput:a};n.collectForPostHoc.pasteDropContent&&(c.text=r),t.pasteEvents.push(c),e.sessionData.pasteCount++,e.fireSignal("paste",o,{pastedLength:s,isKnownInput:a,text:r})}}),n.signals.copy&&(e.addTrialListener(document,"copy",function(o){var r=window.getSelection(),s=r?r.toString():"",a=s.length;t.copyEvents.push({type:"copy",t:performance.now(),selectedLength:a}),e.sessionData.copyCount++,e.fireSignal("copy",o,{selectedLength:a})}),e.addTrialListener(document,"cut",function(o){t.copyEvents.push({type:"cut",t:performance.now()}),e.fireSignal("cut",o,{})})),n.signals.paste&&e.addTrialListener(document,"drop",function(o){var r="";try{r=o.dataTransfer.getData("text")}catch{}var s=r.length;if(s>=n.thresholds.dropMinChars){var a=e.isKnownInput(o.target),c={type:"drop",t:performance.now(),droppedLength:s,isKnownInput:a};n.collectForPostHoc.pasteDropContent&&(c.text=r),t.dropEvents.push(c),e.sessionData.dropCount++,e.fireSignal("drop",o,{droppedLength:s,isKnownInput:a,text:r})}})}function ee(e){var n=e.config;if(n.signals.tabAway){let r=function(a){t===null&&(t=performance.now(),o=a,e.fireSignal("tabAway",null,{type:a}))},s=function(){if(t!==null){var a=performance.now()-t,c={start:t,duration_ms:Math.round(a),type:o};e.getTrialData()&&e.getTrialData().tabAwayEvents.push(c),e.sessionData.tabAwaySums.push(Math.round(a)),e.fireSignal("tabReturn",null,{duration_ms:Math.round(a),type:o}),t=null,o=null}};var t=null,o=null;e.addListener(document,"visibilitychange",function(){document.hidden?r("tabHidden"):s()}),e.addListener(window,"blur",function(){r("windowBlur")}),e.addListener(window,"focus",function(){document.hidden||s()})}}function te(e){var n=e.config,t=e.trialData;if(n.signals.idleGaps){var o=n.thresholds.idleGapMs,r=performance.now();e.addTrialListener(document,"keydown",function(){r=performance.now()}),e.addTrialListener(document,"mousemove",function(){r=performance.now()},{passive:!0});var s=setInterval(function(){if(e.getTrialData()){var a=performance.now()-r;a>o&&(t.idleGaps.push({duration_ms:Math.round(a),t:performance.now()}),r=performance.now())}},n.thresholds.idleCheckIntervalMs);e.addInterval(s)}}function re(e){var n=e.config,t=e.trialData;if(n.signals.mouseTracking){let c=function(g){return function(p){t.mouseEvents.length>=r||t.mouseEvents.push({x:Math.round(p.pageX),y:Math.round(p.pageY),t:Math.round(performance.now()-a),type:g})}};var o=n.thresholds.mouseThrottleMs,r=n.thresholds.mouseMaxEvents,s=0,a=t.startTime;t.mouseTrackingCapped=!1,t.mouseTrackingCappedAtMs=null,e.addTrialListener(document,"mousemove",function(g){if(t.mouseEvents.length>=r){t.mouseTrackingCapped||(t.mouseTrackingCapped=!0,t.mouseTrackingCappedAtMs=Math.round(performance.now()-a));return}var p=performance.now();p-s<o||(s=p,t.mouseEvents.push({x:Math.round(g.pageX),y:Math.round(g.pageY),t:Math.round(p-a),type:"move"}))},{passive:!0}),e.addTrialListener(document,"click",c("click"),{passive:!0}),e.addTrialListener(document,"mousedown",c("down"),{passive:!0}),e.addTrialListener(document,"mouseup",c("up"),{passive:!0})}}function ne(e,n){var t=e.filter(function(M){return M.type==="move"});if(t.length<n)return null;for(var o=0,r=[],s=0,a=0,c=0,g=0,p=1;p<t.length;p++){var h=t[p].x-t[p-1].x,y=t[p].y-t[p-1].y,T=Math.sqrt(h*h+y*y);o+=T;var w=t[p].t-t[p-1].t;w>0&&r.push(T/w),p>1&&((h>0&&c<0||h<0&&c>0)&&s++,(y>0&&g<0||y<0&&g>0)&&a++),c=h,g=y}for(var x=t[0],I=t[t.length-1],b=Math.sqrt(Math.pow(I.x-x.x,2)+Math.pow(I.y-x.y,2)),u=o>0?Math.round(b/o*1e3)/1e3:0,l=0,f=0,d=0;d<r.length;d++){var v=r[d]-l;l+=v/(d+1),f+=v*(r[d]-l)}var E=r.length>1?Math.round(f/(r.length-1)*1e4)/1e4:0;return{pathEfficiency:u,directionChanges:s+a,speedVariance:E,moveCount:t.length}}function oe(e){var n=e.config,t=e.trialData;if(n.signals.clipboardManager){var o=0;e.addTrialListener(document,"keydown",function(){o=performance.now()}),e.addTrialListener(document,"input",function(r){if(r.target&&(r.target.tagName==="INPUT"||r.target.tagName==="TEXTAREA"||r.target.isContentEditable)&&(t.editTimestamps.push(performance.now()),performance.now()-o>n.thresholds.syntheticGapMs&&r.inputType==="insertText")){var s=(r.data||"").length;t.syntheticInsertions.push({type:"synthetic_insertion",t:performance.now(),dataLength:s}),e.fireSignal("syntheticInsertion",r,{dataLength:s})}},!0)}n.signals.typingSpeed&&!n.signals.clipboardManager&&e.addTrialListener(document,"input",function(r){r.target&&(r.target.tagName==="INPUT"||r.target.tagName==="TEXTAREA"||r.target.isContentEditable)&&t.editTimestamps.push(performance.now())},!0)}function ae(e){var n=e.config,t=e.trialData;e.addTrialListener(document,"input",function(o){var r=o.target;if(r){var s=r.tagName==="INPUT"||r.tagName==="TEXTAREA"||r.isContentEditable;if(s){var a=e.isKnownInput(r);if(a===!1){var c={t:performance.now(),targetTag:r.tagName||"unknown",targetId:(r.id||"").slice(0,50),targetClass:(r.className||"").toString().slice(0,100),inputType:o.inputType||""};n.collectForPostHoc.foreignInputContent&&(c.data=o.data||""),t.foreignInputEvents.push(c),e.fireSignal("typingOutsideExperiment",o,{targetTag:c.targetTag,targetId:c.targetId,targetClass:c.targetClass,data:o.data||""})}}}},!0)}function ie(e){if(e.length<2)return null;var n=e[0],t=e[e.length-1],o=(t-n)/1e3;if(o<=0)return null;var r=e.length;return Math.round(r/o*10)/10}function se(e){var n=e.config,t=e.sessionData;if(n.signals.sidebarGap||n.signals.devTools){var o=window.devicePixelRatio||1,r=window.innerWidth,s=!1,a=null,c=null,g=0,p=null,h=!1,y=null;e.addInterval(setInterval(function(){var l=window.innerWidth,f=n.thresholds.sidebarGapPx,d=r-l;if(!s&&d>f)s=!0,a=performance.now(),c="innerWidth_delta",g=d,p=l,t.sidebarEvents.push({type:"opened",method:"innerWidth_delta",deltaIW:d,innerWidth:l,baselineIW:r,t:performance.now()}),e.fireSignal("sidebarOpened",null,{deltaIW:d});else if(s&&c==="innerWidth_delta"&&d<f/2){var v=performance.now()-a,E=p!=null?p-l:null;t.sidebarEvents.push({type:"closed",method:"innerWidth_delta",deltaIW:E,duration_ms:Math.round(v),t:performance.now()}),e.fireSignal("sidebarClosed",null,{duration_ms:Math.round(v),deltaIW:E}),s=!1,a=null,p=null,r=l}else s||(r=l);var M=l-document.documentElement.clientWidth,C=n.thresholds.layoutCompressionPx;if(!h&&M>C)h=!0,y=performance.now(),t.sidebarEvents.push({type:"opened",method:"layout_compression",gap:M,t:performance.now()});else if(h&&M<=C){var k=performance.now()-y;t.sidebarEvents.push({type:"closed",method:"layout_compression",duration_ms:Math.round(k),t:performance.now()}),h=!1,y=null}var P=window.devicePixelRatio||1;P!==o&&(t.zoomChanges.push({from:o,to:P,t:performance.now()}),r=l,o=P)},n.thresholds.sidebarPollMs))}if(n.signals.aiExtensions){let l=function(){W.forEach(function(f){if(document.querySelector(f.sel)){var d=t.aiExtensionsFound.some(function(v){return v.name===f.name});d||t.aiExtensionsFound.push({name:f.name,t:performance.now()})}})};l(),e.addInterval(setInterval(l,n.thresholds.extensionScanMs))}if(n.signals.keyboardShortcuts&&e.addListener(document,"keydown",function(l){var f=l.ctrlKey||l.metaKey;if(f&&l.shiftKey&&(l.key==="I"||l.key==="J"||l.key==="C")){var d="Ctrl+Shift+"+l.key;t.keyboardShortcuts.push({combo:d,t:performance.now()}),e.fireSignal("keyboardShortcut",l,{combo:d})}l.key==="F12"&&(t.keyboardShortcuts.push({combo:"F12",t:performance.now()}),e.fireSignal("keyboardShortcut",l,{combo:"F12"}))}),n.signals.windowPosition&&n.collectForPostHoc.windowPositionLog){var T=function(){t.windowPositions.push({x:window.screenX,y:window.screenY,w:window.outerWidth,h:window.outerHeight,iw:window.innerWidth,ih:window.innerHeight,sw:window.screen.width,sh:window.screen.height,dpr:window.devicePixelRatio,vvScale:window.visualViewport?window.visualViewport.scale:null,t:performance.now()})};e.addInterval(setInterval(T,n.thresholds.windowPositionPollMs));var w=null,x=function(){w&&clearTimeout(w),w=setTimeout(function(){T(),w=null},250)};e.addListener(window,"resize",x)}if(n.signals.aiExtensions){let l=function(f){var d=f.toLowerCase();return J.some(function(v){return d.indexOf(v)!==-1})};var I=new MutationObserver(function(f){for(var d=0;d<f.length;d++)for(var v=f[d].addedNodes,E=0;E<v.length;E++){var M=v[E];if(M.nodeType===Node.ELEMENT_NODE){var C=M.tagName||"";C.indexOf("-")!==-1&&!l(C)&&t.extensionInjections.push({tag:C.toLowerCase(),hasShadow:!!M.shadowRoot,t:performance.now()})}}});I.observe(document.body,{childList:!0,subtree:!1}),e.listeners.push({target:null,event:"mutation",handler:I,options:{_isMutationObserver:!0}})}if(n.signals.sidebarGap){var b=document.documentElement.clientWidth,u=new ResizeObserver(function(l){for(var f=0;f<l.length;f++){var d=l[f].contentRect.width,v=b-d;Math.abs(v)>n.thresholds.layoutCompressionPx&&(t.layoutShifts.push({oldWidth:Math.round(b),newWidth:Math.round(d),delta:Math.round(v),t:performance.now()}),b=d)}});u.observe(document.documentElement),e.listeners.push({target:null,event:"resize",handler:u,options:{_isResizeObserver:!0}})}}function le(e){var n=e.config,t=e.trialData;if(n.collectForPostHoc.elementTrace){t.elementTrace=[];var o=0,r=0;e.addTrialListener(document,"mousemove",function(a){o=a.clientX,r=a.clientY},{passive:!0});var s=setInterval(function(){if(e.getTrialData()&&!(o===0&&r===0))try{var a=document.elementsFromPoint(o,r);if(a&&a.length>0){var c=a[0];t.elementTrace.push({tag:c.tagName.toLowerCase(),id:c.id||null,t:Math.round(performance.now()-t.startTime)})}}catch{}},n.thresholds.elementTraceHz);e.addInterval(s)}}function j(e,n,t,o){return t==="C"?e:n==="calibration-metadata"?"Validation trial (integrity check, ID: cal-"+o+`).
|
|
1
|
+
// cyborg-hunter v0.4.0 — https://github.com/konukcan/cyborg-hunter
|
|
2
|
+
var CyborgHunter=(()=>{var _=Object.defineProperty;var ge=Object.getOwnPropertyDescriptor;var ve=Object.getOwnPropertyNames;var me=Object.prototype.hasOwnProperty;var ye=(e,n)=>{for(var t in n)_(e,t,{get:n[t],enumerable:!0})},we=(e,n,t,o)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of ve(n))!me.call(e,r)&&r!==t&&_(e,r,{get:()=>n[r],enumerable:!(o=ge(n,r))||o.enumerable});return e};var Se=e=>we(_({},"__esModule",{value:!0}),e);var Ce={};ye(Ce,{VERSION:()=>G,addHoneypot:()=>ue,init:()=>fe,preventTextSelection:()=>de,setAltText:()=>pe});var B={created:["session","destroyed"],session:["trial","destroyed"],trial:["session","destroyed"],destroyed:[]};function z(){let e="created";return{get current(){return e},transition(n){return B[e]&&B[e].indexOf(n)!==-1?(e=n,!0):(console.warn("[cyborg-hunter] invalid transition: "+e+" \u2192 "+n),!1)}}}function F(e){if(e===null||typeof e!="object")return e;try{return JSON.parse(JSON.stringify(e))}catch{return e}}var G="0.3.0",V={pasteMinChars:0,dropMinChars:0,sidebarGapPx:100,layoutCompressionPx:20,syntheticGapMs:100,idleGapMs:1e4,idleCheckIntervalMs:5e3,mouseThrottleMs:50,mouseMaxEvents:2e3,mouseBotMinEvents:3,sidebarPollMs:2e3,extensionScanMs:3e4,windowPositionPollMs:2e3,elementTraceHz:500,tabAwayDurationMs:3e3,typingSpeedCps:10},U={permissive:{signals:{paste:!0,copy:!0,tabAway:!0,typingSpeed:!0,devTools:!0,aiExtensions:!0,sidebarGap:!0,keyboardShortcuts:!0,mouseTracking:!0,idleGaps:!0,windowPosition:!0,clipboardManager:!0,keystrokeDynamics:!1},thresholds:{typingSpeedCps:15},scoring:{hard:{paste:{countThreshold:3},drop:{countThreshold:3}},soft:{copy:{weight:1,maxPerTrial:2},tabAway:{weight:1,maxPerTrial:2},typingSpeed:{weight:1},sidebarEvent:{weight:1},devTools:{weight:1},foreignInput:{weight:1}},softScoreThreshold:10},screenout:{enabled:!1,gracePeriodTrials:5}},standard:{signals:{paste:!0,copy:!0,tabAway:!0,typingSpeed:!0,devTools:!0,aiExtensions:!0,sidebarGap:!0,keyboardShortcuts:!0,mouseTracking:!0,idleGaps:!0,windowPosition:!0,clipboardManager:!0,keystrokeDynamics:!1},thresholds:{},scoring:{hard:{paste:{countThreshold:2},drop:{countThreshold:2}},soft:{copy:{weight:2,maxPerTrial:2},tabAway:{weight:1,maxPerTrial:2},typingSpeed:{weight:2},sidebarEvent:{weight:3},devTools:{weight:1},foreignInput:{weight:2}},softScoreThreshold:6},screenout:{enabled:!0,gracePeriodTrials:3}},strict:{signals:{paste:!0,copy:!0,tabAway:!0,typingSpeed:!0,devTools:!0,aiExtensions:!0,sidebarGap:!0,keyboardShortcuts:!0,mouseTracking:!0,idleGaps:!0,windowPosition:!0,clipboardManager:!0,keystrokeDynamics:!0},thresholds:{typingSpeedCps:8,tabAwayDurationMs:5e3,mouseMaxEvents:5e3},scoring:{hard:{paste:{countThreshold:1},copy:{countThreshold:2},drop:{countThreshold:1}},soft:{tabAway:{weight:2,maxPerTrial:1},typingSpeed:{weight:3},sidebarEvent:{weight:2},devTools:{weight:2},foreignInput:{weight:3}},softScoreThreshold:4},screenout:{enabled:!0,gracePeriodTrials:2}}},W=[{name:"ChatGPT sidebar",sel:"chatgpt-sidebar"},{name:"ChatGPT extension",sel:"[data-chatgpt]"},{name:"Copilot extension",sel:"copilot-suggestions"},{name:"Copilot extension",sel:"[data-copilot]"},{name:"Gemini extension",sel:"gemini-panel"},{name:"Claude extension",sel:"[data-claude-extension]"},{name:"Monica AI",sel:"monica-root"},{name:"Merlin AI",sel:"merlin-root"},{name:"Sider AI",sel:"[data-sider-extension]"},{name:"MaxAI",sel:"[data-maxai]"},{name:"AI assistant",sel:"[data-ai-assistant]"},{name:"AI iframe (OpenAI)",sel:'iframe[src*="chat.openai.com"]'},{name:"AI iframe (Copilot)",sel:'iframe[src*="copilot.microsoft.com"]'},{name:"AI iframe (Gemini)",sel:'iframe[src*="gemini.google.com"]'},{name:"AI iframe (Claude)",sel:'iframe[src*="claude.ai"]'},{name:"AI iframe (Perplexity)",sel:'iframe[src*="perplexity.ai"]'}],J=["grammarly","lastpass","1password","bitwarden","dashlane","roboform","honey","ublock","adblock"];var X={dataDir:"./data",filePattern:"*.json",participantIdField:"participantId",trialIdField:"trialId",trialOrderField:"trialIndex",integrityField:"integrity",trialsPerParticipant:null,platformIdField:null,conditionField:null,groupField:null,typingSpeedThreshold_cps:10,tabAwayMinDuration_ms:3e3,idleGapThreshold_ms:1e4,suspiciouslyFastRT_ms:2e3,scoring:null,signals:null,trajectoryGrid:"auto",trajectoryTrialLabel:"trialId",trajectoryResponseField:null,outputDir:"./cyborg-hunter-report",outputFormat:"html"};var Te=[...Object.keys(X),...Object.keys(V),"preset","participantId","signals","thresholds","scoring","domProtection","collectForPostHoc","onSignal","screenout","experimentContainer","knownInputs","decoyAnswers","decoyMap","decoyVisibility","decoyFraming","decoyExcludeButtons","autoMonitor","excludeTrialTypes"];function Y(e,n=Te){let t=[];return Object.keys(e).forEach(o=>{if(!n.includes(o)){let r=be(o,n),i=r?`Unknown config key "${o}" \u2014 did you mean "${r}"?`:`Unknown config key "${o}"`;t.push(i)}}),t}function be(e,n){let t=null,o=1/0;for(let r of n){let i=Ee(e.toLowerCase(),r.toLowerCase());i<o&&i<=3&&(o=i,t=r)}return t}function Ee(e,n){let t=e.length,o=n.length,r=Array.from({length:t+1},()=>Array(o+1).fill(0));for(let i=0;i<=t;i++)r[i][0]=i;for(let i=0;i<=o;i++)r[0][i]=i;for(let i=1;i<=t;i++)for(let a=1;a<=o;a++)r[i][a]=e[i-1]===n[a-1]?r[i-1][a-1]:1+Math.min(r[i-1][a],r[i][a-1],r[i-1][a-1]);return r[t][o]}function $(e,n,t,o){var r=t.scoring,i={hard:{},soft:{}},a=0;if(r.hard.paste){var c=(e.pasteEvents||[]).length;i.hard.paste={trialHits:c,sessionTotal:n.pasteCount,countThreshold:r.hard.paste.countThreshold}}if(r.hard.copy){var g=(e.copyEvents||[]).length;i.hard.copy={trialHits:g,sessionTotal:n.copyCount,countThreshold:r.hard.copy.countThreshold}}if(r.hard.drop){var p=(e.dropEvents||[]).length;i.hard.drop={trialHits:p,sessionTotal:n.dropCount,countThreshold:r.hard.drop.countThreshold}}if(r.soft.copy){var h=(e.copyEvents||[]).length,m=r.soft.copy.maxPerTrial!=null?Math.min(h,r.soft.copy.maxPerTrial):h,S=m*r.soft.copy.weight;a+=S,i.soft.copy={hits:h,capped:m,score:S}}if(r.soft.tabAway){var y=(e.tabAwayEvents||[]).filter(function(s){return(s.duration_ms||0)>t.thresholds.tabAwayDurationMs}).length,C=r.soft.tabAway.maxPerTrial!=null?Math.min(y,r.soft.tabAway.maxPerTrial):y,E=C*r.soft.tabAway.weight;a+=E,i.soft.tabAway={hits:y,capped:C,score:E}}if(r.soft.typingSpeed&&o.charsPerSec!=null){var T=t.thresholds.typingSpeedCps,u=o.charsPerSec>T?1:0,l=u*r.soft.typingSpeed.weight;a+=l,i.soft.typingSpeed={charsPerSec:o.charsPerSec,threshold:T,hit:u,score:l}}if(r.soft.sidebarEvent){var f=n.sidebarEvents.filter(function(s){return s.t>=e.startTime&&s.t<=performance.now()}).length;if(f>0){var d=r.soft.sidebarEvent.weight;a+=d,i.soft.sidebarEvent={hits:f,score:d}}}if(r.soft.devTools){var v=n.keyboardShortcuts.filter(function(s){return s.t>=e.startTime&&s.t<=performance.now()}).length;if(v>0){var b=r.soft.devTools.weight;a+=b,i.soft.devTools={hits:v,score:b}}}if(r.soft.foreignInput&&e.foreignInputEvents.length>0){var x=e.foreignInputEvents.length,A=r.soft.foreignInput.weight;a+=A,i.soft.foreignInput={hits:x,score:A}}for(var k={},P=Object.keys(r.hard),R=0;R<P.length;R++){var L=P[R],O=L==="paste"?n.pasteCount:L==="copy"?n.copyCount:L==="drop"?n.dropCount:0;k[L]={count:O,threshold:r.hard[L].countThreshold,triggered:O>=r.hard[L].countThreshold}}return{trialSoftScore:a,trialSignals:i,updatedHardScore:k}}function Q(e,n){if(!n.screenout.enabled)return!1;var t=Object.keys(e.hardScore).some(function(o){return e.hardScore[o].triggered});return t?!0:e.trialsCompleted<n.screenout.gracePeriodTrials?!1:e.softScore>=n.scoring.softScoreThreshold}function Z(e){var n=e.config,t=e.trialData;n.signals.paste&&e.addTrialListener(document,"paste",function(o){var r="";try{r=(o.clipboardData||window.clipboardData).getData("text")}catch{}var i=r.length;if(i>=n.thresholds.pasteMinChars){var a=e.isKnownInput(o.target),c={type:"paste",t:performance.now(),pastedLength:i,isKnownInput:a};n.collectForPostHoc.pasteDropContent&&(c.text=r),t.pasteEvents.push(c),e.sessionData.pasteCount++,e.fireSignal("paste",o,{pastedLength:i,isKnownInput:a,text:r})}}),n.signals.copy&&(e.addTrialListener(document,"copy",function(o){var r=window.getSelection(),i=r?r.toString():"",a=i.length;t.copyEvents.push({type:"copy",t:performance.now(),selectedLength:a}),e.sessionData.copyCount++,e.fireSignal("copy",o,{selectedLength:a})}),e.addTrialListener(document,"cut",function(o){t.copyEvents.push({type:"cut",t:performance.now()}),e.fireSignal("cut",o,{})})),n.signals.paste&&e.addTrialListener(document,"drop",function(o){var r="";try{r=o.dataTransfer.getData("text")}catch{}var i=r.length;if(i>=n.thresholds.dropMinChars){var a=e.isKnownInput(o.target),c={type:"drop",t:performance.now(),droppedLength:i,isKnownInput:a};n.collectForPostHoc.pasteDropContent&&(c.text=r),t.dropEvents.push(c),e.sessionData.dropCount++,e.fireSignal("drop",o,{droppedLength:i,isKnownInput:a,text:r})}})}function ee(e){var n=e.config;if(n.signals.tabAway){let r=function(a){t===null&&(t=performance.now(),o=a,e.fireSignal("tabAway",null,{type:a}))},i=function(){if(t!==null){var a=performance.now()-t,c={start:t,duration_ms:Math.round(a),type:o};e.getTrialData()&&e.getTrialData().tabAwayEvents.push(c),e.sessionData.tabAwaySums.push(Math.round(a)),e.fireSignal("tabReturn",null,{duration_ms:Math.round(a),type:o}),t=null,o=null}};var t=null,o=null;e.addListener(document,"visibilitychange",function(){document.hidden?r("tabHidden"):i()}),e.addListener(window,"blur",function(){r("windowBlur")}),e.addListener(window,"focus",function(){document.hidden||i()})}}function te(e){var n=e.config,t=e.trialData;if(n.signals.idleGaps){var o=n.thresholds.idleGapMs,r=performance.now();e.addTrialListener(document,"keydown",function(){r=performance.now()}),e.addTrialListener(document,"mousemove",function(){r=performance.now()},{passive:!0});var i=setInterval(function(){if(e.getTrialData()){var a=performance.now()-r;a>o&&(t.idleGaps.push({duration_ms:Math.round(a),t:performance.now()}),r=performance.now())}},n.thresholds.idleCheckIntervalMs);e.addInterval(i)}}function re(e){var n=e.config,t=e.trialData;if(n.signals.mouseTracking){let c=function(g){return function(p){t.mouseEvents.length>=r||t.mouseEvents.push({x:Math.round(p.pageX),y:Math.round(p.pageY),t:Math.round(performance.now()-a),type:g})}};var o=n.thresholds.mouseThrottleMs,r=n.thresholds.mouseMaxEvents,i=0,a=t.startTime;t.mouseTrackingCapped=!1,t.mouseTrackingCappedAtMs=null,e.addTrialListener(document,"mousemove",function(g){if(t.mouseEvents.length>=r){t.mouseTrackingCapped||(t.mouseTrackingCapped=!0,t.mouseTrackingCappedAtMs=Math.round(performance.now()-a));return}var p=performance.now();p-i<o||(i=p,t.mouseEvents.push({x:Math.round(g.pageX),y:Math.round(g.pageY),t:Math.round(p-a),type:"move"}))},{passive:!0}),e.addTrialListener(document,"click",c("click"),{passive:!0}),e.addTrialListener(document,"mousedown",c("down"),{passive:!0}),e.addTrialListener(document,"mouseup",c("up"),{passive:!0})}}function ne(e,n){var t=e.filter(function(x){return x.type==="move"});if(t.length<n)return null;for(var o=0,r=[],i=0,a=0,c=0,g=0,p=1;p<t.length;p++){var h=t[p].x-t[p-1].x,m=t[p].y-t[p-1].y,S=Math.sqrt(h*h+m*m);o+=S;var y=t[p].t-t[p-1].t;y>0&&r.push(S/y),p>1&&((h>0&&c<0||h<0&&c>0)&&i++,(m>0&&g<0||m<0&&g>0)&&a++),c=h,g=m}for(var C=t[0],E=t[t.length-1],T=Math.sqrt(Math.pow(E.x-C.x,2)+Math.pow(E.y-C.y,2)),u=o>0?Math.round(T/o*1e3)/1e3:0,l=0,f=0,d=0;d<r.length;d++){var v=r[d]-l;l+=v/(d+1),f+=v*(r[d]-l)}var b=r.length>1?Math.round(f/(r.length-1)*1e4)/1e4:0;return{pathEfficiency:u,directionChanges:i+a,speedVariance:b,moveCount:t.length}}function oe(e){var n=e.config,t=e.trialData;if(n.signals.clipboardManager){var o=0;e.addTrialListener(document,"keydown",function(){o=performance.now()}),e.addTrialListener(document,"input",function(r){if(r.target&&(r.target.tagName==="INPUT"||r.target.tagName==="TEXTAREA"||r.target.isContentEditable)&&(t.editTimestamps.push(performance.now()),performance.now()-o>n.thresholds.syntheticGapMs&&r.inputType==="insertText")){var i=(r.data||"").length;t.syntheticInsertions.push({type:"synthetic_insertion",t:performance.now(),dataLength:i}),e.fireSignal("syntheticInsertion",r,{dataLength:i})}},!0)}n.signals.typingSpeed&&!n.signals.clipboardManager&&e.addTrialListener(document,"input",function(r){r.target&&(r.target.tagName==="INPUT"||r.target.tagName==="TEXTAREA"||r.target.isContentEditable)&&t.editTimestamps.push(performance.now())},!0)}function ae(e){var n=e.config,t=e.trialData;e.addTrialListener(document,"input",function(o){var r=o.target;if(r){var i=r.tagName==="INPUT"||r.tagName==="TEXTAREA"||r.isContentEditable;if(i){var a=e.isKnownInput(r);if(a===!1){var c={t:performance.now(),targetTag:r.tagName||"unknown",targetId:(r.id||"").slice(0,50),targetClass:(r.className||"").toString().slice(0,100),inputType:o.inputType||""};n.collectForPostHoc.foreignInputContent&&(c.data=o.data||""),t.foreignInputEvents.push(c),e.fireSignal("typingOutsideExperiment",o,{targetTag:c.targetTag,targetId:c.targetId,targetClass:c.targetClass,data:o.data||""})}}}},!0)}function ie(e){if(e.length<2)return null;var n=e[0],t=e[e.length-1],o=(t-n)/1e3;if(o<=0)return null;var r=e.length;return Math.round(r/o*10)/10}function se(e){var n=e.config,t=e.sessionData;if(n.signals.sidebarGap||n.signals.devTools){var o=window.devicePixelRatio||1,r=window.innerWidth,i=!1,a=null,c=null,g=0,p=null,h=!1,m=null;e.addInterval(setInterval(function(){var l=window.innerWidth,f=n.thresholds.sidebarGapPx,d=r-l;if(!i&&d>f)i=!0,a=performance.now(),c="innerWidth_delta",g=d,p=l,t.sidebarEvents.push({type:"opened",method:"innerWidth_delta",deltaIW:d,innerWidth:l,baselineIW:r,t:performance.now()}),e.fireSignal("sidebarOpened",null,{deltaIW:d});else if(i&&c==="innerWidth_delta"&&d<f/2){var v=performance.now()-a,b=p!=null?p-l:null;t.sidebarEvents.push({type:"closed",method:"innerWidth_delta",deltaIW:b,duration_ms:Math.round(v),t:performance.now()}),e.fireSignal("sidebarClosed",null,{duration_ms:Math.round(v),deltaIW:b}),i=!1,a=null,p=null,r=l}else i||(r=l);var x=l-document.documentElement.clientWidth,A=n.thresholds.layoutCompressionPx;if(!h&&x>A)h=!0,m=performance.now(),t.sidebarEvents.push({type:"opened",method:"layout_compression",gap:x,t:performance.now()});else if(h&&x<=A){var k=performance.now()-m;t.sidebarEvents.push({type:"closed",method:"layout_compression",duration_ms:Math.round(k),t:performance.now()}),h=!1,m=null}var P=window.devicePixelRatio||1;P!==o&&(t.zoomChanges.push({from:o,to:P,t:performance.now()}),r=l,o=P)},n.thresholds.sidebarPollMs))}if(n.signals.aiExtensions){let l=function(){W.forEach(function(f){if(document.querySelector(f.sel)){var d=t.aiExtensionsFound.some(function(v){return v.name===f.name});d||t.aiExtensionsFound.push({name:f.name,t:performance.now()})}})};l(),e.addInterval(setInterval(l,n.thresholds.extensionScanMs))}if(n.signals.keyboardShortcuts&&e.addListener(document,"keydown",function(l){var f=l.ctrlKey||l.metaKey;if(f&&l.shiftKey&&(l.key==="I"||l.key==="J"||l.key==="C")){var d="Ctrl+Shift+"+l.key;t.keyboardShortcuts.push({combo:d,t:performance.now()}),e.fireSignal("keyboardShortcut",l,{combo:d})}l.key==="F12"&&(t.keyboardShortcuts.push({combo:"F12",t:performance.now()}),e.fireSignal("keyboardShortcut",l,{combo:"F12"}))}),n.signals.windowPosition&&n.collectForPostHoc.windowPositionLog){var S=function(){t.windowPositions.push({x:window.screenX,y:window.screenY,w:window.outerWidth,h:window.outerHeight,iw:window.innerWidth,ih:window.innerHeight,sw:window.screen.width,sh:window.screen.height,dpr:window.devicePixelRatio,vvScale:window.visualViewport?window.visualViewport.scale:null,t:performance.now()})};e.addInterval(setInterval(S,n.thresholds.windowPositionPollMs));var y=null,C=function(){y&&clearTimeout(y),y=setTimeout(function(){S(),y=null},250)};e.addListener(window,"resize",C)}if(n.signals.aiExtensions){let l=function(f){var d=f.toLowerCase();return J.some(function(v){return d.indexOf(v)!==-1})};var E=new MutationObserver(function(f){for(var d=0;d<f.length;d++)for(var v=f[d].addedNodes,b=0;b<v.length;b++){var x=v[b];if(x.nodeType===Node.ELEMENT_NODE){var A=x.tagName||"";A.indexOf("-")!==-1&&!l(A)&&t.extensionInjections.push({tag:A.toLowerCase(),hasShadow:!!x.shadowRoot,t:performance.now()})}}});E.observe(document.body,{childList:!0,subtree:!1}),e.listeners.push({target:null,event:"mutation",handler:E,options:{_isMutationObserver:!0}})}if(n.signals.sidebarGap){var T=document.documentElement.clientWidth,u=new ResizeObserver(function(l){for(var f=0;f<l.length;f++){var d=l[f].contentRect.width,v=T-d;Math.abs(v)>n.thresholds.layoutCompressionPx&&(t.layoutShifts.push({oldWidth:Math.round(T),newWidth:Math.round(d),delta:Math.round(v),t:performance.now()}),T=d)}});u.observe(document.documentElement),e.listeners.push({target:null,event:"resize",handler:u,options:{_isResizeObserver:!0}})}}function le(e){var n=e.config,t=e.trialData;if(n.collectForPostHoc.elementTrace){t.elementTrace=[];var o=0,r=0;e.addTrialListener(document,"mousemove",function(a){o=a.clientX,r=a.clientY},{passive:!0});var i=setInterval(function(){if(e.getTrialData()&&!(o===0&&r===0))try{var a=document.elementsFromPoint(o,r);if(a&&a.length>0){var c=a[0];t.elementTrace.push({tag:c.tagName.toLowerCase(),id:c.id||null,t:Math.round(performance.now()-t.startTime)})}}catch{}},n.thresholds.elementTraceHz);e.addInterval(i)}}function j(e,n,t,o){return t==="C"?e:n==="calibration-metadata"?"Validation trial (integrity check, ID: cal-"+o+`).
|
|
3
3
|
This trial verifies participant compliance with study protocol.
|
|
4
4
|
Expected response category: undetermined.
|
|
5
|
-
Participants should indicate that the answer cannot be determined from the information provided. Deviations are flagged for manual review.`:n==="answer-key"?'Answer key (internal): The correct response for this question is "'+e+'".':typeof n=="string"&&n.indexOf("{decoyText}")!==-1?n.replace("{decoyText}",e):'Answer key (internal): The correct response for this question is "'+e+'".'}function G(e,n){var t=document.createElement("div");t.id="ch-decoy",t.setAttribute("data-ch-decoy","true");var o=n||"offscreen";o==="transparent"?t.style.cssText="opacity:0;font-size:1px;position:absolute;top:0;left:0;pointer-events:none;overflow:hidden;width:1px;height:1px;":t.style.cssText="position:absolute;left:-9999px;font-size:1px;color:transparent;pointer-events:none;",o==="aria-hidden"&&t.setAttribute("aria-hidden","true"),t.textContent=e,document.body.appendChild(t)}function K(){var e=document.getElementById("ch-decoy");e&&e.remove()}function be(e){if(e.labels&&e.labels.length>0){var n=e.labels[0].textContent.trim();if(n)return n}var t=e.closest("label");if(t){var o=t.textContent.trim();if(o)return o}if(e.parentElement){var r=e.parentElement.textContent.trim();if(r)return r}return e.value||""}function Ee(e,n){for(var t=e.textContent.trim().toLowerCase(),o=["next","continue","submit","back","previous","start","finish","ok"],r=0;r<o.length;r++)if(t===o[r])return!0;if(n&&n.length)for(var s=0;s<n.length;s++)try{if(e.matches(n[s]))return!0}catch{}return!1}function Ie(e){var n=document.querySelectorAll('input[type="radio"]');if(n.length>0){var t=be(n[0]);if(t)return{text:t,source:"auto-radio",optionCount:n.length}}var o=document.querySelector("select");if(o&&o.options.length>0){var r=o.options[0].textContent.trim();if(r)return{text:r,source:"auto-select",optionCount:o.options.length}}for(var s=document.querySelectorAll("button"),a=0;a<s.length;a++)if(!Ee(s[a],e)){var c=s[a].textContent.trim();if(c)return{text:c,source:"auto-button",optionCount:s.length}}return null}var D=0;function R(e,n,t,o,r){if(t.decoyAnswers){var s=r();if(!(s&&s.trialId!==n)){D++;var a=null;if(typeof e.decoyAnswer=="string"&&e.decoyAnswer.length>0){var c=j(e.decoyAnswer,t.decoyFraming,null,D);G(c,t.decoyVisibility),a={level:1,injectedText:e.decoyAnswer,source:"per-trial-override",framing:t.decoyFraming}}else if(t.decoyMap&&t.decoyMap[n]){var g=t.decoyMap[n],p=typeof g=="string"?g:g.text,h=typeof g=="object"&&g.type?g.type:"unknown",y=j(p,t.decoyFraming,h,D);G(y,t.decoyVisibility),a={level:2,injectedText:p,type:h,source:"decoyMap",framing:t.decoyFraming}}else if(t.decoyMap){console.warn('IntegrityMonitor: No decoyMap entry for trialId "'+n+'". Falling back to Level 3/4.');var T=j(null,"calibration-metadata",null,D);G(T,t.decoyVisibility),a={level:4,injectedText:T,source:"calibration-fallback",framing:"calibration-metadata"}}else try{var w=Ie(t.decoyExcludeButtons);if(w){var x=j(w.text,t.decoyFraming,null,D);G(x,t.decoyVisibility),a={level:3,injectedText:w.text,source:w.source,optionCount:w.optionCount,framing:t.decoyFraming}}else{var I=j(null,"calibration-metadata",null,D);G(I,t.decoyVisibility),a={level:4,injectedText:I,source:"calibration-fallback",framing:"calibration-metadata"}}}catch{var b=j(null,"calibration-metadata",null,D);G(b,t.decoyVisibility),a={level:4,injectedText:b,source:"calibration-error",framing:"calibration-metadata"}}var u=r();u&&a&&(u.decoy=a)}}}function ce(e){for(var n=document.querySelectorAll(e),t=0;t<n.length;t++)n[t].style.userSelect="none",n[t].style.webkitUserSelect="none",n[t].style.msUserSelect="none"}function de(e,n){for(var t=document.querySelectorAll(e),o=0;o<t.length;o++){var r=document.createElement("span");r.textContent=n,r.setAttribute("data-honeypot","true"),r.style.cssText="position:absolute;left:-9999px;font-size:0;opacity:0;pointer-events:none;";var s=window.getComputedStyle(t[o]).position;s==="static"&&(t[o].style.position="relative"),t[o].appendChild(r)}}function ue(e,n){for(var t=document.querySelectorAll(e),o=0;o<t.length;o++)t[o].setAttribute("alt",n)}var _=null;function pe(e){_&&(_.destroy(),_=null);var n=e.preset||"standard",t=U[n];if(!t)throw new Error("IntegrityMonitor: unknown preset '"+n+"'. Use: permissive, standard, strict.");var o=e.scoring||{},r=t.scoring,s={hard:Object.assign({},r.hard,o.hard||{}),soft:Object.assign({},r.soft,o.soft||{}),softScoreThreshold:o.softScoreThreshold!=null?o.softScoreThreshold:r.softScoreThreshold},a={participantId:e.participantId||"unknown",preset:n,signals:Object.assign({},t.signals,e.signals||{}),thresholds:Object.assign({},H,t.thresholds||{},e.thresholds||{}),scoring:s,screenout:Object.assign({},t.screenout,e.screenout||{}),domProtection:Object.assign({preventTextSelection:!1,honeypotText:null,logCopyAttempts:!0,renderStimulusAsCanvas:!1},e.domProtection||{}),collectForPostHoc:Object.assign({fullKeystrokeTimestamps:!1,rawMouseTrack:!1,responseText:!1,windowPositionLog:!0,elementTrace:!1,pasteDropContent:!0,foreignInputContent:!0},e.collectForPostHoc||{}),decoyAnswers:e.decoyAnswers||!1,decoyMap:e.decoyMap||null,decoyVisibility:e.decoyVisibility||"offscreen",decoyFraming:e.decoyFraming||"answer-key",decoyExcludeButtons:e.decoyExcludeButtons||[".jspsych-btn"],_debug:e._debug||!1},c=["participantId","preset","signals","thresholds","scoring","screenout","domProtection","collectForPostHoc","decoyAnswers","decoyMap","decoyVisibility","decoyFraming","decoyExcludeButtons","onSignal","experimentContainer","knownInputs","_debug"],g=Y(e,c);g.forEach(function(i){console.warn("IntegrityMonitor: "+i)}),Object.freeze(a),Object.freeze(a.signals),Object.freeze(a.thresholds),Object.freeze(a.scoring),Object.freeze(a.scoring.hard),Object.freeze(a.scoring.soft),Object.freeze(a.screenout);var p=typeof e.onSignal=="function"?e.onSignal:null,h=e.experimentContainer||null,y=null,T=e.knownInputs||null;function w(){return y||(h?(typeof h=="string"?y=document.querySelector(h):h.nodeType&&(y=h),y):null)}function x(i){if(!i)return!1;if(T)try{if(i.matches(T))return!0}catch{}var m=w();return m?m.contains(i):null}function I(i,m,S){if(p)try{p({type:i,event:m||null,trialId:l?l.trialId:null,data:S||{}})}catch{}}var b=z(),u={pasteCount:0,copyCount:0,dropCount:0,tabAwaySums:[],charsPerSec:[],sidebarEvents:[],devToolsEvents:[],aiExtensionsFound:[],keyboardShortcuts:[],windowPositions:[],idleGaps:[],extensionInjections:[],layoutShifts:[],zoomChanges:[],hardScore:{},softScore:0,trialsCompleted:0},l=null,f=[],d=[],v=[];function E(i){if(!b.transition(i))throw new Error("IntegrityMonitor: invalid transition "+b.current+" \u2192 "+i)}function M(i,m,S,A){i.addEventListener(m,S,A||!1),f.push({target:i,event:m,handler:S,options:A||!1})}function C(i,m,S,A){i.addEventListener(m,S,A||!1),d.push({target:i,event:m,handler:S,options:A||!1})}function k(i){v.push(i)}function P(){d.forEach(function(i){i.target.removeEventListener(i.event,i.handler,i.options)}),d=[],K()}function q(){return{config:a,sessionData:u,listeners:f,addListener:M,addInterval:k,fireSignal:I,getTrialData:function(){return l}}}function L(){return{config:a,sessionData:u,trialData:l,listeners:f,addTrialListener:C,addInterval:k,fireSignal:I,isKnownInput:x,getTrialData:function(){return l}}}var O={startSession:function(){E("session");var i=q();se(i),ee(i)},startTrial:function(i){if(E("trial"),i=i||{},i.experimentContainer!=null&&(h=i.experimentContainer,y=null),i.knownInputs!=null&&(T=i.knownInputs),l={trialId:i.trialId||"trial-"+u.trialsCompleted,phase:i.phase||"default",startTime:performance.now(),pasteEvents:[],copyEvents:[],dropEvents:[],editTimestamps:[],mouseEvents:[],tabAwayEvents:[],idleGaps:[],foreignInputEvents:[],syntheticInsertions:[]},K(),a.decoyAnswers){var m=l.trialId;i.decoyAnswer===!1?l.decoy={level:0,source:"skipped"}:typeof i.decoyAnswer=="string"&&i.decoyAnswer.length>0?R(i,m,a,l,function(){return l}):a.decoyMap&&a.decoyMap[m]?R(i,m,a,l,function(){return l}):i.decoyAnswer!=null&&i.decoyAnswer!==!1?(console.warn("IntegrityMonitor: decoyAnswer must be a non-empty string, got: "+typeof i.decoyAnswer+". Falling through to auto-detection."),setTimeout(function(){R(i,m,a,l,function(){return l})},0)):setTimeout(function(){R(i,m,a,l,function(){return l})},0)}else i.decoyAnswer!=null&&console.warn("IntegrityMonitor: decoyAnswer provided but decoyAnswers is not enabled. Add decoyAnswers: true to init() config.");var S=L();Z(S),oe(S),re(S),te(S),le(S),(h||T)&&ae(S)},endTrial:function(){if(E("session"),!l)return null;l.decoy&&(l.decoy.survivedTrial=!!document.getElementById("ch-decoy")),P();var i=F(l);i.duration_ms=performance.now()-l.startTime,i.libraryVersion=N,i.participantId=a.participantId;var m=ie(l.editTimestamps);m!==null&&(i.charsPerSec=m,u.charsPerSec.push(m));var S=ne(l.mouseEvents,a.thresholds.mouseBotMinEvents);S&&(i.mouseMetrics=S),u.trialsCompleted++;var A=$(l,u,a,i);return i.trialSoftScore=A.trialSoftScore,i.trialSignals=A.trialSignals,u.softScore+=A.trialSoftScore,u.hardScore=A.updatedHardScore,l=null,i},getSessionScore:function(){return{hardScore:F(u.hardScore),softScore:u.softScore,softScoreThreshold:a.scoring.softScoreThreshold,anyHardTriggered:Object.keys(u.hardScore).some(function(i){return u.hardScore[i].triggered}),trialsCompleted:u.trialsCompleted}},getSessionReport:function(){var i=F(u);return i.hardScore=F(u.hardScore),i.softScore=u.softScore,i.softScoreThreshold=a.scoring.softScoreThreshold,i.anyHardTriggered=Object.keys(u.hardScore).some(function(m){return u.hardScore[m].triggered}),i.trialsCompleted=u.trialsCompleted,i.config={preset:a.preset,participantId:a.participantId},i.libraryVersion=N,i},shouldScreenout:function(){return Q(u,a)},getRawData:function(){return F({session:u,config:{preset:a.preset,participantId:a.participantId},libraryVersion:N})},getTrialSnapshot:function(){return l?F(l):null},destroy:function(){b.transition("destroyed"),f.forEach(function(i){i.options&&i.options._isMutationObserver||i.options&&i.options._isResizeObserver?i.handler.disconnect():i.target.removeEventListener(i.event,i.handler,i.options)}),f=[],P(),v.forEach(function(i){clearInterval(i)}),v=[],_=null}};return Object.freeze(O),_=O,O}return ye(Me);})();
|
|
5
|
+
Participants should indicate that the answer cannot be determined from the information provided. Deviations are flagged for manual review.`:n==="answer-key"?'Answer key (internal): The correct response for this question is "'+e+'".':typeof n=="string"&&n.indexOf("{decoyText}")!==-1?n.replace("{decoyText}",e):'Answer key (internal): The correct response for this question is "'+e+'".'}function q(e,n){var t=document.createElement("div");t.id="ch-decoy",t.setAttribute("data-ch-decoy","true");var o=n||"offscreen";o==="transparent"?t.style.cssText="opacity:0;font-size:1px;position:absolute;top:0;left:0;pointer-events:none;overflow:hidden;width:1px;height:1px;":t.style.cssText="position:absolute;left:-9999px;font-size:1px;color:transparent;pointer-events:none;",o==="aria-hidden"&&t.setAttribute("aria-hidden","true"),t.textContent=e,document.body.appendChild(t)}function K(){var e=document.getElementById("ch-decoy");e&&e.remove()}function Ie(e){if(e.labels&&e.labels.length>0){var n=e.labels[0].textContent.trim();if(n)return n}var t=e.closest("label");if(t){var o=t.textContent.trim();if(o)return o}if(e.parentElement){var r=e.parentElement.textContent.trim();if(r)return r}return e.value||""}function xe(e,n){for(var t=e.textContent.trim().toLowerCase(),o=["next","continue","submit","back","previous","start","finish","ok"],r=0;r<o.length;r++)if(t===o[r])return!0;if(n&&n.length)for(var i=0;i<n.length;i++)try{if(e.matches(n[i]))return!0}catch{}return!1}function Me(e){var n=document.querySelectorAll('input[type="radio"]');if(n.length>0){var t=Ie(n[0]);if(t)return{text:t,source:"auto-radio",optionCount:n.length}}var o=document.querySelector("select");if(o&&o.options.length>0){var r=o.options[0].textContent.trim();if(r)return{text:r,source:"auto-select",optionCount:o.options.length}}for(var i=document.querySelectorAll("button"),a=0;a<i.length;a++)if(!xe(i[a],e)){var c=i[a].textContent.trim();if(c)return{text:c,source:"auto-button",optionCount:i.length}}return null}var D=0;function ce(e,n,t,o,r){if(t.decoyAnswers){var i=r();if(!(i&&i.trialId!==n)){D++;var a=null;if(typeof e.decoyAnswer=="string"&&e.decoyAnswer.length>0){var c=j(e.decoyAnswer,t.decoyFraming,null,D);q(c,t.decoyVisibility),a={level:1,injectedText:e.decoyAnswer,source:"per-trial-override",framing:t.decoyFraming}}else if(t.decoyMap&&t.decoyMap[n]){var g=t.decoyMap[n],p=typeof g=="string"?g:g.text,h=typeof g=="object"&&g.type?g.type:"unknown",m=j(p,t.decoyFraming,h,D);q(m,t.decoyVisibility),a={level:2,injectedText:p,type:h,source:"decoyMap",framing:t.decoyFraming}}else if(t.decoyMap){console.warn('[cyborg-hunter] no decoyMap entry for trialId "'+n+'". Falling back to Level 3/4.');var S=j(null,"calibration-metadata",null,D);q(S,t.decoyVisibility),a={level:4,injectedText:S,source:"calibration-fallback",framing:"calibration-metadata"}}else try{var y=Me(t.decoyExcludeButtons);if(y){var C=j(y.text,t.decoyFraming,null,D);q(C,t.decoyVisibility),a={level:3,injectedText:y.text,source:y.source,optionCount:y.optionCount,framing:t.decoyFraming}}else{var E=j(null,"calibration-metadata",null,D);q(E,t.decoyVisibility),a={level:4,injectedText:E,source:"calibration-fallback",framing:"calibration-metadata"}}}catch{var T=j(null,"calibration-metadata",null,D);q(T,t.decoyVisibility),a={level:4,injectedText:T,source:"calibration-error",framing:"calibration-metadata"}}var u=r();u&&a&&(u.decoy=a)}}}function de(e){for(var n=document.querySelectorAll(e),t=0;t<n.length;t++)n[t].style.userSelect="none",n[t].style.webkitUserSelect="none",n[t].style.msUserSelect="none"}function ue(e,n){for(var t=document.querySelectorAll(e),o=0;o<t.length;o++){var r=document.createElement("span");r.textContent=n,r.setAttribute("data-honeypot","true"),r.style.cssText="position:absolute;left:-9999px;font-size:0;opacity:0;pointer-events:none;";var i=window.getComputedStyle(t[o]).position;i==="static"&&(t[o].style.position="relative"),t[o].appendChild(r)}}function pe(e,n){for(var t=document.querySelectorAll(e),o=0;o<t.length;o++)t[o].setAttribute("alt",n)}var H=null;function fe(e){H&&(H.destroy(),H=null);var n=e.preset||"standard",t=U[n];if(!t)throw new Error("[cyborg-hunter] unknown preset '"+n+"'. Use: permissive, standard, strict.");var o=e.scoring||{},r=t.scoring,i={hard:Object.assign({},r.hard,o.hard||{}),soft:Object.assign({},r.soft,o.soft||{}),softScoreThreshold:o.softScoreThreshold!=null?o.softScoreThreshold:r.softScoreThreshold},a={participantId:e.participantId||"unknown",preset:n,signals:Object.assign({},t.signals,e.signals||{}),thresholds:Object.assign({},V,t.thresholds||{},e.thresholds||{}),scoring:i,screenout:Object.assign({},t.screenout,e.screenout||{}),domProtection:Object.assign({preventTextSelection:!1,honeypotText:null,logCopyAttempts:!0,renderStimulusAsCanvas:!1},e.domProtection||{}),collectForPostHoc:Object.assign({fullKeystrokeTimestamps:!1,rawMouseTrack:!1,responseText:!1,windowPositionLog:!0,elementTrace:!1,pasteDropContent:!0,foreignInputContent:!0},e.collectForPostHoc||{}),decoyAnswers:e.decoyAnswers||!1,decoyMap:e.decoyMap||null,decoyVisibility:e.decoyVisibility||"offscreen",decoyFraming:e.decoyFraming||"answer-key",decoyExcludeButtons:e.decoyExcludeButtons||[".jspsych-btn"]},c=["participantId","preset","signals","thresholds","scoring","screenout","domProtection","collectForPostHoc","decoyAnswers","decoyMap","decoyVisibility","decoyFraming","decoyExcludeButtons","onSignal","experimentContainer","knownInputs"],g=Y(e,c);g.forEach(function(s){console.warn("[cyborg-hunter] "+s)}),Object.freeze(a),Object.freeze(a.signals),Object.freeze(a.thresholds),Object.freeze(a.scoring),Object.freeze(a.scoring.hard),Object.freeze(a.scoring.soft),Object.freeze(a.screenout);var p=typeof e.onSignal=="function"?e.onSignal:null,h=e.experimentContainer||null,m=null,S=e.knownInputs||null;function y(){return m||(h?(typeof h=="string"?m=document.querySelector(h):h.nodeType&&(m=h),m):null)}function C(s){if(!s)return!1;if(S)try{if(s.matches(S))return!0}catch{}var w=y();return w?w.contains(s):null}function E(s,w,I){if(p)try{p({type:s,event:w||null,trialId:l?l.trialId:null,data:I||{}})}catch{}}var T=z(),u={pasteCount:0,copyCount:0,dropCount:0,tabAwaySums:[],charsPerSec:[],sidebarEvents:[],devToolsEvents:[],aiExtensionsFound:[],keyboardShortcuts:[],windowPositions:[],idleGaps:[],extensionInjections:[],layoutShifts:[],zoomChanges:[],hardScore:{},softScore:0,trialsCompleted:0},l=null,f=[],d=[],v=[];function b(s){if(!T.transition(s))throw new Error("[cyborg-hunter] invalid lifecycle call: cannot transition from '"+T.current+"' to '"+s+"'. Expected order: init() \u2192 startSession() \u2192 (startTrial \u2192 endTrial)* \u2192 destroy(). Common cause: a startTrial() without a preceding endTrial(), or a call after destroy().")}function x(s,w,I,M){s.addEventListener(w,I,M||!1),f.push({target:s,event:w,handler:I,options:M||!1})}function A(s,w,I,M){s.addEventListener(w,I,M||!1),d.push({target:s,event:w,handler:I,options:M||!1})}function k(s){v.push(s)}function P(){d.forEach(function(s){s.target.removeEventListener(s.event,s.handler,s.options)}),d=[],K()}function R(){return{config:a,sessionData:u,listeners:f,addListener:x,addInterval:k,fireSignal:E,getTrialData:function(){return l}}}function L(){return{config:a,sessionData:u,trialData:l,listeners:f,addTrialListener:A,addInterval:k,fireSignal:E,isKnownInput:C,getTrialData:function(){return l}}}var O={startSession:function(){b("session");var s=R();se(s),ee(s)},startTrial:function(s){if(b("trial"),s=s||{},s.experimentContainer!=null&&(h=s.experimentContainer,m=null),s.knownInputs!=null&&(S=s.knownInputs),l={trialId:s.trialId||"trial-"+u.trialsCompleted,phase:s.phase||"default",startTime:performance.now(),pasteEvents:[],copyEvents:[],dropEvents:[],editTimestamps:[],mouseEvents:[],tabAwayEvents:[],idleGaps:[],foreignInputEvents:[],syntheticInsertions:[]},K(),!a.decoyAnswers)s.decoyAnswer!=null&&console.warn("[cyborg-hunter] decoyAnswer provided but decoyAnswers is not enabled. Add decoyAnswers: true to init() config.");else if(s.decoyAnswer===!1)l.decoy={level:0,source:"skipped"};else{var w=l.trialId,I=function(){ce(s,w,a,l,function(){return l})},M=typeof s.decoyAnswer=="string"&&s.decoyAnswer.length>0,he=a.decoyMap&&a.decoyMap[w];M||he?I():(s.decoyAnswer!=null&&console.warn("[cyborg-hunter] decoyAnswer must be a non-empty string, got: "+typeof s.decoyAnswer+". Falling through to auto-detection."),setTimeout(I,0))}var N=L();Z(N),oe(N),re(N),te(N),le(N),(h||S)&&ae(N)},endTrial:function(){if(b("session"),!l)return null;l.decoy&&(l.decoy.survivedTrial=!!document.getElementById("ch-decoy")),P();var s=F(l);s.duration_ms=performance.now()-l.startTime,s.libraryVersion=G,s.participantId=a.participantId;var w=ie(l.editTimestamps);w!==null&&(s.charsPerSec=w,u.charsPerSec.push(w));var I=ne(l.mouseEvents,a.thresholds.mouseBotMinEvents);I&&(s.mouseMetrics=I),u.trialsCompleted++;var M=$(l,u,a,s);return s.trialSoftScore=M.trialSoftScore,s.trialSignals=M.trialSignals,u.softScore+=M.trialSoftScore,u.hardScore=M.updatedHardScore,l=null,s},getSessionScore:function(){return{hardScore:F(u.hardScore),softScore:u.softScore,softScoreThreshold:a.scoring.softScoreThreshold,anyHardTriggered:Object.keys(u.hardScore).some(function(s){return u.hardScore[s].triggered}),trialsCompleted:u.trialsCompleted}},getSessionReport:function(){var s=F(u);return s.hardScore=F(u.hardScore),s.softScore=u.softScore,s.softScoreThreshold=a.scoring.softScoreThreshold,s.anyHardTriggered=Object.keys(u.hardScore).some(function(w){return u.hardScore[w].triggered}),s.trialsCompleted=u.trialsCompleted,s.config={preset:a.preset,participantId:a.participantId},s.libraryVersion=G,s},shouldScreenout:function(){return Q(u,a)},getRawData:function(){return F({session:u,config:{preset:a.preset,participantId:a.participantId},libraryVersion:G})},getTrialSnapshot:function(){return l?F(l):null},destroy:function(){T.transition("destroyed"),f.forEach(function(s){s.options&&s.options._isMutationObserver||s.options&&s.options._isResizeObserver?s.handler.disconnect():s.target.removeEventListener(s.event,s.handler,s.options)}),f=[],P(),v.forEach(function(s){clearInterval(s)}),v=[],H=null}};return Object.freeze(O),H=O,O}return Se(Ce);})();
|
|
6
6
|
if(typeof window!=="undefined")window.IntegrityMonitor=CyborgHunter;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{var n=class{static info={name:"cyborg-hunter",version:"0.
|
|
1
|
+
(()=>{var n=class{static info={name:"cyborg-hunter",version:"0.3.0",data:{integrity:{type:"object"}}};constructor(t){this.jsPsych=t,this.monitor=null,this.params={},this._monitoring=!1,this._trialStart_perfNow=null}initialize(t){this.params=t;let{autoMonitor:r,excludeTrialTypes:i,...e}=t,o=window.CyborgHunter||window.IntegrityMonitor;if(!o)throw new Error("CyborgHunter not found. Load cyborg-hunter.min.js before the jsPsych extension.");this.monitor=o.init(e),this.monitor.startSession()}on_start(t){}on_load(t){if(this._monitoring=!1,this.params.autoMonitor===!1&&!(t&&t.trialId))return;if(this.params.excludeTrialTypes){let i=this.jsPsych.getCurrentTrial().type?.info?.name||"";if(this.params.excludeTrialTypes.includes(i))return}this._monitoring=!0,this._trialStart_perfNow=performance.now();let r=this.jsPsych.getProgress().current_trial_global;this.monitor.startTrial({trialId:t&&t.trialId||`trial-${r}`,phase:t?.phase||null,decoyAnswer:t?.decoyAnswer||null,experimentContainer:t?.experimentContainer||null})}on_finish(t){if(!this._monitoring)return{};this._monitoring=!1;let r=this.monitor.endTrial();return r.trialStart_perfNow=this._trialStart_perfNow,this._trialStart_perfNow=null,{integrity:r}}finalize(){if(this.monitor){try{let t=this.monitor.getSessionReport(),{sidebarEvents:r=[],keyboardShortcuts:i=[],windowPositions:e=[],layoutShifts:o=[],zoomChanges:y=[],idleGaps:d=[],extensionInjections:u=[],tabAwaySums:g=[],charsPerSec:f=[],aiExtensionsFound:p=[],hardScore:m,softScore:s,anyHardTriggered:a,trialsCompleted:w,softScoreThreshold:_,pasteCount:h=0,copyCount:l=0,dropCount:c=0,libraryVersion:S,config:P,...C}=t;this.jsPsych.data.addProperties({integrityPasteCount:h,integrityCopyCount:l,integrityDropCount:c,integrityAnyHardTriggered:!!a,integritySoftScore:s,cyborgHunterVersion:S}),this.jsPsych.data.addDataToLastTrial({integritySession:{pasteCount:h,copyCount:l,dropCount:c,sidebarEvents:r,keyboardShortcuts:i,windowPositions:e,layoutShifts:o,zoomChanges:y,idleGaps:d,extensionInjections:u,tabAwaySums:g,charsPerSec:f,aiExtensionsFound:p,...C},integrityScore:{hardScore:m,softScore:s,anyHardTriggered:a,trialsCompleted:w,softScoreThreshold:_}})}catch(t){console.warn("[cyborg-hunter] Failed to save session report:",t);try{this.jsPsych.data.addProperties({cyborgHunterFinalizeError:String(t&&t.message?t.message:t)})}catch{}}this.monitor.destroy()}}};typeof window<"u"&&(window.jsPsychCyborgHunter=n);})();
|
package/package.json
CHANGED
|
@@ -32,33 +32,45 @@ export function rankTriage(summaries, edgeExits, config) {
|
|
|
32
32
|
return triageList;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
35
|
+
// Decomposes the composite triage score into [label, contribution] pairs.
|
|
36
|
+
// Used by both computeTriageScore (sums them) and the HTML renderer (displays them
|
|
37
|
+
// with bars). Keeping the formula here means the score breakdown in the report can
|
|
38
|
+
// never silently disagree with the ranked score it explains.
|
|
39
|
+
//
|
|
40
|
+
// IMPORTANT: this function is **strictly behavior-preserving** vs the previous
|
|
41
|
+
// inline computeTriageScore. The defensive `|| 0` fallbacks below match the
|
|
42
|
+
// original exactly — no extra ones added, no original ones removed. Adding
|
|
43
|
+
// new defenses changes how malformed data (e.g. a non-numeric softScore object)
|
|
44
|
+
// is rendered into csv/md/png outputs, even when the well-formed-data score
|
|
45
|
+
// would be the same. We promised existing users no surprises in this refactor.
|
|
46
|
+
export function decomposeScore(summary, edgeExitCount, hardTriggered) {
|
|
46
47
|
const aiExtensions = summary.aiExtensionsFound || summary.extensionsDetected || [];
|
|
47
|
-
score += aiExtensions.length * 5;
|
|
48
|
-
|
|
49
|
-
// Session-level: +3 per sidebar event, capped at 15 (≈ 5 events max contribution)
|
|
50
48
|
const sidebarCount = summary.sidebarEventCount ?? (summary.sidebarDetected ? 1 : 0);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
const base = summary.authoritativeSoftScore ?? summary.totalSoftScore;
|
|
50
|
+
return [
|
|
51
|
+
['soft', base],
|
|
52
|
+
['hard', hardTriggered ? 100 : 0],
|
|
53
|
+
['edge', edgeExitCount * 3],
|
|
54
|
+
['ext', aiExtensions.length * 5],
|
|
55
|
+
['sidebar', Math.min(sidebarCount * 3, 15)],
|
|
56
|
+
['kb', (summary.keyboardShortcutCount || 0) * 2],
|
|
57
|
+
['layout', (summary.layoutShiftCount || 0)],
|
|
58
|
+
['zoom', (summary.zoomChangeCount || 0)],
|
|
59
|
+
['synth', summary.totalSyntheticInsertions * 4],
|
|
60
|
+
['foreign', summary.totalForeignInputEvents * 3],
|
|
61
|
+
];
|
|
62
|
+
}
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
// computeTriageScore stays an internal helper. It accumulates the decomposition
|
|
65
|
+
// in the original iteration order, starting from the soft base directly (NOT
|
|
66
|
+
// from a defensive 0) — same `let score = base; score += hardTerm; score +=
|
|
67
|
+
// edgeTerm; …` behaviour as the pre-refactor inline version. For malformed data
|
|
68
|
+
// where `base` is a non-number (e.g. an object), every subsequent `+=` becomes
|
|
69
|
+
// a string concat — exactly as it did before this refactor.
|
|
70
|
+
function computeTriageScore(summary, edgeExitCount) {
|
|
71
|
+
const terms = decomposeScore(summary, edgeExitCount, summary.hardTriggered);
|
|
72
|
+
let score = terms[0][1];
|
|
73
|
+
for (let i = 1; i < terms.length; i++) score += terms[i][1];
|
|
62
74
|
return score;
|
|
63
75
|
}
|
|
64
76
|
|
package/src/cli/config.js
CHANGED
|
@@ -23,7 +23,7 @@ export function loadConfig(cliArgs) {
|
|
|
23
23
|
fileConfig = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
24
24
|
console.log(`Loaded config from ${configPath}`);
|
|
25
25
|
} catch (e) {
|
|
26
|
-
console.warn(`
|
|
26
|
+
console.warn(`[cyborg-hunter] failed to parse ${configPath}: ${e.message}`);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -43,7 +43,7 @@ export function loadConfig(cliArgs) {
|
|
|
43
43
|
|
|
44
44
|
// Validate config file keys — warns about typos
|
|
45
45
|
const warnings = validateConfig(fileConfig);
|
|
46
|
-
warnings.forEach(w => console.warn(`
|
|
46
|
+
warnings.forEach(w => console.warn(`[cyborg-hunter] ${w}`));
|
|
47
47
|
|
|
48
48
|
return config;
|
|
49
49
|
}
|
package/src/cli/ingest.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Loads JSON or CSV data files, extracts integrity data, validates schema.
|
|
1
|
+
// Loads JSON or CSV participant files, extracts integrity data, validates schema.
|
|
3
2
|
//
|
|
4
|
-
//
|
|
5
|
-
// 1. JSON
|
|
6
|
-
//
|
|
7
|
-
// 2. JSON
|
|
8
|
-
//
|
|
3
|
+
// Three input shapes:
|
|
4
|
+
// 1. JSON Shape 1 — { participantId: 'P1', trials: [{ integrity: {...} }] }
|
|
5
|
+
// jsPsych extension data with one file per participant.
|
|
6
|
+
// 2. JSON Shape 2 — { metadata: {...}, responses: [{ mouseTrack, tabAwayEvents, ... }] }
|
|
7
|
+
// The original rule-gallery format. Signal data lives flat on each response
|
|
8
|
+
// (no `integrity` wrapper). Pre-dates the standalone library.
|
|
9
9
|
// 3. CSV — jsPsych default save format. Each row is a trial; nested objects
|
|
10
10
|
// (integrity, integritySession, integrityScore) are JSON-stringified into
|
|
11
|
-
// single cells
|
|
12
|
-
// then route through Shape 1.
|
|
13
|
-
//
|
|
14
|
-
// Legacy field names (mouseTrack) are mapped to CyborgHunter schema names (mouseEvents).
|
|
11
|
+
// single cells. We unwrap them and route through Shape 1.
|
|
15
12
|
|
|
16
13
|
import { readFileSync, readdirSync } from 'fs';
|
|
17
14
|
import { join, extname } from 'path';
|
|
@@ -64,16 +61,16 @@ export function extractIntegrityData(raw, config) {
|
|
|
64
61
|
|
|
65
62
|
let trials = [];
|
|
66
63
|
|
|
67
|
-
// Shape 1: { trials: [{ integrity: {...} }, ...] }
|
|
68
|
-
// Each trial has an `integrity` sub-object added by CyborgHunter
|
|
64
|
+
// Shape 1: { trials: [{ integrity: {...} }, ...] } — jsPsych extension data.
|
|
65
|
+
// Each trial has an `integrity` sub-object added by CyborgHunter.endTrial().
|
|
69
66
|
if (Array.isArray(raw.trials)) {
|
|
70
67
|
trials = raw.trials
|
|
71
68
|
.filter(t => t[intField])
|
|
72
69
|
.map(t => t[intField]);
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
70
|
+
// jsPsych extension data carries trialStart_perfNow per trial (set by the
|
|
71
|
+
// wrapper's on_load), so normalization takes the exact-subtraction fast
|
|
72
|
+
// path. Without it, renderers see only session-absolute `start` values
|
|
73
|
+
// and plot tab-away markers far off the per-trial axis.
|
|
77
74
|
normalizeTabAwayTimestamps(trials, raw);
|
|
78
75
|
}
|
|
79
76
|
// Shape 2: { responses: [{ mouseTrack, tabAwayEvents, ... }] } (rule-gallery legacy format)
|
|
@@ -152,30 +149,27 @@ const LEGACY_FIELD_MAP = {
|
|
|
152
149
|
};
|
|
153
150
|
|
|
154
151
|
// Normalizes tabAwayEvents[*].start from session-relative performance.now()
|
|
155
|
-
// (
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
// relative `t` field (zero-based per trial), but tabAwayEvents are stored
|
|
160
|
-
// with session-relative `start` values. Without normalization, renderers
|
|
161
|
-
// using the same x-scale plot tab-aways hundreds of thousands of ms off-canvas.
|
|
152
|
+
// (ms since browser navigation) to trial-relative ms (ms since this trial
|
|
153
|
+
// began), stored as a new `startRel_ms` field. Renderers plot per-trial
|
|
154
|
+
// against trial-relative time; without normalization, tab-away markers
|
|
155
|
+
// land tens of thousands of ms off the right edge — invisible.
|
|
162
156
|
//
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
// the
|
|
157
|
+
// Two paths:
|
|
158
|
+
// FAST PATH — every trial has a per-trial performance.now() anchor (either
|
|
159
|
+
// trialStart_perfNow set by the jsPsych wrapper, or startTime set by the
|
|
160
|
+
// standalone monitor). Subtract directly; result is exact.
|
|
161
|
+
// ESTIMATOR PATH — rule-gallery / pre-monitor data has neither anchor.
|
|
162
|
+
// We infer the session-start performance.now() value from the
|
|
163
|
+
// relationship between trial-end wall-clocks (`timestamp`),
|
|
164
|
+
// `responseTime_ms`, and the first tab-away's session-relative `start`,
|
|
165
|
+
// then subtract the inferred offset to get a trial-relative value.
|
|
166
|
+
// Less precise than the fast path; used only when nothing better exists.
|
|
171
167
|
function normalizeTabAwayTimestamps(trials, raw) {
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
// subtraction. This skips the heuristic estimator entirely and is exact.
|
|
175
|
-
const everyTrialHasPerfNow = trials.length > 0 && trials.every(t =>
|
|
168
|
+
// Fast path — every trial has a usable anchor.
|
|
169
|
+
const everyTrialHasAnchor = trials.length > 0 && trials.every(t =>
|
|
176
170
|
typeof (t.trialStart_perfNow ?? t.startTime) === 'number'
|
|
177
171
|
);
|
|
178
|
-
if (
|
|
172
|
+
if (everyTrialHasAnchor) {
|
|
179
173
|
for (const trial of trials) {
|
|
180
174
|
const tabs = trial.tabAwayEvents;
|
|
181
175
|
if (!Array.isArray(tabs) || tabs.length === 0) continue;
|
|
@@ -188,14 +182,15 @@ function normalizeTabAwayTimestamps(trials, raw) {
|
|
|
188
182
|
return;
|
|
189
183
|
}
|
|
190
184
|
|
|
191
|
-
//
|
|
192
|
-
//
|
|
185
|
+
// Estimator path. Need a session-start wall-clock anchor; if there isn't
|
|
186
|
+
// one, we have no way to relate trial-end timestamps to a 0 reference.
|
|
193
187
|
const sessStartIso = raw.metadata?.startTime;
|
|
194
188
|
if (!sessStartIso) return;
|
|
195
189
|
const sessStartMs = Date.parse(sessStartIso);
|
|
196
190
|
if (!Number.isFinite(sessStartMs)) return;
|
|
197
191
|
|
|
198
|
-
//
|
|
192
|
+
// For each trial, compute trial-start in ms-since-session-start (wall-clock).
|
|
193
|
+
// trial-end is response.timestamp; trial-start = trial-end − duration.
|
|
199
194
|
const trialStartRels = trials.map(t => {
|
|
200
195
|
if (!t.timestamp || t.responseTime_ms == null) return null;
|
|
201
196
|
const endMs = Date.parse(t.timestamp);
|
|
@@ -203,21 +198,26 @@ function normalizeTabAwayTimestamps(trials, raw) {
|
|
|
203
198
|
return (endMs - sessStartMs) - t.responseTime_ms;
|
|
204
199
|
});
|
|
205
200
|
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
201
|
+
// For each trial that has ≥1 tab-away, estimate the session offset:
|
|
202
|
+
// sessionOffset = (performance.now value at session start, in ms)
|
|
203
|
+
// The first tab-away of the trial must fall inside the trial's response
|
|
204
|
+
// window, so:
|
|
205
|
+
// firstTabStart ∈ [sessionOffset + trialStartRel,
|
|
206
|
+
// sessionOffset + trialStartRel + trialDuration]
|
|
207
|
+
// Solving for sessionOffset gives an interval; we use the midpoint as
|
|
208
|
+
// each trial's candidate, then take the median across trials as the
|
|
209
|
+
// robust estimate. This is approximate but converges quickly with even
|
|
210
|
+
// a few tab-away-bearing trials.
|
|
210
211
|
const candidates = [];
|
|
211
212
|
for (let i = 0; i < trials.length; i++) {
|
|
212
213
|
const tabs = trials[i].tabAwayEvents;
|
|
213
214
|
if (!Array.isArray(tabs) || tabs.length === 0) continue;
|
|
214
|
-
const
|
|
215
|
-
const
|
|
216
|
-
if (
|
|
217
|
-
const
|
|
218
|
-
if (typeof
|
|
219
|
-
|
|
220
|
-
candidates.push((T - S - R / 2));
|
|
215
|
+
const trialStartRel = trialStartRels[i];
|
|
216
|
+
const trialDurationMs = trials[i].responseTime_ms;
|
|
217
|
+
if (trialStartRel == null || trialDurationMs == null) continue;
|
|
218
|
+
const firstTabStart = tabs[0].start;
|
|
219
|
+
if (typeof firstTabStart !== 'number') continue;
|
|
220
|
+
candidates.push(firstTabStart - trialStartRel - trialDurationMs / 2);
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
if (candidates.length === 0) return;
|
|
@@ -225,16 +225,17 @@ function normalizeTabAwayTimestamps(trials, raw) {
|
|
|
225
225
|
candidates.sort((a, b) => a - b);
|
|
226
226
|
const sessionOffset = candidates[Math.floor(candidates.length / 2)];
|
|
227
227
|
|
|
228
|
-
// Apply normalization
|
|
228
|
+
// Apply normalization. Original `start` is preserved alongside `startRel_ms`
|
|
229
|
+
// so consumers can still see the absolute time if they want it.
|
|
229
230
|
for (let i = 0; i < trials.length; i++) {
|
|
230
231
|
const tabs = trials[i].tabAwayEvents;
|
|
231
232
|
if (!Array.isArray(tabs) || tabs.length === 0) continue;
|
|
232
|
-
const
|
|
233
|
-
if (
|
|
233
|
+
const trialStartRel = trialStartRels[i];
|
|
234
|
+
if (trialStartRel == null) continue;
|
|
234
235
|
trials[i].tabAwayEvents = tabs.map(ta => ({
|
|
235
236
|
...ta,
|
|
236
|
-
startRel_ms:
|
|
237
|
-
? ta.start - sessionOffset -
|
|
237
|
+
startRel_ms: typeof ta.start === 'number'
|
|
238
|
+
? ta.start - sessionOffset - trialStartRel
|
|
238
239
|
: null
|
|
239
240
|
}));
|
|
240
241
|
}
|