@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,33 @@
1
+ /**
2
+ * §8's console evidence bucket (level · message · stack if present ·
3
+ * timestamp) plus page errors -- uncaught exceptions in the page's own JS,
4
+ * captured via Playwright's `pageerror` event. Both ride the same
5
+ * `EvidenceSource: "browser-console"` bucket the contracts already name.
6
+ *
7
+ * Page-error stack text is preserved verbatim in `payload.stack` (plan §9:
8
+ * "the runtime must preserve the original observation") but NOT
9
+ * structurally parsed into `StackTrace` frames with resolved
10
+ * `SourceLocation`s in V1 -- reflected honestly in `capabilities()`
11
+ * (`stackCapture`/`sourceMapping`: unavailable), not silently produced as
12
+ * a lesser version. Doing that for real would need mapping the browser's
13
+ * `http://` script URLs back to on-disk files via the frontend's static
14
+ * file server root, which this collector has no way to know -- a real,
15
+ * separate feature, not built here.
16
+ */
17
+ import type { Collector } from "@descryy/runtime-contracts";
18
+ import type { StackTraceParser } from "@descryy/runtime-backend-observation";
19
+ import type { BrowserSession } from "./browser-session.ts";
20
+ export interface BrowserConsoleCollectorOptions {
21
+ readonly session: BrowserSession;
22
+ readonly service?: string;
23
+ /**
24
+ * Injected, never constructed here — a browser page runs JavaScript, but
25
+ * this package stays language-neutral the same way `LogCollector` does:
26
+ * whoever wires the collector supplies the parser for the runtime it is
27
+ * actually watching. Omit it and page-error stacks are preserved as text
28
+ * exactly as before, with `capabilities()` saying so.
29
+ */
30
+ readonly stackTraceParser?: StackTraceParser;
31
+ }
32
+ export declare function createBrowserConsoleCollector(options: BrowserConsoleCollectorOptions): Collector;
33
+ //# sourceMappingURL=browser-console-collector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-console-collector.d.ts","sourceRoot":"","sources":["../src/browser-console-collector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAqF,MAAM,4BAA4B,CAAC;AAG/I,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAE7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI3D,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CAC9C;AAmDD,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,8BAA8B,GAAG,SAAS,CA8HhG"}
@@ -0,0 +1,185 @@
1
+ /**
2
+ * §8's console evidence bucket (level · message · stack if present ·
3
+ * timestamp) plus page errors -- uncaught exceptions in the page's own JS,
4
+ * captured via Playwright's `pageerror` event. Both ride the same
5
+ * `EvidenceSource: "browser-console"` bucket the contracts already name.
6
+ *
7
+ * Page-error stack text is preserved verbatim in `payload.stack` (plan §9:
8
+ * "the runtime must preserve the original observation") but NOT
9
+ * structurally parsed into `StackTrace` frames with resolved
10
+ * `SourceLocation`s in V1 -- reflected honestly in `capabilities()`
11
+ * (`stackCapture`/`sourceMapping`: unavailable), not silently produced as
12
+ * a lesser version. Doing that for real would need mapping the browser's
13
+ * `http://` script URLs back to on-disk files via the frontend's static
14
+ * file server root, which this collector has no way to know -- a real,
15
+ * separate feature, not built here.
16
+ */
17
+ import { primaryFrameLocation } from "@descryy/runtime-contracts";
18
+ import { extractCorrelationIds } from "@descryy/runtime-backend-observation";
19
+ import { rewriteScriptUrls } from "./script-url-mapping.js";
20
+ import { COLLECTOR_VERSION } from "./collector-version.js";
21
+ function notThisCollectorsJob(what) {
22
+ return { availability: "unavailable", reason: `BrowserConsoleCollector handles console/page-error observation only, not ${what} -- see the dedicated collector.` };
23
+ }
24
+ const CAPABILITIES = {
25
+ domObservation: notThisCollectorsJob("DOM inspection"),
26
+ consoleObservation: { availability: "available", reason: null },
27
+ networkObservation: notThisCollectorsJob("network observation"),
28
+ backendLogAccess: { availability: "unavailable", reason: "this collector observes a browser page, not a backend process." },
29
+ distributedTrace: {
30
+ availability: "degraded",
31
+ reason: "extracts trace-id/request-id fields from console message text; does not parse W3C traceparent from console text.",
32
+ },
33
+ sourceMapping: {
34
+ availability: "unavailable",
35
+ reason: "page-error stack text is preserved but not structurally parsed/resolved to source locations in V1.",
36
+ },
37
+ stackCapture: {
38
+ availability: "unavailable",
39
+ reason: "raw page-error stack text is preserved in payload but not structurally parsed into StackTrace frames in V1.",
40
+ },
41
+ processLifecycle: notThisCollectorsJob("process lifecycle observation"),
42
+ databaseObservation: notThisCollectorsJob("database query observation"),
43
+ externalServiceObservation: notThisCollectorsJob("external service observation"),
44
+ };
45
+ /**
46
+ * RT-023 landed: with a parser injected and `CollectorContext.resolveSourceRoot`
47
+ * present, page-error stacks are parsed and their `http://` script URLs are
48
+ * rewritten to on-disk paths.
49
+ *
50
+ * `sourceMapping` stays **degraded**, not available, and the distinction is
51
+ * load-bearing: the origin-to-root lookup proves the origin maps to a
52
+ * configured directory, not that the file the browser executed came from
53
+ * the checkout in it (`declaredCwd` is named for exactly that). A script
54
+ * this execution did not serve — a CDN, an extension — is left as a URL.
55
+ */
56
+ const CAPABILITIES_WITH_STACKS = {
57
+ ...CAPABILITIES,
58
+ sourceMapping: {
59
+ availability: "degraded",
60
+ reason: "page-error stacks are parsed and script URLs are rewritten to on-disk paths via the execution's configured service roots; " +
61
+ "a successful rewrite proves the origin maps to a configured directory, not that the served file came from that checkout, " +
62
+ "and a script this execution did not serve is left as a URL.",
63
+ },
64
+ stackCapture: { availability: "available", reason: null },
65
+ };
66
+ export function createBrowserConsoleCollector(options) {
67
+ const { session } = options;
68
+ const { page } = session;
69
+ let emit;
70
+ let resolveSourceRoot;
71
+ /** Only true once both halves are present: a parser to read the stack, and a way to map its URLs. */
72
+ let stacksParsed = false;
73
+ /**
74
+ * In-flight `onPageError` handlers.
75
+ *
76
+ * Parsing a stack is asynchronous, and Playwright's `pageerror` listener
77
+ * is not — so the handler starts work and returns, and `stop()` used to
78
+ * clear `emit` out from under it. Any page error whose parse had not
79
+ * finished was **silently deleted**: `emit?.()` on an undefined `emit` is
80
+ * a no-op, and nothing recorded that an observation had been dropped.
81
+ * `stop()` is called at the end of every execution, which is precisely
82
+ * when a page's last error arrives, so this was not a rare window.
83
+ *
84
+ * `stop()` now drains this set before clearing `emit`.
85
+ */
86
+ const inFlight = new Set();
87
+ function onConsole(msg) {
88
+ const text = msg.text();
89
+ const { traceId, requestId } = extractCorrelationIds(text);
90
+ emit?.({
91
+ timestamp: new Date().toISOString(),
92
+ source: "browser-console",
93
+ service: options.service ?? null,
94
+ process: null,
95
+ eventType: "CONSOLE_MESSAGE",
96
+ payload: { level: msg.type(), text, location: msg.location(), browserSessionId: session.sessionId },
97
+ traceId,
98
+ requestId,
99
+ correlationId: null,
100
+ graphNodeId: null,
101
+ sourceLocation: null,
102
+ stackTrace: null,
103
+ confidence: 1,
104
+ redactionStatus: "pending-redaction",
105
+ collectorVersion: COLLECTOR_VERSION,
106
+ });
107
+ }
108
+ function onPageError(error) {
109
+ const rawStack = error.stack ?? null;
110
+ const handled = (async () => {
111
+ let stackTrace = null;
112
+ let mapping = null;
113
+ if (stacksParsed && rawStack !== null && options.stackTraceParser !== undefined) {
114
+ mapping = rewriteScriptUrls(rawStack, resolveSourceRoot);
115
+ stackTrace = await options.stackTraceParser.parse(mapping.text);
116
+ }
117
+ emit?.({
118
+ timestamp: new Date().toISOString(),
119
+ source: "browser-console",
120
+ service: options.service ?? null,
121
+ process: null,
122
+ eventType: "EXCEPTION",
123
+ payload: {
124
+ message: error.message,
125
+ // The browser's own text, always, whatever the mapping did to it
126
+ // (plan §9: preserve the original observation).
127
+ stack: rawStack,
128
+ browserSessionId: session.sessionId,
129
+ // Per-URL outcomes rather than a boolean: "three scripts, two
130
+ // mapped, one is a CDN" is a coverage statement, and collapsing
131
+ // it would imply the unmapped one simply was not there.
132
+ ...(mapping !== null ? { scriptUrlMapping: mapping.outcomes } : {}),
133
+ },
134
+ traceId: null,
135
+ requestId: null,
136
+ correlationId: null,
137
+ graphNodeId: null,
138
+ // Was its own `frames[0]` copy of a rule `backend-observation` had
139
+ // already moved onto `primaryFrameIndex` (RT-083). The two agreed
140
+ // only because V8 reports index 0 — a coincidence, not a shared
141
+ // rule. The full chain is in stackTrace either way.
142
+ sourceLocation: primaryFrameLocation(stackTrace),
143
+ stackTrace,
144
+ confidence: 1,
145
+ redactionStatus: "pending-redaction",
146
+ collectorVersion: COLLECTOR_VERSION,
147
+ });
148
+ })();
149
+ inFlight.add(handled);
150
+ void handled.finally(() => inFlight.delete(handled));
151
+ }
152
+ return {
153
+ collectorId: `browser-console:${session.sessionId}`,
154
+ start(context) {
155
+ if (!session.isUsable()) {
156
+ return Promise.resolve({ available: false, reason: "the browser session's page has already crashed or closed." });
157
+ }
158
+ emit = context.emit;
159
+ resolveSourceRoot = context.resolveSourceRoot;
160
+ // Both halves or neither: a parser with no way to map URLs would
161
+ // produce frames pointing at http:// locations nothing can open, and
162
+ // a mapper with no parser has nothing to map.
163
+ stacksParsed = options.stackTraceParser !== undefined && context.resolveSourceRoot !== undefined;
164
+ page.on("console", onConsole);
165
+ page.on("pageerror", onPageError);
166
+ return Promise.resolve({ available: true });
167
+ },
168
+ async stop() {
169
+ // Listeners off first, so nothing new joins the set while it drains.
170
+ page.off("console", onConsole);
171
+ page.off("pageerror", onPageError);
172
+ // Then drain, THEN clear `emit` — in that order. Clearing first is
173
+ // what silently deleted the last page error of every execution.
174
+ // `allSettled`: a handler that threw has already failed on its own
175
+ // terms, and stop() refusing to complete because of it would take the
176
+ // rest of the run's evidence down with it.
177
+ await Promise.allSettled([...inFlight]);
178
+ emit = undefined;
179
+ },
180
+ capabilities() {
181
+ return stacksParsed ? CAPABILITIES_WITH_STACKS : CAPABILITIES;
182
+ },
183
+ };
184
+ }
185
+ //# sourceMappingURL=browser-console-collector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-console-collector.js","sourceRoot":"","sources":["../src/browser-console-collector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * §8's network evidence bucket: method · URL · safe request metadata/body
3
+ * · response status · safe response metadata/body · timing · initiator
4
+ * where available. `NETWORK_REQUEST` on `request`; `NETWORK_RESPONSE` (and
5
+ * `HTTP_ERROR` alongside it for status >= 400) on `requestfinished`, which
6
+ * is when `request.timing()` is actually complete; a network-level
7
+ * failure with no HTTP response at all (`requestfailed` -- DNS, connection
8
+ * refused, aborted) also becomes `HTTP_ERROR`, with `status: null`
9
+ * distinguishing "never got a response" from "got a bad one."
10
+ *
11
+ * Auth headers/cookies/tokens are not stripped here -- `headers()` already
12
+ * lower-cases header names, and `EvidenceStore.write()`'s generic
13
+ * redaction (RT-015) deep-redacts `authorization`/`cookie`/`set-cookie`/
14
+ * etc. by key name unconditionally at persist time, so a header object
15
+ * passed straight into payload is already covered by an existing,
16
+ * already-tested guarantee -- reimplementing it here would be a second,
17
+ * divergent copy of the same rule.
18
+ *
19
+ * A third `status: null` case (RT-194): a request that gets neither
20
+ * `requestfinished` nor `requestfailed` within this collector's own
21
+ * timeout window (`networkTimeoutMs`, default `context.configuration.
22
+ * timeoutMs` -- RT-030's precedent, not a second disagreeing default) is
23
+ * reported as `HTTP_ERROR` with `networkError` describing a collector-side
24
+ * timeout, distinguishable from a real Playwright `requestfailed` (whose
25
+ * `networkError` carries Chromium's own `net::ERR_*` text). Nothing in
26
+ * Playwright itself imposes a timeout on an in-page `fetch`/`XHR` that
27
+ * never completes -- a hung server would otherwise produce silence
28
+ * forever, not evidence.
29
+ *
30
+ * **"initiator where available" above is no longer only `resourceType`.**
31
+ * With a `stackTraceParser` injected, `NETWORK_REQUEST.stackTrace` carries
32
+ * a real, resolved call-site stack for `fetch()` requests (Chromium only —
33
+ * see `frontend-initiator-capture.ts` for the mechanism and the write it
34
+ * makes to the observed page). This is still not the same claim as
35
+ * `@descryy/runtime-graph-correlator`'s `resolveObservedFrontendCaller`
36
+ * upgrading a structural candidate to `observed`: that join happens one
37
+ * layer up, against the graph. This collector's job stops at attaching the
38
+ * resolved stack to the evidence record.
39
+ *
40
+ * **RT-225: WebSocket traffic is a fourth listener here, not a sibling
41
+ * collector.** `page.on("request"/"requestfinished"/"requestfailed")` never
42
+ * fires for a WebSocket connection -- Playwright surfaces it through a
43
+ * wholly separate `page.on("websocket")` stream, and before this a refused
44
+ * or dropped WebSocket connection produced zero evidence of any kind, not
45
+ * degraded, silently absent
46
+ * (`documents/decisions-inbox/RT-PENDING-websocket-network-observation-gap.md`).
47
+ * This collector draws its boundary by domain watched (browser network
48
+ * traffic), not by wire protocol, so WS traffic belongs here.
49
+ *
50
+ * Three moments, three choices:
51
+ * - **connection attempt** (`page.on("websocket")` firing -- "Emitted when
52
+ * WebSocket request is sent," Playwright's own doc for the event) reuses
53
+ * `NETWORK_REQUEST`: a real outbound call to a URL, the same fact that
54
+ * type exists to record. No `method`/`headers`/`postData` fields are
55
+ * invented -- Playwright's `WebSocket` object exposes only `url()`, so
56
+ * the payload states only what was actually observed, plus a `protocol:
57
+ * "websocket"` discriminator.
58
+ * - **connection failure** (`socketerror`) reuses `HTTP_ERROR` with
59
+ * `status: null` -- the identical "no usable response ever came back"
60
+ * convention already applied above to a refused `fetch()`
61
+ * (`requestfailed`) and to this collector's own timeout. A refused
62
+ * WebSocket is the same class of fact under a different wire protocol,
63
+ * not a different fact.
64
+ * - **close** gets its own type, `WEBSOCKET_CLOSED` (`runtime-event.ts`),
65
+ * because there is no honest way to fold it into `NETWORK_RESPONSE`:
66
+ * that type means a response with a status arrived, and a close is
67
+ * neither a response nor does it carry a status. Measured, not assumed:
68
+ * Playwright's public `WebSocket.on("close")` carries no code or reason
69
+ * at all, and probing it directly (a clean server-sent 1000 close vs. a
70
+ * raw abrupt TCP termination) produced byte-identical event shapes --
71
+ * so this collector does not claim "normal" vs "abnormal" either, only
72
+ * that the socket closed. Only emitted when no `socketerror` already
73
+ * reported the same socket as failed, so one failure is never reported
74
+ * twice.
75
+ *
76
+ * Deliberately **not** captured: per-message frame traffic
77
+ * (`framesent`/`framereceived`) -- high-volume and not what the proven gap
78
+ * asked for; `EventSource`/SSE, which shares the same uncovered shape but
79
+ * is unmeasured and explicitly out of scope for this change, same as the
80
+ * RT-PENDING note that proved this gap already said.
81
+ */
82
+ import type { Collector } from "@descryy/runtime-contracts";
83
+ import type { StackTraceParser } from "@descryy/runtime-backend-observation";
84
+ import type { BrowserSession } from "./browser-session.ts";
85
+ export interface BrowserNetworkCollectorOptions {
86
+ readonly session: BrowserSession;
87
+ readonly service?: string;
88
+ /**
89
+ * Overrides `context.configuration.timeoutMs` (the execution-wide
90
+ * default) for this collector's own no-response detection. Exists so a
91
+ * test can drive the timeout path in milliseconds rather than waiting
92
+ * out a real 30s execution timeout; production callers should leave
93
+ * this absent.
94
+ */
95
+ readonly networkTimeoutMs?: number;
96
+ /**
97
+ * Injected, never constructed here — the same language-neutrality
98
+ * `BrowserConsoleCollector` already holds itself to: this package does
99
+ * not know what runtime serves the frontend it is watching, so whoever
100
+ * wires the collector supplies the parser for it.
101
+ *
102
+ * When present (and `CollectorContext.resolveSourceRoot` is also
103
+ * present, same two-halves-or-neither rule as the console collector),
104
+ * this collector additionally installs `frontend-initiator-capture.ts`'s
105
+ * `fetch()` wrapper and attempts to resolve each `NETWORK_REQUEST`'s
106
+ * real call-site stack — Chromium + `fetch()` only. Absent, requests are
107
+ * captured exactly as before, with no header added to outgoing traffic.
108
+ */
109
+ readonly stackTraceParser?: StackTraceParser;
110
+ }
111
+ export declare function createBrowserNetworkCollector(options: BrowserNetworkCollectorOptions): Collector;
112
+ //# sourceMappingURL=browser-network-collector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-network-collector.d.ts","sourceRoot":"","sources":["../src/browser-network-collector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgFG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAqG,MAAM,4BAA4B,CAAC;AAE/J,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAE7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAM3D,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CAC9C;AA6ED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,8BAA8B,GAAG,SAAS,CAujBhG"}