cyborg-hunter 0.7.0 → 0.7.2
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/CHANGELOG.md +46 -0
- package/CITATION.cff +2 -2
- package/README.md +4 -2
- package/bin/cyborg-hunter.js +6 -2
- package/dist/cyborg-hunter-replay.js +3 -0
- package/dist/cyborg-hunter.esm.js +1619 -0
- package/dist/cyborg-hunter.min.js +6 -0
- package/dist/extension-cyborg-hunter.js +1 -0
- package/dist/extension-guard-friction.js +36 -0
- package/dist/extension-guard-honeypot.js +1 -0
- package/package.json +8 -3
- package/src/cli/extract-core.js +552 -0
- package/src/cli/ingest.js +11 -536
- package/src/cli/preview-entry.js +36 -0
- package/src/cli/renderers/html-index-core.js +1273 -0
- package/src/cli/renderers/html-index.js +6 -1179
- package/src/cli/renderers/replay-assets.js +8 -118
- package/src/cli/renderers/replay-viewer.client.js +179 -16
- package/src/cli/renderers/session-timeline-core.js +907 -0
- package/src/cli/renderers/session-timeline.js +15 -899
- package/src/cli/renderers/trajectories-core.js +717 -0
- package/src/cli/renderers/trajectories.js +31 -696
- package/src/cli/renderers/typing-profile-core.js +211 -0
- package/src/cli/renderers/typing-profile.js +16 -192
- package/src/core/monitor.js +17 -0
- package/src/jspsych/extension-cyborg-hunter-replay.js +1 -1
- package/src/jspsych/extension-cyborg-hunter.js +1 -1
- package/src/jspsych/extension-guard-friction.js +11 -2
- package/src/replay/viewer-model.js +125 -0
- package/src/shared/constants.js +1 -1
- package/src/shared/paths.js +20 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,52 @@
|
|
|
3
3
|
All notable changes to **cyborg-hunter** are documented here. This project follows
|
|
4
4
|
[Semantic Versioning](https://semver.org).
|
|
5
5
|
|
|
6
|
+
## [0.7.2] — 2026-07-29
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- Replay viewer: a keycast overlay at the bottom of the stage shows keys as
|
|
10
|
+
they're pressed (chips appear on keydown, fade after keyup), including a
|
|
11
|
+
redacted chip for keystrokes captured with `keys:'full'` inside a
|
|
12
|
+
redacted field. Recordings made with `keys:'off'` show no keycast, since
|
|
13
|
+
there's no key data to show.
|
|
14
|
+
- Replay viewer: the buffer-cap notice is now an expandable explanation
|
|
15
|
+
(was a one-line chip) covering what `maxEventsPerTrial`/`maxCharsPerTrial`
|
|
16
|
+
actually cap (per trial, not the whole session), what happens when a
|
|
17
|
+
trial crosses one, and that both are configurable. Mirrored in
|
|
18
|
+
`docs/using-cyborg-hunter.md`'s configuration table.
|
|
19
|
+
- Replay viewer: continuous whole-session playback, default on. Pressing
|
|
20
|
+
play now rolls through every trial in the recording as one continuous
|
|
21
|
+
video instead of stopping at each trial boundary; a "pause at trial
|
|
22
|
+
boundaries" toggle restores the previous per-trial-stop behavior. A
|
|
23
|
+
"Trial k of N" indicator tracks position across the session.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Internal refactor, no behavior change: the report index renderer and its
|
|
27
|
+
three plot renderers (session-timeline, trajectories, typing-profile) are
|
|
28
|
+
each split into a pure core (string- or canvas-returning) plus a thin fs
|
|
29
|
+
wrapper; `buildViewerModel` and `getByPath` moved to pure shared modules.
|
|
30
|
+
`renderIndexHtml` also gained optional demo-mode opts (`imageSources`,
|
|
31
|
+
`inlineReplayModels`) for inline image/replay embedding, with a hash-sync
|
|
32
|
+
guard for the opaque-origin iframe the demo renders reports inside. Together
|
|
33
|
+
these let the live demo render reports and plots directly in the browser;
|
|
34
|
+
default (CLI) output is byte-identical.
|
|
35
|
+
- The live demo (https://konukcan.github.io/cyborg-hunter/) was remodeled
|
|
36
|
+
into a 13-step guided tour; not part of the npm package.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
- Raw mouse coordinates were persisted in every trial report regardless of the
|
|
40
|
+
documented off-by-default `collectForPostHoc.rawMouseTrack` toggle. Reports
|
|
41
|
+
now omit the raw track unless the toggle is enabled, in which case it
|
|
42
|
+
persists as `mouseTrack` (ingest maps it back to `mouseEvents`); the derived
|
|
43
|
+
`mouseMetrics` signal is unaffected either way.
|
|
44
|
+
|
|
45
|
+
## [0.7.1] — 2026-07-29
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
- The npm package now includes `dist/` — 0.7.0 shipped without it, so every
|
|
49
|
+
documented unpkg/script-tag URL returned 404.
|
|
50
|
+
- `cyborg-hunter --version` prints the version instead of "Unknown command".
|
|
51
|
+
|
|
6
52
|
## [0.7.0] — 2026-07-14
|
|
7
53
|
|
|
8
54
|
### Added
|
package/CITATION.cff
CHANGED
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Detects AI-tool use during browser-based behavioral experiments. Captures paste,
|
|
|
4
4
|
|
|
5
5
|
Optional companion deterrence modules (`extension-guard-friction.js`, `extension-guard-honeypot.js`) ship in the same package: friction enforces fullscreen + blocks sidebars + scrambles content during violations + asks cooperative LLMs to refuse; honeypot exposes both hidden and visible bait fields that AI agents fill while human participants don't see them.
|
|
6
6
|
|
|
7
|
+
**[Try the live demo →](https://konukcan.github.io/cyborg-hunter/)** — run the tour in your browser; nothing leaves your machine.
|
|
8
|
+
|
|
7
9
|
### Example: what a report looks like
|
|
8
10
|
|
|
9
11
|
The bundled three-participant synthetic dataset (`examples/synthetic-pilot/` — every number hand-authored, no real participant behind any of it) triages like this:
|
|
@@ -49,7 +51,7 @@ Browser (experiment page) — load via unpkg or copy `dist/*.js` into your proje
|
|
|
49
51
|
<script src="https://unpkg.com/cyborg-hunter/dist/cyborg-hunter-replay.js"></script>
|
|
50
52
|
```
|
|
51
53
|
|
|
52
|
-
For production studies, pin a version: `https://unpkg.com/cyborg-hunter@0.7.
|
|
54
|
+
For production studies, pin a version: `https://unpkg.com/cyborg-hunter@0.7.1/dist/...`.
|
|
53
55
|
|
|
54
56
|
## Plug into a jsPsych experiment
|
|
55
57
|
|
|
@@ -183,7 +185,7 @@ Run the test suite with `npm test`. Before publishing, run `scripts/check-public
|
|
|
183
185
|
author = {Konuk, Can and Btesh, Victor and Nunez, Jose Luis},
|
|
184
186
|
title = {cyborg-hunter: detecting AI-tool use in browser-based behavioral experiments},
|
|
185
187
|
year = {2026},
|
|
186
|
-
version = {0.7.
|
|
188
|
+
version = {0.7.1},
|
|
187
189
|
url = {https://github.com/konukcan/cyborg-hunter},
|
|
188
190
|
license = {MIT}
|
|
189
191
|
}
|
package/bin/cyborg-hunter.js
CHANGED
|
@@ -4,17 +4,21 @@
|
|
|
4
4
|
// npx cyborg-hunter init # generate starter config
|
|
5
5
|
// npx cyborg-hunter report [--config path] ... # generate report
|
|
6
6
|
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
7
8
|
import { run } from '../src/cli/report.js';
|
|
8
9
|
import { runInit } from '../src/cli/init.js';
|
|
9
10
|
|
|
10
11
|
const args = process.argv.slice(2);
|
|
11
12
|
const subcommand = args[0];
|
|
12
13
|
|
|
13
|
-
if (subcommand === '
|
|
14
|
+
if (subcommand === '--version' || subcommand === '-v') {
|
|
15
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
16
|
+
console.log(pkg.version);
|
|
17
|
+
} else if (subcommand === 'init') {
|
|
14
18
|
runInit().catch(err => { console.error('Error:', err.message); process.exit(1); });
|
|
15
19
|
} else if (subcommand === 'report' || !subcommand) {
|
|
16
20
|
run(args).catch(err => { console.error('Error:', err.message); process.exit(1); });
|
|
17
21
|
} else {
|
|
18
|
-
console.error(`Unknown command: ${subcommand}. Use "report" or "
|
|
22
|
+
console.error(`Unknown command: ${subcommand}. Use "report", "init", or "--version".`);
|
|
19
23
|
process.exit(1);
|
|
20
24
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// cyborg-hunter-replay v0.7.2 — https://github.com/konukcan/cyborg-hunter
|
|
2
|
+
(()=>{var K={participantId:"unknown",tier:"trace",keys:"full",mouseHz:30,redactSelector:"[data-ch-redact]",keepBait:!1,root:null,autoSave:{mode:"none"},maxEventsPerTrial:5e4,maxCharsPerTrial:8e6},ye={created:["session","destroyed"],session:["trial","stopped","destroyed"],trial:["session","stopped","destroyed"],stopped:["destroyed"],destroyed:[]};function Z(e){var n=Object.assign({},K,e||{});n.autoSave=Object.assign({},K.autoSave,(e||{}).autoSave||{});var r="created",t=[],a=[],l=0,f=[],c=new WeakMap,d=new WeakSet;function y(s){try{return JSON.stringify(s).length}catch{return 0}}var p={participantId:n.participantId,tier:n.tier,keys:n.keys,sessionStart:null,sessionStartEpoch:null,viewport:null,stylesheets:[],trials:[],guardViolations:[],captureFailures:[],captureStopped:!1,endReason:null,markerAttr:null},h=null,u=null;function v(s){if(ye[r].indexOf(s)===-1)throw new Error("[cyborg-hunter-replay] invalid lifecycle call: "+r+" \u2192 "+s+". Expected order: attach() \u2192 startSession() \u2192 (startTrial \u2192 endTrial)* \u2192 stopSession() \u2192 destroy()."+(r==="created"?" Did you forget startSession()?":""));r=s}function T(s,m){return l++,{trialIndex:l-1,trialId:s&&s.trialId||(m?"__session__":"trial-"+(l-1)),plugin:s&&s.plugin||"ch:standalone",implicit:!!m,tLoad:m?p.sessionStart:performance.now(),tStart:(s&&s.tStart)!=null?s.tStart:null,tDomReady:(s&&s.tDomReady)!=null?s.tDomReady:null,tEnd:null,initialDom:"",events:[]}}function E(){h.tEnd=performance.now(),p.trials.push(h),h=null}function x(s){for(var m=0;m<f.length;m++)try{f[m](s)}catch(_){p.captureFailures.push({channel:"trial_start_hook",message:_&&_.message?String(_.message):String(_),t:performance.now()})}}var P={config:n,startSession:function(){v("session"),p.sessionStart=performance.now(),p.sessionStartEpoch=Date.now();var s=typeof window<"u"?window:null,m=typeof document<"u"&&document.documentElement?document.documentElement:null;p.viewport=s?{width:s.innerWidth||null,height:s.innerHeight||null,client_width:m&&m.clientWidth||null,client_height:m&&m.clientHeight||null,dpr:s.devicePixelRatio||1,visual_viewport:s.visualViewport?{width:s.visualViewport.width,height:s.visualViewport.height,scale:s.visualViewport.scale}:null}:{width:null,height:null,client_width:null,client_height:null,dpr:null,visual_viewport:null},n.autoSave.mode==="none"&&console.warn('[cyborg-hunter-replay] autoSave.mode is "none" \u2014 the recording will be lost unless you call getRecording() yourself.')},startTrial:function(s){r==="trial"?(this.pushEvent("ch:lifecycle_error",{reason:"startTrial_without_endTrial"}),E(),r="session"):h&&E(),v("trial"),h=T(s,!1),x(h)},endTrial:function(){v("session"),h&&E()},stopSession:function(s){r==="trial"&&(r="session"),h&&E(),v("stopped"),p.endReason=s||"finished"},pushEvent:function(s,m,_){if(r==="destroyed")throw new Error("[cyborg-hunter-replay] pushEvent called on a destroyed recorder");if(!(r==="created"||r==="stopped")&&(h||(h=T(null,!0),x(h)),!d.has(h))){if(h.events.length>=n.maxEventsPerTrial){d.add(h),p.captureStopped=!0,h.events.push({t:_??performance.now(),kind:"ch:capture_stopped",limit:n.maxEventsPerTrial});return}var b=Object.assign({t:_??performance.now(),kind:s},m||{});if(n.maxCharsPerTrial!=null){var M=c.get(h);if(M==null&&(M=h.initialDom?h.initialDom.length:0),M+=y(b),M>n.maxCharsPerTrial){d.add(h),p.captureStopped=!0,h.events.push({t:b.t,kind:"ch:capture_stopped",limit_chars:n.maxCharsPerTrial});return}c.set(h,M)}h.events.push(b)}},captureFailure:function(s,m){p.captureFailures.push({channel:s,message:m&&m.message?String(m.message):String(m),t:performance.now()})},onTrialStart:function(s){f.push(s)},setStylesheets:function(s){p.stylesheets=s||[]},setMarkers:function(s){u=s},getMarkers:function(){return u},setMarkerAttr:function(s){p.markerAttr=s||null},addListener:function(s,m,_,b){s.addEventListener(m,_,b||!1),t.push({target:s,event:m,handler:_,options:b||!1})},addInterval:function(s){a.push(s)},getState:function(){var s=p.trials.slice();return h&&s.push(h),Object.assign({},p,{trials:s,state:r})},destroy:function(){r!=="destroyed"&&(v("destroyed"),t.forEach(function(s){s.options&&s.options._isObserver?s.handler.disconnect():s.target.removeEventListener(s.event,s.handler,s.options)}),t=[],a.forEach(function(s){clearInterval(s)}),a=[])}};return P}function w(e,n,r){return function(t){try{r(t)}catch(a){e.captureFailure(n,a)}}}function $(e){if(!e||!e.tagName)return null;var n=e.tagName.toLowerCase();return e.id&&(n+="#"+e.id),n}function we(e){return!!(e&&e.tagName==="INPUT"&&e.type==="password")}function Se(e,n){if(we(e))return!0;if(!n||!e||typeof e.matches!="function")return!1;try{return e.matches(n)}catch{return!1}}function N(e,n){for(var r=e;r;){if(Se(r,n))return!0;r=r.parentNode}return!1}function k(e){return Math.round(e*10)/10}function ee(e,n){n=n||{};var r=n.doc||document,t=n.win||window,a=n.now||function(){return performance.now()},l=n.raf||(typeof requestAnimationFrame<"u"?requestAnimationFrame.bind(null):function(i){setTimeout(i,16)}),f=e.config;function c(){var i=r.documentElement;return{cw:i&&i.clientWidth?i.clientWidth:t.innerWidth||null,ch:i&&i.clientHeight?i.clientHeight:t.innerHeight||null}}function d(i,o){return o.clientX!=null&&(i.cx=Math.round(o.clientX)),o.clientY!=null&&(i.cy=Math.round(o.clientY)),i}function y(i){var o=i&&i.target;if(!o||!o.tagName)return null;var g=o.tagName.toLowerCase();if(N(o,f.redactSelector))return{redacted:!0,tag:g};var S={tag:g};try{if(typeof i.composedPath=="function"){var A=i.composedPath()[0];A&&A!==o&&(S.shadow=!0)}}catch{}o.id&&(S.id=o.id);var Q=typeof e.getMarkers=="function"?e.getMarkers():null;Q&&(S.n=Q.refFor(o));try{if(typeof o.getBoundingClientRect=="function"){var D=o.getBoundingClientRect();S.rect=[k(D.left),k(D.top),k(D.width),k(D.height)]}}catch{}return S}var p=null,h=new Map,u=null,v=null,T=!1,E=!1,x=!1;function P(){T=!1,p&&(e.pushEvent("scroll",{x:Math.round(t.scrollX||0),y:Math.round(t.scrollY||0)},p.t),p=null),h.size>0&&(h.forEach(function(i,o){var g;if(N(o,f.redactSelector))g={redacted:!0,tag:o.tagName?o.tagName.toLowerCase():null,x:Math.round(o.scrollLeft||0),y:Math.round(o.scrollTop||0)};else{g={el:$(o),id:o.id||null,x:Math.round(o.scrollLeft||0),y:Math.round(o.scrollTop||0)};var S=typeof e.getMarkers=="function"?e.getMarkers():null;S&&(g.n=S.refFor(o))}e.pushEvent("scroll",g,i.t)}),h.clear())}function s(i){return i}function m(){if(E=!1,!!u){var i=c();e.pushEvent("resize",{w:t.innerWidth,h:t.innerHeight,cw:i.cw,ch:i.ch,dpr:t.devicePixelRatio||1},u.t),u=null}}function _(){if(x=!1,!!v){var i=t.visualViewport;i&&e.pushEvent("vv",{w:k(i.width),h:k(i.height),scale:i.scale,px:k(i.pageLeft||0),py:k(i.pageTop||0)},v.t),v=null}}function b(){P(),m(),_()}function M(i){var o=c();return i.sx=Math.round(t.scrollX||0),i.sy=Math.round(t.scrollY||0),i.vw=t.innerWidth||null,i.vh=t.innerHeight||null,i.cw=o.cw,i.ch=o.ch,i}var ve=1e3/(f.mouseHz||30),J=-1/0;e.addListener(r,"mousemove",w(e,"mouse",function(i){var o=a();o-J<ve||(J=o,e.pushEvent("mousemove",d({x:Math.round(i.pageX),y:Math.round(i.pageY)},i),o))}),{passive:!0,capture:!0}),["mousedown","mouseup","click"].forEach(function(i){e.addListener(r,i,w(e,"mouse",function(o){b();var g=M(d({x:Math.round(o.pageX),y:Math.round(o.pageY)},o)),S=y(o);S&&(g.target=S),e.pushEvent(i,g,a())}),{passive:!0,capture:!0})}),f.keys!=="off"&&["keydown","keyup"].forEach(function(i){e.addListener(r,i,w(e,"keys",function(o){if(N(o.target,f.redactSelector)){e.pushEvent(i,{redacted:!0},a());return}e.pushEvent(i,{key:o.key,code:o.code},a())}),!0)}),e.addListener(r,"paste",w(e,"clipboard",function(i){b();var o=null;try{o=i.clipboardData.getData("text").length}catch{}e.pushEvent("paste",{len:o},a())}),!0),e.addListener(r,"copy",w(e,"clipboard",function(){b(),e.pushEvent("copy",{},a())}),!0),e.addListener(r,"cut",w(e,"clipboard",function(){b(),e.pushEvent("cut",{},a())}),!0),e.addListener(r,"drop",w(e,"clipboard",function(i){b();var o=null;try{o=i.dataTransfer.getData("text").length}catch{}e.pushEvent("ch:drop",{len:o},a())}),!0);var F=new Map,z=!1;function me(){z=!1,F.forEach(function(i,o){var g={el:$(o),id:o.id||null},S=o.value!=null?String(o.value):o.isContentEditable&&o.textContent!=null?String(o.textContent):"";if(N(o,f.redactSelector))g.redacted=!0,g.value_len=S.length;else{g.value=S;var A=typeof e.getMarkers=="function"?e.getMarkers():null;A&&(g.n=A.refFor(o))}!g.redacted&&(o.type==="checkbox"||o.type==="radio")&&(g.checked=!!o.checked),e.pushEvent("input",g,i)}),F.clear()}e.addListener(r,"input",w(e,"input",function(i){i.target&&(F.set(i.target,a()),z||(z=!0,l(w(e,"input",me))))}),!0),e.addListener(t,"scroll",w(e,"scroll",function(i){var o=i&&i.target&&i.target.tagName?i.target:null;o?h.set(o,{t:a()}):p={t:a()},T||(T=!0,l(w(e,"scroll",P)))}),{passive:!0,capture:!0}),e.addListener(r,"touchstart",w(e,"touch",function(i){b();var o=i.touches&&i.touches[0];if(o){var g=M(d({x:Math.round(o.pageX),y:Math.round(o.pageY)},o)),S=y(i);S&&(g.target=S),e.pushEvent("touchstart",g,a())}}),{passive:!0,capture:!0});var j=!1,L=null,q=0;e.addListener(r,"touchmove",w(e,"touch",function(i){L=i.touches&&i.touches[0],q=a(),!j&&(j=!0,l(w(e,"touch",function(){j=!1,L&&e.pushEvent("touchmove",d({x:Math.round(L.pageX),y:Math.round(L.pageY)},L),q)})))}),{passive:!0,capture:!0}),e.addListener(r,"touchend",w(e,"touch",function(i){b();var o=i.changedTouches&&i.changedTouches[0];if(o){var g=M(d({x:Math.round(o.pageX),y:Math.round(o.pageY)},o)),S=y(i);S&&(g.target=S),e.pushEvent("touchend",g,a())}}),{passive:!0,capture:!0}),e.addListener(t,"blur",w(e,"focus",function(){e.pushEvent("blur",{},a())})),e.addListener(t,"focus",w(e,"focus",function(){e.pushEvent("focus",{},a())})),e.addListener(r,"visibilitychange",w(e,"focus",function(){e.pushEvent("visibility",{hidden:!!r.hidden},a())})),e.addListener(t,"resize",w(e,"viewport",function(){u={t:a()},E||(E=!0,l(w(e,"viewport",m)))})),t.visualViewport&&typeof t.visualViewport.addEventListener=="function"&&["resize","scroll"].forEach(function(i){e.addListener(t.visualViewport,i,w(e,"viewport",function(){v={t:a()},x||(x=!0,l(w(e,"viewport",_)))}))}),e.onTrialStart(function(i){var o=c();i.viewState={x:Math.round(t.scrollX||0),y:Math.round(t.scrollY||0),w:t.innerWidth||null,h:t.innerHeight||null,cw:o.cw,ch:o.ch,dpr:t.devicePixelRatio||1}})}var O=1,_e=3,be={SCRIPT:!0,NOSCRIPT:!0};function Te(e){var n=new WeakMap,r=1;return{attr:"data-chn-"+e,refFor:function(t){var a=n.get(t);return a==null&&(a=r++,n.set(t,a)),a}}}var Ee={AREA:!0,BASE:!0,BR:!0,COL:!0,EMBED:!0,HR:!0,IMG:!0,INPUT:!0,LINK:!0,META:!0,SOURCE:!0,TRACK:!0,WBR:!0},xe=/^[a-zA-Z_:][-a-zA-Z0-9_:.]*$/;function ae(e){return xe.test(e)&&!/^on/i.test(e)}function G(e){return String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function ie(e,n){if(n&&n.keepBait||!e||e.nodeType!==O)return!1;if(e.id==="ch-decoy")return!0;for(var r=e.attributes||[],t=0;t<r.length;t++)if(r[t].name==="data-ch-role"||r[t].name==="data-ch-decoy")return!0;return!1}function Me(e){return e.tagName==="INPUT"&&(e.type==="password"||H(e,"type","password"))}function oe(e,n){if(!e)return!1;if(Me(e))return!0;var r=n&&n.redactSelector;if(!r||typeof e.matches!="function")return!1;try{return e.matches(r)}catch{return!1}}function V(e,n){for(var r=e;r;){if(r.nodeType===O&&oe(r,n))return!0;r=r.parentNode}return!1}function H(e,n,r){for(var t=e.attributes||[],a=0;a<t.length;a++)if(t[a].name===n)return r==null||t[a].value===r;return!1}function B(e,n){n=n||{};var r=[];return U(e,n,r),r.join("")}function Re(e){var n=null,r=null;try{if(typeof e.getBoundingClientRect=="function"){var t=e.getBoundingClientRect();t&&(n=Math.round(t.width||0),r=Math.round(t.height||0))}}catch{}return n==null&&(n=parseInt(H(e,"width")?re(e,"width"):"",10),r=parseInt(H(e,"height")?re(e,"height"):"",10),n>0||(n=300),r>0||(r=150)),{w:n,h:r}}var te=["position","top","right","bottom","left","margin","float","vertical-align","z-index"];function se(e){var n=Re(e),r="",t="inline-block";try{var a=e.ownerDocument&&e.ownerDocument.defaultView;if(a&&typeof a.getComputedStyle=="function"){var l=a.getComputedStyle(e);l.display&&l.display!=="inline"&&(t=l.display);for(var f=0;f<te.length;f++){var c=te[f],d=l.getPropertyValue(c);d&&d!=="auto"&&d!=="none"&&d!=="normal"&&d!=="baseline"&&d!=="0px"&&(r+=c+":"+d+";")}}}catch{}return"display:"+t+";"+r+"width:"+n.w+"px;height:"+n.h+"px"}function Pe(e,n,r){r.push('<span data-ch-iframe=""'),n.markers&&r.push(" "+n.markers.attr+'="'+n.markers.refFor(e)+'"'),r.push(' style="'+se(e)+'"></span>')}function re(e,n){for(var r=e.attributes||[],t=0;t<r.length;t++)if(r[t].name===n)return r[t].value;return""}function U(e,n,r){if(e){if(e.nodeType===_e){r.push(G(e.textContent||""));return}if(e.nodeType===O){var t=e.tagName;if(!be[t]&&!ie(e,n)){if(t==="IFRAME"){Pe(e,n,r);return}var a=t.toLowerCase();r.push("<"+a),n.markers&&r.push(" "+n.markers.attr+'="'+n.markers.refFor(e)+'"'),e.shadowRoot&&r.push(' data-ch-shadow=""');for(var l=oe(e,n),f=t==="IMG"&&e.src?e.src:null,c=!1,d=e.attributes||[],y=0;y<d.length;y++){var p=d[y].name,h=d[y].value;ae(p)&&(p==="value"&&l||(p==="src"&&f&&(h=f,c=!0),r.push(" "+p+'="'+G(h)+'"')))}if(f&&!c&&r.push(' src="'+G(f)+'"'),l&&r.push(' data-ch-redacted="true"'),r.push(">"),!Ee[t]){if(!l)for(var u=e.childNodes||[],v=0;v<u.length;v++)U(u[v],n,r);r.push("</"+a+">")}}}}}function ne(e,n){for(var r=[],t=e;t&&t!==n;){var a=t.parentNode;if(!a)return null;var l=ke(a,t);if(l===-1)return null;r.unshift(l),t=a}return t===n?r:null}function ke(e,n){for(var r=e.childNodes||[],t=0;t<r.length;t++)if(r[t]===n)return t;return-1}function Ce(e,n,r){for(var t=e.target,a=t;a;){if(ie(a,r))return null;a=a.parentNode}if(e.type==="characterData"&&r.markers){var l=t.parentNode;if(!l||ne(l,n)===null)return null;e={type:"childList",target:l},t=l}var f=ne(t,n);if(f===null)return null;var c=r.markers&&t.nodeType===O?{n:r.markers.refFor(t),tag:t.tagName==="IFRAME"?"span":t.tagName.toLowerCase()}:null;if(e.type==="childList")return Object.assign({op:"childList",path:f,html:V(t,r)?"":Ie(t,r)},c||{});if(e.type==="attributes"){if(t.tagName==="IFRAME")return Object.assign({op:"attributes",path:f,name:"style",value:se(t)},c||{});var d=e.attributeName;if(!ae(d))return null;var y=typeof t.getAttribute=="function"?t.getAttribute(d):null;return d==="value"&&V(t,r)&&(y=null),Object.assign({op:"attributes",path:f,name:d,value:y},c||{})}if(e.type==="characterData"){var p=V(t,r)?"":t.textContent||"";return{op:"characterData",path:f,value:p}}return null}function Ie(e,n){for(var r=[],t=e.childNodes||[],a=0;a<t.length;a++)U(t[a],n,r);return r.join("")}function Le(e){for(var n=[],r=e&&e.styleSheets||[],t=0;t<r.length;t++){var a=r[t];try{for(var l=a.cssRules,f=[],c=0;c<l.length;c++)f.push(l[c].cssText);n.push({css:f.join(`
|
|
3
|
+
`)})}catch{n.push({href:a.href||null})}}return n}function ue(e,n){n=n||{};var r=n.doc||document,t=n.win||window,a=n.now||function(){return performance.now()},l=n.MutationObserver||(typeof MutationObserver<"u"?MutationObserver:null),f=Te(Math.random().toString(16).slice(2,10)||"fallback0");typeof e.setMarkers=="function"&&(e.setMarkers(f),e.setMarkerAttr(f.attr));var c={keepBait:e.config.keepBait,redactSelector:e.config.redactSelector,markers:f};function d(){var u=e.config.root;if(typeof u=="string")try{return r.querySelector(u)||r.body}catch{return r.body}return u||r.body}try{e.setStylesheets(Le(r))}catch(u){e.captureFailure("stylesheets",u)}if(e.onTrialStart(function(u){try{var v=B(d(),c),T=e.config.maxCharsPerTrial;T!=null&&v.length>T?(e.captureFailure("dom_snapshot",new Error("initial DOM snapshot length "+v.length+" exceeds maxCharsPerTrial "+T+"; dropped to bound payload size")),u.initialDom=""):u.initialDom=v}catch(E){e.captureFailure("dom_snapshot",E),u.initialDom=""}}),l){var y=new l(function(u){try{for(var v=d(),T=new Set,E=function(m){return m.type==="childList"?m.target:m.type==="characterData"&&c.markers&&m.target&&m.target.parentNode?m.target.parentNode:null},x=0;x<u.length;x++){var P=E(u[x]);if(P){if(T.has(P))continue;T.add(P)}var s=Ce(u[x],v,c);s&&e.pushEvent("mutation",s,a())}}catch(m){e.captureFailure("mutations",m)}});try{y.observe(d(),{childList:!0,attributes:!0,characterData:!0,subtree:!0}),e.addListener({addEventListener:function(){},removeEventListener:function(){}},"_mutation_observer",y,{_isObserver:!0})}catch(u){e.captureFailure("mutations",u)}}if(t.GuardFriction&&typeof t.GuardFriction.onViolation=="function")try{var p=e.getState().state;if((p==="session"||p==="trial")&&typeof t.GuardFriction.getCurrentState=="function"){var h=t.GuardFriction.getCurrentState();h&&h.in_violation&&e.pushEvent("ch:guard_violation",{reason:h.current_reason||"unknown",phase:"start",synthesized_at_subscribe:!0,dom_at_subscribe:B(d(),c)},a())}t.GuardFriction.onViolation(function(u){try{u&&u.phase==="start"?e.pushEvent("ch:guard_violation",{reason:u.reason,phase:"start",pre_scramble_dom:B(d(),c)},a()):u&&u.phase&&e.pushEvent("ch:guard_violation",{reason:u.reason,phase:u.phase,duration_ms:u.duration!=null?u.duration:null},a())}catch(v){e.captureFailure("guard_violation",v)}})}catch(u){e.captureFailure("guard_violation",u)}}var W="0.7.2";function le(e){return String(e).replace(/[^a-zA-Z0-9_.-]/g,"_")}function C(e,n){return e==null?null:Math.round((e-n)*10)/10}function R(e,n){return(e||[]).map(function(r){var t=Object.assign({},r);return typeof t.t=="number"&&(t.t=C(t.t,n)),typeof t.start=="number"&&(t.start=C(t.start,n)),t})}function ce(e,n){n=n||{};var r=e.sessionStart;if(r==null)throw new Error("[cyborg-hunter-replay] cannot serialize before startSession() \u2014 call startSession() first, or the timestamps would be meaningless (Unix epoch).");var t=n.chSessionReport||null,a=r,l=e.trials.map(function(c){var d=c.events.slice().sort(function(p,h){return p.t-h.t}),y=d.length>0?d[d.length-1].t:c.tLoad;return y>a&&(a=y),{trial_index:c.trialIndex,trial_id:c.trialId,plugin:c.plugin,t_start:C(c.tStart,r),t_dom_ready:C(c.tDomReady,r),t_load:C(c.tLoad,r),t_end:C(c.tEnd!=null?c.tEnd:y,r),view_state:c.viewState||null,initial_dom:c.initialDom||"",events:d.map(function(p){var h=Object.assign({},p);return h.t=C(p.t,r),h}),trial_data:{}}}),f={replay_version:W,marker_attr:e.markerAttr||null,ch_version:t&&t.libraryVersion||null,preset:t&&t.config?t.config.preset:null,scoring:t?{hard_score:t.hardScore||{},soft_score:t.softScore!=null?t.softScore:null,soft_score_threshold:t.softScoreThreshold!=null?t.softScoreThreshold:null,any_hard_triggered:!!t.anyHardTriggered,trials_completed:t.trialsCompleted!=null?t.trialsCompleted:null}:null,session_signals:t?{sidebar_events:R(t.sidebarEvents,r),ai_extensions_found:R(t.aiExtensionsFound,r),devtools_events:R(t.devToolsEvents,r),window_positions:R(t.windowPositions,r),layout_shifts:R(t.layoutShifts,r),zoom_changes:R(t.zoomChanges,r),extension_injections:R(t.extensionInjections,r)}:null,guard_violations:R(e.guardViolations,r),capture_failures:R(e.captureFailures,r),capture_stopped:!!e.captureStopped};return{schema_version:1,metadata:{start_time:new Date(e.sessionStartEpoch).toISOString(),end_time:new Date(e.sessionStartEpoch+Math.max(0,Math.round(a-r))).toISOString(),end_reason:e.endReason||"aborted",participant_id:e.participantId,recorder:"cyborg-hunter-replay@"+W,tier:e.tier,keys:e.keys},viewport:e.viewport,stylesheets:{initial:e.stylesheets||[],events:[]},rng_calls:[],trials:l,ch_extensions:f}}var Ae="https://pipe.jspsych.org/api/data/";function de(e){var n=le(e.metadata.participant_id),r=Date.parse(e.metadata.start_time);return n+"-replay-"+r+".json"}function he(e,n){return{schema_version:e.schema_version,tier:e.metadata.tier,saved_to:n,bytes_uncompressed:JSON.stringify(e).length,capture_failures:(e.ch_extensions.capture_failures||[]).map(function(r){return r.channel}),capture_stopped:!!e.ch_extensions.capture_stopped}}async function fe(e,n){var r=n||{mode:"none"},t=de(e),a=typeof window<"u"?window:{};if(r.mode==="none")return{saved_to:"none",filename:t};if(r.mode==="datapipe"){if(!r.experimentId)return{saved_to:"failed",filename:t,error:'autoSave.mode "datapipe" requires autoSave.experimentId'};try{var l=JSON.stringify(e);if(a.jsPsychPipe&&typeof a.jsPsychPipe.saveData=="function")await a.jsPsychPipe.saveData(r.experimentId,t,l);else{var f=a.fetch||(typeof fetch<"u"?fetch:null);if(!f)throw new Error("no fetch available for DataPipe POST");var c=await f(Ae,{method:"POST",headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify({experimentID:r.experimentId,filename:t,data:l})});if(c&&c.ok===!1)throw new Error("DataPipe HTTP error")}return{saved_to:"datapipe:"+r.experimentId+"/"+t,filename:t}}catch(v){return console.error("[cyborg-hunter-replay] DataPipe autosave failed:",v),{saved_to:"failed",filename:t,error:v&&v.message?v.message:String(v)}}}if(r.mode==="download")try{var d=typeof document<"u"?document:null,y=a.Blob||(typeof Blob<"u"?Blob:null),p=a.URL||(typeof URL<"u"?URL:null);if(!d||!y||!p)throw new Error("download mode needs a browser");var h=new y([JSON.stringify(e)],{type:"application/json"}),u=d.createElement("a");return u.href=p.createObjectURL(h),u.download=t,d.body.appendChild(u),u.click(),d.body.removeChild(u),p.revokeObjectURL(u.href),{saved_to:"download",filename:t}}catch(v){return console.error("[cyborg-hunter-replay] download autosave failed:",v),{saved_to:"failed",filename:t,error:v&&v.message?v.message:String(v)}}return{saved_to:"failed",filename:t,error:'unknown autoSave.mode "'+r.mode+'"'}}async function pe(e){var n=JSON.stringify(e),r=typeof window<"u"?window:{},t=r.CompressionStream||(typeof CompressionStream<"u"?CompressionStream:null),a=r.Blob||(typeof Blob<"u"?Blob:null);if(!a)return null;if(!t)return new a([n],{type:"application/json"});var l=new a([n]).stream().pipeThrough(new t("gzip")),f=r.Response||(typeof Response<"u"?Response:null),c=await new f(l).arrayBuffer();return new a([c],{type:"application/gzip"})}var I=null;function Y(e){I&&(console.warn("[cyborg-hunter-replay] attach() called while a recorder is active \u2014 replacing the previous instance (its unsaved recording is discarded)."),I.destroy(),I=null);var n=Z(e),r={_recorder:n,config:n.config,startSession:function(){return n.startSession(),ee(n),(n.config.tier==="dom"||n.config.tier==="canvas")&&ue(n),r},startTrial:function(t){return n.startTrial(t),r},endTrial:function(){return n.endTrial(),r},stopSession:function(t){return n.stopSession(t),r},getRecording:function(t){return ce(n.getState(),t||{})},getRecordingCompressed:function(t){return pe(r.getRecording(t))},autoSaveNow:async function(t){if(n.getState().sessionStart==null)return{recording:null,saveResult:{saved_to:"no_session"},meta:{schema_version:1,saved_to:"no_session",note:"recorder was never started; nothing to save"}};var a=r.getRecording(t),l=await fe(a,n.config.autoSave),f=he(a,l.saved_to);return l.error&&(f.save_error=l.error),{recording:a,saveResult:l,meta:f}},destroy:function(){n.destroy(),I===r&&(I=null)}};return I=r,r}typeof window<"u"&&(window.CyborgHunterReplay={attach:Y});var X=class{static info={name:"cyborg-hunter-replay",version:"0.7.2",data:{}};constructor(n){this.jsPsych=n,this.api=null,this._chMonitor=null,this._lastRecording=null,this._monitoring=!1}initialize(n){this.params=n||{},this.api=Y(this.params),this.api.startSession(),this._stashChMonitor()}_stashChMonitor(){try{var n=this.jsPsych&&this.jsPsych.extensions&&this.jsPsych.extensions["cyborg-hunter"];n&&n.monitor&&(this._chMonitor=n.monitor)}catch{}}on_start(n){}on_load(n){if(this.api){this._monitoring=!0;var r=0,t="unknown";try{r=this.jsPsych.getProgress().current_trial_global}catch{}try{t=(this.jsPsych.getCurrentTrial()||{}).type?.info?.name||"unknown"}catch{}this.api.startTrial({trialId:n&&n.trialId||"trial-"+r,plugin:t})}}on_finish(n){return this.api&&this._monitoring&&(this._monitoring=!1,this.api.endTrial()),{}}async finalize(){if(this.api){try{try{this.api.stopSession("finished")}catch{}this._chMonitor||this._stashChMonitor();var n=null;try{n=this._chMonitor?this._chMonitor.getSessionReport():null}catch(t){console.warn("[cyborg-hunter-replay] could not pull CH session report:",t)}var r=await this.api.autoSaveNow({chSessionReport:n});this._lastRecording=r.recording,this.jsPsych.data.addProperties({integrityReplayMeta:r.meta})}catch(t){console.warn("[cyborg-hunter-replay] finalize failed:",t);try{this.jsPsych.data.addProperties({replayFinalizeError:String(t&&t.message?t.message:t)})}catch{}}try{this.api.destroy()}catch{}this.api=null}}getLastRecording(){return this._lastRecording}};typeof window<"u"&&(window.jsPsychCyborgHunterReplay=X);})();
|