cyborg-hunter 0.7.0 → 0.7.3

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,66 @@
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.3] — 2026-08-04
7
+
8
+ ### Added
9
+ - `report` now nudges about newer releases: a zero-dependency check against
10
+ the npm registry's `latest` dist-tag prints a short update notice after the
11
+ report summary. At most one registry request per day (cache file in
12
+ `~/.cache/cyborg-hunter/`), silent on any network failure, skipped under
13
+ `CI` or `NO_UPDATE_NOTIFIER`, and CLI-only — the browser library still
14
+ makes no network requests of its own.
15
+ - `report` prints an offline staleness note when ingested sessions were
16
+ collected with a different library version than the CLI (payloads stamp
17
+ `libraryVersion`). This catches the case the registry check can't: an
18
+ experiment still serving an old bundle to participants.
19
+
20
+ ## [0.7.2] — 2026-07-29
21
+
22
+ ### Added
23
+ - Replay viewer: a keycast overlay at the bottom of the stage shows keys as
24
+ they're pressed (chips appear on keydown, fade after keyup), including a
25
+ redacted chip for keystrokes captured with `keys:'full'` inside a
26
+ redacted field. Recordings made with `keys:'off'` show no keycast, since
27
+ there's no key data to show.
28
+ - Replay viewer: the buffer-cap notice is now an expandable explanation
29
+ (was a one-line chip) covering what `maxEventsPerTrial`/`maxCharsPerTrial`
30
+ actually cap (per trial, not the whole session), what happens when a
31
+ trial crosses one, and that both are configurable. Mirrored in
32
+ `docs/using-cyborg-hunter.md`'s configuration table.
33
+ - Replay viewer: continuous whole-session playback, default on. Pressing
34
+ play now rolls through every trial in the recording as one continuous
35
+ video instead of stopping at each trial boundary; a "pause at trial
36
+ boundaries" toggle restores the previous per-trial-stop behavior. A
37
+ "Trial k of N" indicator tracks position across the session.
38
+
39
+ ### Changed
40
+ - Internal refactor, no behavior change: the report index renderer and its
41
+ three plot renderers (session-timeline, trajectories, typing-profile) are
42
+ each split into a pure core (string- or canvas-returning) plus a thin fs
43
+ wrapper; `buildViewerModel` and `getByPath` moved to pure shared modules.
44
+ `renderIndexHtml` also gained optional demo-mode opts (`imageSources`,
45
+ `inlineReplayModels`) for inline image/replay embedding, with a hash-sync
46
+ guard for the opaque-origin iframe the demo renders reports inside. Together
47
+ these let the live demo render reports and plots directly in the browser;
48
+ default (CLI) output is byte-identical.
49
+ - The live demo (https://konukcan.github.io/cyborg-hunter/) was remodeled
50
+ into a 13-step guided tour; not part of the npm package.
51
+
52
+ ### Fixed
53
+ - Raw mouse coordinates were persisted in every trial report regardless of the
54
+ documented off-by-default `collectForPostHoc.rawMouseTrack` toggle. Reports
55
+ now omit the raw track unless the toggle is enabled, in which case it
56
+ persists as `mouseTrack` (ingest maps it back to `mouseEvents`); the derived
57
+ `mouseMetrics` signal is unaffected either way.
58
+
59
+ ## [0.7.1] — 2026-07-29
60
+
61
+ ### Fixed
62
+ - The npm package now includes `dist/` — 0.7.0 shipped without it, so every
63
+ documented unpkg/script-tag URL returned 404.
64
+ - `cyborg-hunter --version` prints the version instead of "Unknown command".
65
+
6
66
  ## [0.7.0] — 2026-07-14
7
67
 
8
68
  ### 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.3
29
+ date-released: '2026-08-04'
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.3/dist/...`.
53
55
 
54
56
  ## Plug into a jsPsych experiment
55
57
 
@@ -172,6 +174,10 @@ The optional **guard** extensions add: fullscreen / sidebar / focus enforcement
172
174
  - [docs/configuration.md](docs/configuration.md) — config file fields and CLI flags
173
175
  - [docs/cli-reference.md](docs/cli-reference.md) — commands and output structure
174
176
 
177
+ ## Reporting issues
178
+
179
+ Bug reports and feature requests go to the [issue tracker](https://github.com/konukcan/cyborg-hunter/issues); the bug form asks for your version and environment. One hard rule: issues are public, so never paste participant data (session payloads, replay files, identifiers). Reproduce with synthetic or personal test runs instead.
180
+
175
181
  ## Development
176
182
 
177
183
  Run the test suite with `npm test`. Before publishing, run `scripts/check-public-hygiene.sh` — it fails if any tracked file contains personal or internal-process leakage.
@@ -183,7 +189,7 @@ Run the test suite with `npm test`. Before publishing, run `scripts/check-public
183
189
  author = {Konuk, Can and Btesh, Victor and Nunez, Jose Luis},
184
190
  title = {cyborg-hunter: detecting AI-tool use in browser-based behavioral experiments},
185
191
  year = {2026},
186
- version = {0.7.0},
192
+ version = {0.7.3},
187
193
  url = {https://github.com/konukcan/cyborg-hunter},
188
194
  license = {MIT}
189
195
  }
@@ -4,17 +4,31 @@
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
- runInit().catch(err => { console.error('Error:', err.message); process.exit(1); });
14
+ // Printed on unexpected crashes only; deliberate exits (like the no-data
15
+ // guidance in report.js) call process.exit directly and never reach it.
16
+ function crash(err) {
17
+ console.error('Error:', err.message);
18
+ console.error('\nIf this looks like a bug, please report it at');
19
+ console.error(' https://github.com/konukcan/cyborg-hunter/issues');
20
+ console.error('(include the command, the error, and your version; never include participant data)');
21
+ process.exit(1);
22
+ }
23
+
24
+ if (subcommand === '--version' || subcommand === '-v') {
25
+ const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
26
+ console.log(pkg.version);
27
+ } else if (subcommand === 'init') {
28
+ runInit().catch(crash);
15
29
  } else if (subcommand === 'report' || !subcommand) {
16
- run(args).catch(err => { console.error('Error:', err.message); process.exit(1); });
30
+ run(args).catch(crash);
17
31
  } else {
18
- console.error(`Unknown command: ${subcommand}. Use "report" or "init".`);
32
+ console.error(`Unknown command: ${subcommand}. Use "report", "init", or "--version".`);
19
33
  process.exit(1);
20
34
  }
@@ -0,0 +1,3 @@
1
+ // cyborg-hunter-replay v0.7.3 — 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.3";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.3",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);})();