Display all puppeteer plugins on a browser instance - puppeteer

I have a service that accepts a puppeteer instance. I want to add a way of warning the developer if the browser instance is using the puppeteer-extra-plugin-stealth.
How can I easily check which plugins are enabled in a puppeteer browser instance?
function detectStealthPlugin(browserInstance) {
if (browserInstance.containsStealthPlugin) { // this does not work obviously
console.warn("🚧 WARNING: you are useing puppeteer with stealth plugin.")
}
}
I know you can use .plugins on the puppeteer object, but I cannot find out how to use it on the browser instance.
const puppeteer = require("puppeteer");
const isUsingStealthPlugin = (puppeteer) => {
if (!puppeteer.plugins) {
return false;
}
const pluginsUsed = puppeteer.plugins.map(p => p.constructor.name);
return pluginsUsed.includes("StealthPlugin");
}
const browserInstance = await puppeteer.launch();
// this works ✅
isUsingStealthPlugin(puppeteer);
// THIS DOES NOT WORK ❌
isUsingStealthPlugin(browserInstance);

Related

Chrome DevTools Extension - Intercept network in background

I'm working on an extension that intercepts network requests using the chrome.devtools.network API.
The extension is written with React.
Currently, the interception is done on the devTools panel, so the interception started only when the user enter my extension.
I can move the interception logic to the devTools but I wonder how to pass the data to the panel dynamically, so the React component on the panel will be updated with the new data and can show it.
Any suggestion?
I've solved it by doing something similar to this answer like this:
devTools.tsx:
chrome.devtools.panels.create('Apex Network', "", 'panel.html', function (extensionPanel) {
var _window; // Going to hold the reference to panel.html's `window`
let reqs = [];
const requestHandler = request => {
reqs.push(request);
};
chrome.devtools.network.onRequestFinished.addListener(requestHandler)
extensionPanel.onShown.addListener(function tmp(panelWindow) {
extensionPanel.onShown.removeListener(tmp); // Run once only
chrome.devtools.network.onRequestFinished.removeListener(requestHandler)
_window = panelWindow;
_window.INITIAL_DATA = reqs;
}) })
Panel.tsx
declare global {
interface Window { INITIAL_DATA: any; }
}
window.INITIAL_DATA = window.INITIAL_DATA || {};
const Panel = () => {
const requestHandler = (request) => {...}
useEffect(()=>{
window.INITIAL_DATA?.forEach(requestHandler)
window.INITIAL_DATA = [];
chrome.devtools.network.onRequestFinished.addListener(requestHandler)
});
}
So in the end, it's done by doing that:
When the devtools is open, the devtools register to the network requests and store them in an array.
When the devtools extension is open, the devtools unregister for the network requests and the requests is passed to the panel and the devtools stop capturing requests
The panel handle the request that captured at the devtools
The panel register to the network requests
Thanks

Fastest way to port a Web App to Mobile App

Is there any way to port a complete Web App (which is already responsive and fully compatible with small screens, already has touch UI controls, etc.) to Android/iOS?
My Web App is barebone HTML/JS/CSS, so is super vanilla (I don't even use jQuery).
I thought I could just smash my web app into an empty Ionic-Cordova project and be good with it, but I was wondering is there is a faster/better way to do this?
Maybe a tool or service i don't know about that takes as input a folder and pops out an android/IOS executable?
You can make a PWA (Progressive Web App).
Progressive Web Apps (PWAs) are modern, high quality applications built using web technology. PWAs offer similar capabilities to iOS/Android/desktop apps, they are reliable even in unstable network conditions, and are installable making it easier for users to find and use them.
Basically you have to add a manifest file in .json to your project root where you'll inform many things about your App like icon, name, main color, display mode (choose standalone if you want it to be like an real app) and etc...
(see it here: https://web.dev/add-manifest/) and link to your project pages:
<link rel="manifest" href="/manifest.json">
After that you have to make it installable (https://web.dev/codelab-make-installable/), to do that you will need a service-worker script in your project, you can get one here (https://glitch.com/edit/#!/make-it-installable?path=service-worker.js%3A1%3A0)
const CACHE_NAME = 'offline';
const OFFLINE_URL = 'offline.html';
self.addEventListener('install', function(event) {
console.log('[ServiceWorker] Install');
event.waitUntil((async () => {
const cache = await caches.open(CACHE_NAME);
// Setting {cache: 'reload'} in the new request will ensure that the response
// isn't fulfilled from the HTTP cache; i.e., it will be from the network.
await cache.add(new Request(OFFLINE_URL, {cache: 'reload'}));
})());
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
console.log('[ServiceWorker] Activate');
event.waitUntil((async () => {
// Enable navigation preload if it's supported.
// See https://developers.google.com/web/updates/2017/02/navigation-preload
if ('navigationPreload' in self.registration) {
await self.registration.navigationPreload.enable();
}
})());
// Tell the active service worker to take control of the page immediately.
self.clients.claim();
});
self.addEventListener('fetch', function(event) {
// console.log('[Service Worker] Fetch', event.request.url);
if (event.request.mode === 'navigate') {
event.respondWith((async () => {
try {
const preloadResponse = await event.preloadResponse;
if (preloadResponse) {
return preloadResponse;
}
const networkResponse = await fetch(event.request);
return networkResponse;
} catch (error) {
console.log('[Service Worker] Fetch failed; returning offline page instead.', error);
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(OFFLINE_URL);
return cachedResponse;
}
})());
}
});
Just add and save it in .js file in your project.
After that make sure you register the service worker using that code in your project:
/* Only register a service worker if it's supported */
// Service Worker
window.addEventListener('load', () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js');
}
});
Now you can make your site installable via some <button> element for example:
window.addEventListener('beforeinstallprompt', (event) => {
// Get the event first
window.deferredPrompt = event;
});
document.querySelector('#buttonInstall').addEventListener('click', () => {
const promptEvent = window.deferredPrompt;
if (! promptEvent) {
return;
}
promptEvent.prompt();
promptEvent.userChoice.then((result) => {
window.deferredPrompt = null;
});
}
});
You can hide the install button when people are in your PWA this way:
if (! window.matchMedia('(display-mode: standalone)').matches) {
// hide your install button
}
Here is some important things:
Your app have to meets certain criteria to be installable, you can
see it here: https://web.dev/install-criteria/
If the install pop-up doesn't appear, it means you made something
wrong, or your manifest is broken or your script.
You can see if your manifest.json is ok in browser developer tools open it (F12), go to Application tab and go to Manifest, this will show all your manifest parameters and it will show if something is wrong too.
I recommend you to read all the links above, there is a lot more
details an explanation about PWAs
simple way to port web app to mobile app is to make a WebView app in android. then give it your web app link address

