@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,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"}
|