@mochi.js/core 0.6.0 → 0.8.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/README.md CHANGED
@@ -22,7 +22,7 @@ await session.close();
22
22
 
23
23
  ## Status
24
24
 
25
- `v0.4.x` (v0.2 wave-4 surfaces). `mochi.launch()` is fully wired: pipe-mode CDP transport, relational `(profile, seed)` Matrix, JIT-friendly inject delivered via `Fetch.fulfillRequest` body splice (with `Page.addScriptToEvaluateOnNewDocument({ runImmediately: true, worldName: "" })` as the `about:blank` / `data:` fallback), behavioral synth, and JA4-coherent `session.fetch` via Rust+wreq.
25
+ `v0.7.x`. `mochi.launch()` is fully wired: pipe-mode CDP transport, relational `(profile, seed)` Matrix, JIT-friendly inject delivered via `Fetch.fulfillRequest` body splice (with `Page.addScriptToEvaluateOnNewDocument({ runImmediately: true, worldName: "" })` as the `about:blank` / `data:` fallback), behavioral synth, and a Chromium-native `session.fetch` (routes through CDP — `Network.loadNetworkResource` for simple GETs, `page.evaluate("fetch")` for non-GET — so JA4 is real Chrome by definition).
26
26
 
27
27
  The full [v0.1.4 → v0.2] surface lands as additive minor bumps. See [`CHANGELOG.md`](https://github.com/0xchasercat/mochi/blob/main/CHANGELOG.md).
28
28
 
@@ -31,7 +31,7 @@ The full [v0.1.4 → v0.2] surface lands as additive minor bumps. See [`CHANGELO
31
31
  - `mochi.launch(opts)` — spawn a Chromium-for-Testing instance with a relationally-locked fingerprint matrix derived from `(profile, seed)`. Options include `proxy`, `headless`, `binary`, `timeout`, `geoConsistency` (IP/TZ/locale exit reconciliation), and `challenges` (Turnstile auto-click).
32
32
  - `Session` and `Page` — the runtime objects you drive.
33
33
  - `page.humanClick / humanType / humanScroll` — biomechanically-shaped input synthesis (Bezier + Fitts + Gaussian jitter).
34
- - `session.fetch` — out-of-band requests with profile-matching JA3/JA4/H2 via the Rust+wreq backend.
34
+ - `session.fetch` — out-of-band requests routed through Chromium itself via CDP. JA4/JA3/H2 are real Chrome by definition because Chromium is the client; cookies inherit from the page's origin; CORS applies for non-GET cross-origin calls.
35
35
  - `page.screenshot(opts?)` — PNG / JPEG / WebP via CDP `Page.captureScreenshot`. Options: `format`, `quality`, `fullPage`, `clip`, `omitBackground`, `encoding`. Element-bounded capture (`{ element: handle }`) is deferred — see <https://mochijs.com/docs/reference/limits>.
36
36
  - `session.cookies.{save,load}(path, { pattern? })` — JSON cookie jar with version header + regex domain filter. Round-trips losslessly via `Storage.getCookies` / `Storage.setCookies`.
37
37
  - `page.localStorage.{get,set}` and `page.sessionStorage.{get,set}` — direct `DOMStorage` CDP access, frame-scoped (defaults to current main-frame origin; pass `{ origin }` for cross-origin).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mochi.js/core",
3
- "version": "0.6.0",
4
- "description": "Bun-native browser automation framework relational fingerprint locking, zero-jitter injection, behavioral playback. The primary entry point.",
3
+ "version": "0.8.0",
4
+ "description": "The library for faithful browser automation. Bun-native; relational fingerprint matrix, biomechanical input, stock Chromium-for-Testing.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./src/index.ts",
@@ -52,8 +52,7 @@
52
52
  "@mochi.js/behavioral": "^0.1.4",
53
53
  "@mochi.js/challenges": "^0.2.1",
54
54
  "@mochi.js/consistency": "^0.1.3",
55
- "@mochi.js/inject": "^0.3.0",
56
- "@mochi.js/net": "^0.1.1"
55
+ "@mochi.js/inject": "^0.3.0"
57
56
  },
58
57
  "publishConfig": {
59
58
  "access": "public"
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Unit tests for the {@link Session.cookies} jar surface (task 0257):
2
+ * Unit tests for the {@link Session.cookies} jar surface:
3
3
  *
4
4
  * - `cookies.get()` → `Storage.getCookies` round-trip with optional
5
5
  * url-host filter.
@@ -19,7 +19,6 @@
19
19
  * `ChromiumProcess` whose pipe reader/writer let us observe every CDP
20
20
  * request sent and inject canned responses.
21
21
  *
22
- * @see tasks/0257-dx-cluster-cookies-storage-permissions.md
23
22
  * @see docs/audits/nodriver.md (LOW finding 2 — pickle → JSON port)
24
23
  */
25
24
 
@@ -193,7 +192,7 @@ const SAMPLE_COOKIES: Cookie[] = [
193
192
  },
194
193
  ];
195
194
 
196
- describe("Session.cookies (task 0257)", () => {
195
+ describe("Session.cookies", () => {
197
196
  let fake: FakeBrowser;
198
197
  let session: Session;
199
198
  let tmpFile: string;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Unit tests for the host-OS profile auto-pick (task 0272).
2
+ * Unit tests for the host-OS profile auto-pick.
3
3
  *
4
4
  * Three layers under test:
5
5
  *
@@ -18,11 +18,9 @@
18
18
  * exported `defaultProfileForHost` introspection helper. We do NOT spawn
19
19
  * Chromium here — the goal is to lock the decisions against regressions
20
20
  * without taking the cost of a real launch. Mirrors the
21
- * `proc-linux-server.test.ts` pattern (task 0258).
21
+ * `proc-linux-server.test.ts` pattern.
22
22
  *
23
23
  * @see packages/core/src/default-profile.ts
24
- * @see tasks/0271-the-linux-os-thesis.md (strategic thesis + production evidence)
25
- * @see tasks/0272-host-os-profile-auto-default.md (engineering brief)
26
24
  */
27
25
 
28
26
  import { afterEach, describe, expect, it } from "bun:test";
@@ -33,7 +31,7 @@ import {
33
31
  unsupportedHostMessage,
34
32
  } from "../default-profile";
35
33
 
36
- describe("resolveDefaultProfileForHost — pure mapping table (task 0272)", () => {
34
+ describe("resolveDefaultProfileForHost — pure mapping table", () => {
37
35
  it("linux/x64 → linux-chrome-stable", () => {
38
36
  expect(resolveDefaultProfileForHost("linux", "x64")).toBe("linux-chrome-stable");
39
37
  });
@@ -87,7 +85,7 @@ describe("resolveDefaultProfileForHost — pure mapping table (task 0272)", () =
87
85
  });
88
86
  });
89
87
 
90
- describe("defaultProfileForHost — live wrapper (task 0272)", () => {
88
+ describe("defaultProfileForHost — live wrapper", () => {
91
89
  // Stub `process.platform` / `process.arch` so the live wrapper exercises
92
90
  // the same table as the pure resolver. `Object.defineProperty` is the
93
91
  // standard Bun-test pattern (matches `proc-linux-server.test.ts`).
@@ -127,7 +125,7 @@ describe("defaultProfileForHost — live wrapper (task 0272)", () => {
127
125
  });
128
126
  });
129
127
 
130
- describe("unsupportedHostMessage — failure-mode diagnostic (task 0272)", () => {
128
+ describe("unsupportedHostMessage — failure-mode diagnostic", () => {
131
129
  it("names the host platform + arch verbatim", () => {
132
130
  const msg = unsupportedHostMessage("freebsd", "x64");
133
131
  expect(msg).toContain("platform=freebsd");
@@ -153,7 +151,7 @@ describe("unsupportedHostMessage — failure-mode diagnostic (task 0272)", () =>
153
151
  });
154
152
  });
155
153
 
156
- describe("introspection contract — defaultProfileForHost is pure (task 0272)", () => {
154
+ describe("introspection contract — defaultProfileForHost is pure", () => {
157
155
  it("returns the same value on repeated calls (no caching, no mutation)", () => {
158
156
  // The launcher consults this helper at every launch; pinning purity
159
157
  // here means a downstream `console.log(mochi.defaultProfileForHost())`
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Live conformance test for the DX cluster (task 0257).
2
+ * Live conformance test for the DX cluster.
3
3
  *
4
4
  * Gated by `MOCHI_E2E=1`. Drives a real Chromium-for-Testing through the
5
5
  * public mochi launch path to verify, end-to-end, that:
@@ -18,7 +18,6 @@
18
18
  * Budget: < 30 seconds.
19
19
  *
20
20
  * @see PLAN.md §14
21
- * @see tasks/0257-dx-cluster-cookies-storage-permissions.md
22
21
  */
23
22
 
24
23
  import { describe, expect, it } from "bun:test";
@@ -6,7 +6,6 @@
6
6
  * Pure JS — no FFI, no CDP, no network. The {@link reconcileGeoConsistency}
7
7
  * function is a pure transform on `(matrix, geo, mode)`.
8
8
  *
9
- * @see tasks/0262-ip-tz-locale-exit-consistency.md
10
9
  * @see packages/core/src/geo-consistency.ts
11
10
  */
12
11
 
@@ -8,17 +8,18 @@
8
8
  * / parser-null and respects the 4-attempt cap.
9
9
  * - all-fail returns `null`.
10
10
  *
11
- * The probe's `fetch` injection seam is an internal — production wires it
12
- * to `@mochi.js/net`'s `fetch`, which carries the matrix's wreq preset.
11
+ * Post-0.7 the probe's `fetch` seam delegates to `globalThis.fetch` by
12
+ * default; production launch paths inject a `Session.fetch`-backed
13
+ * adapter so the probe rides Chromium's network stack (real Chrome JA4
14
+ * by definition).
13
15
  *
14
- * @see tasks/0262-ip-tz-locale-exit-consistency.md
15
16
  * @see packages/core/src/geo-probe.ts
16
17
  */
17
18
 
18
19
  import { describe, expect, it } from "bun:test";
19
20
  import { ADAPTERS, type ProbeFetch, probeExitGeo } from "../geo-probe";
20
21
 
21
- const MATRIX_STUB = { wreqPreset: "chrome_131_macos" };
22
+ const MATRIX_STUB = {};
22
23
 
23
24
  /** Build a `ProbeFetch` that returns canned JSON for each URL. */
24
25
  function fakeFetch(
@@ -335,10 +336,10 @@ describe("probeExitGeo — strategy", () => {
335
336
  expect(calls).toBe(4);
336
337
  });
337
338
 
338
- it("forwards proxy + matrix.wreqPreset to the fetch impl", async () => {
339
- let captured: { url?: string; preset?: string; proxy?: string } = {};
339
+ it("forwards proxy diagnostic to the fetch impl", async () => {
340
+ let captured: { url?: string; proxy?: string } = {};
340
341
  const fetchSpy: ProbeFetch = async (url, init) => {
341
- captured = { url, preset: init.preset, proxy: init.proxy };
342
+ captured = { url, proxy: init.proxy };
342
343
  return new Response(
343
344
  JSON.stringify({
344
345
  proxy: { ip: "1" },
@@ -349,22 +350,21 @@ describe("probeExitGeo — strategy", () => {
349
350
  );
350
351
  };
351
352
  await probeExitGeo({
352
- matrix: { wreqPreset: "chrome_131_linux" },
353
+ matrix: MATRIX_STUB,
353
354
  proxy: "http://user:pass@proxy.example:8080",
354
355
  fetch: fetchSpy,
355
356
  shuffle: noShuffle,
356
357
  maxAttempts: 1,
357
358
  perEndpointTimeoutMs: 100,
358
359
  });
359
- expect(captured.preset).toBe("chrome_131_linux");
360
360
  expect(captured.proxy).toBe("http://user:pass@proxy.example:8080");
361
361
  });
362
362
 
363
- it("synchronous throw from fetch (e.g. dlopen failure) → null, NEVER propagates", async () => {
363
+ it("synchronous throw from fetch → null, NEVER propagates", async () => {
364
364
  const fetchSpy: ProbeFetch = () => {
365
- // Simulate the cdylib-missing case: throws synchronously off the
366
- // top of the body, before Promise.resolve.
367
- throw new Error("dlopen: libmochi-net.dylib not found");
365
+ // Simulate a transport-level synchronous throw e.g. a Session
366
+ // that's already been closed when the probe fires.
367
+ throw new Error("transport unavailable");
368
368
  };
369
369
  const geo = await probeExitGeo({
370
370
  matrix: MATRIX_STUB,
@@ -16,7 +16,6 @@
16
16
  * Gated by `MOCHI_E2E=1`. Set `MOCHI_CHROMIUM_PATH` if needed.
17
17
  *
18
18
  * @see PLAN.md §8.4
19
- * @see tasks/0266-fetch-fulfill-init-script.md
20
19
  */
21
20
 
22
21
  import { describe, expect, it } from "bun:test";
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Unit tests for the init-injector building blocks (task 0266).
2
+ * Unit tests for the init-injector building blocks.
3
3
  *
4
4
  * Covers:
5
5
  * - {@link rewriteCsp}: no-nonce / nonce / strict-dynamic / unsafe-inline
@@ -12,7 +12,6 @@
12
12
  * - {@link wrapSelfRemovingPayload}: first statement is the self-remove +
13
13
  * marker; the inner payload is left intact.
14
14
  *
15
- * @see tasks/0266-fetch-fulfill-init-script.md
16
15
  */
17
16
 
18
17
  import { describe, expect, it } from "bun:test";
@@ -10,7 +10,6 @@
10
10
  * `MessageRouter`, so the test implicitly enforces those too.
11
11
  *
12
12
  * @see PLAN.md §12.1 — capture must run against bare Chromium.
13
- * @see tasks/0040-mochi-capture.md — `bypassInject: true` requirement.
14
13
  * @see tests/helpers/cdp-fixture.ts — shared helper consolidating fake-pipe boilerplate.
15
14
  */
16
15
 
@@ -129,7 +128,7 @@ describe("Session.bypassInject (PLAN.md §12.1, task 0040)", () => {
129
128
  expect(session._internalBypassInject()).toBe(true);
130
129
  });
131
130
 
132
- it("with bypassInject omitted — Session installs the unified Fetch-domain injector instead of Page.addScriptToEvaluateOnNewDocument (task 0266)", async () => {
131
+ it("with bypassInject omitted — Session installs the unified Fetch-domain injector instead of Page.addScriptToEvaluateOnNewDocument", async () => {
133
132
  // Override the script identifier for this test — it asserts that the
134
133
  // dual-mechanism `addScriptToEvaluateOnNewDocument` call (commit 2 of
135
134
  // 0266) carries the wrapped matrix payload.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Unit tests for the Page DX cluster (task 0257):
2
+ * Unit tests for the Page DX cluster:
3
3
  * - `Page.localStorage.{get,set}` → DOMStorage.getDOMStorageItems /
4
4
  * DOMStorage.setDOMStorageItem.
5
5
  * - `Page.sessionStorage.{get,set}` → same shape, `isLocalStorage: false`.
@@ -9,7 +9,6 @@
9
9
  * Driven against a hand-rolled fake CDP transport — same fixture pattern as
10
10
  * the cookies-jar tests, kept inline here so each test file stands alone.
11
11
  *
12
- * @see tasks/0257-dx-cluster-cookies-storage-permissions.md
13
12
  */
14
13
 
15
14
  import { afterEach, beforeEach, describe, expect, it } from "bun:test";
@@ -114,7 +113,7 @@ function wireOriginResolver(fake: FakeBrowser, origin: string): void {
114
113
  });
115
114
  }
116
115
 
117
- describe("Page.localStorage / Page.sessionStorage (task 0257)", () => {
116
+ describe("Page.localStorage / Page.sessionStorage", () => {
118
117
  let fake: FakeBrowser;
119
118
  let router: MessageRouter;
120
119
  let page: Page;
@@ -224,7 +223,7 @@ describe("Page.localStorage / Page.sessionStorage (task 0257)", () => {
224
223
  });
225
224
  });
226
225
 
227
- describe("Page.grantAllPermissions (task 0257)", () => {
226
+ describe("Page.grantAllPermissions", () => {
228
227
  let fake: FakeBrowser;
229
228
  let router: MessageRouter;
230
229
  let page: Page;
@@ -3,7 +3,7 @@
3
3
  * (`packages/core/src/page/piercing.ts`). Drives a hand-crafted
4
4
  * `PierceDomNode` tree that mirrors the CDP `DOM.getDocument({ depth:-1,
5
5
  * pierce:true })` shape — including a closed-shadow-rooted iframe, which is
6
- * the whole point of task 0253.
6
+ * the whole point of
7
7
  *
8
8
  * The findPiercingMatches output is verified by `backendNodeId`, the only
9
9
  * field the host-side `Page.querySelectorPiercing` cares about for the
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Unit tests for the Linux-server environment auto-detection that drives
3
- * `LaunchOptions.headlessMode` defaulting (task 0258).
3
+ * `LaunchOptions.headlessMode` defaulting.
4
4
  *
5
5
  * Two layers under test:
6
6
  *
@@ -41,7 +41,7 @@ function probes(overrides: Partial<LinuxServerProbes> = {}): LinuxServerProbes {
41
41
  };
42
42
  }
43
43
 
44
- describe("detectLinuxServerEnv — server-no-display classifier (task 0258)", () => {
44
+ describe("detectLinuxServerEnv — server-no-display classifier", () => {
45
45
  it("flags Linux + no DISPLAY + no WAYLAND_DISPLAY as serverNoDisplay=true", () => {
46
46
  const env = detectLinuxServerEnv(probes());
47
47
  expect(env.serverNoDisplay).toBe(true);
@@ -130,7 +130,7 @@ describe("detectLinuxServerEnv — server-no-display classifier (task 0258)", ()
130
130
  });
131
131
  });
132
132
 
133
- describe("resolveHeadlessMode — precedence table (task 0258)", () => {
133
+ describe("resolveHeadlessMode — precedence table", () => {
134
134
  const SERVER_ENV = detectLinuxServerEnv(probes());
135
135
  const DEV_ENV = detectLinuxServerEnv(probes({ display: ":0" }));
136
136
 
@@ -198,7 +198,7 @@ describe("resolveHeadlessMode — precedence table (task 0258)", () => {
198
198
  });
199
199
  });
200
200
 
201
- describe("buildChromiumArgs — headlessMode dispatch (task 0258)", () => {
201
+ describe("buildChromiumArgs — headlessMode dispatch", () => {
202
202
  function baseCfg(overrides: Partial<SpawnConfig> = {}): SpawnConfig {
203
203
  return { binary: FAKE_BINARY, headless: false, ...overrides };
204
204
  }
@@ -4,7 +4,7 @@
4
4
  * spawn a real Chromium here; the goal is to lock the flag set against
5
5
  * regressions, particularly the matrix-derived `--lang=<locale>` flag that
6
6
  * closes the I-5 leak between Chromium's network-layer `Accept-Language`
7
- * header and the JS-layer `navigator.language(s)` spoof (task 0251).
7
+ * header and the JS-layer `navigator.language(s)` spoof.
8
8
  *
9
9
  * The flag is sourced from `MatrixV1.locale` (the canonical primary BCP-47
10
10
  * string) and MUST come from the matrix, never from the host OS.
@@ -75,7 +75,7 @@ describe("buildChromiumArgs / baseline", () => {
75
75
  });
76
76
  });
77
77
 
78
- describe("buildChromiumArgs / --lang (task 0251 — matrix.locale → Accept-Language)", () => {
78
+ describe("buildChromiumArgs / --lang (— matrix.locale → Accept-Language)", () => {
79
79
  it("appends --lang=<value> when locale is set", () => {
80
80
  const args = buildChromiumArgs(
81
81
  baseCfg({ locale: "en-US" }),
@@ -337,7 +337,7 @@ describe("buildChromiumArgs — task 0256 (hermetic-mode knob)", () => {
337
337
  });
338
338
 
339
339
  /**
340
- * Diagnostic-tail classifier — task 0259. Locks the two patterns we currently
340
+ * Diagnostic-tail classifier — Locks the two patterns we currently
341
341
  * surface (root-sandbox refusal and missing shared libs) against regressions
342
342
  * without spawning a real Chromium.
343
343
  */
@@ -13,7 +13,6 @@
13
13
  * - The defensive `Fetch.requestPaused` handler issues `Fetch.continueRequest`.
14
14
  * - `dispose()` sends `Fetch.disable` and is idempotent.
15
15
  *
16
- * @see tasks/0160-proxy-auth-and-ci-fix.md
17
16
  */
18
17
 
19
18
  import { describe, expect, it } from "bun:test";
@@ -152,7 +151,7 @@ function makeRouter(): FakeRouter {
152
151
  }
153
152
 
154
153
  describe("installProxyAuth", () => {
155
- it("sends Fetch.enable with handleAuthRequests:true and Document-first patterns (task 0266)", async () => {
154
+ it("sends Fetch.enable with handleAuthRequests:true and Document-first patterns", async () => {
156
155
  const f = makeRouter();
157
156
  const handle = await installProxyAuth(f.router, { username: "u", password: "p" });
158
157
  const enable = f.pipe.written.find((c) => c.parsed.method === "Fetch.enable");
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Live conformance test for `Page.screenshot` (task 0265).
2
+ * Live conformance test for `Page.screenshot`.
3
3
  *
4
4
  * Gated by `MOCHI_E2E=1` so the default `bun test` run stays fast and offline.
5
5
  * Spawns a real Chromium-for-Testing instance, navigates to a tiny data URL,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Unit tests for `Page.screenshot` (task 0265).
2
+ * Unit tests for `Page.screenshot`.
3
3
  *
4
4
  * Exercises the CDP wire shape against a `MessageRouter` driven over a fake
5
5
  * pipe — no real Chromium spawn, no real `Session`. The router still applies
@@ -22,7 +22,6 @@
22
22
  *
23
23
  * Gated by `MOCHI_E2E=1`. Set `MOCHI_CHROMIUM_PATH` to a real binary.
24
24
  *
25
- * @see tasks/0252-window-size-flag-from-matrix.md
26
25
  * @see UDC `__init__.py:410-411`, UDC issue #2242
27
26
  */
28
27
 
package/src/cdp/types.ts CHANGED
@@ -83,7 +83,6 @@ export interface DomNode {
83
83
  *
84
84
  * @see PLAN.md §8.2 — `DOM.getDocument` and `DOM.resolveNode` are not on the
85
85
  * forbidden list; both are fine to use.
86
- * @see tasks/0253-closed-shadow-piercing-locator.md
87
86
  */
88
87
  export interface PierceDomNode {
89
88
  nodeId: number;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-pick the host-OS-matching profile when `LaunchOptions.profile` is
3
- * omitted (task 0272). The function below is the pure decision table the
3
+ * omitted. The function below is the pure decision table the
4
4
  * launcher consults; tests stub `(platform, arch)` and assert the mapping
5
5
  * without spinning a Chromium.
6
6
  *
@@ -17,7 +17,7 @@
17
17
  * by hand" into a default removes the entire class of "user accidentally
18
18
  * spoofed Windows from a Linux DC and looked weird to the WAF" failures —
19
19
  * the same argument that drove `detectLinuxServerEnv` for headless mode in
20
- * task 0259.
20
+ *
21
21
  *
22
22
  * ## Mapping
23
23
  *
@@ -38,12 +38,10 @@
38
38
  * The current profile catalog (`packages/profiles/data/`) ships
39
39
  * `mac-chrome-stable` as a darwin/arm64 capture (its `os.arch === "arm64"`
40
40
  * in `profile.json`). The mapping above still routes darwin/x64 to
41
- * `mac-chrome-stable` per task 0272's success criteria; users on Intel Macs
41
+ * `mac-chrome-stable`; users on Intel Macs
42
42
  * who want a strict arch match should pass `profile` explicitly until an
43
43
  * `mac-intel-chrome-stable` capture lands.
44
44
  *
45
- * @see tasks/0271-the-linux-os-thesis.md — the strategic thesis + evidence
46
- * @see tasks/0272-host-os-profile-auto-default.md — engineering brief
47
45
  */
48
46
 
49
47
  import type { ProfileId } from "./launch";
@@ -64,7 +62,7 @@ export function defaultProfileForHost(): ProfileId | null {
64
62
  /**
65
63
  * Internal pure resolver, exposed so the unit tests can drive the table
66
64
  * without stubbing global `process`. Mirrors the precedence-table style of
67
- * `resolveHeadlessMode` (task 0258).
65
+ * `resolveHeadlessMode`.
68
66
  *
69
67
  * @internal
70
68
  */
@@ -82,7 +80,7 @@ export function resolveDefaultProfileForHost(
82
80
  /**
83
81
  * The six real-device profile IDs that `defaultProfileForHost` can return,
84
82
  * surfaced by the launcher's failure-mode diagnostic. Order matches the
85
- * task 0272 brief verbatim so the user-facing message is stable.
83
+ * brief verbatim so the user-facing message is stable.
86
84
  *
87
85
  * @internal
88
86
  */
@@ -97,7 +95,7 @@ export const EXPLICIT_PROFILE_IDS = [
97
95
 
98
96
  /**
99
97
  * Build the precise diagnostic emitted when `profile` is omitted on an
100
- * unsupported host. Format pinned by task 0272 — keep stable so docs +
98
+ * unsupported host. Format pinned — keep stable so docs +
101
99
  * LLM-context blocks stay correct.
102
100
  *
103
101
  * @internal
@@ -11,7 +11,6 @@
11
11
  * + en-US looks like every Tor user.
12
12
  *
13
13
  * @see PLAN.md §9 — relational consistency, IP/TZ/Locale axis
14
- * @see tasks/0262-ip-tz-locale-exit-consistency.md
15
14
  */
16
15
 
17
16
  import type { MatrixV1 } from "@mochi.js/consistency";
package/src/geo-probe.ts CHANGED
@@ -9,11 +9,13 @@
9
9
  * first half of the fix (the second half is {@link reconcileGeoConsistency}
10
10
  * in `launch.ts`).
11
11
  *
12
- * The probe issues a single GET through the same `wreq` preset the session
13
- * would use for user traffic, so the geolocation service sees the **same
14
- * JA4 / headers** as the actual page the probe doesn't itself become
15
- * detectable. The probe respects the `proxy` option; if unset, the probe
16
- * goes direct (which is fine the user's exit IP is the host's IP).
12
+ * Post-0.7 the probe rides Chromium itself same network stack as
13
+ * `page.goto`, same TLS / H2 / JA4 by definition. The default `ProbeFetch`
14
+ * adapter delegates to `globalThis.fetch` so {@link probeExitGeo} can run
15
+ * in a test runner without requiring a live Session; production callers
16
+ * inject a `Session.fetch`-backed adapter via `launch.ts`. The probe
17
+ * respects the `proxy` option as a diagnostic-only field — Chromium picks
18
+ * up `--proxy-server` from the launch flags directly.
17
19
  *
18
20
  * ### Endpoint registry (verified working 2026-05-09)
19
21
  *
@@ -34,14 +36,12 @@
34
36
  * mode (default `privacy-fallback`).
35
37
  *
36
38
  * **No cross-session caching** — proxy IPs rotate; stale cache is worse
37
- * than no cache. (`docs/limits.md` — task 0262.)
39
+ * than no cache. (`docs/limits.md` —)
38
40
  *
39
41
  * @see PLAN.md §9 (relational consistency — IP/TZ/Locale axis)
40
- * @see tasks/0262-ip-tz-locale-exit-consistency.md
41
42
  */
42
43
 
43
44
  import type { MatrixV1 } from "@mochi.js/consistency";
44
- import { fetch as netFetch } from "@mochi.js/net";
45
45
 
46
46
  /**
47
47
  * Normalised geolocation derived from one of the probe endpoints. The
@@ -429,22 +429,30 @@ const DEFAULT_PER_ENDPOINT_TIMEOUT_MS = 2000;
429
429
 
430
430
  /**
431
431
  * Injection seam for the underlying HTTP transport. Production uses
432
- * `@mochi.js/net`'s `fetch` (so the probe carries the same JA4/headers as
433
- * user traffic). Tests inject a stub.
432
+ * `Session.fetch` (so the probe carries the same JA4/headers as user
433
+ * traffic — it IS the browser). Tests inject a stub.
434
+ *
435
+ * The `proxy` field is forwarded for diagnostic purposes only; the
436
+ * actual proxy egress is wired at the Chromium `--proxy-server` flag.
434
437
  *
435
438
  * @internal
436
439
  */
437
440
  export type ProbeFetch = (
438
441
  url: string,
439
- init: { preset: string; proxy?: string; timeoutMs: number },
442
+ init: { proxy?: string; timeoutMs: number },
440
443
  ) => Promise<Response>;
441
444
 
442
445
  /** Options for {@link probeExitGeo}. */
443
446
  export interface ProbeOptions {
444
- /** Optional outbound proxy URL — `user:pass@host:port` form is fine. */
447
+ /** Optional outbound proxy URL — diagnostic-only post-0.7. */
445
448
  readonly proxy?: string;
446
- /** The matrix whose `wreqPreset` drives the TLS fingerprint of the probe. */
447
- readonly matrix: Pick<MatrixV1, "wreqPreset">;
449
+ /**
450
+ * The matrix is retained on the API surface for forward-compat (so any
451
+ * future per-locale endpoint shuffle can read from it) and to keep
452
+ * call sites stable across the 0.6 → 0.7 transition. The probe itself
453
+ * no longer reads any field — Chromium owns the TLS fingerprint.
454
+ */
455
+ readonly matrix?: Partial<MatrixV1>;
448
456
  /**
449
457
  * Override the default 4-attempt cap. Tests use 2 to keep wall-time low;
450
458
  * production sticks with 4.
@@ -454,7 +462,9 @@ export interface ProbeOptions {
454
462
  readonly perEndpointTimeoutMs?: number;
455
463
  /**
456
464
  * Inject a custom `fetch` transport (for tests). Defaults to
457
- * `@mochi.js/net`'s `fetch` so the probe shares the session's TLS preset.
465
+ * `globalThis.fetch` (test/standalone use) production calls override
466
+ * this with a `Session.fetch`-backed adapter so the probe rides the
467
+ * same Chromium network stack as user traffic.
458
468
  * @internal
459
469
  */
460
470
  readonly fetch?: ProbeFetch;
@@ -467,27 +477,23 @@ export interface ProbeOptions {
467
477
  }
468
478
 
469
479
  /**
470
- * Default `ProbeFetch` — issues the request through `@mochi.js/net`'s
471
- * one-shot `fetch` so the geo service sees the same JA4 as user traffic.
480
+ * Default `ProbeFetch` — falls back to `globalThis.fetch` so the probe
481
+ * works standalone in tests without a live Session. Production launch
482
+ * paths inject a `Session.fetch`-backed adapter so the probe shares
483
+ * Chromium's network stack (and therefore JA4) with user traffic.
472
484
  *
473
- * The per-call timeout is mapped onto the wreq `timeoutMs` field; we do
474
- * NOT also wrap with `AbortController` because Bun's `Response` from the
475
- * FFI path is synchronous and we want the wreq layer to own the timeout
476
- * (a layered `AbortController` would race with the Rust handle drop).
485
+ * The per-call timeout is enforced via `AbortController`.
477
486
  */
478
487
  const defaultFetch: ProbeFetch = (url, init) => {
479
- return Promise.resolve(
480
- netFetch(url, {
481
- preset: init.preset,
482
- ...(init.proxy !== undefined ? { proxy: init.proxy } : {}),
488
+ const ac = new AbortController();
489
+ const timer = setTimeout(() => ac.abort(), init.timeoutMs);
490
+ return globalThis
491
+ .fetch(url, {
483
492
  method: "GET",
484
493
  headers: { Accept: "application/json" },
485
- timeoutMs: init.timeoutMs,
486
- // Connect timeout matches the per-endpoint cap — a stuck SYN is the
487
- // dominant failure mode against rate-limited endpoints.
488
- connectTimeoutMs: init.timeoutMs,
489
- }),
490
- );
494
+ signal: ac.signal,
495
+ })
496
+ .finally(() => clearTimeout(timer));
491
497
  };
492
498
 
493
499
  /** Fisher-Yates shuffle — non-deterministic, Math.random-backed. */
@@ -570,7 +576,6 @@ export async function probeExitGeo(opts: ProbeOptions): Promise<ExitGeo | null>
570
576
  new Promise<Response>((resolve, reject) => {
571
577
  try {
572
578
  fetchFn(adapter.url, {
573
- preset: opts.matrix.wreqPreset,
574
579
  ...(opts.proxy !== undefined ? { proxy: opts.proxy } : {}),
575
580
  timeoutMs: perEndpointTimeoutMs,
576
581
  }).then(resolve, reject);
package/src/index.ts CHANGED
@@ -20,7 +20,7 @@ export {
20
20
  type Unsubscribe,
21
21
  } from "./cdp/router";
22
22
  // Auto-pick host-OS-matching profile when `LaunchOptions.profile` is omitted
23
- // (task 0272 — paired with the strategic thesis in task 0271).
23
+ // (— paired with the strategic thesis in task 0271).
24
24
  export {
25
25
  defaultProfileForHost,
26
26
  EXPLICIT_PROFILE_IDS,