@descryy/runtime-browser 0.2.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.
Files changed (41) hide show
  1. package/dist/browser-action-collector.d.ts +181 -0
  2. package/dist/browser-action-collector.d.ts.map +1 -0
  3. package/dist/browser-action-collector.js +427 -0
  4. package/dist/browser-action-collector.js.map +1 -0
  5. package/dist/browser-console-collector.d.ts +33 -0
  6. package/dist/browser-console-collector.d.ts.map +1 -0
  7. package/dist/browser-console-collector.js +185 -0
  8. package/dist/browser-console-collector.js.map +1 -0
  9. package/dist/browser-network-collector.d.ts +112 -0
  10. package/dist/browser-network-collector.d.ts.map +1 -0
  11. package/dist/browser-network-collector.js +682 -0
  12. package/dist/browser-network-collector.js.map +1 -0
  13. package/dist/browser-session.d.ts +144 -0
  14. package/dist/browser-session.d.ts.map +1 -0
  15. package/dist/browser-session.js +136 -0
  16. package/dist/browser-session.js.map +1 -0
  17. package/dist/collector-version.d.ts +10 -0
  18. package/dist/collector-version.d.ts.map +1 -0
  19. package/dist/collector-version.js +12 -0
  20. package/dist/collector-version.js.map +1 -0
  21. package/dist/dom-evidence.d.ts +20 -0
  22. package/dist/dom-evidence.d.ts.map +1 -0
  23. package/dist/dom-evidence.js +32 -0
  24. package/dist/dom-evidence.js.map +1 -0
  25. package/dist/frontend-initiator-capture.d.ts +87 -0
  26. package/dist/frontend-initiator-capture.d.ts.map +1 -0
  27. package/dist/frontend-initiator-capture.js +142 -0
  28. package/dist/frontend-initiator-capture.js.map +1 -0
  29. package/dist/index.d.ts +16 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +9 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/network-correlation.d.ts +15 -0
  34. package/dist/network-correlation.d.ts.map +1 -0
  35. package/dist/network-correlation.js +29 -0
  36. package/dist/network-correlation.js.map +1 -0
  37. package/dist/script-url-mapping.d.ts +40 -0
  38. package/dist/script-url-mapping.d.ts.map +1 -0
  39. package/dist/script-url-mapping.js +96 -0
  40. package/dist/script-url-mapping.js.map +1 -0
  41. package/package.json +36 -0
