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 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
@@ -25,5 +25,5 @@ keywords:
25
25
  - prolific
26
26
  - behavioral-research
27
27
  license: MIT
28
- version: 0.7.0
29
- date-released: '2026-07-14'
28
+ version: 0.7.1
29
+ date-released: '2026-07-29'
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.0/dist/...`.
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.0},
188
+ version = {0.7.1},
187
189
  url = {https://github.com/konukcan/cyborg-hunter},
188
190
  license = {MIT}
189
191
  }
@@ -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 === 'init') {
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 "init".`);
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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}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);})();