@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.
- package/dist/browser-action-collector.d.ts +181 -0
- package/dist/browser-action-collector.d.ts.map +1 -0
- package/dist/browser-action-collector.js +427 -0
- package/dist/browser-action-collector.js.map +1 -0
- package/dist/browser-console-collector.d.ts +33 -0
- package/dist/browser-console-collector.d.ts.map +1 -0
- package/dist/browser-console-collector.js +185 -0
- package/dist/browser-console-collector.js.map +1 -0
- package/dist/browser-network-collector.d.ts +112 -0
- package/dist/browser-network-collector.d.ts.map +1 -0
- package/dist/browser-network-collector.js +682 -0
- package/dist/browser-network-collector.js.map +1 -0
- package/dist/browser-session.d.ts +144 -0
- package/dist/browser-session.d.ts.map +1 -0
- package/dist/browser-session.js +136 -0
- package/dist/browser-session.js.map +1 -0
- package/dist/collector-version.d.ts +10 -0
- package/dist/collector-version.d.ts.map +1 -0
- package/dist/collector-version.js +12 -0
- package/dist/collector-version.js.map +1 -0
- package/dist/dom-evidence.d.ts +20 -0
- package/dist/dom-evidence.d.ts.map +1 -0
- package/dist/dom-evidence.js +32 -0
- package/dist/dom-evidence.js.map +1 -0
- package/dist/frontend-initiator-capture.d.ts +87 -0
- package/dist/frontend-initiator-capture.d.ts.map +1 -0
- package/dist/frontend-initiator-capture.js +142 -0
- package/dist/frontend-initiator-capture.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/network-correlation.d.ts +15 -0
- package/dist/network-correlation.d.ts.map +1 -0
- package/dist/network-correlation.js +29 -0
- package/dist/network-correlation.js.map +1 -0
- package/dist/script-url-mapping.d.ts +40 -0
- package/dist/script-url-mapping.d.ts.map +1 -0
- package/dist/script-url-mapping.js +96 -0
- package/dist/script-url-mapping.js.map +1 -0
- 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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"}
|