@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.
- package/config.example.json +4 -0
- package/lib/plugin/playwright.js +150 -11
- package/package.json +1 -1
package/config.example.json
CHANGED
|
@@ -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,
|
package/lib/plugin/playwright.js
CHANGED
|
@@ -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: '
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
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
|
|