@@ -0,0 +1,181 @@
1
+ /**
2
+ * §7's action vocabulary (navigate/click/type/wait) as a `Collector`.
3
+ *
4
+ * Actions are commands, not a passive stream -- the opposite shape from
5
+ * `LogCollector`/console/network collectors, which push evidence as
6
+ * external events happen. `Collector.emit` is only ever reachable through
7
+ * `CollectorContext` passed into `start()` (RT-005: "never exposed
8
+ * outward"), so this stores that `emit` internally at `start()` and
9
+ * exposes action methods alongside the three Collector methods, rather
10
+ * than inventing a second, external emit path.
11
+ *
12
+ * `waitFor` deliberately emits nothing: the frozen `RuntimeEventType`
13
+ * vocabulary has no "wait" event, and a wait is a synchronization
14
+ * primitive for the driver, not an observable page fact the way a click or
15
+ * a navigation is.
16
+ *
17
+ * `start()` also emits exactly one `PAGE_CREATED`, using `session.startedAt`
18
+ * — the page's real creation time, not this moment. This is the answer to
19
+ * the non-goal `browser-session.ts` itself declares (RT-053): the page's
20
+ * existence is real evidence and this is the one collector already
21
+ * translating this session's facts into Evidence, so a second, near-empty
22
+ * collector whose only job was one event type would be structure for its
23
+ * own sake.
24
+ *
25
+ * `select`/`submit`/`inspect`/`assertVisible`/`assertText` (RT-058, §5) fill
26
+ * the rest of the checklist's action gap without needing a single new
27
+ * `RuntimeEventType`:
28
+ * - `select` reuses `INPUT` (`dom-evidence.ts`'s own reasoning: a select's
29
+ * new value is a form-input fact, not a distinct kind of fact).
30
+ * - `submit` locates the form's own submit control and calls `click()` on
31
+ * it, so it is real `CLICK` evidence of the control the user actually
32
+ * activates, not a synthesized event with no vocabulary to be.
33
+ * - `inspect`/`assertVisible`/`assertText` emit nothing, the same
34
+ * reasoning `waitFor` already established: these are synchronization/
35
+ * verification primitives for the driver, not an observable page fact
36
+ * the way a click or a navigation is. `inspect` and `assertText` both
37
+ * run through `captureElementSnapshot` rather than a second DOM-reading
38
+ * path, so "what the collector saw" stays one code path.
39
+ *
40
+ * `screenshot` (RT-061) captures the viewport and emits `SCREENSHOT`
41
+ * (`packages/contracts`, added in the same change as this method, per
42
+ * RT-053's own lesson: a declared-but-unemitted type is the mistake, not a
43
+ * type existing at all).
44
+ *
45
+ * **Capture is preceded by a best-effort settle wait (RT-221), not a raw,
46
+ * instant `page.screenshot()`.** The original version fired the instant it
47
+ * was called -- no network-idle wait, no hydration check -- so a capture
48
+ * mid-transition (a still-loading page, a spinner not yet replaced by real
49
+ * content) was stored as ordinary evidence with no signal anything was off.
50
+ * `waitForSettle()` below waits for Playwright's own `networkidle` load
51
+ * state (capped short, so a page with continuous background traffic --
52
+ * polling, a websocket -- degrades to "waited as long as reasonable" rather
53
+ * than hanging), then two animation-frame ticks so a render queued by the
54
+ * network response's own handler has actually painted before capture.
55
+ * **This is not the same failure DEC-D234 (descry-desktop) found and fixed
56
+ * with a double-capture convergence loop** -- that bug was Electron's
57
+ * `webContents.capturePage()` returning a stale, pre-mutation frame even
58
+ * after the DOM had already settled, on a `show: false` window; DEC-D234's
59
+ * own verification confirms a plain, composited `chromium.launch()` page
60
+ * (what this collector drives) does not share that compositor-level race.
61
+ * The gap here runs the other direction -- captured too early, before the
62
+ * page finishes settling -- so the fix is a settle wait, not a frame
63
+ * comparison. **Disclosed, not a guarantee:** a DOM mutation scheduled via a
64
+ * bare `setTimeout`/`setInterval` outside a `requestAnimationFrame`-driven
65
+ * paint, and firing after this wait completes, is not caught.
66
+ *
67
+ * Redaction here does NOT follow the pattern every other method in this file
68
+ * uses:
69
+ *
70
+ * - The mask is applied at CAPTURE time, via Playwright's real
71
+ * pixel-level `page.screenshot({ mask, maskColor })` — every
72
+ * `input[type="password"]` on the page by default, plus any
73
+ * caller-supplied `maskSelectors`. **Coverage is caller-declared, not
74
+ * detected** (§20): this collector has no way to recognize a sensitive
75
+ * value that isn't in a password field — a token in a URL bar, a name
76
+ * in a table, a body panel left open in dev tools all pass through
77
+ * unmasked unless the caller names them.
78
+ * - The payload key is `imageBase64`, chosen specifically because it does
79
+ * NOT match `evidence-store`'s `BODY_BEARING_KEY_PATTERN`
80
+ * (`body|post-data|request-body|response-body|raw`). That pattern's
81
+ * `GENERIC_TOKEN_PATTERN` text scanner reads base64 as a wall of
82
+ * 24+-character tokens and would shred a real image into
83
+ * `[REDACTED]==` garbage — it corrupts pixels, it does not redact them.
84
+ * This is a deliberate, stated EXEMPTION from store-side scanning, not
85
+ * an accident of naming: the capture-time mask above is this payload's
86
+ * only real protection. Renaming this key to anything
87
+ * `BODY_BEARING_KEY_PATTERN` matches (or renaming a body-bearing key TO
88
+ * something like it) silently changes which protection applies and
89
+ * must be a deliberate re-decision, not a drive-by rename.
90
+ * - The evidence this produces will carry `redactionStatus:
91
+ * "not-required"` once `EvidenceStore.write()` runs `redact()` on it —
92
+ * same string every OTHER "scanned, found nothing sensitive" case
93
+ * produces, but it means something different here: this payload was
94
+ * never scanned at all, because a text/JSON scanner cannot read
95
+ * pixels. Do not read a screenshot's `not-required` as "this image was
96
+ * checked."
97
+ *
98
+ * `captureVideo` (video evidence, competitive research proposal) emits
99
+ * `VIDEO` and is structurally different from every method above it in one
100
+ * way: it is not something this collector *does* to the page, it is a
101
+ * report of a fact `browser-session.ts` already finished producing.
102
+ * Playwright's `recordVideo` starts automatically at context creation (the
103
+ * `BrowserConfiguration.recordVideo` toggle, `browser-session.ts`) and only
104
+ * *finishes* the file once the browser context closes — so unlike
105
+ * `screenshot()`, which can be called any time the page is alive,
106
+ * `captureVideo()` can only be called **after** `session.close()` has
107
+ * resolved, and it must be called **before** this collector's own `stop()`
108
+ * (`stop()` clears `emit`, and there would be nothing left to emit into).
109
+ * That ordering is the opposite of `stop()`-then-`close()`, which is how
110
+ * every existing caller in this repo already sequences things — genuinely
111
+ * different for this one method, not a mistake to unify away, because the
112
+ * video file cannot exist any earlier.
113
+ *
114
+ * Redaction follows `screenshot()`'s exemption for the identical reason:
115
+ * `videoBase64` does not match `BODY_BEARING_KEY_PATTERN` either, so a real
116
+ * captured video's bytes reach a reader intact rather than shredded by a
117
+ * text scanner. There is no capture-time pixel mask here, unlike
118
+ * `screenshot()` — Playwright's `mask` option applies to `page.screenshot()`
119
+ * only, not to `recordVideo` — so a caller enabling video evidence should
120
+ * know it is unmasked footage of whatever the page displayed; that gap is
121
+ * disclosed, not solved, by this change (see this repo's decision record for
122
+ * this feature).
123
+ */
124
+ import type { Collector } from "@descryy/runtime-contracts";
125
+ import type { BrowserSession } from "./browser-session.ts";
126
+ import { type DomElementSnapshot } from "./dom-evidence.ts";
127
+ export interface BrowserActionCollectorOptions {
128
+ readonly session: BrowserSession;
129
+ /** Which owned system this collector's evidence should be attributed to (plan §16.7). Null when not applicable. */
130
+ readonly service?: string;
131
+ }
132
+ export interface WaitForOptions {
133
+ readonly timeoutMs?: number;
134
+ }
135
+ export interface ScreenshotOptions {
136
+ /**
137
+ * Extra selectors to mask, beyond the automatic default of every
138
+ * `input[type="password"]` on the page. See this module's own header:
139
+ * coverage is caller-declared, not detected.
140
+ */
141
+ readonly maskSelectors?: readonly string[];
142
+ }
143
+ export interface BrowserActionCollector extends Collector {
144
+ navigate(url: string): Promise<void>;
145
+ click(selector: string): Promise<void>;
146
+ /**
147
+ * **Real keystrokes, appended.** `page.locator().pressSequentially()` --
148
+ * one key event per character, on top of whatever the field already
149
+ * contains.
150
+ *
151
+ * This used to be `page.fill()`, which meant the package shipped one text
152
+ * verb wearing two names: "type" that silently replaced. The distinction is
153
+ * not cosmetic. A field that opens an autocomplete on `keydown`, validates
154
+ * per character, or debounces a search never sees anything at all from
155
+ * `fill()`, so a caller reaching for "type" to exercise that behaviour got a
156
+ * page that had not been typed into. Use `fill()` when the goal is to put a
157
+ * known value in a field, and this when the goal is to type.
158
+ */
159
+ type(selector: string, text: string): Promise<void>;
160
+ /** **Replaces** the field's contents in one operation (`page.fill()`), firing a single `input` event and no key events. The verb for setting a value, as distinct from `type()`'s verb for typing one. */
161
+ fill(selector: string, text: string): Promise<void>;
162
+ select(selector: string, value: string): Promise<void>;
163
+ submit(formSelector: string): Promise<void>;
164
+ waitFor(selector: string, options?: WaitForOptions): Promise<void>;
165
+ inspect(selector: string): Promise<DomElementSnapshot>;
166
+ assertVisible(selector: string): Promise<void>;
167
+ assertText(selector: string, expected: string): Promise<void>;
168
+ screenshot(options?: ScreenshotOptions): Promise<void>;
169
+ /**
170
+ * Emits `VIDEO` for this session's finished `recordVideo` file, or emits
171
+ * nothing at all when the session was never launched with
172
+ * `recordVideo: true` (see this module's own header for why -- there is
173
+ * no video to report, and that is a real, non-error state, the same shape
174
+ * `waitFor`'s "emits nothing" already establishes for a different reason).
175
+ * Must be called after `session.close()` and before this collector's own
176
+ * `stop()`.
177
+ */
178
+ captureVideo(): Promise<void>;
179
+ }
180
+ export declare function createBrowserActionCollector(options: BrowserActionCollectorOptions): BrowserActionCollector;
181
+ //# sourceMappingURL=browser-action-collector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-action-collector.d.ts","sourceRoot":"","sources":["../src/browser-action-collector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0HG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAyE,MAAM,4BAA4B,CAAC;AAEnI,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAA0B,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAGpF,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,mHAAmH;IACnH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5C;AAED,MAAM,WAAW,sBAAuB,SAAQ,SAAS;IACvD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,0MAA0M;IAC1M,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACvD,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,UAAU,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD;;;;;;;;OAQG;IACH,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAwCD,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,6BAA6B,GAAG,sBAAsB,CAgS3G"}
@@ -0,0 +1,427 @@
1
+ /**
2
+ * §7's action vocabulary (navigate/click/type/wait) as a `Collector`.
3
+ *
4
+ * Actions are commands, not a passive stream -- the opposite shape from
5
+ * `LogCollector`/console/network collectors, which push evidence as
6
+ * external events happen. `Collector.emit` is only ever reachable through
7
+ * `CollectorContext` passed into `start()` (RT-005: "never exposed
8
+ * outward"), so this stores that `emit` internally at `start()` and
9
+ * exposes action methods alongside the three Collector methods, rather
10
+ * than inventing a second, external emit path.
11
+ *
12
+ * `waitFor` deliberately emits nothing: the frozen `RuntimeEventType`
13
+ * vocabulary has no "wait" event, and a wait is a synchronization
14
+ * primitive for the driver, not an observable page fact the way a click or
15
+ * a navigation is.
16
+ *
17
+ * `start()` also emits exactly one `PAGE_CREATED`, using `session.startedAt`
18
+ * — the page's real creation time, not this moment. This is the answer to
19
+ * the non-goal `browser-session.ts` itself declares (RT-053): the page's
20
+ * existence is real evidence and this is the one collector already
21
+ * translating this session's facts into Evidence, so a second, near-empty
22
+ * collector whose only job was one event type would be structure for its
23
+ * own sake.
24
+ *
25
+ * `select`/`submit`/`inspect`/`assertVisible`/`assertText` (RT-058, §5) fill
26
+ * the rest of the checklist's action gap without needing a single new
27
+ * `RuntimeEventType`:
28
+ * - `select` reuses `INPUT` (`dom-evidence.ts`'s own reasoning: a select's
29
+ * new value is a form-input fact, not a distinct kind of fact).
30
+ * - `submit` locates the form's own submit control and calls `click()` on
31
+ * it, so it is real `CLICK` evidence of the control the user actually
32
+ * activates, not a synthesized event with no vocabulary to be.
33
+ * - `inspect`/`assertVisible`/`assertText` emit nothing, the same
34
+ * reasoning `waitFor` already established: these are synchronization/
35
+ * verification primitives for the driver, not an observable page fact
36
+ * the way a click or a navigation is. `inspect` and `assertText` both
37
+ * run through `captureElementSnapshot` rather than a second DOM-reading
38
+ * path, so "what the collector saw" stays one code path.
39
+ *
40
+ * `screenshot` (RT-061) captures the viewport and emits `SCREENSHOT`
41
+ * (`packages/contracts`, added in the same change as this method, per
42
+ * RT-053's own lesson: a declared-but-unemitted type is the mistake, not a
43
+ * type existing at all).
44
+ *
45
+ * **Capture is preceded by a best-effort settle wait (RT-221), not a raw,
46
+ * instant `page.screenshot()`.** The original version fired the instant it
47
+ * was called -- no network-idle wait, no hydration check -- so a capture
48
+ * mid-transition (a still-loading page, a spinner not yet replaced by real
49
+ * content) was stored as ordinary evidence with no signal anything was off.
50
+ * `waitForSettle()` below waits for Playwright's own `networkidle` load
51
+ * state (capped short, so a page with continuous background traffic --
52
+ * polling, a websocket -- degrades to "waited as long as reasonable" rather
53
+ * than hanging), then two animation-frame ticks so a render queued by the
54
+ * network response's own handler has actually painted before capture.
55
+ * **This is not the same failure DEC-D234 (descry-desktop) found and fixed
56
+ * with a double-capture convergence loop** -- that bug was Electron's
57
+ * `webContents.capturePage()` returning a stale, pre-mutation frame even
58
+ * after the DOM had already settled, on a `show: false` window; DEC-D234's
59
+ * own verification confirms a plain, composited `chromium.launch()` page
60
+ * (what this collector drives) does not share that compositor-level race.
61
+ * The gap here runs the other direction -- captured too early, before the
62
+ * page finishes settling -- so the fix is a settle wait, not a frame
63
+ * comparison. **Disclosed, not a guarantee:** a DOM mutation scheduled via a
64
+ * bare `setTimeout`/`setInterval` outside a `requestAnimationFrame`-driven
65
+ * paint, and firing after this wait completes, is not caught.
66
+ *
67
+ * Redaction here does NOT follow the pattern every other method in this file
68
+ * uses:
69
+ *
70
+ * - The mask is applied at CAPTURE time, via Playwright's real
71
+ * pixel-level `page.screenshot({ mask, maskColor })` — every
72
+ * `input[type="password"]` on the page by default, plus any
73
+ * caller-supplied `maskSelectors`. **Coverage is caller-declared, not
74
+ * detected** (§20): this collector has no way to recognize a sensitive
75
+ * value that isn't in a password field — a token in a URL bar, a name
76
+ * in a table, a body panel left open in dev tools all pass through
77
+ * unmasked unless the caller names them.
78
+ * - The payload key is `imageBase64`, chosen specifically because it does
79
+ * NOT match `evidence-store`'s `BODY_BEARING_KEY_PATTERN`
80
+ * (`body|post-data|request-body|response-body|raw`). That pattern's
81
+ * `GENERIC_TOKEN_PATTERN` text scanner reads base64 as a wall of
82
+ * 24+-character tokens and would shred a real image into
83
+ * `[REDACTED]==` garbage — it corrupts pixels, it does not redact them.
84
+ * This is a deliberate, stated EXEMPTION from store-side scanning, not
85
+ * an accident of naming: the capture-time mask above is this payload's
86
+ * only real protection. Renaming this key to anything
87
+ * `BODY_BEARING_KEY_PATTERN` matches (or renaming a body-bearing key TO
88
+ * something like it) silently changes which protection applies and
89
+ * must be a deliberate re-decision, not a drive-by rename.
90
+ * - The evidence this produces will carry `redactionStatus:
91
+ * "not-required"` once `EvidenceStore.write()` runs `redact()` on it —
92
+ * same string every OTHER "scanned, found nothing sensitive" case
93
+ * produces, but it means something different here: this payload was
94
+ * never scanned at all, because a text/JSON scanner cannot read
95
+ * pixels. Do not read a screenshot's `not-required` as "this image was
96
+ * checked."
97
+ *
98
+ * `captureVideo` (video evidence, competitive research proposal) emits
99
+ * `VIDEO` and is structurally different from every method above it in one
100
+ * way: it is not something this collector *does* to the page, it is a
101
+ * report of a fact `browser-session.ts` already finished producing.
102
+ * Playwright's `recordVideo` starts automatically at context creation (the
103
+ * `BrowserConfiguration.recordVideo` toggle, `browser-session.ts`) and only
104
+ * *finishes* the file once the browser context closes — so unlike
105
+ * `screenshot()`, which can be called any time the page is alive,
106
+ * `captureVideo()` can only be called **after** `session.close()` has
107
+ * resolved, and it must be called **before** this collector's own `stop()`
108
+ * (`stop()` clears `emit`, and there would be nothing left to emit into).
109
+ * That ordering is the opposite of `stop()`-then-`close()`, which is how
110
+ * every existing caller in this repo already sequences things — genuinely
111
+ * different for this one method, not a mistake to unify away, because the
112
+ * video file cannot exist any earlier.
113
+ *
114
+ * Redaction follows `screenshot()`'s exemption for the identical reason:
115
+ * `videoBase64` does not match `BODY_BEARING_KEY_PATTERN` either, so a real
116
+ * captured video's bytes reach a reader intact rather than shredded by a
117
+ * text scanner. There is no capture-time pixel mask here, unlike
118
+ * `screenshot()` — Playwright's `mask` option applies to `page.screenshot()`
119
+ * only, not to `recordVideo` — so a caller enabling video evidence should
120
+ * know it is unmasked footage of whatever the page displayed; that gap is
121
+ * disclosed, not solved, by this change (see this repo's decision record for
122
+ * this feature).
123
+ */
124
+ import { captureElementSnapshot } from "./dom-evidence.js";
125
+ import { COLLECTOR_VERSION } from "./collector-version.js";
126
+ function notThisCollectorsJob(what) {
127
+ return { availability: "unavailable", reason: `BrowserActionCollector handles DOM actions only, not ${what} -- see the dedicated collector.` };
128
+ }
129
+ const CAPABILITIES = {
130
+ domObservation: { availability: "available", reason: null },
131
+ consoleObservation: notThisCollectorsJob("console/page-error observation"),
132
+ networkObservation: notThisCollectorsJob("network observation"),
133
+ backendLogAccess: { availability: "unavailable", reason: "this collector observes a browser page, not a backend process." },
134
+ distributedTrace: { availability: "unavailable", reason: "DOM actions carry no trace/request identifiers of their own." },
135
+ sourceMapping: { availability: "unavailable", reason: "not applicable -- DOM actions produce no stack traces." },
136
+ stackCapture: { availability: "unavailable", reason: "not applicable -- DOM actions produce no stack traces." },
137
+ processLifecycle: notThisCollectorsJob("process lifecycle observation"),
138
+ databaseObservation: notThisCollectorsJob("database query observation"),
139
+ externalServiceObservation: notThisCollectorsJob("external service observation"),
140
+ };
141
+ async function isPasswordField(page, selector) {
142
+ const type = await page.getAttribute(selector, "type").catch(() => null);
143
+ return type === "password";
144
+ }
145
+ /** Capped so a page with continuous background traffic (polling, a websocket) degrades to "waited as long as reasonable" rather than hanging `screenshot()`. See this module's header for the full rationale (RT-221). */
146
+ const SETTLE_NETWORK_IDLE_TIMEOUT_MS = 2000;
147
+ async function waitForSettle(page) {
148
+ await page.waitForLoadState("networkidle", { timeout: SETTLE_NETWORK_IDLE_TIMEOUT_MS }).catch(() => { });
149
+ // `requestAnimationFrame` isn't declared under this project's Node-only
150
+ // `lib` (ES2023, no `dom` -- see tsconfig.base.json's own comment on why),
151
+ // so it's reached off `globalThis` rather than pulling `dom` into every
152
+ // file's ambient scope for one in-page callback. Real at runtime: this
153
+ // callback executes inside the page, where it's Chromium's own global.
154
+ await page.evaluate(() => {
155
+ const raf = globalThis.requestAnimationFrame;
156
+ return new Promise((resolve) => raf(() => raf(() => resolve())));
157
+ });
158
+ }
159
+ export function createBrowserActionCollector(options) {
160
+ const { session } = options;
161
+ const { page } = session;
162
+ let emit;
163
+ function emitEvidence(partial) {
164
+ // A collector not yet start()-ed (or already stop()-ed) has nowhere to
165
+ // push evidence -- silently doing nothing here would look identical to
166
+ // "nothing happened," so the action itself still runs; only its
167
+ // evidence is dropped, same as any other stopped collector.
168
+ emit?.({ ...partial, collectorVersion: COLLECTOR_VERSION });
169
+ }
170
+ async function navigate(url) {
171
+ const response = await page.goto(url);
172
+ emitEvidence({
173
+ timestamp: new Date().toISOString(),
174
+ source: "browser-dom",
175
+ service: options.service ?? null,
176
+ process: null,
177
+ eventType: "NAVIGATION",
178
+ payload: { url, statusCode: response?.status() ?? null, browserSessionId: session.sessionId },
179
+ traceId: null,
180
+ requestId: null,
181
+ correlationId: null,
182
+ graphNodeId: null,
183
+ sourceLocation: null,
184
+ stackTrace: null,
185
+ confidence: 1,
186
+ redactionStatus: "pending-redaction",
187
+ });
188
+ }
189
+ async function click(selector) {
190
+ const element = await captureElementSnapshot(page, selector);
191
+ await page.click(selector);
192
+ emitEvidence({
193
+ timestamp: new Date().toISOString(),
194
+ source: "browser-dom",
195
+ service: options.service ?? null,
196
+ process: null,
197
+ eventType: "CLICK",
198
+ payload: { selector, element, browserSessionId: session.sessionId },
199
+ traceId: null,
200
+ requestId: null,
201
+ correlationId: null,
202
+ graphNodeId: null,
203
+ sourceLocation: null,
204
+ stackTrace: null,
205
+ confidence: 1,
206
+ redactionStatus: "pending-redaction",
207
+ });
208
+ }
209
+ /**
210
+ * The two text verbs share everything except the one line that actually
211
+ * touches the page, so they share one implementation. Written this way on
212
+ * purpose: the password-withholding rule below belongs to text entry as a
213
+ * class, and a second verb with its own copy of this body is exactly how a
214
+ * later edit ends up redacting in one of them and not the other.
215
+ */
216
+ async function enterText(selector, text, mode) {
217
+ const isPassword = await isPasswordField(page, selector);
218
+ const element = await captureElementSnapshot(page, selector);
219
+ if (mode === "replace") {
220
+ await page.fill(selector, text);
221
+ }
222
+ else {
223
+ // Real key events, one per character, on top of the existing value --
224
+ // `page.type()` is deprecated in favour of this, and `fill()` is not a
225
+ // substitute for it (see the interface comment).
226
+ await page.locator(selector).pressSequentially(text);
227
+ }
228
+ emitEvidence({
229
+ timestamp: new Date().toISOString(),
230
+ source: "browser-dom",
231
+ service: options.service ?? null,
232
+ process: null,
233
+ eventType: "INPUT",
234
+ // The typed text itself is withheld for a password field -- "when
235
+ // safe" (plan §8) is a content-sensitivity judgment this collector
236
+ // has to make itself; generic key-name redaction at write time
237
+ // wouldn't catch a password's actual characters sitting under a
238
+ // "text" key.
239
+ //
240
+ // `mode` is recorded because the payload otherwise reads identically
241
+ // for the two verbs, and "the field now contains X" and "X was added to
242
+ // whatever the field contained" are different facts about the run.
243
+ payload: { selector, text: isPassword ? null : text, mode, element, browserSessionId: session.sessionId },
244
+ traceId: null,
245
+ requestId: null,
246
+ correlationId: null,
247
+ graphNodeId: null,
248
+ sourceLocation: null,
249
+ stackTrace: null,
250
+ confidence: 1,
251
+ redactionStatus: "pending-redaction",
252
+ });
253
+ }
254
+ function type(selector, text) {
255
+ return enterText(selector, text, "append");
256
+ }
257
+ function fill(selector, text) {
258
+ return enterText(selector, text, "replace");
259
+ }
260
+ async function select(selector, value) {
261
+ const element = await captureElementSnapshot(page, selector);
262
+ await page.selectOption(selector, value);
263
+ emitEvidence({
264
+ timestamp: new Date().toISOString(),
265
+ source: "browser-dom",
266
+ service: options.service ?? null,
267
+ process: null,
268
+ eventType: "INPUT",
269
+ payload: { selector, value, element, browserSessionId: session.sessionId },
270
+ traceId: null,
271
+ requestId: null,
272
+ correlationId: null,
273
+ graphNodeId: null,
274
+ sourceLocation: null,
275
+ stackTrace: null,
276
+ confidence: 1,
277
+ redactionStatus: "pending-redaction",
278
+ });
279
+ }
280
+ async function submit(formSelector) {
281
+ // The form itself has nothing to click -- what actually happens when a
282
+ // user "submits" is activating its submit control. Finding that control
283
+ // and routing through the existing `click()` makes this real CLICK
284
+ // evidence of a real element, not a synthesized "SUBMIT" fact the
285
+ // frozen vocabulary has no type for.
286
+ const submitControlSelector = `${formSelector} button[type="submit"], ${formSelector} input[type="submit"]`;
287
+ const matched = (await page.locator(submitControlSelector).count().catch(() => 0)) > 0;
288
+ if (!matched) {
289
+ throw new Error(`submit(${JSON.stringify(formSelector)}): no submit control (button[type="submit"] or input[type="submit"]) found inside this form.`);
290
+ }
291
+ await click(submitControlSelector);
292
+ }
293
+ async function waitFor(selector, waitOptions = {}) {
294
+ await page.waitForSelector(selector, { timeout: waitOptions.timeoutMs ?? 30000 });
295
+ }
296
+ async function inspect(selector) {
297
+ return captureElementSnapshot(page, selector);
298
+ }
299
+ async function assertVisible(selector) {
300
+ const visible = await page
301
+ .locator(selector)
302
+ .first()
303
+ .isVisible()
304
+ .catch(() => false);
305
+ if (!visible) {
306
+ throw new Error(`assertVisible(${JSON.stringify(selector)}): element is not visible (or not present).`);
307
+ }
308
+ }
309
+ async function assertText(selector, expected) {
310
+ const element = await captureElementSnapshot(page, selector);
311
+ if (element.text === null || !element.text.includes(expected)) {
312
+ throw new Error(`assertText(${JSON.stringify(selector)}): expected text to include ${JSON.stringify(expected)}, got ${JSON.stringify(element.text)}.`);
313
+ }
314
+ }
315
+ async function screenshot(screenshotOptions = {}) {
316
+ const maskSelectors = ['input[type="password"]', ...(screenshotOptions.maskSelectors ?? [])];
317
+ await waitForSettle(page);
318
+ const buffer = await page.screenshot({
319
+ mask: maskSelectors.map((selector) => page.locator(selector)),
320
+ maskColor: "#FF00FF",
321
+ });
322
+ emitEvidence({
323
+ timestamp: new Date().toISOString(),
324
+ source: "browser-dom",
325
+ service: options.service ?? null,
326
+ process: null,
327
+ eventType: "SCREENSHOT",
328
+ payload: {
329
+ // See this module's own header for why `imageBase64` -- not
330
+ // `raw`/`body`/anything BODY_BEARING_KEY_PATTERN matches -- is
331
+ // deliberate, and why the store-side redaction this key exempts
332
+ // itself from is not this payload's real protection.
333
+ imageBase64: buffer.toString("base64"),
334
+ maskedSelectors: maskSelectors,
335
+ browserSessionId: session.sessionId,
336
+ },
337
+ traceId: null,
338
+ requestId: null,
339
+ correlationId: null,
340
+ graphNodeId: null,
341
+ sourceLocation: null,
342
+ stackTrace: null,
343
+ confidence: 1,
344
+ // Same literal every other method here sets, and EvidenceStore.write()
345
+ // recomputes it regardless -- see this module's header for what the
346
+ // resulting "not-required" will actually mean for THIS payload.
347
+ redactionStatus: "pending-redaction",
348
+ });
349
+ }
350
+ async function captureVideo() {
351
+ const bytes = await session.finishedVideo();
352
+ if (bytes === null)
353
+ return; // recordVideo was never enabled for this session -- nothing to report, not an error.
354
+ emitEvidence({
355
+ timestamp: new Date().toISOString(),
356
+ source: "browser-dom",
357
+ service: options.service ?? null,
358
+ process: null,
359
+ eventType: "VIDEO",
360
+ payload: {
361
+ // Mirrors screenshot()'s `imageBase64` choice, for the identical
362
+ // reason -- see this module's header -- `videoBase64` does not
363
+ // match evidence-store's BODY_BEARING_KEY_PATTERN either.
364
+ videoBase64: Buffer.from(bytes).toString("base64"),
365
+ browserSessionId: session.sessionId,
366
+ },
367
+ traceId: null,
368
+ requestId: null,
369
+ correlationId: null,
370
+ graphNodeId: null,
371
+ sourceLocation: null,
372
+ stackTrace: null,
373
+ confidence: 1,
374
+ // Same literal every other method here sets; EvidenceStore.write()
375
+ // recomputes it via redact() -- see this module's header for what the
376
+ // resulting "not-required" will mean for THIS payload (never scanned,
377
+ // not "checked and clean").
378
+ redactionStatus: "pending-redaction",
379
+ });
380
+ }
381
+ return {
382
+ collectorId: `browser-action:${session.sessionId}`,
383
+ start(context) {
384
+ if (!session.isUsable()) {
385
+ return Promise.resolve({ available: false, reason: "the browser session's page has already crashed or closed." });
386
+ }
387
+ emit = context.emit;
388
+ emitEvidence({
389
+ timestamp: session.startedAt,
390
+ source: "browser-dom",
391
+ service: options.service ?? null,
392
+ process: null,
393
+ eventType: "PAGE_CREATED",
394
+ payload: { browserSessionId: session.sessionId, initialUrl: page.url() },
395
+ traceId: null,
396
+ requestId: null,
397
+ correlationId: null,
398
+ graphNodeId: null,
399
+ sourceLocation: null,
400
+ stackTrace: null,
401
+ confidence: 1,
402
+ redactionStatus: "pending-redaction",
403
+ });
404
+ return Promise.resolve({ available: true });
405
+ },
406
+ stop() {
407
+ emit = undefined;
408
+ return Promise.resolve();
409
+ },
410
+ capabilities() {
411
+ return CAPABILITIES;
412
+ },
413
+ navigate,
414
+ click,
415
+ type,
416
+ fill,
417
+ select,
418
+ submit,
419
+ waitFor,
420
+ inspect,
421
+ assertVisible,
422
+ assertText,
423
+ screenshot,
424
+ captureVideo,
425
+ };
426
+ }
427
+ //# sourceMappingURL=browser-action-collector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-action-collector.js","sourceRoot":"","sources":["../src/browser-action-collector.ts"],"names":[],"mappings":"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"}