@mochi.js/core
Advanced tools
+5
-4
| { | ||
| "name": "@mochi.js/core", | ||
| "version": "0.8.0", | ||
| "version": "0.8.1", | ||
| "description": "The library for faithful browser automation. Bun-native; relational fingerprint matrix, biomechanical input, stock Chromium-for-Testing.", | ||
@@ -52,6 +52,7 @@ "license": "MIT", | ||
| "dependencies": { | ||
| "@mochi.js/behavioral": "^0.1.4", | ||
| "@mochi.js/behavioral": "^0.1.5", | ||
| "@mochi.js/challenges": "^0.2.1", | ||
| "@mochi.js/consistency": "^0.1.3", | ||
| "@mochi.js/inject": "^0.3.0" | ||
| "@mochi.js/consistency": "^0.1.4", | ||
| "@mochi.js/inject": "^0.3.1", | ||
| "@mochi.js/profiles": "^0.2.0" | ||
| }, | ||
@@ -58,0 +59,0 @@ "publishConfig": { |
+84
-22
@@ -14,2 +14,3 @@ /** | ||
| import { deriveMatrix, type ProfileV1 } from "@mochi.js/consistency"; | ||
| import { getProfile, ProfileBaselineMissingError, UnknownProfileIdError } from "@mochi.js/profiles"; | ||
| import { resolveBinary } from "./binary"; | ||
@@ -266,5 +267,7 @@ import { defaultProfileForHost, unsupportedHostMessage } from "./default-profile"; | ||
| // Inline `ProfileV1` objects flow straight through; string profile ids | ||
| // are resolved against a placeholder profile until `@mochi.js/profiles` | ||
| // ships its first capture (phase 0.4). The matrix is bit-stable per | ||
| // `(profile, seed)` excluding the `derivedAt` timestamp. | ||
| // resolve to the captured `data/<id>/profile.json` baseline shipped by | ||
| // `@mochi.js/profiles`. When the catalog declares an id but no captured | ||
| // baseline ships yet (e.g. `mac-m2-chrome-stable`), we fall back to a | ||
| // synthesized placeholder so the launch still succeeds. The matrix is | ||
| // bit-stable per `(profile, seed)` excluding the `derivedAt` timestamp. | ||
| // | ||
@@ -275,3 +278,3 @@ // Task 0272 — when `profile` is omitted, auto-pick the host-OS-matching | ||
| // musl). Explicit `profile:` always wins; the auto-pick never overrides. | ||
| const profileSource = resolveProfileSource(opts.profile); | ||
| const profileSource = await resolveProfileSource(opts.profile); | ||
| const matrix = deriveMatrix(profileSource.profile, opts.seed); | ||
@@ -515,17 +518,23 @@ if (profileSource.autoPicked) { | ||
| * false; `id` taken from the inline object. | ||
| * 2. Explicit `ProfileId` string — same placeholder synthesis as before. | ||
| * `autoPicked` false. | ||
| * 2. Explicit `ProfileId` string — load the captured baseline from | ||
| * `@mochi.js/profiles`. If the id is known to the catalog but no | ||
| * captured baseline ships, fall back to a placeholder synthesis so | ||
| * the launch still succeeds (and the consistency engine still locks | ||
| * a relationally-consistent Matrix from the skeleton). Unknown ids | ||
| * propagate as a hard error. `autoPicked` false. | ||
| * 3. `undefined` — task 0272: call `defaultProfileForHost()`. Throw with | ||
| * the unsupported-host diagnostic when the resolver returns `null`. | ||
| * `autoPicked` true. | ||
| * `autoPicked` true; same captured-vs-placeholder fallback as branch | ||
| * 2. | ||
| * | ||
| * Pure function — does not log. The launcher emits the INFO line itself | ||
| * after observing `autoPicked === true` so test fixtures can assert the | ||
| * resolution without intercepting `console`. | ||
| * Async because `getProfile` reads `data/<id>/profile.json` from disk via | ||
| * `Bun.file().json()`. The launcher does not log here — the INFO line for | ||
| * `autoPicked === true` is emitted at the call-site so test fixtures can | ||
| * assert the resolution without intercepting `console`. | ||
| */ | ||
| function resolveProfileSource(profile: ProfileId | ProfileV1 | undefined): { | ||
| async function resolveProfileSource(profile: ProfileId | ProfileV1 | undefined): Promise<{ | ||
| profile: ProfileV1; | ||
| id: ProfileId; | ||
| autoPicked: boolean; | ||
| } { | ||
| }> { | ||
| if (typeof profile === "object") { | ||
@@ -536,3 +545,3 @@ return { profile, id: profile.id, autoPicked: false }; | ||
| return { | ||
| profile: synthesizePlaceholderProfile(profile), | ||
| profile: await loadProfileWithFallback(profile), | ||
| id: profile, | ||
@@ -548,3 +557,3 @@ autoPicked: false, | ||
| return { | ||
| profile: synthesizePlaceholderProfile(picked), | ||
| profile: await loadProfileWithFallback(picked), | ||
| id: picked, | ||
@@ -556,7 +565,58 @@ autoPicked: true, | ||
| /** | ||
| * Synthesize a generic placeholder `ProfileV1` from a profile id. Until | ||
| * `@mochi.js/profiles.getProfile` lands (phase 0.4), the consistency engine | ||
| * still produces a real, relationally-locked Matrix from this skeleton — | ||
| * the id is what flows into `sha256(profile.id + seed)`. | ||
| * Load a `ProfileV1` for `id` from `@mochi.js/profiles` if a captured | ||
| * baseline ships, otherwise synthesize a placeholder. Unknown ids also fall | ||
| * back to the placeholder (with a console.warn) — preserving the | ||
| * pre-getProfile() contract that any string id produces a working session. | ||
| * E2E test fixtures rely on synthetic ids like "test-humanize". | ||
| * | ||
| * Critical correctness path: the captured baselines pin tip-of-stable Chrome | ||
| * majors (147+ as of 2026-05). The pre-fix code path called | ||
| * `synthesizePlaceholderProfile` for every string id, which hardcoded | ||
| * Chrome 131 and produced a UA mismatch with the actual Chromium-for-Testing | ||
| * binary. | ||
| */ | ||
| async function loadProfileWithFallback(id: ProfileId): Promise<ProfileV1> { | ||
| try { | ||
| // `ProfileId` here is the loose `string` alias the launcher accepts | ||
| // (see comment near the type definition). `getProfile` narrows it | ||
| // back to the catalog union at runtime and throws | ||
| // `UnknownProfileIdError` for ids outside the catalog. | ||
| return await getProfile(id as Parameters<typeof getProfile>[0]); | ||
| } catch (err) { | ||
| if (err instanceof ProfileBaselineMissingError) { | ||
| // Known catalog entry, no baseline shipped yet — fall back to the | ||
| // synthesized placeholder so the launch still succeeds. | ||
| return synthesizePlaceholderProfile(id); | ||
| } | ||
| if (err instanceof UnknownProfileIdError) { | ||
| // Caller passed an id that isn't in `KNOWN_PROFILE_IDS`. Surface a | ||
| // warning so typos are visible, but fall back to the placeholder so | ||
| // synthetic test-fixture ids (e.g. "test-humanize") keep working. | ||
| // biome-ignore lint/suspicious/noConsole: dev-facing diagnostic | ||
| console.warn( | ||
| `[mochi] profile id "${id}" is not in @mochi.js/profiles.KNOWN_PROFILE_IDS; ` + | ||
| "falling back to a synthesized placeholder. Pass a ProfileV1 object directly " + | ||
| "or use one of the catalog ids to silence this warning.", | ||
| ); | ||
| return synthesizePlaceholderProfile(id); | ||
| } | ||
| throw err; | ||
| } | ||
| } | ||
| /** | ||
| * Synthesize a generic placeholder `ProfileV1` from a profile id, used as | ||
| * a fallback when the catalog declares an id but no captured baseline | ||
| * ships in `@mochi.js/profiles` yet. The consistency engine still produces | ||
| * a real, relationally-locked Matrix from this skeleton — the id is what | ||
| * flows into `sha256(profile.id + seed)`. | ||
| * | ||
| * The major version pinned here MUST track the live Chromium-for-Testing | ||
| * pin (`packages/cli/src/browsers/manifest.ts:PINNED_FALLBACK_VERSION`) | ||
| * and the tip entry in | ||
| * `packages/consistency/src/rules/lookups/browser.ts:BROWSER_TIP_FULL_VERSION`. | ||
| * A drift between these surfaces ships a UA whose major doesn't match the | ||
| * installed binary — the canonical fingerprint-mismatch bug R-004 was | ||
| * meant to prevent. Bump all three together. | ||
| */ | ||
| function synthesizePlaceholderProfile(profile: ProfileId): ProfileV1 { | ||
@@ -567,3 +627,3 @@ return { | ||
| engine: "chromium", | ||
| browser: { name: "chrome", channel: "stable", minVersion: "131", maxVersion: "133" }, | ||
| browser: { name: "chrome", channel: "stable", minVersion: "148", maxVersion: "148" }, | ||
| os: { name: "linux", version: "22", arch: "x64" }, | ||
@@ -593,7 +653,9 @@ device: { | ||
| behavior: { hand: "right", tremor: 0.18, wpm: 60, scrollStyle: "smooth" }, | ||
| // Deprecated — kept for one release for migration; runtime no longer | ||
| // reads the field. Drops in 0.8. | ||
| // `wreqPreset` is required by the ProfileV1 schema for one release of | ||
| // back-compat (see `schemas/profile.schema.json`). The runtime no | ||
| // longer reads it — `Session.fetch` rides Chromium's network stack via | ||
| // CDP, so JA4 is real Chrome by definition. Drops in 0.8. | ||
| wreqPreset: "chrome_148_linux", | ||
| userAgent: | ||
| "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", | ||
| "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36", | ||
| uaCh: {}, | ||
@@ -600,0 +662,0 @@ entropyBudget: { fixed: [], perSeed: [] }, |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
513014
0.69%12529
0.49%5
25%+ Added
+ Added
Updated
Updated
Updated