@xcanwin/manyoyo 5.1.8 → 5.1.10

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.
@@ -38,6 +38,10 @@
38
38
  "vncPasswordEnvKey": "VNC_PASSWORD",
39
39
  // playwright ext-download 的 CRX prodversion 参数
40
40
  "extensionProdversion": "132.0.0.0",
41
+ // 注入 navigator.platform(默认与内置 UA 对齐为 MacIntel)
42
+ "navigatorPlatform": "MacIntel",
43
+ // 是否禁用 WebRTC(默认 false)
44
+ "disableWebRTC": false,
41
45
  "ports": {
42
46
  "contHeadless": 8931,
43
47
  "contHeaded": 8932,
@@ -65,6 +65,21 @@ const DEFAULT_FINGERPRINT_PROFILE = {
65
65
  width: 1366,
66
66
  height: 768
67
67
  };
68
+ const DISABLE_WEBRTC_LAUNCH_ARGS = ['--disable-webrtc'];
69
+
70
+ function platformFromUserAgent(userAgent) {
71
+ const ua = String(userAgent || '').toLowerCase();
72
+ if (ua.includes('macintosh') || ua.includes('mac os x')) {
73
+ return 'MacIntel';
74
+ }
75
+ if (ua.includes('windows')) {
76
+ return 'Win32';
77
+ }
78
+ if (ua.includes('android') || ua.includes('linux')) {
79
+ return 'Linux x86_64';
80
+ }
81
+ return 'MacIntel';
82
+ }
68
83
 
69
84
  function sleep(ms) {
70
85
  return new Promise(resolve => setTimeout(resolve, ms));
@@ -94,6 +109,22 @@ function asStringArray(value, fallback) {
94
109
  .filter(Boolean);
95
110
  }
96
111
 
112
+ function asBoolean(value, fallback = false) {
113
+ if (typeof value === 'boolean') {
114
+ return value;
115
+ }
116
+ if (typeof value === 'string') {
117
+ const normalized = value.trim().toLowerCase();
118
+ if (normalized === 'true') {
119
+ return true;
120
+ }
121
+ if (normalized === 'false') {
122
+ return false;
123
+ }
124
+ }
125
+ return fallback;
126
+ }
127
+
97
128
  function isHostPermission(value) {
98
129
  if (value === '<all_urls>') {
99
130
  return true;
@@ -236,12 +267,14 @@ class PlaywrightPlugin {
236
267
  hostListen: '127.0.0.1',
237
268
  mcpDefaultHost: 'host.docker.internal',
238
269
  dockerTag: process.env.PLAYWRIGHT_MCP_DOCKER_TAG || 'latest',
239
- containerRuntime: 'podman',
270
+ containerRuntime: '',
240
271
  vncPasswordEnvKey: 'VNC_PASSWORD',
241
272
  headedImage: 'localhost/xcanwin/manyoyo-playwright-headed',
242
273
  configDir: path.join(pluginRootDir, 'config'),
243
274
  runDir: path.join(pluginRootDir, 'run'),
244
275
  extensionProdversion: '132.0.0.0',
276
+ navigatorPlatform: platformFromUserAgent(DEFAULT_FINGERPRINT_PROFILE.userAgent),
277
+ disableWebRTC: false,
245
278
  composeDir: path.join(__dirname, 'playwright-assets'),
246
279
  ports: {
247
280
  contHeadless: 8931,
@@ -272,6 +305,9 @@ class PlaywrightPlugin {
272
305
  this.runConfig.enabledScenes,
273
306
  asStringArray(this.globalConfig.enabledScenes, [...defaultConfig.enabledScenes])
274
307
  );
308
+ merged.containerRuntime = this.resolveContainerRuntime(merged.containerRuntime);
309
+ merged.navigatorPlatform = String(merged.navigatorPlatform || defaultConfig.navigatorPlatform).trim() || defaultConfig.navigatorPlatform;
310
+ merged.disableWebRTC = asBoolean(merged.disableWebRTC, defaultConfig.disableWebRTC);
275
311
 
276
312
  if (merged.enabledScenes.length === 0) {
277
313
  throw new Error('playwright.enabledScenes 不能为空');
@@ -285,6 +321,22 @@ class PlaywrightPlugin {
285
321
  return merged;
286
322
  }
287
323
 
324
+ resolveContainerRuntime(configuredRuntime) {
325
+ const configured = String(configuredRuntime || '').trim().toLowerCase();
326
+ if (configured) {
327
+ return configured;
328
+ }
329
+
330
+ const candidates = ['docker', 'podman'];
331
+ for (const cmd of candidates) {
332
+ if (this.ensureCommandAvailable(cmd)) {
333
+ return cmd;
334
+ }
335
+ }
336
+
337
+ return 'docker';
338
+ }
339
+
288
340
  writeStdout(line = '') {
289
341
  this.stdout.write(`${line}\n`);
290
342
  }
@@ -360,6 +412,72 @@ class PlaywrightPlugin {
360
412
  return !fs.existsSync(this.sceneConfigPath(sceneName));
361
413
  }
362
414
 
415
+ sceneInitScriptPath(sceneName) {
416
+ const configFile = path.basename(this.sceneConfigPath(sceneName), '.json');
417
+ return path.join(this.config.configDir, `${configFile}.init.js`);
418
+ }
419
+
420
+ legacySceneInitScriptPath(sceneName) {
421
+ return path.join(this.config.configDir, `${sceneName}.init.js`);
422
+ }
423
+
424
+ buildInitScriptContent() {
425
+ const lines = [
426
+ "'use strict';",
427
+ '(function () {',
428
+ ` const platformValue = ${JSON.stringify(this.config.navigatorPlatform)};`,
429
+ ' try {',
430
+ ' const navProto = Object.getPrototypeOf(navigator);',
431
+ " Object.defineProperty(navProto, 'platform', {",
432
+ ' configurable: true,',
433
+ ' get: () => platformValue',
434
+ ' });',
435
+ ' } catch (_) {}'
436
+ ];
437
+
438
+ if (this.config.disableWebRTC) {
439
+ lines.push(
440
+ ' try {',
441
+ ' const scope = globalThis;',
442
+ " const blocked = ['RTCPeerConnection', 'webkitRTCPeerConnection', 'RTCIceCandidate', 'RTCRtpSender', 'RTCRtpReceiver', 'RTCRtpTransceiver', 'RTCDataChannel'];",
443
+ ' for (const name of blocked) {',
444
+ " Object.defineProperty(scope, name, { configurable: true, writable: true, value: undefined });",
445
+ ' }',
446
+ ' if (navigator.mediaDevices) {',
447
+ ' const errorFactory = () => {',
448
+ ' try {',
449
+ " return new DOMException('WebRTC is disabled', 'NotAllowedError');",
450
+ ' } catch (_) {',
451
+ " const error = new Error('WebRTC is disabled');",
452
+ " error.name = 'NotAllowedError';",
453
+ ' return error;',
454
+ ' }',
455
+ ' };',
456
+ " Object.defineProperty(navigator.mediaDevices, 'getUserMedia', {",
457
+ ' configurable: true,',
458
+ ' writable: true,',
459
+ ' value: async () => { throw errorFactory(); }',
460
+ ' });',
461
+ ' }',
462
+ ' } catch (_) {}'
463
+ );
464
+ }
465
+
466
+ lines.push('})();', '');
467
+ return lines.join('\n');
468
+ }
469
+
470
+ ensureSceneInitScript(sceneName) {
471
+ const filePath = this.sceneInitScriptPath(sceneName);
472
+ const content = this.buildInitScriptContent();
473
+ fs.writeFileSync(filePath, content, 'utf8');
474
+ const legacyFilePath = this.legacySceneInitScriptPath(sceneName);
475
+ if (legacyFilePath !== filePath) {
476
+ fs.rmSync(legacyFilePath, { force: true });
477
+ }
478
+ return filePath;
479
+ }
480
+
363
481
  defaultBrowserName(sceneName) {
364
482
  const cfg = this.buildSceneConfig(sceneName);
365
483
  const browserName = cfg && cfg.browser && cfg.browser.browserName;
@@ -536,6 +654,7 @@ class PlaywrightPlugin {
536
654
  const def = SCENE_DEFS[sceneName];
537
655
  const port = this.scenePort(sceneName);
538
656
  const extensionPaths = asStringArray(options.extensionPaths, []);
657
+ const initScript = asStringArray(options.initScript, []);
539
658
  const baseLaunchArgs = [
540
659
  `--user-agent=${DEFAULT_FINGERPRINT_PROFILE.userAgent}`,
541
660
  `--lang=${DEFAULT_FINGERPRINT_PROFILE.locale}`,
@@ -552,6 +671,9 @@ class PlaywrightPlugin {
552
671
  if (extensionPaths.length > 0) {
553
672
  launchOptions.args.push(...this.buildExtensionLaunchArgs(extensionPaths));
554
673
  }
674
+ if (this.config.disableWebRTC) {
675
+ launchOptions.args.push(...DISABLE_WEBRTC_LAUNCH_ARGS);
676
+ }
555
677
 
556
678
  return {
557
679
  server: {
@@ -567,6 +689,7 @@ class PlaywrightPlugin {
567
689
  browser: {
568
690
  chromiumSandbox: true,
569
691
  browserName: 'chromium',
692
+ initScript,
570
693
  launchOptions,
571
694
  contextOptions: {
572
695
  userAgent: DEFAULT_FINGERPRINT_PROFILE.userAgent,
@@ -590,7 +713,13 @@ class PlaywrightPlugin {
590
713
 
591
714
  ensureSceneConfig(sceneName, options = {}) {
592
715
  fs.mkdirSync(this.config.configDir, { recursive: true });
593
- const payload = this.buildSceneConfig(sceneName, options);
716
+ const initScriptPath = this.ensureSceneInitScript(sceneName);
717
+ const configuredInitScript = asStringArray(options.initScript, []);
718
+ const initScript = configuredInitScript.length > 0 ? configuredInitScript : [initScriptPath];
719
+ const payload = this.buildSceneConfig(sceneName, {
720
+ ...options,
721
+ initScript
722
+ });
594
723
  const filePath = this.sceneConfigPath(sceneName);
595
724
  fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 4)}\n`, 'utf8');
596
725
  return filePath;
@@ -700,21 +829,31 @@ class PlaywrightPlugin {
700
829
  }
701
830
 
702
831
  const incomingExtensionPaths = asStringArray(options.extensionPaths, []);
703
- let configOptions = { ...options, extensionPaths: incomingExtensionPaths };
832
+ const hostInitScriptPath = this.sceneInitScriptPath(sceneName);
833
+ const containerInitScriptPath = path.posix.join('/app/config', path.basename(hostInitScriptPath));
834
+ let configOptions = {
835
+ ...options,
836
+ extensionPaths: incomingExtensionPaths,
837
+ initScript: [containerInitScriptPath]
838
+ };
704
839
  const composeFiles = [this.containerComposePath(sceneName)];
840
+ const volumeMounts = [`${hostInitScriptPath}:${containerInitScriptPath}:ro`];
705
841
 
706
842
  if (incomingExtensionPaths.length > 0) {
707
843
  const mapped = this.buildContainerExtensionMounts(incomingExtensionPaths);
708
- const overridePath = this.ensureContainerComposeOverride(sceneName, mapped.volumeMounts);
709
- if (overridePath) {
710
- composeFiles.push(overridePath);
711
- }
712
- configOptions = { ...options, extensionPaths: mapped.containerPaths };
713
- } else {
714
- this.ensureContainerComposeOverride(sceneName, []);
844
+ volumeMounts.push(...mapped.volumeMounts);
845
+ configOptions = {
846
+ ...options,
847
+ extensionPaths: mapped.containerPaths,
848
+ initScript: [containerInitScriptPath]
849
+ };
715
850
  }
716
-
717
851
  const cfgPath = this.ensureSceneConfig(sceneName, configOptions);
852
+ const overridePath = this.ensureContainerComposeOverride(sceneName, volumeMounts);
853
+ if (overridePath) {
854
+ composeFiles.push(overridePath);
855
+ }
856
+
718
857
  const env = this.containerEnv(sceneName, cfgPath, { requireVncPassword: true });
719
858
  const def = SCENE_DEFS[sceneName];
720
859
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "5.1.8",
3
+ "version": "5.1.10",
4
4
  "imageVersion": "1.8.1-common",
5
5
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",
6
6
  "keywords": [