@mochi.js/core 0.3.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 +19 -10
- package/package.json +5 -6
- package/src/__tests__/cookies-jar.test.ts +360 -0
- package/src/__tests__/default-profile.test.ts +179 -0
- package/src/__tests__/dx-cluster.e2e.test.ts +244 -0
- package/src/__tests__/geo-consistency.test.ts +0 -1
- package/src/__tests__/geo-probe.test.ts +13 -13
- package/src/__tests__/init-injector.e2e.test.ts +143 -0
- package/src/__tests__/init-injector.test.ts +248 -0
- package/src/__tests__/inject.test.ts +80 -165
- package/src/__tests__/page-dx-cluster.test.ts +291 -0
- package/src/__tests__/piercing.test.ts +1 -1
- package/src/__tests__/proc-linux-server.test.ts +243 -0
- package/src/__tests__/proc.test.ts +3 -3
- package/src/__tests__/proxy-auth.test.ts +22 -56
- package/src/__tests__/screenshot.e2e.test.ts +126 -0
- package/src/__tests__/screenshot.test.ts +363 -0
- package/src/__tests__/window-size.e2e.test.ts +0 -1
- package/src/cdp/init-injector.ts +644 -0
- package/src/cdp/types.ts +0 -1
- package/src/default-profile.ts +110 -0
- package/src/geo-consistency.ts +0 -1
- package/src/geo-probe.ts +37 -32
- package/src/index.ts +33 -1
- package/src/launch.ts +225 -50
- package/src/linux-server.ts +157 -0
- package/src/page/element-handle.ts +0 -1
- package/src/page/piercing.ts +0 -1
- package/src/page/selector.ts +0 -1
- package/src/page.ts +429 -10
- package/src/proc.ts +52 -10
- package/src/proxy-auth.ts +25 -108
- package/src/session.ts +846 -182
- package/src/version.ts +1 -1
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-pick the host-OS-matching profile when `LaunchOptions.profile` is
|
|
3
|
+
* omitted. The function below is the pure decision table the
|
|
4
|
+
* launcher consults; tests stub `(platform, arch)` and assert the mapping
|
|
5
|
+
* without spinning a Chromium.
|
|
6
|
+
*
|
|
7
|
+
* ## Why
|
|
8
|
+
*
|
|
9
|
+
* Task 0271 documents the strategic thesis: spoofing Windows from a Linux
|
|
10
|
+
* server is the wrong default. Linux is a real-user signal, not a bot
|
|
11
|
+
* signal. WAFs trained on real traffic do not penalize Linux UAs because
|
|
12
|
+
* Linux desktops are massively overrepresented in high-LTV segments
|
|
13
|
+
* (developers, engineers, researchers). The signal was always
|
|
14
|
+
* `HeadlessChrome`, never Linux.
|
|
15
|
+
*
|
|
16
|
+
* Lifting host-OS-matching from "user types `profile: 'linux-chrome-stable'`
|
|
17
|
+
* by hand" into a default removes the entire class of "user accidentally
|
|
18
|
+
* spoofed Windows from a Linux DC and looked weird to the WAF" failures —
|
|
19
|
+
* the same argument that drove `detectLinuxServerEnv` for headless mode in
|
|
20
|
+
*
|
|
21
|
+
*
|
|
22
|
+
* ## Mapping
|
|
23
|
+
*
|
|
24
|
+
* The host pairs `(process.platform, process.arch)` we currently support:
|
|
25
|
+
*
|
|
26
|
+
* - `linux/x64` → `linux-chrome-stable`
|
|
27
|
+
* - `darwin/arm64` → `mac-m4-chrome-stable`
|
|
28
|
+
* - `darwin/x64` → `mac-chrome-stable`
|
|
29
|
+
* - `win32/x64` → `windows-chrome-stable`
|
|
30
|
+
*
|
|
31
|
+
* Everything else (linux/arm64, freebsd, alpine-musl detection, win32/arm64,
|
|
32
|
+
* etc.) returns `null` — the launcher then throws with a precise diagnostic
|
|
33
|
+
* listing the six explicit profile IDs and a pointer to the
|
|
34
|
+
* choose-your-profile guide. We never silently fall back to a placeholder.
|
|
35
|
+
*
|
|
36
|
+
* ## Caveat — darwin/x64
|
|
37
|
+
*
|
|
38
|
+
* The current profile catalog (`packages/profiles/data/`) ships
|
|
39
|
+
* `mac-chrome-stable` as a darwin/arm64 capture (its `os.arch === "arm64"`
|
|
40
|
+
* in `profile.json`). The mapping above still routes darwin/x64 to
|
|
41
|
+
* `mac-chrome-stable`; users on Intel Macs
|
|
42
|
+
* who want a strict arch match should pass `profile` explicitly until an
|
|
43
|
+
* `mac-intel-chrome-stable` capture lands.
|
|
44
|
+
*
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import type { ProfileId } from "./launch";
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Pure decision table: given the current host's `(process.platform,
|
|
51
|
+
* process.arch)` pair, return the profile id that best matches the host
|
|
52
|
+
* OS axis. Returns `null` for unsupported hosts so the launcher can throw
|
|
53
|
+
* with a precise diagnostic.
|
|
54
|
+
*
|
|
55
|
+
* No I/O, no logging — call sites can introspect the value cheaply (e.g.
|
|
56
|
+
* `console.log(mochi.defaultProfileForHost())`).
|
|
57
|
+
*/
|
|
58
|
+
export function defaultProfileForHost(): ProfileId | null {
|
|
59
|
+
return resolveDefaultProfileForHost(process.platform, process.arch);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Internal pure resolver, exposed so the unit tests can drive the table
|
|
64
|
+
* without stubbing global `process`. Mirrors the precedence-table style of
|
|
65
|
+
* `resolveHeadlessMode`.
|
|
66
|
+
*
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
export function resolveDefaultProfileForHost(
|
|
70
|
+
platform: NodeJS.Platform,
|
|
71
|
+
arch: string,
|
|
72
|
+
): ProfileId | null {
|
|
73
|
+
if (platform === "linux" && arch === "x64") return "linux-chrome-stable";
|
|
74
|
+
if (platform === "darwin" && arch === "arm64") return "mac-m4-chrome-stable";
|
|
75
|
+
if (platform === "darwin" && arch === "x64") return "mac-chrome-stable";
|
|
76
|
+
if (platform === "win32" && arch === "x64") return "windows-chrome-stable";
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The six real-device profile IDs that `defaultProfileForHost` can return,
|
|
82
|
+
* surfaced by the launcher's failure-mode diagnostic. Order matches the
|
|
83
|
+
* brief verbatim so the user-facing message is stable.
|
|
84
|
+
*
|
|
85
|
+
* @internal
|
|
86
|
+
*/
|
|
87
|
+
export const EXPLICIT_PROFILE_IDS = [
|
|
88
|
+
"mac-m4-chrome-stable",
|
|
89
|
+
"mac-chrome-stable",
|
|
90
|
+
"mac-chrome-beta",
|
|
91
|
+
"windows-chrome-stable",
|
|
92
|
+
"linux-chrome-stable",
|
|
93
|
+
"mac-brave-stable",
|
|
94
|
+
] as const satisfies readonly ProfileId[];
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Build the precise diagnostic emitted when `profile` is omitted on an
|
|
98
|
+
* unsupported host. Format pinned — keep stable so docs +
|
|
99
|
+
* LLM-context blocks stay correct.
|
|
100
|
+
*
|
|
101
|
+
* @internal
|
|
102
|
+
*/
|
|
103
|
+
export function unsupportedHostMessage(platform: NodeJS.Platform, arch: string): string {
|
|
104
|
+
const list = EXPLICIT_PROFILE_IDS.map((id) => ` - ${id}`).join("\n");
|
|
105
|
+
return (
|
|
106
|
+
`[mochi] launch: no profile supplied and no host-matching default for ` +
|
|
107
|
+
`platform=${platform} arch=${arch}. Pick one explicitly:\n${list}\n` +
|
|
108
|
+
`See https://mochijs.com/docs/guides/choose-your-profile for the decision aid.`
|
|
109
|
+
);
|
|
110
|
+
}
|
package/src/geo-consistency.ts
CHANGED
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
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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` —
|
|
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
|
-
*
|
|
433
|
-
*
|
|
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: {
|
|
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 —
|
|
447
|
+
/** Optional outbound proxy URL — diagnostic-only post-0.7. */
|
|
445
448
|
readonly proxy?: string;
|
|
446
|
-
/**
|
|
447
|
-
|
|
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
|
-
*
|
|
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` —
|
|
471
|
-
*
|
|
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
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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
|
@@ -19,6 +19,13 @@ export {
|
|
|
19
19
|
type SendOptions,
|
|
20
20
|
type Unsubscribe,
|
|
21
21
|
} from "./cdp/router";
|
|
22
|
+
// Auto-pick host-OS-matching profile when `LaunchOptions.profile` is omitted
|
|
23
|
+
// (— paired with the strategic thesis in task 0271).
|
|
24
|
+
export {
|
|
25
|
+
defaultProfileForHost,
|
|
26
|
+
EXPLICIT_PROFILE_IDS,
|
|
27
|
+
resolveDefaultProfileForHost,
|
|
28
|
+
} from "./default-profile";
|
|
22
29
|
// Error surface.
|
|
23
30
|
export { NotImplementedError } from "./errors";
|
|
24
31
|
// Exit-IP / TZ / locale reconciliation (task 0262, PLAN.md §9).
|
|
@@ -40,16 +47,33 @@ export {
|
|
|
40
47
|
mochi,
|
|
41
48
|
type ProfileId,
|
|
42
49
|
type ProxyConfig,
|
|
50
|
+
resolveHeadlessMode,
|
|
43
51
|
} from "./launch";
|
|
52
|
+
// Linux-server environment detection. Pure helpers for users who want to
|
|
53
|
+
// introspect what mochi inferred (and override `headlessMode` from there).
|
|
54
|
+
// Task 0258 — `mochi.detectLinuxServerEnv()` calls `probeLinuxServerEnv`.
|
|
55
|
+
export {
|
|
56
|
+
detectLinuxServerEnv,
|
|
57
|
+
type LinuxServerEnv,
|
|
58
|
+
type LinuxServerProbes,
|
|
59
|
+
probeLinuxServerEnv,
|
|
60
|
+
snapshotProbes,
|
|
61
|
+
} from "./linux-server";
|
|
44
62
|
export {
|
|
63
|
+
ALL_BROWSER_PERMISSIONS,
|
|
64
|
+
type BrowserPermission,
|
|
45
65
|
type Cookie,
|
|
66
|
+
type DomStorage,
|
|
67
|
+
type DomStorageOptions,
|
|
46
68
|
type GotoOptions,
|
|
69
|
+
type GrantAllPermissionsOptions,
|
|
47
70
|
type HumanClickOptions,
|
|
48
71
|
type HumanMoveOptions,
|
|
49
72
|
type HumanScrollOptions,
|
|
50
73
|
type HumanTypeOptions,
|
|
51
74
|
Page,
|
|
52
75
|
type PageInit,
|
|
76
|
+
type ScreenshotOptions,
|
|
53
77
|
type WaitForOptions,
|
|
54
78
|
type WaitState,
|
|
55
79
|
type WaitUntil,
|
|
@@ -58,5 +82,13 @@ export { ElementHandle, type ElementHandleInit } from "./page/element-handle";
|
|
|
58
82
|
// Proxy URL parsing — exported so tests + downstream tools can normalize
|
|
59
83
|
// proxy strings without going through `launch()`.
|
|
60
84
|
export { type ParsedProxy, parseProxyUrl } from "./proxy-auth";
|
|
61
|
-
export {
|
|
85
|
+
export {
|
|
86
|
+
COOKIE_JAR_FORMAT_VERSION,
|
|
87
|
+
type CookieJar,
|
|
88
|
+
type CookieJarFile,
|
|
89
|
+
type CookieJarOptions,
|
|
90
|
+
Session,
|
|
91
|
+
type SessionInit,
|
|
92
|
+
type StorageSnapshot,
|
|
93
|
+
} from "./session";
|
|
62
94
|
export { VERSION } from "./version";
|