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.
Files changed (58) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/CITATION.cff +29 -0
  3. package/LICENSE +21 -0
  4. package/README.md +80 -21
  5. package/bin/cyborg-hunter.js +6 -2
  6. package/dist/cyborg-hunter-replay.js +3 -0
  7. package/dist/cyborg-hunter.esm.js +114 -22
  8. package/dist/cyborg-hunter.min.js +3 -3
  9. package/dist/extension-cyborg-hunter.js +1 -1
  10. package/dist/extension-guard-friction.js +5 -5
  11. package/dist/extension-guard-honeypot.js +1 -1
  12. package/package.json +15 -3
  13. package/src/cli/analyzers/edge-exit.js +4 -1
  14. package/src/cli/analyzers/phase-scope.js +83 -0
  15. package/src/cli/analyzers/summary.js +161 -28
  16. package/src/cli/analyzers/triage.js +59 -27
  17. package/src/cli/config.js +26 -1
  18. package/src/cli/extract-core.js +552 -0
  19. package/src/cli/ingest.js +250 -193
  20. package/src/cli/init.js +1 -1
  21. package/src/cli/preview-entry.js +36 -0
  22. package/src/cli/renderers/event-log.js +18 -19
  23. package/src/cli/renderers/extensions.js +12 -3
  24. package/src/cli/renderers/html-index-core.js +1273 -0
  25. package/src/cli/renderers/html-index.js +13 -1047
  26. package/src/cli/renderers/replay-assets.js +67 -0
  27. package/src/cli/renderers/replay-viewer.client.js +1185 -0
  28. package/src/cli/renderers/session-timeline-core.js +907 -0
  29. package/src/cli/renderers/session-timeline.js +33 -0
  30. package/src/cli/renderers/summary-csv.js +5 -0
  31. package/src/cli/renderers/trajectories-core.js +717 -0
  32. package/src/cli/renderers/trajectories.js +31 -635
  33. package/src/cli/renderers/triage-md.js +10 -4
  34. package/src/cli/renderers/typing-profile-core.js +211 -0
  35. package/src/cli/renderers/typing-profile.js +16 -186
  36. package/src/cli/report.js +42 -8
  37. package/src/core/monitor.js +77 -7
  38. package/src/core/scoring.js +11 -2
  39. package/src/core/signals/browser.js +51 -18
  40. package/src/core/signals/clipboard.js +10 -2
  41. package/src/core/signals/dom-protection.js +9 -0
  42. package/src/core/signals/focus.js +16 -2
  43. package/src/jspsych/extension-cyborg-hunter-replay.js +135 -0
  44. package/src/jspsych/extension-cyborg-hunter.js +9 -2
  45. package/src/jspsych/extension-guard-friction.js +43 -14
  46. package/src/jspsych/extension-guard-honeypot.js +25 -1
  47. package/src/replay/capture-dom.js +575 -0
  48. package/src/replay/capture-trace.js +468 -0
  49. package/src/replay/index.js +104 -0
  50. package/src/replay/persistence.js +141 -0
  51. package/src/replay/recorder.js +315 -0
  52. package/src/replay/serializer.js +119 -0
  53. package/src/replay/viewer-model.js +125 -0
  54. package/src/shared/constants.js +12 -6
  55. package/src/shared/paths.js +20 -0
  56. package/src/shared/schema.js +5 -0
  57. package/src/shared/validation.js +55 -0
  58. 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
+ }