cyborg-hunter 0.5.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 +156 -0
- package/CITATION.cff +29 -0
- package/LICENSE +21 -0
- package/README.md +80 -21
- package/bin/cyborg-hunter.js +6 -2
- package/dist/cyborg-hunter-replay.js +3 -0
- package/dist/cyborg-hunter.esm.js +114 -22
- package/dist/cyborg-hunter.min.js +3 -3
- package/dist/extension-cyborg-hunter.js +1 -1
- package/dist/extension-guard-friction.js +5 -5
- package/dist/extension-guard-honeypot.js +1 -1
- package/package.json +15 -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/extract-core.js +552 -0
- package/src/cli/ingest.js +250 -193
- package/src/cli/init.js +1 -1
- package/src/cli/preview-entry.js +36 -0
- package/src/cli/renderers/event-log.js +18 -19
- package/src/cli/renderers/extensions.js +12 -3
- package/src/cli/renderers/html-index-core.js +1273 -0
- package/src/cli/renderers/html-index.js +13 -1047
- package/src/cli/renderers/replay-assets.js +67 -0
- package/src/cli/renderers/replay-viewer.client.js +1185 -0
- package/src/cli/renderers/session-timeline-core.js +907 -0
- package/src/cli/renderers/session-timeline.js +33 -0
- package/src/cli/renderers/summary-csv.js +5 -0
- package/src/cli/renderers/trajectories-core.js +717 -0
- package/src/cli/renderers/trajectories.js +31 -635
- package/src/cli/renderers/triage-md.js +10 -4
- package/src/cli/renderers/typing-profile-core.js +211 -0
- package/src/cli/renderers/typing-profile.js +16 -186
- package/src/cli/report.js +42 -8
- package/src/core/monitor.js +77 -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-cyborg-hunter.js +9 -2
- package/src/jspsych/extension-guard-friction.js +43 -14
- package/src/jspsych/extension-guard-honeypot.js +25 -1
- 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/replay/viewer-model.js +125 -0
- package/src/shared/constants.js +12 -6
- package/src/shared/paths.js +20 -0
- package/src/shared/schema.js +5 -0
- package/src/shared/validation.js +55 -0
- package/src/cli/renderers/tab-timeline.js +0 -149
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// src/cli/renderers/replay-assets.js
|
|
2
|
+
// Emits per-participant replay assets into <outputDir>/replay/ as
|
|
3
|
+
// JSONP-style scripts: window.__chReplay['<pid>'] = <viewer model>.
|
|
4
|
+
//
|
|
5
|
+
// Why script files instead of JSON + fetch(): the HTML report is opened
|
|
6
|
+
// via file:// where fetch() of local files is CORS-blocked, but <script>
|
|
7
|
+
// tags load fine. The report injects the script lazily when the analyst
|
|
8
|
+
// opens a participant's Replay section.
|
|
9
|
+
//
|
|
10
|
+
// The wire→viewer time conversion (the second of the two allowed conversion
|
|
11
|
+
// points) lives in replay/viewer-model.js (0.7.2 extraction, pure/no Node
|
|
12
|
+
// APIs so a browser demo can bundle it); this module re-exports it below.
|
|
13
|
+
|
|
14
|
+
import { writeFileSync, mkdirSync } from 'fs';
|
|
15
|
+
import { join } from 'path';
|
|
16
|
+
|
|
17
|
+
import { sanitizeId as sanitize } from '../../shared/constants.js';
|
|
18
|
+
import { buildViewerModel } from '../../replay/viewer-model.js';
|
|
19
|
+
|
|
20
|
+
// Load-bearing re-export: renderReplayAssets (below) calls buildViewerModel
|
|
21
|
+
// in-file, and tests/cli/replay-render.test.js + tests/replay/alignment-viewer-model.test.js
|
|
22
|
+
// import it from here.
|
|
23
|
+
export { buildViewerModel };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Writes replay/<sanitizedPid>.replay.js for every participant with an
|
|
27
|
+
* attached recording. Returns { count, totalBytes } so report.js can print
|
|
28
|
+
* an honest size line (replay assets dominate report size at dom tier).
|
|
29
|
+
*/
|
|
30
|
+
export function renderReplayAssets(participants, outputDir) {
|
|
31
|
+
let count = 0;
|
|
32
|
+
let totalBytes = 0;
|
|
33
|
+
const withReplay = participants.filter((p) => p.replay && p.replay.recording);
|
|
34
|
+
if (withReplay.length === 0) return { count, totalBytes };
|
|
35
|
+
|
|
36
|
+
const replayDir = join(outputDir, 'replay');
|
|
37
|
+
mkdirSync(replayDir, { recursive: true });
|
|
38
|
+
|
|
39
|
+
// Sanitization is lossy ('a/b' and 'a_b' both map to a_b) — dedupe with a
|
|
40
|
+
// stable numeric suffix so a later write can never overwrite an earlier
|
|
41
|
+
// participant's asset. The actual path is stamped on the participant
|
|
42
|
+
// (replay.assetPath) and consumed by html-index, which must not recompute.
|
|
43
|
+
const usedNames = new Set();
|
|
44
|
+
for (const p of withReplay) {
|
|
45
|
+
const model = buildViewerModel(p.replay.recording);
|
|
46
|
+
// The store is keyed by the RAW participant id (what the report's
|
|
47
|
+
// loader passes); the filename uses the sanitized form.
|
|
48
|
+
// Null-prototype store: participant ids are untrusted, and a pid like
|
|
49
|
+
// "__proto__" on a plain object would mutate the store's prototype
|
|
50
|
+
// instead of creating an entry (prototype pollution).
|
|
51
|
+
const src =
|
|
52
|
+
'window.__chReplay = window.__chReplay || Object.create(null);\n' +
|
|
53
|
+
'window.__chReplay[' + JSON.stringify(String(p.participantId)) + '] = ' +
|
|
54
|
+
JSON.stringify(model).replace(/</g, '\\u003c') + ';\n';
|
|
55
|
+
let base = sanitize(p.participantId);
|
|
56
|
+
let name = base + '.replay.js';
|
|
57
|
+
for (let n = 2; usedNames.has(name.toLowerCase()); n++) {
|
|
58
|
+
name = base + '~' + n + '.replay.js';
|
|
59
|
+
}
|
|
60
|
+
usedNames.add(name.toLowerCase());
|
|
61
|
+
writeFileSync(join(replayDir, name), src);
|
|
62
|
+
p.replay.assetPath = 'replay/' + name;
|
|
63
|
+
count++;
|
|
64
|
+
totalBytes += Buffer.byteLength(src);
|
|
65
|
+
}
|
|
66
|
+
return { count, totalBytes };
|
|
67
|
+
}
|