@descryy/runtime-browser 0.2.0 → 0.4.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/LICENSE +6 -0
- package/dist/browser-action-collector.d.ts.map +1 -1
- package/dist/browser-action-collector.js +22 -2
- package/dist/browser-action-collector.js.map +1 -1
- package/dist/browser-console-collector.d.ts +10 -18
- package/dist/browser-console-collector.d.ts.map +1 -1
- package/dist/browser-console-collector.js +24 -47
- package/dist/browser-console-collector.js.map +1 -1
- package/dist/browser-network-collector.d.ts +17 -0
- package/dist/browser-network-collector.d.ts.map +1 -1
- package/dist/browser-network-collector.js +70 -1
- package/dist/browser-network-collector.js.map +1 -1
- package/dist/browser-session.d.ts +46 -78
- package/dist/browser-session.d.ts.map +1 -1
- package/dist/browser-session.js +21 -41
- package/dist/browser-session.js.map +1 -1
- package/dist/collector-version.d.ts +3 -6
- package/dist/collector-version.d.ts.map +1 -1
- package/dist/collector-version.js +3 -6
- package/dist/collector-version.js.map +1 -1
- package/dist/dom-evidence.d.ts +3 -6
- package/dist/dom-evidence.d.ts.map +1 -1
- package/dist/dom-evidence.js +3 -6
- package/dist/dom-evidence.js.map +1 -1
- package/dist/frontend-initiator-capture.d.ts +29 -64
- package/dist/frontend-initiator-capture.d.ts.map +1 -1
- package/dist/frontend-initiator-capture.js +31 -67
- package/dist/frontend-initiator-capture.js.map +1 -1
- package/dist/network-correlation.d.ts +4 -7
- package/dist/network-correlation.d.ts.map +1 -1
- package/dist/network-correlation.js +4 -7
- package/dist/network-correlation.js.map +1 -1
- package/dist/script-url-mapping.d.ts +11 -22
- package/dist/script-url-mapping.d.ts.map +1 -1
- package/dist/script-url-mapping.js +18 -47
- package/dist/script-url-mapping.js.map +1 -1
- package/package.json +9 -4
|
@@ -1,35 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* §7
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* isolation unit -- separate cookies/storage/cache per context) plus its
|
|
7
|
-
* one page, which is what plan §7's "isolated context/session · page
|
|
8
|
-
* creation" already asks for; nothing here re-derives isolation Playwright
|
|
9
|
-
* already gives structurally.
|
|
2
|
+
* §7 launch/isolated-context/page creation, a thin wrapper over Playwright
|
|
3
|
+
* (see PROPOSAL-B-playwright-for-browser-runtime.md for why Playwright over
|
|
4
|
+
* raw CDP). One `BrowserSession` = one Playwright `BrowserContext` (its own
|
|
5
|
+
* isolation unit) plus its one page.
|
|
10
6
|
*
|
|
11
|
-
* Deliberately does NOT
|
|
12
|
-
*
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* for `ProcessCollector`, for the identical reason: whether session
|
|
17
|
-
* lifecycle becomes Evidence at all is an orchestration-layer question,
|
|
18
|
-
* not this seam's. Both non-goals now have an answer, in each package's
|
|
19
|
-
* own collector — `createProcessCollector` (`@descryy/runtime-controller`)
|
|
20
|
-
* and `BrowserActionCollector.start()` (`browser-action-collector.ts`,
|
|
21
|
-
* PAGE_CREATED) — never here. `startedAt` is captured below only so that
|
|
22
|
-
* answer has a real creation timestamp to report rather than "whenever a
|
|
23
|
-
* collector got around to starting."
|
|
7
|
+
* Deliberately does NOT turn session launch into Evidence -- that's a
|
|
8
|
+
* collector's job (`createProcessCollector` for process lifecycle;
|
|
9
|
+
* `BrowserActionCollector.start()`'s PAGE_CREATED for this), never this
|
|
10
|
+
* module's. `startedAt` is captured below only so that answer has a real
|
|
11
|
+
* creation timestamp to report.
|
|
24
12
|
*/
|
|
25
13
|
import { type Page } from "playwright-core";
|
|
26
14
|
import type { BrowserConfiguration } from "@descryy/runtime-contracts";
|
|
27
15
|
/**
|
|
28
|
-
* RT-068: this
|
|
29
|
-
* `ExecutionConfiguration.browser
|
|
30
|
-
*
|
|
31
|
-
* already refuses elsewhere (see `InstrumentedExecutionInput.execution`'s
|
|
32
|
-
* own comment) -- so this is the contracts type, not a look-alike of it.
|
|
16
|
+
* RT-068: this is the contracts type (`BrowserConfiguration`), not a
|
|
17
|
+
* look-alike declared independently of `ExecutionConfiguration.browser` --
|
|
18
|
+
* two shapes with the same field would drift.
|
|
33
19
|
*/
|
|
34
20
|
export type BrowserSessionOptions = BrowserConfiguration;
|
|
35
21
|
export interface BrowserSession {
|
|
@@ -39,64 +25,47 @@ export interface BrowserSession {
|
|
|
39
25
|
readonly startedAt: string;
|
|
40
26
|
/**
|
|
41
27
|
* False once the page has crashed or closed. Every collector's `start()`
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* `
|
|
45
|
-
*
|
|
46
|
-
* longer run a single action. The `'crash'` event is the only signal
|
|
47
|
-
* that actually fires for that case, so it's tracked here once, for
|
|
48
|
-
* every collector sharing this session, rather than each collector
|
|
49
|
-
* re-attaching its own listener.
|
|
28
|
+
* checks this before reporting available -- `page.isClosed()` alone
|
|
29
|
+
* isn't enough: verified that it still returns false after a real
|
|
30
|
+
* `chrome://crash`. Tracked once here via the `'crash'` event, shared
|
|
31
|
+
* across every collector on this session.
|
|
50
32
|
*/
|
|
51
33
|
isUsable(): boolean;
|
|
52
34
|
close(): Promise<void>;
|
|
53
35
|
/**
|
|
54
|
-
* The finished `recordVideo` file's bytes, or `null`
|
|
55
|
-
*
|
|
36
|
+
* The finished `recordVideo` file's bytes, or `null` if this session
|
|
37
|
+
* wasn't launched with `recordVideo: true`.
|
|
56
38
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* turning a raw fact into Evidence is a collector's job
|
|
60
|
-
* (`BrowserActionCollector.captureVideo()`), never this module's.
|
|
39
|
+
* Not Evidence -- same non-goal as session launch above; that's a
|
|
40
|
+
* collector's job (`BrowserActionCollector.captureVideo()`).
|
|
61
41
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* hang waiting for a close that this method does not itself trigger, so it
|
|
66
|
-
* throws immediately instead of hanging on a caller's mistake.
|
|
42
|
+
* Must be called after `close()` resolves: `Video.path()` doesn't
|
|
43
|
+
* resolve until the context has closed, and this method doesn't trigger
|
|
44
|
+
* that close itself, so it throws immediately rather than hanging.
|
|
67
45
|
*
|
|
68
|
-
* Reads the file
|
|
69
|
-
*
|
|
70
|
-
* already makes at a smaller scale (this module's sibling,
|
|
71
|
-
* `browser-action-collector.ts`), and once the bytes are safely returned
|
|
72
|
-
* here the on-disk copy has no further purpose.
|
|
46
|
+
* Reads the whole file into memory and removes the temp dir -- same
|
|
47
|
+
* tradeoff `screenshot()` makes at a smaller scale.
|
|
73
48
|
*/
|
|
74
49
|
finishedVideo(): Promise<Uint8Array | null>;
|
|
75
50
|
}
|
|
76
51
|
/**
|
|
77
|
-
* Why this exists, and why it
|
|
52
|
+
* Why this exists, and why it's not a throw.
|
|
78
53
|
*
|
|
79
|
-
* This package depends on `playwright-core`, not `playwright
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* and, with it, the guarantee that a browser is present at all.
|
|
54
|
+
* This package depends on `playwright-core`, not `playwright` -- only
|
|
55
|
+
* `playwright`'s postinstall downloads Chromium, and that cost is
|
|
56
|
+
* transitive to everything depending on this package (e.g. `@descryy/mcp`,
|
|
57
|
+
* whose users mostly never call a browser tool): several hundred megabytes
|
|
58
|
+
* for a browser never launched. `playwright-core` drops the download and
|
|
59
|
+
* the guarantee a browser is present.
|
|
86
60
|
*
|
|
87
|
-
* So absence is
|
|
88
|
-
*
|
|
89
|
-
* `
|
|
90
|
-
*
|
|
91
|
-
* caller mapping this onto a user-visible refusal must be able to print the
|
|
92
|
-
* exact command rather than paraphrase it, and a remedy buried in prose is a
|
|
93
|
-
* remedy the caller has to parse back out.
|
|
61
|
+
* So absence is a normal state, shaped like this codebase's other expected
|
|
62
|
+
* inabilities (`ServiceRootLookup`'s `{ found: false, refusal, message }`),
|
|
63
|
+
* plus a `remedy` field so a caller can print the exact fix rather than
|
|
64
|
+
* paraphrase it.
|
|
94
65
|
*
|
|
95
|
-
*
|
|
96
|
-
* `chromium.executablePath()`
|
|
97
|
-
*
|
|
98
|
-
* when that is set -- so the check and the launch below are handed the same
|
|
99
|
-
* string, and cannot disagree about which binary was meant.
|
|
66
|
+
* Checked via `existsSync` on the path `playwright-core` itself computes --
|
|
67
|
+
* `chromium.executablePath()` doesn't verify existence, it returns the path
|
|
68
|
+
* this build would use, so the check and the launch below can't disagree.
|
|
100
69
|
*/
|
|
101
70
|
export interface ChromiumUnavailable {
|
|
102
71
|
readonly refusal: "chromiumNotInstalled";
|
|
@@ -120,10 +89,9 @@ export type BrowserSessionLaunch = {
|
|
|
120
89
|
} & ChromiumUnavailable);
|
|
121
90
|
export declare const CHROMIUM_INSTALL_REMEDY = "npx playwright install chromium";
|
|
122
91
|
/**
|
|
123
|
-
* Resolved at launch time,
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* which is the exact cost `playwright-core` was adopted to remove.
|
|
92
|
+
* Resolved at launch time, not import time -- a module-level check would
|
|
93
|
+
* fail merely importing this package on a machine with no browser, the
|
|
94
|
+
* exact cost `playwright-core` was adopted to remove.
|
|
127
95
|
*/
|
|
128
96
|
export declare function resolveChromiumExecutable(): ChromiumAvailability;
|
|
129
97
|
/** Thrown by `launchBrowserSession` when `resolveChromiumExecutable()` refuses. Carries the refusal whole, so a caller that catches rather than branching still gets the remedy. */
|
|
@@ -134,10 +102,10 @@ export declare class BrowserUnavailableError extends Error {
|
|
|
134
102
|
constructor(unavailable: ChromiumUnavailable);
|
|
135
103
|
}
|
|
136
104
|
/**
|
|
137
|
-
*
|
|
138
|
-
* reportable outcome
|
|
139
|
-
* `
|
|
140
|
-
*
|
|
105
|
+
* Refusal-returning launch. Prefer this where a missing browser is a
|
|
106
|
+
* reportable outcome, not a bug (e.g. an MCP tool's `refused` state).
|
|
107
|
+
* `launchBrowserSession` below is the same call for callers that would
|
|
108
|
+
* only rethrow.
|
|
141
109
|
*/
|
|
142
110
|
export declare function launchBrowserSessionOrRefuse(options?: BrowserSessionOptions): Promise<BrowserSessionLaunch>;
|
|
143
111
|
export declare function launchBrowserSession(options?: BrowserSessionOptions): Promise<BrowserSession>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-session.d.ts","sourceRoot":"","sources":["../src/browser-session.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"browser-session.d.ts","sourceRoot":"","sources":["../src/browser-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAA+C,KAAK,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAMzF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAEvE;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,oBAAoB,CAAC;AAEzD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,2JAA2J;IAC3J,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;;;OAMG;IACH,QAAQ,IAAI,OAAO,CAAC;IACpB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB;;;;;;;;;;;;;OAaG;IACH,aAAa,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;CAC7C;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,sBAAsB,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gIAAgI;IAChI,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;CACzC;AAED,MAAM,MAAM,oBAAoB,GAC5B;IAAE,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;CAAE,GAC7D,CAAC;IAAE,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAA;CAAE,GAAG,mBAAmB,CAAC,CAAC;AAE1D,MAAM,MAAM,oBAAoB,GAC5B;IAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GAC7D,CAAC;IAAE,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAA;CAAE,GAAG,mBAAmB,CAAC,CAAC;AAEzD,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAEzE;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,oBAAoB,CAYhE;AAED,oLAAoL;AACpL,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC,SAAS,CAAC,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;gBAE5B,WAAW,EAAE,mBAAmB;CAO7C;AAED;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAAC,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAOrH;AAED,wBAAsB,oBAAoB,CAAC,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,cAAc,CAAC,CAOvG"}
|
package/dist/browser-session.js
CHANGED
|
@@ -1,26 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* §7
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* isolation unit -- separate cookies/storage/cache per context) plus its
|
|
7
|
-
* one page, which is what plan §7's "isolated context/session · page
|
|
8
|
-
* creation" already asks for; nothing here re-derives isolation Playwright
|
|
9
|
-
* already gives structurally.
|
|
2
|
+
* §7 launch/isolated-context/page creation, a thin wrapper over Playwright
|
|
3
|
+
* (see PROPOSAL-B-playwright-for-browser-runtime.md for why Playwright over
|
|
4
|
+
* raw CDP). One `BrowserSession` = one Playwright `BrowserContext` (its own
|
|
5
|
+
* isolation unit) plus its one page.
|
|
10
6
|
*
|
|
11
|
-
* Deliberately does NOT
|
|
12
|
-
*
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* for `ProcessCollector`, for the identical reason: whether session
|
|
17
|
-
* lifecycle becomes Evidence at all is an orchestration-layer question,
|
|
18
|
-
* not this seam's. Both non-goals now have an answer, in each package's
|
|
19
|
-
* own collector — `createProcessCollector` (`@descryy/runtime-controller`)
|
|
20
|
-
* and `BrowserActionCollector.start()` (`browser-action-collector.ts`,
|
|
21
|
-
* PAGE_CREATED) — never here. `startedAt` is captured below only so that
|
|
22
|
-
* answer has a real creation timestamp to report rather than "whenever a
|
|
23
|
-
* collector got around to starting."
|
|
7
|
+
* Deliberately does NOT turn session launch into Evidence -- that's a
|
|
8
|
+
* collector's job (`createProcessCollector` for process lifecycle;
|
|
9
|
+
* `BrowserActionCollector.start()`'s PAGE_CREATED for this), never this
|
|
10
|
+
* module's. `startedAt` is captured below only so that answer has a real
|
|
11
|
+
* creation timestamp to report.
|
|
24
12
|
*/
|
|
25
13
|
import { chromium } from "playwright-core";
|
|
26
14
|
import { randomUUID } from "node:crypto";
|
|
@@ -30,10 +18,9 @@ import { tmpdir } from "node:os";
|
|
|
30
18
|
import { join } from "node:path";
|
|
31
19
|
export const CHROMIUM_INSTALL_REMEDY = "npx playwright install chromium";
|
|
32
20
|
/**
|
|
33
|
-
* Resolved at launch time,
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* which is the exact cost `playwright-core` was adopted to remove.
|
|
21
|
+
* Resolved at launch time, not import time -- a module-level check would
|
|
22
|
+
* fail merely importing this package on a machine with no browser, the
|
|
23
|
+
* exact cost `playwright-core` was adopted to remove.
|
|
37
24
|
*/
|
|
38
25
|
export function resolveChromiumExecutable() {
|
|
39
26
|
const executablePath = chromium.executablePath();
|
|
@@ -62,10 +49,10 @@ export class BrowserUnavailableError extends Error {
|
|
|
62
49
|
}
|
|
63
50
|
}
|
|
64
51
|
/**
|
|
65
|
-
*
|
|
66
|
-
* reportable outcome
|
|
67
|
-
* `
|
|
68
|
-
*
|
|
52
|
+
* Refusal-returning launch. Prefer this where a missing browser is a
|
|
53
|
+
* reportable outcome, not a bug (e.g. an MCP tool's `refused` state).
|
|
54
|
+
* `launchBrowserSession` below is the same call for callers that would
|
|
55
|
+
* only rethrow.
|
|
69
56
|
*/
|
|
70
57
|
export async function launchBrowserSessionOrRefuse(options = {}) {
|
|
71
58
|
const chromiumBinary = resolveChromiumExecutable();
|
|
@@ -86,11 +73,8 @@ export async function launchBrowserSession(options = {}) {
|
|
|
86
73
|
async function launchWithExecutable(options, executablePath) {
|
|
87
74
|
const browser = await chromium.launch({ headless: options.headless ?? true, executablePath });
|
|
88
75
|
const sessionId = randomUUID();
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
// is exposed. Created before newContext() because Playwright's
|
|
92
|
-
// `recordVideo.dir` must already exist and be known at context-creation
|
|
93
|
-
// time; there is no way to attach recording after the fact.
|
|
76
|
+
// session-scoped temp dir, never caller-supplied (see BrowserConfiguration.recordVideo).
|
|
77
|
+
// created before newContext(): Playwright's recordVideo.dir must exist and be known at context-creation time.
|
|
94
78
|
const videoDir = options.recordVideo === true ? await mkdtemp(join(tmpdir(), `descry-video-${sessionId}-`)) : null;
|
|
95
79
|
const context = await browser.newContext(videoDir !== null ? { recordVideo: { dir: videoDir } } : {});
|
|
96
80
|
const page = await context.newPage();
|
|
@@ -117,17 +101,13 @@ async function launchWithExecutable(options, executablePath) {
|
|
|
117
101
|
"method does not itself perform). Call session.close() first.");
|
|
118
102
|
}
|
|
119
103
|
const video = page.video();
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
// under that context) -- guarded rather than asserted with `!`,
|
|
123
|
-
// because a null here would otherwise surface as a confusing crash
|
|
124
|
-
// inside readFile(undefined) instead of a clear "nothing to return."
|
|
104
|
+
// shouldn't happen (recordVideo requested implies Playwright attaches a Video) -- guarded rather
|
|
105
|
+
// than asserted with `!`, since a null here would otherwise surface as a confusing crash.
|
|
125
106
|
if (video === null)
|
|
126
107
|
return null;
|
|
127
108
|
const videoFilePath = await video.path();
|
|
128
109
|
const bytes = await readFile(videoFilePath);
|
|
129
|
-
//
|
|
130
|
-
// cleanup failure here (e.g. already removed) must not fail the call.
|
|
110
|
+
// best-effort: bytes are already captured, so a cleanup failure here must not fail the call.
|
|
131
111
|
await rm(videoDir, { recursive: true, force: true }).catch(() => { });
|
|
132
112
|
return bytes;
|
|
133
113
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-session.js","sourceRoot":"","sources":["../src/browser-session.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"browser-session.js","sourceRoot":"","sources":["../src/browser-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `Evidence.collectorVersion`
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* from what actually shipped. Same idea as `nodeVersion` elsewhere in this
|
|
6
|
-
* repo (`process.version`, always known, never probed) applied to a
|
|
7
|
-
* package instead of the Node runtime.
|
|
2
|
+
* `Evidence.collectorVersion` source: reads this package's own package.json
|
|
3
|
+
* "version" instead of a hand-duplicated string that can drift. Same idea
|
|
4
|
+
* as `nodeVersion` using `process.version`.
|
|
8
5
|
*/
|
|
9
6
|
export declare const COLLECTOR_VERSION: string;
|
|
10
7
|
//# sourceMappingURL=collector-version.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collector-version.d.ts","sourceRoot":"","sources":["../src/collector-version.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"collector-version.d.ts","sourceRoot":"","sources":["../src/collector-version.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,eAAO,MAAM,iBAAiB,EAAE,MAA6E,CAAC"}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `Evidence.collectorVersion`
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* from what actually shipped. Same idea as `nodeVersion` elsewhere in this
|
|
6
|
-
* repo (`process.version`, always known, never probed) applied to a
|
|
7
|
-
* package instead of the Node runtime.
|
|
2
|
+
* `Evidence.collectorVersion` source: reads this package's own package.json
|
|
3
|
+
* "version" instead of a hand-duplicated string that can drift. Same idea
|
|
4
|
+
* as `nodeVersion` using `process.version`.
|
|
8
5
|
*/
|
|
9
6
|
import { createRequire } from "node:module";
|
|
10
7
|
const require = createRequire(import.meta.url);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collector-version.js","sourceRoot":"","sources":["../src/collector-version.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"collector-version.js","sourceRoot":"","sources":["../src/collector-version.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,MAAM,CAAC,MAAM,iBAAiB,GAAY,OAAO,CAAC,iBAAiB,CAAkC,CAAC,OAAO,CAAC"}
|
package/dist/dom-evidence.d.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* event type -- the frozen `RuntimeEventType` vocabulary has no dedicated
|
|
6
|
-
* DOM-snapshot type, and a DOM fact is naturally "what did the element
|
|
7
|
-
* this action touched look like," not an independent stream.
|
|
2
|
+
* DOM evidence for an action (§8): element/selector/text/value/a11y info.
|
|
3
|
+
* Captured alongside the action, not as its own event type -- no dedicated
|
|
4
|
+
* DOM-snapshot type exists in `RuntimeEventType`.
|
|
8
5
|
*/
|
|
9
6
|
import type { Page } from "playwright-core";
|
|
10
7
|
export interface DomElementSnapshot {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom-evidence.d.ts","sourceRoot":"","sources":["../src/dom-evidence.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"dom-evidence.d.ts","sourceRoot":"","sources":["../src/dom-evidence.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,8FAA8F;IAC9F,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/C;AAWD,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAetG"}
|
package/dist/dom-evidence.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* event type -- the frozen `RuntimeEventType` vocabulary has no dedicated
|
|
6
|
-
* DOM-snapshot type, and a DOM fact is naturally "what did the element
|
|
7
|
-
* this action touched look like," not an independent stream.
|
|
2
|
+
* DOM evidence for an action (§8): element/selector/text/value/a11y info.
|
|
3
|
+
* Captured alongside the action, not as its own event type -- no dedicated
|
|
4
|
+
* DOM-snapshot type exists in `RuntimeEventType`.
|
|
8
5
|
*/
|
|
9
6
|
const VALUE_BEARING_TAGS = new Set(["input", "textarea", "select"]);
|
|
10
7
|
async function safeValue(locator, tagName) {
|
package/dist/dom-evidence.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom-evidence.js","sourceRoot":"","sources":["../src/dom-evidence.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"dom-evidence.js","sourceRoot":"","sources":["../src/dom-evidence.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;AAEpE,KAAK,UAAU,SAAS,CAAC,OAAgB,EAAE,OAAsB;IAC/D,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IACtE,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IACvE,IAAI,SAAS,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC;IAC1C,OAAO,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,IAAU,EAAE,QAAgB;IACvE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,CAAC;IAC/C,MAAM,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC3D,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,qBAAqB,EAAE,IAAI,EAAE,CAAC;IAC3F,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAC3F,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,qBAAqB,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC7D,OAAO,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;QACvC,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC;QAC3B,OAAO,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;KACzC,CAAC,CAAC;IAEH,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC;AACnE,CAAC"}
|
|
@@ -1,87 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Fetch-initiator capture
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* structural limit, `@descryy/runtime-graph-correlator`).
|
|
2
|
+
* Fetch-initiator capture: page-side half of resolving which frontend
|
|
3
|
+
* function issued an observed request (structural capability alone is
|
|
4
|
+
* `resolveFrontendCallers`'s job, in `@descryy/runtime-graph-correlator`).
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
* `
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* run when Descry is not attached — a same-origin request can pick up a
|
|
12
|
-
* fresh CORS preflight it would not otherwise have had. That side effect is
|
|
13
|
-
* the one `PROPOSAL-observed-frontend-caller-needs-initiator-capture.md`
|
|
14
|
-
* asked a human to sign off on before this was built; nothing here reopens
|
|
15
|
-
* that call.
|
|
6
|
+
* This is a write, not a read: the wrapped fetch() adds a real header
|
|
7
|
+
* (`x-descry-initiator-id`) to a real outgoing request, which can trigger a
|
|
8
|
+
* fresh CORS preflight on a same-origin request. Signed off in
|
|
9
|
+
* `PROPOSAL-observed-frontend-caller-needs-initiator-capture.md`.
|
|
16
10
|
*
|
|
17
|
-
* The call-site stack
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* binding channel (`page.exposeFunction`), straight to `take()` below,
|
|
21
|
-
* keyed by that same id.
|
|
11
|
+
* The call-site stack never travels with application traffic -- only an
|
|
12
|
+
* opaque per-call id does, as the header value. The stack text travels
|
|
13
|
+
* over Playwright's CDP binding (`page.exposeFunction`) to `take()` below.
|
|
22
14
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* binding call, and Chromium's own network stack — happens to land at this
|
|
27
|
-
* process first. That is exactly the kind of timing assumption §27's
|
|
28
|
-
* "similarity must never be treated as causality" rules out. Awaiting the
|
|
29
|
-
* binding call before dispatching `fetch` makes the ordering true by
|
|
30
|
-
* construction instead: the real request cannot be sent until this module
|
|
31
|
-
* has already buffered the stack it will be looked up by.
|
|
15
|
+
* The real fetch() is held until the exposed call returns, so ordering is
|
|
16
|
+
* true by construction rather than a race between the CDP binding and
|
|
17
|
+
* Chromium's network stack (§27: never treat timing as causality).
|
|
32
18
|
*
|
|
33
|
-
* Chromium only
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* `PROPOSAL-observed-frontend-caller-needs-initiator-capture.md` drew, not
|
|
37
|
-
* an oversight of this module.
|
|
19
|
+
* Chromium only (`page.addInitScript`/`page.exposeFunction`), fetch() only
|
|
20
|
+
* (not XMLHttpRequest) -- scope drawn by the proposal above, not an
|
|
21
|
+
* oversight.
|
|
38
22
|
*/
|
|
39
23
|
import type { Page } from "playwright-core";
|
|
40
24
|
export declare const INITIATOR_HEADER_NAME = "x-descry-initiator-id";
|
|
41
25
|
export interface FrontendInitiatorCapture {
|
|
42
26
|
/**
|
|
43
|
-
* Wraps
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* caller wants covered, exactly like `BrowserConsoleCollector`'s own
|
|
47
|
-
* `page.on(...)` registration ordering.
|
|
27
|
+
* Wraps window.fetch in every document from this point forward --
|
|
28
|
+
* addInitScript doesn't reach an already-loaded document. Call before
|
|
29
|
+
* navigating, same ordering as BrowserConsoleCollector's page.on(...).
|
|
48
30
|
*/
|
|
49
31
|
install(page: Page): Promise<void>;
|
|
50
32
|
/**
|
|
51
|
-
* Pops the buffered
|
|
52
|
-
*
|
|
53
|
-
* issued by this capture (a stale id, a different page), or the buffer
|
|
54
|
-
* evicted it under `MAX_BUFFERED_STACKS`. Popped rather than peeked: a
|
|
55
|
-
* captured stack is consumed by the one request it was captured for, and
|
|
56
|
-
* leaving it behind would let a second, unrelated lookup of a reused id
|
|
57
|
-
* (impossible by construction here, but not worth relying on) read
|
|
58
|
-
* another request's call site.
|
|
33
|
+
* Pops the buffered stack for `id`, or undefined if never captured or
|
|
34
|
+
* evicted. Popped not peeked: a captured stack belongs to one request.
|
|
59
35
|
*/
|
|
60
36
|
take(id: string): string | undefined;
|
|
61
37
|
}
|
|
62
38
|
export declare function createFrontendInitiatorCapture(): FrontendInitiatorCapture;
|
|
63
39
|
/**
|
|
64
|
-
* Strips
|
|
65
|
-
*
|
|
66
|
-
* wrapper.
|
|
40
|
+
* Strips descryFetch's own frame from a raw V8 stack so the first
|
|
41
|
+
* remaining frame is the real call site.
|
|
67
42
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* from the synchronous call chain live at capture time, so the innermost
|
|
73
|
-
* frame is deterministically `descryFetch` itself, every time, regardless
|
|
74
|
-
* of what called it. Stripping by *position* (first frame line) rather than
|
|
75
|
-
* by *name* also survives minification of this wrapper's own name, which a
|
|
76
|
-
* name-based strip would not.
|
|
43
|
+
* Works as a fixed "drop first line" rule rather than a name search:
|
|
44
|
+
* Error().stack is captured synchronously as the wrapper's first statement,
|
|
45
|
+
* so the innermost frame is always descryFetch regardless of caller --
|
|
46
|
+
* stripping by position also survives minification of the wrapper's name.
|
|
77
47
|
*
|
|
78
|
-
* Applied to raw text
|
|
79
|
-
*
|
|
80
|
-
* after: `createV8StackTraceParser` already treats frame 0 of whatever it
|
|
81
|
-
* receives as `primaryFrameIndex`, so removing the wrapper's line here
|
|
82
|
-
* means the parser's existing, already-tested convention does the right
|
|
83
|
-
* thing unmodified, instead of teaching every consumer a second index to
|
|
84
|
-
* skip.
|
|
48
|
+
* Applied to raw text before parsing, so createV8StackTraceParser's
|
|
49
|
+
* existing primaryFrameIndex convention (frame 0) just works.
|
|
85
50
|
*/
|
|
86
51
|
export declare function stripInitiatorWrapperFrame(rawStackText: string): string;
|
|
87
52
|
//# sourceMappingURL=frontend-initiator-capture.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontend-initiator-capture.d.ts","sourceRoot":"","sources":["../src/frontend-initiator-capture.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"frontend-initiator-capture.d.ts","sourceRoot":"","sources":["../src/frontend-initiator-capture.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C,eAAO,MAAM,qBAAqB,0BAA0B,CAAC;AAW7D,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC;;;OAGG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACtC;AAED,wBAAgB,8BAA8B,IAAI,wBAAwB,CAsBzE;AAsCD;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAKvE"}
|