@farmslot/adapter-web 0.10.5 → 0.10.7
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/CHANGELOG.md +8 -0
- package/dist/browser-resolver.d.cts +2 -1
- package/dist/chrome-args.d.cts +8 -0
- package/package.json +4 -4
- package/src/browser-resolver.cjs +12 -6
- package/src/chrome-args.cjs +24 -2
- package/src/launch-browser.cjs +3 -2
- package/src/web-dapp/launch.mjs +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,14 @@ All notable changes to `@farmslot/adapter-web` are tracked here.
|
|
|
6
6
|
|
|
7
7
|
- Active-development baseline; add user-facing changes here before release or package publication.
|
|
8
8
|
|
|
9
|
+
## 0.10.7 - 2026-10-15
|
|
10
|
+
|
|
11
|
+
- Disable macOS code-signing clones across isolated browser launches, merging existing disabled features; let profile cleanup locks retain live owners without a probe-age timeout.
|
|
12
|
+
|
|
13
|
+
## 0.10.6 - 2026-10-15
|
|
14
|
+
|
|
15
|
+
- Use the operator-wait runtime and protocol dependency set.
|
|
16
|
+
|
|
9
17
|
## 0.10.5 - 2026-10-15
|
|
10
18
|
|
|
11
19
|
- Consume the plain `ProjectRuntime` and `ActionCatalog` contracts.
|
|
@@ -113,10 +113,11 @@ export function resolveBrowser({ env, cft, probe, cachePath, chromeCandidates, e
|
|
|
113
113
|
extensionLoading: string;
|
|
114
114
|
schemaVersion: number;
|
|
115
115
|
}>;
|
|
116
|
-
export function withProbeLock(cachePath: any, key: any, fn: any, { waitMs, beforeClaim, readStartTime }?: {
|
|
116
|
+
export function withProbeLock(cachePath: any, key: any, fn: any, { waitMs, beforeClaim, readStartTime, keepLiveOwner }?: {
|
|
117
117
|
waitMs?: number | undefined;
|
|
118
118
|
beforeClaim?: null | undefined;
|
|
119
119
|
readStartTime?: typeof startTimeOf | undefined;
|
|
120
|
+
keepLiveOwner?: boolean | undefined;
|
|
120
121
|
}): Promise<any>;
|
|
121
122
|
export function readManagedChromePolicy({ platform, plists, exists, readKey, extensionName, }?: {
|
|
122
123
|
platform?: NodeJS.Platform | undefined;
|
package/dist/chrome-args.d.cts
CHANGED
|
@@ -6,6 +6,14 @@ export function clearDetachedLaunchUnproven(profile: any): void;
|
|
|
6
6
|
export function clearValidationPortQuarantine(port: any, profile: any, lease: any): void;
|
|
7
7
|
export function createRuntimeIdentityNonce(): string;
|
|
8
8
|
export function detachedLaunchUnprovenPath(profile: any): string;
|
|
9
|
+
/**
|
|
10
|
+
* Chrome's macOS clone manager runs after startup, regardless of spawn method.
|
|
11
|
+
* Merge feature switches: Chromium honors only the last --disable-features.
|
|
12
|
+
* @param {string[]} args
|
|
13
|
+
* @param {string} [platform]
|
|
14
|
+
* @returns {string[]}
|
|
15
|
+
*/
|
|
16
|
+
export function disableMacCodeSignClone(args: string[], platform?: string): string[];
|
|
9
17
|
export function hasDetachedLaunchUnproven(profile: any): boolean;
|
|
10
18
|
export function isolatedProfileArgs(platform?: NodeJS.Platform): string[];
|
|
11
19
|
export function markDetachedLaunchUnproven(profile: any): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farmslot/adapter-web",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.7",
|
|
4
4
|
"description": "Web platform adapter for Farmslot recipes: resolve and launch an isolated Chromium, own its CDP port, load unpacked extensions, and keep macOS focus.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"exports": {
|
|
@@ -83,9 +83,9 @@
|
|
|
83
83
|
"prepublishOnly": "node ../../scripts/quality/check-farmslot-package-readiness.mjs --publish --packages @farmslot/adapter-web"
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
|
-
"@farmslot/adapter-sdk": "0.15.
|
|
87
|
-
"@farmslot/protocol": "0.
|
|
88
|
-
"@farmslot/recipe-runner": "0.37.
|
|
86
|
+
"@farmslot/adapter-sdk": "0.15.1",
|
|
87
|
+
"@farmslot/protocol": "0.48.0",
|
|
88
|
+
"@farmslot/recipe-runner": "0.37.1",
|
|
89
89
|
"ws": "^8.18.0"
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
package/src/browser-resolver.cjs
CHANGED
|
@@ -38,6 +38,7 @@ const path = require('node:path');
|
|
|
38
38
|
const { createRequire } = require('node:module');
|
|
39
39
|
|
|
40
40
|
const cdp = require('./browser-cdp.cjs');
|
|
41
|
+
const { disableMacCodeSignClone } = require('./chrome-args.cjs');
|
|
41
42
|
|
|
42
43
|
const SCHEMA_VERSION = 2;
|
|
43
44
|
const LOAD_EXTENSION = 'load-extension';
|
|
@@ -445,14 +446,15 @@ const { START_TIME_SLACK_MS } = cdp;
|
|
|
445
446
|
function ticketFree(
|
|
446
447
|
dir,
|
|
447
448
|
number,
|
|
448
|
-
{ nowMs = Date.now(), budgetMs = 30000, readStartTime = startTimeOf } = {},
|
|
449
|
+
{ nowMs = Date.now(), budgetMs = 30000, readStartTime = startTimeOf, keepLiveOwner = false } = {},
|
|
449
450
|
) {
|
|
450
451
|
if (number === 0 || fs.existsSync(path.join(dir, `${number}.released`))) return true;
|
|
451
452
|
const ticket = readTicket(path.join(dir, String(number)));
|
|
452
453
|
if (!ticket) return true;
|
|
453
454
|
const { holder } = ticket;
|
|
454
455
|
if (holder && Number.isInteger(holder.pid) && Number.isFinite(holder.at)) {
|
|
455
|
-
if (!pidAlive(holder.pid) || nowMs - holder.at > PROBE_LOCK_STALE_MS)
|
|
456
|
+
if (!pidAlive(holder.pid) || (!keepLiveOwner && nowMs - holder.at > PROBE_LOCK_STALE_MS))
|
|
457
|
+
return true;
|
|
456
458
|
if (typeof holder.started !== 'string' || holder.pid === process.pid || budgetMs <= 0)
|
|
457
459
|
return false;
|
|
458
460
|
const started = readStartTime(holder.pid, budgetMs);
|
|
@@ -491,11 +493,13 @@ function newestTicket(dir) {
|
|
|
491
493
|
// the next one. The directory grows by two small files per probe; a crashed
|
|
492
494
|
// holder's unreleased ticket stays, judged free once its holder is gone.
|
|
493
495
|
// `beforeClaim` and `readStartTime` are test seams.
|
|
496
|
+
// keepLiveOwner is used by profile deletion: an old but still-live cleanup
|
|
497
|
+
// owner cannot be displaced while removing a large directory.
|
|
494
498
|
async function withProbeLock(
|
|
495
499
|
cachePath,
|
|
496
500
|
key,
|
|
497
501
|
fn,
|
|
498
|
-
{ waitMs = 150000, beforeClaim = null, readStartTime = startTimeOf } = {},
|
|
502
|
+
{ waitMs = 150000, beforeClaim = null, readStartTime = startTimeOf, keepLiveOwner = false } = {},
|
|
499
503
|
) {
|
|
500
504
|
const dir = `${cachePath}.${key.slice(0, 16)}.locks`;
|
|
501
505
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -505,7 +509,9 @@ async function withProbeLock(
|
|
|
505
509
|
let fd = null;
|
|
506
510
|
for (;;) {
|
|
507
511
|
const newest = newestTicket(dir);
|
|
508
|
-
if (
|
|
512
|
+
if (
|
|
513
|
+
ticketFree(dir, newest, { budgetMs: deadline - Date.now(), readStartTime, keepLiveOwner })
|
|
514
|
+
) {
|
|
509
515
|
if (beforeClaim) await beforeClaim(newest);
|
|
510
516
|
if (Date.now() > deadline) throw timedOut();
|
|
511
517
|
try {
|
|
@@ -675,7 +681,7 @@ async function probeLaunch(
|
|
|
675
681
|
// window. Like the launchers, it keeps a window behind the operator's apps
|
|
676
682
|
// rendering.
|
|
677
683
|
const backgroundWindow = display === HEADFUL;
|
|
678
|
-
const args = [
|
|
684
|
+
const args = disableMacCodeSignClone([
|
|
679
685
|
...(display === HEADLESS ? ['--headless=new'] : ['--window-size=480,360']),
|
|
680
686
|
`--user-data-dir=${profile}`,
|
|
681
687
|
'--no-first-run',
|
|
@@ -687,7 +693,7 @@ async function probeLaunch(
|
|
|
687
693
|
'--remote-debugging-address=127.0.0.1',
|
|
688
694
|
`--remote-debugging-port=${port}`,
|
|
689
695
|
backgroundWindow ? '--no-startup-window' : 'about:blank',
|
|
690
|
-
];
|
|
696
|
+
]);
|
|
691
697
|
if (method === LAUNCH_SERVICES) {
|
|
692
698
|
execFileSync('open', ['-g', '-n', '-a', appBundleFor(executable), '--args', ...args], {
|
|
693
699
|
stdio: 'ignore',
|
package/src/chrome-args.cjs
CHANGED
|
@@ -38,11 +38,32 @@ function isolatedProfileArgs(platform = process.platform) {
|
|
|
38
38
|
// Keep requestAnimationFrame, transitions, and input hit testing alive when the
|
|
39
39
|
// slot-owned browser is occluded by the operator's other windows.
|
|
40
40
|
function automationRuntimeArgs() {
|
|
41
|
-
return [
|
|
41
|
+
return disableMacCodeSignClone([
|
|
42
42
|
'--disable-background-timer-throttling',
|
|
43
43
|
'--disable-backgrounding-occluded-windows',
|
|
44
44
|
'--disable-renderer-backgrounding',
|
|
45
|
-
];
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Chrome's macOS clone manager runs after startup, regardless of spawn method.
|
|
50
|
+
* Merge feature switches: Chromium honors only the last --disable-features.
|
|
51
|
+
* @param {string[]} args
|
|
52
|
+
* @param {string} [platform]
|
|
53
|
+
* @returns {string[]}
|
|
54
|
+
*/
|
|
55
|
+
function disableMacCodeSignClone(args, platform = process.platform) {
|
|
56
|
+
if (platform !== 'darwin') return args;
|
|
57
|
+
const disabled = new Set(['MacAppCodeSignClone']);
|
|
58
|
+
const remaining = [];
|
|
59
|
+
for (const arg of args) {
|
|
60
|
+
if (arg.startsWith('--disable-features=')) {
|
|
61
|
+
for (const feature of arg.slice('--disable-features='.length).split(',')) {
|
|
62
|
+
if (feature) disabled.add(feature);
|
|
63
|
+
}
|
|
64
|
+
} else remaining.push(arg);
|
|
65
|
+
}
|
|
66
|
+
return [`--disable-features=${[...disabled].join(',')}`, ...remaining];
|
|
46
67
|
}
|
|
47
68
|
|
|
48
69
|
function createRuntimeIdentityNonce() {
|
|
@@ -353,6 +374,7 @@ module.exports = {
|
|
|
353
374
|
clearValidationPortQuarantine,
|
|
354
375
|
createRuntimeIdentityNonce,
|
|
355
376
|
detachedLaunchUnprovenPath,
|
|
377
|
+
disableMacCodeSignClone,
|
|
356
378
|
hasDetachedLaunchUnproven,
|
|
357
379
|
isolatedProfileArgs,
|
|
358
380
|
markDetachedLaunchUnproven,
|
package/src/launch-browser.cjs
CHANGED
|
@@ -34,6 +34,7 @@ const {
|
|
|
34
34
|
clearValidationPortQuarantine,
|
|
35
35
|
createRuntimeIdentityNonce,
|
|
36
36
|
detachedLaunchUnprovenPath,
|
|
37
|
+
disableMacCodeSignClone,
|
|
37
38
|
hasDetachedLaunchUnproven,
|
|
38
39
|
isolatedProfileArgs,
|
|
39
40
|
markDetachedLaunchUnproven,
|
|
@@ -225,7 +226,7 @@ function runLaunch(opts, acquireLock) {
|
|
|
225
226
|
let owner = null;
|
|
226
227
|
const runtimeNonce = createRuntimeIdentityNonce();
|
|
227
228
|
const startedAt = Date.now();
|
|
228
|
-
const chromeArgs = [
|
|
229
|
+
const chromeArgs = disableMacCodeSignClone([
|
|
229
230
|
`--user-data-dir=${profile}`,
|
|
230
231
|
// Debug-port trio (address + port + scoped allow-origins) from the shared module,
|
|
231
232
|
// so the DevTools origin allow-list can never drift away from the port.
|
|
@@ -241,7 +242,7 @@ function runLaunch(opts, acquireLock) {
|
|
|
241
242
|
'--disable-extensions-content-verification',
|
|
242
243
|
'--disable-features=ExtensionContentVerification,DisableLoadExtensionCommandLineSwitch',
|
|
243
244
|
...extensionLaunchArgs(extensionDir, browser.extensionLoading),
|
|
244
|
-
];
|
|
245
|
+
]);
|
|
245
246
|
// Initial tab: the caller's URL when provided, else the extension home. The
|
|
246
247
|
// extension home avoids leaving disposable chrome://newtab/ or extensions
|
|
247
248
|
// manager tabs in the operator's slot browser. On macOS the browser starts
|
package/src/web-dapp/launch.mjs
CHANGED
|
@@ -45,6 +45,8 @@ import os from 'node:os';
|
|
|
45
45
|
import path from 'node:path';
|
|
46
46
|
import { fileURLToPath } from 'node:url';
|
|
47
47
|
|
|
48
|
+
import { disableMacCodeSignClone } from '../chrome-args.cjs';
|
|
49
|
+
|
|
48
50
|
import { hostResolverRules } from './lib/blocked-hosts.mjs';
|
|
49
51
|
import { browserWebSocketUrl, CdpClient } from './lib/cdp-client.mjs';
|
|
50
52
|
import {
|
|
@@ -596,6 +598,7 @@ export async function launchWebDappBrowser(
|
|
|
596
598
|
// when its first window appears. Windows are created over CDP instead, in
|
|
597
599
|
// the background.
|
|
598
600
|
browserArgs.push(launchMethod === LAUNCH_SERVICES ? '--no-startup-window' : 'about:blank');
|
|
601
|
+
const launchArgs = disableMacCodeSignClone(browserArgs);
|
|
599
602
|
|
|
600
603
|
started = true;
|
|
601
604
|
// Where this browser's console begins in the slot capture, for
|
|
@@ -631,14 +634,14 @@ export async function launchWebDappBrowser(
|
|
|
631
634
|
}
|
|
632
635
|
const browserPid =
|
|
633
636
|
launchMethod === LAUNCH_SERVICES
|
|
634
|
-
? await launchInBackground(application,
|
|
637
|
+
? await launchInBackground(application, launchArgs, {
|
|
635
638
|
runtime,
|
|
636
639
|
cdpPort,
|
|
637
640
|
logFile: path.join(runtime, 'browser.log'),
|
|
638
641
|
pidFile: path.join(runtime, 'browser.pid'),
|
|
639
642
|
timeoutMs: timeouts.browserStartMs,
|
|
640
643
|
})
|
|
641
|
-
: await spawnDetached(browser.bin,
|
|
644
|
+
: await spawnDetached(browser.bin, launchArgs, {
|
|
642
645
|
logFile: path.join(runtime, 'browser.log'),
|
|
643
646
|
pidFile: path.join(runtime, 'browser.pid'),
|
|
644
647
|
env,
|