playwright-with-fingerprints
Version:
A plugin that improves the stealth of the playwright library using fingerprints
103 lines (88 loc) • 3.55 kB
JavaScript
const defaultLauncher = require('./loader').load();
const { FingerprintPlugin } = require('browser-with-fingerprints');
const { onClose, bindHooks, getViewport, setViewport } = require('./utils');
const IGNORED_ARGUMENTS = ['--disable-extensions'];
const UNSUPPORTED_OPTIONS = ['proxy', 'channel', 'firefoxUserPrefs'];
const LAUNCH_FALLBACK_WARNING = [
'The original "launch" method is temporarily unsupported.',
'Under the hood it will use the "launchPersistentContext" method.',
'Therefore, it is recommended to use the second one directly instead.',
].join('\n');
const Plugin = class PlaywrightFingerprintPlugin extends FingerprintPlugin {
async launch(options = {}) {
this.#validateOptions(options);
console.warn(LAUNCH_FALLBACK_WARNING);
return await this.launchPersistentContext('', options);
}
async launchPersistentContext(userDataDir, options = {}) {
this.#validateOptions(options);
const { ignoreDefaultArgs } = options;
const method = 'launchPersistentContext';
if (!this.launcher[method]) {
throw new Error(`The provided launcher doesn't support the "${method}" method`);
}
return await super.launch({
...options,
userDataDir,
viewport: null,
launcher: {
launch: (options = {}) => {
const [userDataDirArg] = options.args.splice(
options.args.findIndex((arg) => arg.startsWith('--user-data-dir')),
1
);
return this.launcher[method](userDataDirArg.split('=')[1], options);
},
},
ignoreDefaultArgs: Array.isArray(ignoreDefaultArgs)
? ignoreDefaultArgs.concat(IGNORED_ARGUMENTS)
: ignoreDefaultArgs || IGNORED_ARGUMENTS,
});
}
/**
* Configures the browser, including viewport size, hook and event binding.
*
* @param {import('playwright').Browser} browser - The target browser instance.
* @param {{width: number, height: number}} bounds - The size of the viewport.
* @param {Promise<void>} sync - Method for syncing browser settings.
* @param {(target: any) => void} cleanup - The cleanup function.
*
* @internal
*/
async configure(cleanup, browser, bounds, sync) {
onClose(browser, () => cleanup(browser));
// Resize pages only if size is set.
if (bounds.width && bounds.height) {
const resize = async (page) => {
const { width, height } = await getViewport(page);
if (width !== bounds.width || height !== bounds.height) {
await sync(() => setViewport(page, bounds));
}
};
bindHooks(browser, { onPageCreated: resize });
// Resize on startup only if there are open pages.
if (browser.pages) {
const [page] = await browser.pages();
if (page) await resize(page);
}
}
}
/**
* Check the options used to launch a browser for compatibility with plugin.
* If any of the specified options are incompatible, an error will be thrown.
*
* @param options - Set of configurable options to set on the browser.
*
* @private
*/
#validateOptions(options = {}) {
for (const option of UNSUPPORTED_OPTIONS) {
if (option in options) {
throw new Error(`The built-in "${option}" option is not supported in this plugin.`);
}
}
}
};
exports.plugin = new Plugin(defaultLauncher);
exports.createPlugin = Plugin.create.bind(Plugin);
exports.UNSUPPORTED_OPTIONS = UNSUPPORTED_OPTIONS;