@xcanwin/manyoyo 5.1.9 → 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;
@@ -242,6 +273,8 @@ class PlaywrightPlugin {
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,
@@ -273,6 +306,8 @@ class PlaywrightPlugin {
273
306
  asStringArray(this.globalConfig.enabledScenes, [...defaultConfig.enabledScenes])
274
307
  );
275
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);
276
311
 
277
312
  if (merged.enabledScenes.length === 0) {
278
313
  throw new Error('playwright.enabledScenes 不能为空');
@@ -377,6 +412,72 @@ class PlaywrightPlugin {
377
412
  return !fs.existsSync(this.sceneConfigPath(sceneName));
378
413
  }
379
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
+
380
481
  defaultBrowserName(sceneName) {
381
482
  const cfg = this.buildSceneConfig(sceneName);
382
483
  const browserName = cfg && cfg.browser && cfg.browser.browserName;
@@ -553,6 +654,7 @@ class PlaywrightPlugin {
553
654
  const def = SCENE_DEFS[sceneName];
554
655
  const port = this.scenePort(sceneName);
555
656
  const extensionPaths = asStringArray(options.extensionPaths, []);
657
+ const initScript = asStringArray(options.initScript, []);
556
658
  const baseLaunchArgs = [
557
659
  `--user-agent=${DEFAULT_FINGERPRINT_PROFILE.userAgent}`,
558
660
  `--lang=${DEFAULT_FINGERPRINT_PROFILE.locale}`,
@@ -569,6 +671,9 @@ class PlaywrightPlugin {
569
671
  if (extensionPaths.length > 0) {
570
672
  launchOptions.args.push(...this.buildExtensionLaunchArgs(extensionPaths));
571
673
  }
674
+ if (this.config.disableWebRTC) {
675
+ launchOptions.args.push(...DISABLE_WEBRTC_LAUNCH_ARGS);
676
+ }
572
677
 
573
678
  return {
574
679
  server: {
@@ -584,6 +689,7 @@ class PlaywrightPlugin {
584
689
  browser: {
585
690
  chromiumSandbox: true,
586
691
  browserName: 'chromium',
692
+ initScript,
587
693
  launchOptions,
588
694
  contextOptions: {
589
695
  userAgent: DEFAULT_FINGERPRINT_PROFILE.userAgent,
@@ -607,7 +713,13 @@ class PlaywrightPlugin {
607
713
 
608
714
  ensureSceneConfig(sceneName, options = {}) {
609
715
  fs.mkdirSync(this.config.configDir, { recursive: true });
610
- 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
+ });
611
723
  const filePath = this.sceneConfigPath(sceneName);
612
724
  fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 4)}\n`, 'utf8');
613
725
  return filePath;
@@ -717,21 +829,31 @@ class PlaywrightPlugin {
717
829
  }
718
830
 
719
831
  const incomingExtensionPaths = asStringArray(options.extensionPaths, []);
720
- 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
+ };
721
839
  const composeFiles = [this.containerComposePath(sceneName)];
840
+ const volumeMounts = [`${hostInitScriptPath}:${containerInitScriptPath}:ro`];
722
841
 
723
842
  if (incomingExtensionPaths.length > 0) {
724
843
  const mapped = this.buildContainerExtensionMounts(incomingExtensionPaths);
725
- const overridePath = this.ensureContainerComposeOverride(sceneName, mapped.volumeMounts);
726
- if (overridePath) {
727
- composeFiles.push(overridePath);
728
- }
729
- configOptions = { ...options, extensionPaths: mapped.containerPaths };
730
- } else {
731
- this.ensureContainerComposeOverride(sceneName, []);
844
+ volumeMounts.push(...mapped.volumeMounts);
845
+ configOptions = {
846
+ ...options,
847
+ extensionPaths: mapped.containerPaths,
848
+ initScript: [containerInitScriptPath]
849
+ };
732
850
  }
733
-
734
851
  const cfgPath = this.ensureSceneConfig(sceneName, configOptions);
852
+ const overridePath = this.ensureContainerComposeOverride(sceneName, volumeMounts);
853
+ if (overridePath) {
854
+ composeFiles.push(overridePath);
855
+ }
856
+
735
857
  const env = this.containerEnv(sceneName, cfgPath, { requireVncPassword: true });
736
858
  const def = SCENE_DEFS[sceneName];
737
859
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "5.1.9",
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": [