@sanohiro/casty 1.1.1 → 1.1.2
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/lib/browser.js +64 -3
- package/lib/config.js +1 -0
- package/package.json +1 -1
package/lib/browser.js
CHANGED
|
@@ -12,9 +12,11 @@ const DEFAULT_CHROME_VERSION = '146.0.7680.80';
|
|
|
12
12
|
// Platform-specific identity (must match real OS to avoid TLS/TCP fingerprint mismatch)
|
|
13
13
|
const PLATFORM = process.platform === 'darwin'
|
|
14
14
|
? { ua: 'Macintosh; Intel Mac OS X 10_15_7', nav: 'MacIntel',
|
|
15
|
+
uaPlatform: 'macOS', uaPlatformVersion: '15.0.0',
|
|
15
16
|
glVendor: 'Google Inc. (Apple)', glRenderer: 'ANGLE (Apple, Apple M1, OpenGL 4.1)' }
|
|
16
17
|
: { ua: `X11; Linux ${process.arch === 'arm64' ? 'aarch64' : 'x86_64'}`,
|
|
17
18
|
nav: `Linux ${process.arch === 'arm64' ? 'aarch64' : 'x86_64'}`,
|
|
19
|
+
uaPlatform: 'Linux', uaPlatformVersion: '',
|
|
18
20
|
glVendor: 'Google Inc. (Intel)', glRenderer: 'ANGLE (Intel, Mesa Intel UHD Graphics, OpenGL 4.5)' };
|
|
19
21
|
|
|
20
22
|
const buildUserAgent = (v) =>
|
|
@@ -31,7 +33,7 @@ const CAPTURE_STUCK_RESET = 5000; // Reset stuck capturing flag (ms)
|
|
|
31
33
|
|
|
32
34
|
// Build stealth script with locale-dependent language settings
|
|
33
35
|
// lang: primary language (e.g. "ja", "en-US")
|
|
34
|
-
function buildStealthScript(lang) {
|
|
36
|
+
function buildStealthScript(lang, { fakeMedia = false } = {}) {
|
|
35
37
|
// Build Accept-Language style list: primary, then en-US/en fallbacks
|
|
36
38
|
const languages = [lang];
|
|
37
39
|
if (lang !== 'en-US' && lang !== 'en') {
|
|
@@ -91,6 +93,9 @@ navigator.permissions.query = (params) => {
|
|
|
91
93
|
if (params.name === 'notifications') {
|
|
92
94
|
return Promise.resolve({ state: Notification.permission });
|
|
93
95
|
}
|
|
96
|
+
${fakeMedia ? ` if (params.name === 'camera' || params.name === 'microphone') {
|
|
97
|
+
return Promise.resolve({ state: 'granted' });
|
|
98
|
+
}` : ''}
|
|
94
99
|
return origQuery(params);
|
|
95
100
|
};
|
|
96
101
|
|
|
@@ -109,6 +114,43 @@ if (!navigator.connection) {
|
|
|
109
114
|
get: () => ({ effectiveType: '4g', rtt: 50, downlink: 10, saveData: false }),
|
|
110
115
|
});
|
|
111
116
|
}
|
|
117
|
+
|
|
118
|
+
${fakeMedia ? `
|
|
119
|
+
// getUserMedia emulation (headless-shell lacks media capture)
|
|
120
|
+
if (navigator.mediaDevices) {
|
|
121
|
+
const fakeStream = (constraints) => {
|
|
122
|
+
const tracks = [];
|
|
123
|
+
if (constraints.video) {
|
|
124
|
+
const w = constraints.video.width?.ideal || constraints.video.width?.max || 640;
|
|
125
|
+
const h = constraints.video.height?.ideal || constraints.video.height?.max || 480;
|
|
126
|
+
const canvas = document.createElement('canvas');
|
|
127
|
+
canvas.width = w;
|
|
128
|
+
canvas.height = h;
|
|
129
|
+
const ctx = canvas.getContext('2d');
|
|
130
|
+
const draw = () => { ctx.fillStyle = '#000'; ctx.fillRect(0, 0, w, h); requestAnimationFrame(draw); };
|
|
131
|
+
draw();
|
|
132
|
+
const stream = canvas.captureStream(30);
|
|
133
|
+
tracks.push(...stream.getVideoTracks());
|
|
134
|
+
}
|
|
135
|
+
if (constraints.audio) {
|
|
136
|
+
const actx = new AudioContext();
|
|
137
|
+
const gain = actx.createGain();
|
|
138
|
+
gain.gain.value = 0;
|
|
139
|
+
gain.connect(actx.destination);
|
|
140
|
+
const dest = actx.createMediaStreamDestination();
|
|
141
|
+
gain.connect(dest);
|
|
142
|
+
tracks.push(...dest.stream.getAudioTracks());
|
|
143
|
+
}
|
|
144
|
+
return Promise.resolve(new MediaStream(tracks));
|
|
145
|
+
};
|
|
146
|
+
navigator.mediaDevices.getUserMedia = fakeStream;
|
|
147
|
+
navigator.mediaDevices.enumerateDevices = () => Promise.resolve([
|
|
148
|
+
{ deviceId: 'default', kind: 'audioinput', label: 'Default Microphone', groupId: 'default' },
|
|
149
|
+
{ deviceId: 'default', kind: 'videoinput', label: 'Default Camera', groupId: 'default' },
|
|
150
|
+
{ deviceId: 'default', kind: 'audiooutput', label: 'Default Speaker', groupId: 'default' },
|
|
151
|
+
]);
|
|
152
|
+
}
|
|
153
|
+
` : ''}
|
|
112
154
|
`;
|
|
113
155
|
}
|
|
114
156
|
|
|
@@ -143,7 +185,7 @@ export async function setupPage({ port, wsUrl }, { width, height, zoom = 1 } = {
|
|
|
143
185
|
|
|
144
186
|
// Detect Chrome version from CDP endpoint
|
|
145
187
|
const versionInfo = await fetchJson(`${baseUrl}/json/version`);
|
|
146
|
-
const chromeVersion = versionInfo?.Browser?.replace(
|
|
188
|
+
const chromeVersion = versionInfo?.Browser?.replace(/^.*\//, '') || DEFAULT_CHROME_VERSION;
|
|
147
189
|
const userAgent = buildUserAgent(chromeVersion);
|
|
148
190
|
|
|
149
191
|
// Try /json/new (single HTTP request, fastest)
|
|
@@ -178,8 +220,27 @@ export async function setupPage({ port, wsUrl }, { width, height, zoom = 1 } = {
|
|
|
178
220
|
client.send('Network.setUserAgentOverride', {
|
|
179
221
|
userAgent: userAgent, platform: PLATFORM.nav,
|
|
180
222
|
acceptLanguage: buildAcceptLanguage(lang),
|
|
223
|
+
userAgentMetadata: {
|
|
224
|
+
brands: [
|
|
225
|
+
{ brand: 'Chromium', version: chromeVersion.split('.')[0] },
|
|
226
|
+
{ brand: 'Google Chrome', version: chromeVersion.split('.')[0] },
|
|
227
|
+
{ brand: 'Not-A.Brand', version: '99' },
|
|
228
|
+
],
|
|
229
|
+
fullVersionList: [
|
|
230
|
+
{ brand: 'Chromium', version: chromeVersion },
|
|
231
|
+
{ brand: 'Google Chrome', version: chromeVersion },
|
|
232
|
+
{ brand: 'Not-A.Brand', version: '99.0.0.0' },
|
|
233
|
+
],
|
|
234
|
+
platform: PLATFORM.uaPlatform,
|
|
235
|
+
platformVersion: PLATFORM.uaPlatformVersion,
|
|
236
|
+
architecture: process.arch === 'arm64' ? 'arm' : 'x86',
|
|
237
|
+
bitness: '64',
|
|
238
|
+
model: '',
|
|
239
|
+
mobile: false,
|
|
240
|
+
wow64: false,
|
|
241
|
+
},
|
|
181
242
|
}),
|
|
182
|
-
client.send('Page.addScriptToEvaluateOnNewDocument', { source: buildStealthScript(lang) }),
|
|
243
|
+
client.send('Page.addScriptToEvaluateOnNewDocument', { source: buildStealthScript(lang, { fakeMedia: config.fakeMedia }) }),
|
|
183
244
|
client.send('Browser.setDownloadBehavior', {
|
|
184
245
|
behavior: 'allowAndName', downloadPath: join(homedir(), 'Downloads'),
|
|
185
246
|
eventsEnabled: true,
|
package/lib/config.js
CHANGED
|
@@ -32,6 +32,7 @@ const DEFAULTS = {
|
|
|
32
32
|
searchUrl: 'https://www.google.com/search?q=',
|
|
33
33
|
transport: 'auto', // 'auto' | 'file' | 'inline'
|
|
34
34
|
format: 'auto', // 'auto' | 'png' | 'jpeg'
|
|
35
|
+
fakeMedia: false, // Emulate camera/mic for WebRTC (headless-shell lacks getUserMedia)
|
|
35
36
|
language: detectLocale(), // System locale (override: "en-US", "ja", etc.)
|
|
36
37
|
};
|
|
37
38
|
|