cyborg-hunter 0.4.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +110 -0
- package/CITATION.cff +29 -0
- package/LICENSE +21 -0
- package/README.md +117 -79
- package/package.json +10 -3
- package/src/cli/analyzers/edge-exit.js +4 -1
- package/src/cli/analyzers/phase-scope.js +83 -0
- package/src/cli/analyzers/summary.js +161 -28
- package/src/cli/analyzers/triage.js +59 -27
- package/src/cli/config.js +26 -1
- package/src/cli/ingest.js +623 -41
- package/src/cli/init.js +1 -1
- package/src/cli/renderers/event-log.js +18 -19
- package/src/cli/renderers/extensions.js +12 -3
- package/src/cli/renderers/html-index.js +163 -24
- package/src/cli/renderers/replay-assets.js +177 -0
- package/src/cli/renderers/replay-viewer.client.js +1022 -0
- package/src/cli/renderers/session-timeline.js +917 -0
- package/src/cli/renderers/summary-csv.js +5 -0
- package/src/cli/renderers/trajectories.js +69 -8
- package/src/cli/renderers/triage-md.js +10 -4
- package/src/cli/renderers/typing-profile.js +7 -1
- package/src/cli/report.js +42 -8
- package/src/core/monitor.js +60 -7
- package/src/core/scoring.js +11 -2
- package/src/core/signals/browser.js +51 -18
- package/src/core/signals/clipboard.js +10 -2
- package/src/core/signals/dom-protection.js +9 -0
- package/src/core/signals/focus.js +16 -2
- package/src/jspsych/extension-cyborg-hunter-replay.js +135 -0
- package/src/jspsych/{extension.js → extension-cyborg-hunter.js} +9 -2
- package/src/jspsych/extension-guard-friction.js +1164 -0
- package/src/jspsych/extension-guard-honeypot.js +492 -0
- package/src/replay/capture-dom.js +575 -0
- package/src/replay/capture-trace.js +468 -0
- package/src/replay/index.js +104 -0
- package/src/replay/persistence.js +141 -0
- package/src/replay/recorder.js +315 -0
- package/src/replay/serializer.js +119 -0
- package/src/shared/constants.js +12 -6
- package/src/shared/schema.js +5 -0
- package/src/shared/validation.js +55 -0
- package/dist/cyborg-hunter.esm.js +0 -1527
- package/dist/cyborg-hunter.min.js +0 -6
- package/dist/jspsych-cyborg-hunter.js +0 -1
- package/src/cli/renderers/tab-timeline.js +0 -149
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// jsPsych extension adapter for the cyborg-hunter session replay recorder.
|
|
2
|
+
// Bundled SELF-CONTAINED (imports the replay core), following the guard-
|
|
3
|
+
// extension precedent: one dist file provides both window.CyborgHunterReplay
|
|
4
|
+
// (standalone use) and window.jsPsychCyborgHunterReplay (jsPsych use).
|
|
5
|
+
//
|
|
6
|
+
// Wiring (order matters at finalize; declaration order is free):
|
|
7
|
+
//
|
|
8
|
+
// const jsPsych = initJsPsych({
|
|
9
|
+
// extensions: [
|
|
10
|
+
// { type: jsPsychCyborgHunter, params: { participantId, preset } },
|
|
11
|
+
// { type: jsPsychGuardFriction },
|
|
12
|
+
// { type: jsPsychGuardHoneypot },
|
|
13
|
+
// { type: jsPsychCyborgHunterReplay, params: {
|
|
14
|
+
// participantId, tier: 'dom',
|
|
15
|
+
// autoSave: { mode: 'datapipe', experimentId: 'ABC123' } } }
|
|
16
|
+
// ],
|
|
17
|
+
// on_finish: async function () {
|
|
18
|
+
// jsPsych.extensions['guard-friction'].finalize();
|
|
19
|
+
// jsPsych.extensions['guard-honeypot'].finalize();
|
|
20
|
+
// jsPsych.extensions['cyborg-hunter'].finalize();
|
|
21
|
+
// await jsPsych.extensions['cyborg-hunter-replay'].finalize();
|
|
22
|
+
// jsPsych.data.get().localSave('csv', 'data.csv');
|
|
23
|
+
// }
|
|
24
|
+
// });
|
|
25
|
+
//
|
|
26
|
+
// Replay finalizes LAST: it pulls CH's session report through a monitor
|
|
27
|
+
// reference stashed at initialize() — never through window.CyborgHunter,
|
|
28
|
+
// whose singleton slot is nulled by CH's destroy(). getSessionReport()
|
|
29
|
+
// remains callable after destroy() (sessionData survives teardown), so the
|
|
30
|
+
// pull works even though CH finalized first.
|
|
31
|
+
|
|
32
|
+
import * as CHReplay from '../replay/index.js';
|
|
33
|
+
import { buildReplayMeta } from '../replay/persistence.js';
|
|
34
|
+
|
|
35
|
+
class CyborgHunterReplayExtension {
|
|
36
|
+
static info = {
|
|
37
|
+
name: 'cyborg-hunter-replay',
|
|
38
|
+
version: '0.7.0',
|
|
39
|
+
data: {} // per-trial return is {}; session meta goes via addProperties
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
constructor(jsPsych) {
|
|
43
|
+
this.jsPsych = jsPsych;
|
|
44
|
+
this.api = null;
|
|
45
|
+
this._chMonitor = null;
|
|
46
|
+
this._lastRecording = null;
|
|
47
|
+
this._monitoring = false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
initialize(params) {
|
|
51
|
+
this.params = params || {};
|
|
52
|
+
this.api = CHReplay.attach(this.params);
|
|
53
|
+
this.api.startSession();
|
|
54
|
+
this._stashChMonitor();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The cyborg-hunter extension initializes before this one when declared
|
|
58
|
+
// earlier in the extensions array; if not, retry the stash at finalize.
|
|
59
|
+
_stashChMonitor() {
|
|
60
|
+
try {
|
|
61
|
+
var ch = this.jsPsych && this.jsPsych.extensions &&
|
|
62
|
+
this.jsPsych.extensions['cyborg-hunter'];
|
|
63
|
+
if (ch && ch.monitor) this._chMonitor = ch.monitor;
|
|
64
|
+
} catch (e) { /* standalone replay is fine */ }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// jsPsych 7 calls on_start unconditionally for every trial that lists the
|
|
68
|
+
// extension — must exist even as a no-op (see extension-cyborg-hunter.js).
|
|
69
|
+
on_start(_params) {}
|
|
70
|
+
|
|
71
|
+
on_load(params) {
|
|
72
|
+
if (!this.api) return;
|
|
73
|
+
this._monitoring = true;
|
|
74
|
+
var trialIndex = 0;
|
|
75
|
+
var plugin = 'unknown';
|
|
76
|
+
try { trialIndex = this.jsPsych.getProgress().current_trial_global; } catch (e) {}
|
|
77
|
+
try {
|
|
78
|
+
plugin = (this.jsPsych.getCurrentTrial() || {}).type?.info?.name || 'unknown';
|
|
79
|
+
} catch (e) {}
|
|
80
|
+
this.api.startTrial({
|
|
81
|
+
trialId: (params && params.trialId) || 'trial-' + trialIndex,
|
|
82
|
+
plugin: plugin
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
on_finish(_params) {
|
|
87
|
+
if (this.api && this._monitoring) {
|
|
88
|
+
this._monitoring = false;
|
|
89
|
+
this.api.endTrial();
|
|
90
|
+
}
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Called manually from the experiment's on_finish, AFTER the other three
|
|
95
|
+
// extensions (see header). Serializes, autosaves, attaches the meta
|
|
96
|
+
// pointer. Never throws — the experiment's save path must always run.
|
|
97
|
+
async finalize() {
|
|
98
|
+
if (!this.api) return;
|
|
99
|
+
try {
|
|
100
|
+
try { this.api.stopSession('finished'); } catch (e) { /* already stopped */ }
|
|
101
|
+
if (!this._chMonitor) this._stashChMonitor();
|
|
102
|
+
var chReport = null;
|
|
103
|
+
try {
|
|
104
|
+
chReport = this._chMonitor ? this._chMonitor.getSessionReport() : null;
|
|
105
|
+
} catch (e) {
|
|
106
|
+
console.warn('[cyborg-hunter-replay] could not pull CH session report:', e);
|
|
107
|
+
}
|
|
108
|
+
var result = await this.api.autoSaveNow({ chSessionReport: chReport });
|
|
109
|
+
this._lastRecording = result.recording;
|
|
110
|
+
this.jsPsych.data.addProperties({ integrityReplayMeta: result.meta });
|
|
111
|
+
} catch (e) {
|
|
112
|
+
console.warn('[cyborg-hunter-replay] finalize failed:', e);
|
|
113
|
+
try {
|
|
114
|
+
this.jsPsych.data.addProperties({
|
|
115
|
+
replayFinalizeError: String(e && e.message ? e.message : e)
|
|
116
|
+
});
|
|
117
|
+
} catch (_) { /* nothing left to do */ }
|
|
118
|
+
}
|
|
119
|
+
try { this.api.destroy(); } catch (e) { /* teardown best-effort */ }
|
|
120
|
+
// Null the handle so a second finalize() (e.g. duplicated on_finish
|
|
121
|
+
// wiring) is a clean no-op instead of re-serializing and re-saving.
|
|
122
|
+
this.api = null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Debug/test access to the finalized recording (also handy in the console
|
|
126
|
+
// during piloting: jsPsych.extensions['cyborg-hunter-replay'].getLastRecording()).
|
|
127
|
+
getLastRecording() {
|
|
128
|
+
return this._lastRecording;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export { CyborgHunterReplayExtension };
|
|
133
|
+
if (typeof window !== 'undefined') {
|
|
134
|
+
window.jsPsychCyborgHunterReplay = CyborgHunterReplayExtension;
|
|
135
|
+
}
|
|
@@ -10,7 +10,7 @@ class CyborgHunterExtension {
|
|
|
10
10
|
// and package.json). Hand-bumped on each release; if you forget, the
|
|
11
11
|
// jsPsych developer console shows a stale number — the actual version
|
|
12
12
|
// attached to data is read from window.CyborgHunter.VERSION at runtime.
|
|
13
|
-
version: '0.
|
|
13
|
+
version: '0.7.0',
|
|
14
14
|
data: { integrity: { type: 'object' } }
|
|
15
15
|
};
|
|
16
16
|
|
|
@@ -71,7 +71,10 @@ class CyborgHunterExtension {
|
|
|
71
71
|
this.monitor.startTrial({
|
|
72
72
|
trialId: (params && params.trialId) || `trial-${trialIndex}`,
|
|
73
73
|
phase: params?.phase || null,
|
|
74
|
-
decoyAnswer:
|
|
74
|
+
// Nullish coalescing (not ||) so an explicit `decoyAnswer: false` — the
|
|
75
|
+
// per-trial "skip the decoy" opt-out — reaches the core as false. With
|
|
76
|
+
// `|| null` it became null, and the core auto-generated a decoy instead.
|
|
77
|
+
decoyAnswer: params?.decoyAnswer ?? null,
|
|
75
78
|
experimentContainer: params?.experimentContainer || null
|
|
76
79
|
});
|
|
77
80
|
}
|
|
@@ -154,6 +157,10 @@ class CyborgHunterExtension {
|
|
|
154
157
|
sidebarEvents, keyboardShortcuts, windowPositions,
|
|
155
158
|
layoutShifts, zoomChanges, idleGaps, extensionInjections,
|
|
156
159
|
tabAwaySums, charsPerSec, aiExtensionsFound,
|
|
160
|
+
// Persist the effective runtime settings (preset + thresholds the CLI
|
|
161
|
+
// consumes) so the report can interpret this participant with the
|
|
162
|
+
// thresholds the library actually used, not analyst-side defaults.
|
|
163
|
+
config,
|
|
157
164
|
...extras
|
|
158
165
|
},
|
|
159
166
|
integrityScore: { hardScore, softScore, anyHardTriggered, trialsCompleted, softScoreThreshold },
|