@mochi.js/core 0.8.0 → 0.8.1
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/package.json +5 -4
- package/src/launch.ts +84 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mochi.js/core",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
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",
|
|
@@ -49,10 +49,11 @@
|
|
|
49
49
|
"build": "echo 'no build step yet — Bun consumes src/ directly'"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@mochi.js/behavioral": "^0.1.
|
|
52
|
+
"@mochi.js/behavioral": "^0.1.5",
|
|
53
53
|
"@mochi.js/challenges": "^0.2.1",
|
|
54
|
-
"@mochi.js/consistency": "^0.1.
|
|
55
|
-
"@mochi.js/inject": "^0.3.
|
|
54
|
+
"@mochi.js/consistency": "^0.1.4",
|
|
55
|
+
"@mochi.js/inject": "^0.3.1",
|
|
56
|
+
"@mochi.js/profiles": "^0.2.0"
|
|
56
57
|
},
|
|
57
58
|
"publishConfig": {
|
|
58
59
|
"access": "public"
|
package/src/launch.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { deriveMatrix, type ProfileV1 } from "@mochi.js/consistency";
|
|
14
|
+
import { getProfile, ProfileBaselineMissingError, UnknownProfileIdError } from "@mochi.js/profiles";
|
|
14
15
|
import { resolveBinary } from "./binary";
|
|
15
16
|
import { defaultProfileForHost, unsupportedHostMessage } from "./default-profile";
|
|
16
17
|
import { type GeoConsistencyMode, reconcileGeoConsistency } from "./geo-consistency";
|
|
@@ -262,15 +263,17 @@ export async function launch(opts: LaunchOptions): Promise<Session> {
|
|
|
262
263
|
// JS-layer spoof.
|
|
263
264
|
//
|
|
264
265
|
// Inline `ProfileV1` objects flow straight through; string profile ids
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
266
|
+
// resolve to the captured `data/<id>/profile.json` baseline shipped by
|
|
267
|
+
// `@mochi.js/profiles`. When the catalog declares an id but no captured
|
|
268
|
+
// baseline ships yet (e.g. `mac-m2-chrome-stable`), we fall back to a
|
|
269
|
+
// synthesized placeholder so the launch still succeeds. The matrix is
|
|
270
|
+
// bit-stable per `(profile, seed)` excluding the `derivedAt` timestamp.
|
|
268
271
|
//
|
|
269
272
|
// Task 0272 — when `profile` is omitted, auto-pick the host-OS-matching
|
|
270
273
|
// profile id. Throws with a precise diagnostic if the host is one of the
|
|
271
274
|
// unsupported ones (FreeBSD, Linux arm64 today, Windows arm64, Alpine
|
|
272
275
|
// musl). Explicit `profile:` always wins; the auto-pick never overrides.
|
|
273
|
-
const profileSource = resolveProfileSource(opts.profile);
|
|
276
|
+
const profileSource = await resolveProfileSource(opts.profile);
|
|
274
277
|
const matrix = deriveMatrix(profileSource.profile, opts.seed);
|
|
275
278
|
if (profileSource.autoPicked) {
|
|
276
279
|
// One info-level log line so users can see what mochi inferred without
|
|
@@ -509,27 +512,33 @@ function normalizeProxy(p: LaunchOptions["proxy"]):
|
|
|
509
512
|
*
|
|
510
513
|
* 1. Explicit `ProfileV1` object — flows through unchanged. `autoPicked`
|
|
511
514
|
* false; `id` taken from the inline object.
|
|
512
|
-
* 2. Explicit `ProfileId` string —
|
|
513
|
-
*
|
|
515
|
+
* 2. Explicit `ProfileId` string — load the captured baseline from
|
|
516
|
+
* `@mochi.js/profiles`. If the id is known to the catalog but no
|
|
517
|
+
* captured baseline ships, fall back to a placeholder synthesis so
|
|
518
|
+
* the launch still succeeds (and the consistency engine still locks
|
|
519
|
+
* a relationally-consistent Matrix from the skeleton). Unknown ids
|
|
520
|
+
* propagate as a hard error. `autoPicked` false.
|
|
514
521
|
* 3. `undefined` — task 0272: call `defaultProfileForHost()`. Throw with
|
|
515
522
|
* the unsupported-host diagnostic when the resolver returns `null`.
|
|
516
|
-
* `autoPicked` true
|
|
523
|
+
* `autoPicked` true; same captured-vs-placeholder fallback as branch
|
|
524
|
+
* 2.
|
|
517
525
|
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
526
|
+
* Async because `getProfile` reads `data/<id>/profile.json` from disk via
|
|
527
|
+
* `Bun.file().json()`. The launcher does not log here — the INFO line for
|
|
528
|
+
* `autoPicked === true` is emitted at the call-site so test fixtures can
|
|
529
|
+
* assert the resolution without intercepting `console`.
|
|
521
530
|
*/
|
|
522
|
-
function resolveProfileSource(profile: ProfileId | ProfileV1 | undefined): {
|
|
531
|
+
async function resolveProfileSource(profile: ProfileId | ProfileV1 | undefined): Promise<{
|
|
523
532
|
profile: ProfileV1;
|
|
524
533
|
id: ProfileId;
|
|
525
534
|
autoPicked: boolean;
|
|
526
|
-
} {
|
|
535
|
+
}> {
|
|
527
536
|
if (typeof profile === "object") {
|
|
528
537
|
return { profile, id: profile.id, autoPicked: false };
|
|
529
538
|
}
|
|
530
539
|
if (typeof profile === "string") {
|
|
531
540
|
return {
|
|
532
|
-
profile:
|
|
541
|
+
profile: await loadProfileWithFallback(profile),
|
|
533
542
|
id: profile,
|
|
534
543
|
autoPicked: false,
|
|
535
544
|
};
|
|
@@ -540,24 +549,75 @@ function resolveProfileSource(profile: ProfileId | ProfileV1 | undefined): {
|
|
|
540
549
|
throw new Error(unsupportedHostMessage(process.platform, process.arch));
|
|
541
550
|
}
|
|
542
551
|
return {
|
|
543
|
-
profile:
|
|
552
|
+
profile: await loadProfileWithFallback(picked),
|
|
544
553
|
id: picked,
|
|
545
554
|
autoPicked: true,
|
|
546
555
|
};
|
|
547
556
|
}
|
|
548
557
|
|
|
549
558
|
/**
|
|
550
|
-
*
|
|
551
|
-
*
|
|
552
|
-
*
|
|
553
|
-
*
|
|
559
|
+
* Load a `ProfileV1` for `id` from `@mochi.js/profiles` if a captured
|
|
560
|
+
* baseline ships, otherwise synthesize a placeholder. Unknown ids also fall
|
|
561
|
+
* back to the placeholder (with a console.warn) — preserving the
|
|
562
|
+
* pre-getProfile() contract that any string id produces a working session.
|
|
563
|
+
* E2E test fixtures rely on synthetic ids like "test-humanize".
|
|
564
|
+
*
|
|
565
|
+
* Critical correctness path: the captured baselines pin tip-of-stable Chrome
|
|
566
|
+
* majors (147+ as of 2026-05). The pre-fix code path called
|
|
567
|
+
* `synthesizePlaceholderProfile` for every string id, which hardcoded
|
|
568
|
+
* Chrome 131 and produced a UA mismatch with the actual Chromium-for-Testing
|
|
569
|
+
* binary.
|
|
570
|
+
*/
|
|
571
|
+
async function loadProfileWithFallback(id: ProfileId): Promise<ProfileV1> {
|
|
572
|
+
try {
|
|
573
|
+
// `ProfileId` here is the loose `string` alias the launcher accepts
|
|
574
|
+
// (see comment near the type definition). `getProfile` narrows it
|
|
575
|
+
// back to the catalog union at runtime and throws
|
|
576
|
+
// `UnknownProfileIdError` for ids outside the catalog.
|
|
577
|
+
return await getProfile(id as Parameters<typeof getProfile>[0]);
|
|
578
|
+
} catch (err) {
|
|
579
|
+
if (err instanceof ProfileBaselineMissingError) {
|
|
580
|
+
// Known catalog entry, no baseline shipped yet — fall back to the
|
|
581
|
+
// synthesized placeholder so the launch still succeeds.
|
|
582
|
+
return synthesizePlaceholderProfile(id);
|
|
583
|
+
}
|
|
584
|
+
if (err instanceof UnknownProfileIdError) {
|
|
585
|
+
// Caller passed an id that isn't in `KNOWN_PROFILE_IDS`. Surface a
|
|
586
|
+
// warning so typos are visible, but fall back to the placeholder so
|
|
587
|
+
// synthetic test-fixture ids (e.g. "test-humanize") keep working.
|
|
588
|
+
// biome-ignore lint/suspicious/noConsole: dev-facing diagnostic
|
|
589
|
+
console.warn(
|
|
590
|
+
`[mochi] profile id "${id}" is not in @mochi.js/profiles.KNOWN_PROFILE_IDS; ` +
|
|
591
|
+
"falling back to a synthesized placeholder. Pass a ProfileV1 object directly " +
|
|
592
|
+
"or use one of the catalog ids to silence this warning.",
|
|
593
|
+
);
|
|
594
|
+
return synthesizePlaceholderProfile(id);
|
|
595
|
+
}
|
|
596
|
+
throw err;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Synthesize a generic placeholder `ProfileV1` from a profile id, used as
|
|
602
|
+
* a fallback when the catalog declares an id but no captured baseline
|
|
603
|
+
* ships in `@mochi.js/profiles` yet. The consistency engine still produces
|
|
604
|
+
* a real, relationally-locked Matrix from this skeleton — the id is what
|
|
605
|
+
* flows into `sha256(profile.id + seed)`.
|
|
606
|
+
*
|
|
607
|
+
* The major version pinned here MUST track the live Chromium-for-Testing
|
|
608
|
+
* pin (`packages/cli/src/browsers/manifest.ts:PINNED_FALLBACK_VERSION`)
|
|
609
|
+
* and the tip entry in
|
|
610
|
+
* `packages/consistency/src/rules/lookups/browser.ts:BROWSER_TIP_FULL_VERSION`.
|
|
611
|
+
* A drift between these surfaces ships a UA whose major doesn't match the
|
|
612
|
+
* installed binary — the canonical fingerprint-mismatch bug R-004 was
|
|
613
|
+
* meant to prevent. Bump all three together.
|
|
554
614
|
*/
|
|
555
615
|
function synthesizePlaceholderProfile(profile: ProfileId): ProfileV1 {
|
|
556
616
|
return {
|
|
557
617
|
id: profile,
|
|
558
618
|
version: "0.0.0-placeholder",
|
|
559
619
|
engine: "chromium",
|
|
560
|
-
browser: { name: "chrome", channel: "stable", minVersion: "
|
|
620
|
+
browser: { name: "chrome", channel: "stable", minVersion: "148", maxVersion: "148" },
|
|
561
621
|
os: { name: "linux", version: "22", arch: "x64" },
|
|
562
622
|
device: {
|
|
563
623
|
vendor: "generic",
|
|
@@ -582,11 +642,13 @@ function synthesizePlaceholderProfile(profile: ProfileId): ProfileV1 {
|
|
|
582
642
|
locale: "en-US",
|
|
583
643
|
languages: ["en-US", "en"],
|
|
584
644
|
behavior: { hand: "right", tremor: 0.18, wpm: 60, scrollStyle: "smooth" },
|
|
585
|
-
//
|
|
586
|
-
//
|
|
645
|
+
// `wreqPreset` is required by the ProfileV1 schema for one release of
|
|
646
|
+
// back-compat (see `schemas/profile.schema.json`). The runtime no
|
|
647
|
+
// longer reads it — `Session.fetch` rides Chromium's network stack via
|
|
648
|
+
// CDP, so JA4 is real Chrome by definition. Drops in 0.8.
|
|
587
649
|
wreqPreset: "chrome_148_linux",
|
|
588
650
|
userAgent:
|
|
589
|
-
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
651
|
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36",
|
|
590
652
|
uaCh: {},
|
|
591
653
|
entropyBudget: { fixed: [], perSeed: [] },
|
|
592
654
|
};
|