Web midi on Chrome works with local server but not when served in the cloud

I built a website that uses the Chrome web midi interface (based on navigator.requestMidiAccess) that works fine in a local development server, but when pushed to a cloud server fails, saying that navigator.requestMidiAccess is not a function. The same code, the same browser. I'll try to include the relevant code:
function initializeMidi() {
navigator.requestMIDIAccess()
.then(
(midi) => midiReady(midi),
(err) => console.log('Something went wrong', err));
}
window.onload = (event) => {
initializeMidi();
};
// this next function builds a list of radio buttons to select the MIDI device
function midiReady(midi) {
globalMidi = midi.outputs
parentElement = document.getElementById('midi-devices-div')
parentElement.innerHTML = ''
var lastMidiPortName = null
midi.outputs.forEach(function (port, key) {
addRadioButton(parentElement, port)
lastMidiPortName = port.name
})
var n = window.localStorage.getItem('selectedMidiPortName')
if (n)
{
var e = document.getElementById(n)
e.checked = true
}
}
The Web MIDI interface is only exposed to SecureContexts, you must serve your document using https://.

how to execute a script in every window that gets loaded in puppeteer?

I need to execute a script in every Window object created in Chrome – that is:
tabs opened through puppeteer
links opened by click()ing links in puppeteer
all the popups (e.g. window.open or "_blank")
all the iframes contained in the above
it must be executed without me evaluating it explicitly for that particular Window object...
I checked Chrome's documentation and what I should be using is Page.addScriptToEvaluateOnNewDocument.
However, it doesn't look to be possible to use through puppeteer.
Any idea? Thanks.
This searches for a target in all browser contexts.
An example of finding a target for a page opened
via window.open() or popups:
await page.evaluate(() => window.open('https://www.example.com/'))
const newWindowTarget = await browser.waitForTarget(async target => {
await page.evaluate(() => {
runTheScriptYouLike()
console.log('Hello StackOverflow!')
})
})
via browser.pages() or tabs
This script run evaluation of a script in the second tab:
const pageTab2 = (await browser.pages())[1]
const runScriptOnTab2 = await pageTab2.evaluate(() => {
runTheScriptYouLike()
console.log('Hello StackOverflow!')
})
via page.frames() or iframes
An example of getting eval from an iframe element:
const frame = page.frames().find(frame => frame.name() === 'myframe')
const result = await frame.evaluate(() => {
return Promise.resolve(8 * 7);
});
console.log(result); // prints "56"
Hope this may help you

Flow type definitions for Puppeteer prevent using default export?

Consider puppeteer's launch() method. The usage guidelines in the Puppeteer readme recommend to use const puppeteer = require('puppeteer') and then puppeteer.launch():
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({path: 'example.png'});
await browser.close();
})();
And Puppeteer itself exports an instance, which seems to further confirm the default export is meant to be used:
module.exports = new Puppeteer(__dirname, preferredRevision, isPuppeteerCore);
However, the Flow type definitions for puppeteer ≥1.12 use declare export function launch() and don't include a default export, so Flow requires me to write import { launch } from "puppeteer" instead of import puppeteer from "puppeteer".
But using the named import yields TypeError: Cannot read property '_launcher' of undefined. Similarly, we can confirm this doesn't work with a simple test (Node v10.15.0, puppeteer 1.19.0):
const puppeteer = require("puppeteer");
puppeteer.launch(); // works
const { launch } = require("puppeteer");
launch(); // TypeError: Cannot read property '_launcher' of undefined
(Presumably this is because const { launch } = gives me an unbound method — this === undefined when called.)
Attempt 2: In Flow you can also use a namespaced import import * as puppeteer from "puppeteer", but it seems this explicitly forgoes access to the default export, instead yielding TypeError: puppeteer.launch is not a function.
(I'm less familiar with TypeScript, but the DefinitelyTyped defs for Puppeteer also use export function launch() rather than a default export. That said, they seem intended to be used with import * as puppeteer from "puppeteer", although I'm not sure why this would work when it doesn't in Flow; perhaps a difference in how TypeScript handles ES6 modules?)
So, am I holding it wrong, or are the Flow type definitions wrong? (In which case, how has nobody else encountered this problem?)