Skip to main content
Glama
by Ritesh-sudo
index.js17.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.addExtra = exports.PuppeteerExtra = void 0; const debug_1 = __importDefault(require("debug")); const debug = (0, debug_1.default)('puppeteer-extra'); const deepmerge_1 = __importDefault(require("deepmerge")); /** * Modular plugin framework to teach `puppeteer` new tricks. * * This module acts as a drop-in replacement for `puppeteer`. * * Allows PuppeteerExtraPlugin's to register themselves and * to extend puppeteer with additional functionality. * * @class PuppeteerExtra * @implements {VanillaPuppeteer} * * @example * const puppeteer = require('puppeteer-extra') * puppeteer.use(require('puppeteer-extra-plugin-anonymize-ua')()) * puppeteer.use(require('puppeteer-extra-plugin-font-size')({defaultFontSize: 18})) * * ;(async () => { * const browser = await puppeteer.launch({headless: false}) * const page = await browser.newPage() * await page.goto('http://example.com', {waitUntil: 'domcontentloaded'}) * await browser.close() * })() */ class PuppeteerExtra { constructor(_pptr, _requireError) { this._pptr = _pptr; this._requireError = _requireError; this._plugins = []; } /** * The **main interface** to register `puppeteer-extra` plugins. * * @example * puppeteer.use(plugin1).use(plugin2) * * @see [PuppeteerExtraPlugin] * * @return The same `PuppeteerExtra` instance (for optional chaining) */ use(plugin) { if (typeof plugin !== 'object' || !plugin._isPuppeteerExtraPlugin) { console.error(`Warning: Plugin is not derived from PuppeteerExtraPlugin, ignoring.`, plugin); return this; } if (!plugin.name) { console.error(`Warning: Plugin with no name registering, ignoring.`, plugin); return this; } if (plugin.requirements.has('dataFromPlugins')) { plugin.getDataFromPlugins = this.getPluginData.bind(this); } plugin._register(Object.getPrototypeOf(plugin)); this._plugins.push(plugin); debug('plugin registered', plugin.name); return this; } /** * To stay backwards compatible with puppeteer's (and our) default export after adding `addExtra` * we need to defer the check if we have a puppeteer instance to work with. * Otherwise we would throw even if the user intends to use their non-standard puppeteer implementation. * * @private */ get pptr() { if (this._pptr) { return this._pptr; } // Whoopsie console.warn(` Puppeteer is missing. :-) Note: puppeteer is a peer dependency of puppeteer-extra, which means you can install your own preferred version. - To get the latest stable version run: 'yarn add puppeteer' or 'npm i puppeteer' Alternatively: - To get puppeteer without the bundled Chromium browser install 'puppeteer-core' `); throw this._requireError || new Error('No puppeteer instance provided.'); } /** * The method launches a browser instance with given arguments. The browser will be closed when the parent node.js process is closed. * * Augments the original `puppeteer.launch` method with plugin lifecycle methods. * * All registered plugins that have a `beforeLaunch` method will be called * in sequence to potentially update the `options` Object before launching the browser. * * @example * const browser = await puppeteer.launch({ * headless: false, * defaultViewport: null * }) * * @param options - See [puppeteer docs](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions). */ async launch(options) { // Ensure there are certain properties (e.g. the `options.args` array) const defaultLaunchOptions = { args: [] }; options = (0, deepmerge_1.default)(defaultLaunchOptions, options || {}); this.resolvePluginDependencies(); this.orderPlugins(); // Give plugins the chance to modify the options before launch options = await this.callPluginsWithValue('beforeLaunch', options); const opts = { context: 'launch', options, defaultArgs: this.defaultArgs }; // Let's check requirements after plugin had the chance to modify the options this.checkPluginRequirements(opts); const browser = await this.pptr.launch(options); this._patchPageCreationMethods(browser); await this.callPlugins('_bindBrowserEvents', browser, opts); return browser; } /** * Attach Puppeteer to an existing Chromium instance. * * Augments the original `puppeteer.connect` method with plugin lifecycle methods. * * All registered plugins that have a `beforeConnect` method will be called * in sequence to potentially update the `options` Object before launching the browser. * * @param options - See [puppeteer docs](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#puppeteerconnectoptions). */ async connect(options) { this.resolvePluginDependencies(); this.orderPlugins(); // Give plugins the chance to modify the options before connect options = await this.callPluginsWithValue('beforeConnect', options); const opts = { context: 'connect', options }; // Let's check requirements after plugin had the chance to modify the options this.checkPluginRequirements(opts); const browser = await this.pptr.connect(options); this._patchPageCreationMethods(browser); await this.callPlugins('_bindBrowserEvents', browser, opts); return browser; } /** * The default flags that Chromium will be launched with. * * @param options - See [puppeteer docs](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#puppeteerdefaultargsoptions). */ defaultArgs(options) { return this.pptr.defaultArgs(options); } /** Path where Puppeteer expects to find bundled Chromium. */ executablePath() { return this.pptr.executablePath(); } /** * This methods attaches Puppeteer to an existing Chromium instance. * * @param options - See [puppeteer docs](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#puppeteercreatebrowserfetcheroptions). */ createBrowserFetcher(options) { return this.pptr.createBrowserFetcher(options); } /** * Patch page creation methods (both regular and incognito contexts). * * Unfortunately it's possible that the `targetcreated` events are not triggered * early enough for listeners (e.g. plugins using `onPageCreated`) to be able to * modify the page instance (e.g. user-agent) before the browser request occurs. * * This only affects the first request of a newly created page target. * * As a workaround I've noticed that navigating to `about:blank` (again), * right after a page has been created reliably fixes this issue and adds * no noticable delay or side-effects. * * This problem is not specific to `puppeteer-extra` but default Puppeteer behaviour. * * Note: This patch only fixes explicitly created pages, implicitly created ones * (e.g. through `window.open`) are still subject to this issue. I didn't find a * reliable mitigation for implicitly created pages yet. * * Puppeteer issues: * https://github.com/GoogleChrome/puppeteer/issues/2669 * https://github.com/puppeteer/puppeteer/issues/3667 * https://github.com/GoogleChrome/puppeteer/issues/386#issuecomment-343059315 * https://github.com/GoogleChrome/puppeteer/issues/1378#issue-273733905 * * @private */ _patchPageCreationMethods(browser) { if (!browser._createPageInContext) { debug('warning: _patchPageCreationMethods failed (no browser._createPageInContext)'); return; } browser._createPageInContext = (function (originalMethod, context) { return async function () { const page = await originalMethod.apply(context, arguments); await page.goto('about:blank'); return page; }; })(browser._createPageInContext, browser); } /** * Get a list of all registered plugins. * * @member {Array<PuppeteerExtraPlugin>} */ get plugins() { return this._plugins; } /** * Get the names of all registered plugins. * * @member {Array<string>} * @private */ get pluginNames() { return this._plugins.map(p => p.name); } /** * Collects the exposed `data` property of all registered plugins. * Will be reduced/flattened to a single array. * * Can be accessed by plugins that listed the `dataFromPlugins` requirement. * * Implemented mainly for plugins that need data from other plugins (e.g. `user-preferences`). * * @see [PuppeteerExtraPlugin]/data * @param name - Filter data by optional plugin name * * @private */ getPluginData(name) { const data = this._plugins .map(p => (Array.isArray(p.data) ? p.data : [p.data])) .reduce((acc, arr) => [...acc, ...arr], []); return name ? data.filter((d) => d.name === name) : data; } /** * Get all plugins that feature a given property/class method. * * @private */ getPluginsByProp(prop) { return this._plugins.filter(plugin => prop in plugin); } /** * Lightweight plugin dependency management to require plugins and code mods on demand. * * This uses the `dependencies` stanza (a `Set`) exposed by `puppeteer-extra` plugins. * * @todo Allow objects as depdencies that contains opts for the requested plugin. * * @private */ resolvePluginDependencies() { // Request missing dependencies from all plugins and flatten to a single Set const missingPlugins = this._plugins .map(p => p._getMissingDependencies(this._plugins)) .reduce((combined, list) => { return new Set([...combined, ...list]); }, new Set()); if (!missingPlugins.size) { debug('no dependencies are missing'); return; } debug('dependencies missing', missingPlugins); // Loop through all dependencies declared missing by plugins for (let name of [...missingPlugins]) { // Check if the dependency hasn't been registered as plugin already. // This might happen when multiple plugins have nested dependencies. if (this.pluginNames.includes(name)) { debug(`ignoring dependency '${name}', which has been required already.`); continue; } // We follow a plugin naming convention, but let's rather enforce it <3 name = name.startsWith('puppeteer-extra-plugin') ? name : `puppeteer-extra-plugin-${name}`; // In case a module sub resource is requested print out the main package name // e.g. puppeteer-extra-plugin-stealth/evasions/console.debug => puppeteer-extra-plugin-stealth const packageName = name.split('/')[0]; let dep = null; try { // Try to require and instantiate the stated dependency dep = require(name)(); // Register it with `puppeteer-extra` as plugin this.use(dep); } catch (err) { console.warn(` A plugin listed '${name}' as dependency, which is currently missing. Please install it: yarn add ${packageName} Note: You don't need to require the plugin yourself, unless you want to modify it's default settings. `); throw err; } // Handle nested dependencies :D if (dep.dependencies.size) { this.resolvePluginDependencies(); } } } /** * Order plugins that have expressed a special placement requirement. * * This is useful/necessary for e.g. plugins that depend on the data from other plugins. * * @todo Support more than 'runLast'. * @todo If there are multiple plugins defining 'runLast', sort them depending on who depends on whom. :D * * @private */ orderPlugins() { debug('orderPlugins:before', this.pluginNames); const runLast = this._plugins .filter(p => p.requirements.has('runLast')) .map(p => p.name); for (const name of runLast) { const index = this._plugins.findIndex(p => p.name === name); this._plugins.push(this._plugins.splice(index, 1)[0]); } debug('orderPlugins:after', this.pluginNames); } /** * Lightweight plugin requirement checking. * * The main intent is to notify the user when a plugin won't work as expected. * * @todo This could be improved, e.g. be evaluated by the plugin base class. * * @private */ checkPluginRequirements(opts = {}) { for (const plugin of this._plugins) { for (const requirement of plugin.requirements) { if (opts.context === 'launch' && requirement === 'headful' && opts.options.headless) { console.warn(`Warning: Plugin '${plugin.name}' is not supported in headless mode.`); } if (opts.context === 'connect' && requirement === 'launch') { console.warn(`Warning: Plugin '${plugin.name}' doesn't support puppeteer.connect().`); } } } } /** * Call plugins sequentially with the same values. * Plugins that expose the supplied property will be called. * * @param prop - The plugin property to call * @param values - Any number of values * @private */ async callPlugins(prop, ...values) { for (const plugin of this.getPluginsByProp(prop)) { await plugin[prop].apply(plugin, values); } } /** * Call plugins sequentially and pass on a value (waterfall style). * Plugins that expose the supplied property will be called. * * The plugins can either modify the value or return an updated one. * Will return the latest, updated value which ran through all plugins. * * @param prop - The plugin property to call * @param value - Any value * @return The new updated value * @private */ async callPluginsWithValue(prop, value) { for (const plugin of this.getPluginsByProp(prop)) { const newValue = await plugin[prop](value); if (newValue) { value = newValue; } } return value; } } exports.PuppeteerExtra = PuppeteerExtra; /** * The **default export** will behave exactly the same as the regular puppeteer * (just with extra plugin functionality) and can be used as a drop-in replacement. * * Behind the scenes it will try to require either `puppeteer` * or [`puppeteer-core`](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#puppeteer-vs-puppeteer-core) * from the installed dependencies. * * @example * // javascript import * const puppeteer = require('puppeteer-extra') * * // typescript/es6 module import * import puppeteer from 'puppeteer-extra' * * // Add plugins * puppeteer.use(...) */ const defaultExport = (() => { return new PuppeteerExtra(...requireVanillaPuppeteer()); })(); exports.default = defaultExport; /** * An **alternative way** to use `puppeteer-extra`: Augments the provided puppeteer with extra plugin functionality. * * This is useful in case you need multiple puppeteer instances with different plugins or to add plugins to a non-standard puppeteer package. * * @example * // js import * const { addExtra } = require('puppeteer-extra') * * // ts/es6 import * import { addExtra } from 'puppeteer-extra' * * // Patch e.g. puppeteer-firefox and add plugins * const puppeteer = addExtra(require('puppeteer-firefox')) * puppeteer.use(...) * * @param puppeteer Any puppeteer API-compatible puppeteer implementation or version. * @return A fresh PuppeteerExtra instance using the provided puppeteer */ const addExtra = (puppeteer) => new PuppeteerExtra(puppeteer); exports.addExtra = addExtra; /** * Attempt to require puppeteer or puppeteer-core from dependencies. * To stay backwards compatible with the existing default export we have to do some gymnastics here. * * @return Either a Puppeteer instance or an Error, which we'll throw later if need be. * @private */ function requireVanillaPuppeteer() { try { return [require('puppeteer'), undefined]; } catch (_) { // noop } try { return [require('puppeteer-core'), undefined]; } catch (err) { return [undefined, err]; } } //# sourceMappingURL=index.js.map

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Ritesh-sudo/MCPJobSearch'

If you have feedback or need assistance with the MCP directory API, please join our Discord server