background

There is a project under development that uses puppeteer, which has the ability to open multiple tabs in the Chrome that puppeteer opens and manages. Although puppeteer can open multiple websites, it is not easy to manage, so I use plug-ins to open multiple websites and manage them.

But there’s a requirement that I do something when the site crashes. But there is currently no good way to listen for crashes on the web.

Puppeteer provides a page.on(‘error’, fn) method to listen.

Note that the page. On (‘error’, fn) method only works on the site where puppeteer is not used to open multiple sites.

Use Service Workers

This method was put forward by my colleague Haitao.

Run a Service Workers on the current site, because the Service Workers will start a separate process while running, the current site and Service Workers are two separate processes. That is, when the website crashes, the Service Workers process is not affected. Therefore, heartbeat detection can be used to determine whether the site crashes.

There is also an online article by Ali’s classmates: How to Monitor Web crashes?

However, I did not use this method, because when Service Workers crash, there is no way, some students may say: the website and Service Workers send heartbeat detection to each other. That could be one way, but I don’t like it very much.

Use Webkit’s remote debugging protocol

introduce

Before we begin, let’s take a look at the source code for Puppeteer and why puppeteer can listen for web crashes.

The code is in the lib/ page.js file.

Page is a Class that inherits EventEmitter, which provides on methods for Page, as we saw earlier: Page.on (‘error’, fn)

This. Emit (‘error’) is called somewhere in the Page Class to emit an error event. I searched and found that the code is in the _onTargetCrashed method. Such as:

We found the way to trigger crash. Where is this _onTargetCrashed triggered?

This event triggers the _onTargetCrashed function. Clinet will not follow this method because there are too many jumps. Ultimately the client is a websocket artifact. The code created by websocket is in lib/ launcher.js. Code position

Note these two lines:

const transport = new PipeTransport((chromeProcess.stdio[3]), (chromeProcess.stdio[4]));

connection = new Connection(' ', transport, slowMo);
Copy the code

ChromeProcess is the spawn of nodeJS with code:

Code position

const chromeProcess = childProcess.spawn(
  chromeExecutable,
  chromeArguments,
  {
    detached: process.platform ! = ='win32',
    env,
    stdio
  }
);
Copy the code

ChromeArguments is a list of startup arguments for Chrome. This list has a –remote-debugging- :

Code position

if(! chromeArguments.some(argument= > argument.startsWith('--remote-debugging-')))
  chromeArguments.push(pipe ? '--remote-debugging-pipe' : '--remote-debugging-port=0');
Copy the code

TargetCrashed this event is provided by the Webkit Remote Debugging Protocol, also known as the Remote Debugging Protocol.

The definition in its Inspector. In the json: Source/WebCore/Inspector/Inspector. Json# L39 – L42

The commit URL for this event is: github.com/WebKit/webk…

Write solution code

We now know that if we can listen for the Inspector. TargetCrashed event, we can tell if the site is down. Let’s add a line of puppeteer startup parameters to the puppeteer startup parameters:

puppeteer.launch({
  '--remote-debugging-port=9222'.// other args
});
Copy the code

When puppeteer is started, port 9222 is listened on. The path/JSON is the current details. Such as:

The format is:

[{"description": ""."devtoolsFrontendUrl": "/devtools/inspector.html? Ws = 127.0.0.1:9222 / devtools/page/A1CB5A9CC25A7EE8A99C6A4A1876E4D3"."faviconUrl": "https://s.ytimg.com/yts/img/favicon_32-vflOogEID.png"."id": "A1CB5A9CC25A7EE8A99C6A4A1876E4D3"."title": "Chang SAN Li Si Chang and Lee [Waiting for Nobody] - YouTube"."type": "page"."url": "https://www.youtube.com/watch?v=lAcUGvpRkig&list=PL3p0C_7POnMHG-b0dzkeTVdNuM6yRE5iQ&index=10&t=0s"."webSocketDebuggerUrl": Ws: / / "127.0.0.1:9222 / devtools/page/A1CB5A9CC25A7EE8A99C6A4A1876E4D3"
  },
  // other
{
Copy the code

Where type is the details of the current process:

  • Page: web page
  • Iframe: indicates the nested IFrame of a web page
  • Background_page: plug-in page
  • service_worker: Service Workers

The purpose of this type is that you only want to listen for one type of crash.

There is a more important field: webSocketDebuggerUrl. We will use the value of this field for the fetch message. Here’s a simple demo:

const http =  require('http');
const WebSocket = require('ws');

http.get('http://127.0.0.1:9222/json', res => {
  res.addListener('data', data => {
    const result = JSON.parse(data.toString());
    result.forEach(info= > {
      const client = new WebSocket(info.webSocketDebuggerUrl);
      client.on('message', data => {
        if (data.indexOf('"method":"Inspector.targetCrashed"')! = =- 1) {
          console.error('crash! '); }}); }); })})Copy the code

Understand this code first, the following code will be easier to understand, because the code is too simple, I won’t introduce it here.

One problem with this code is that there is a delay when the plugin opens the site, which may result in some sites not being listened for, and when the plugin opens the site again after this code runs, it will not be listened for. To solve this problem, the following code has been optimized:

const http =  require('http');
const WebSocket = require('ws');

module.exports = (a)= > {
  const wsList = {};
  let crashStaus = false;

  const getWsList = (a)= > {
    return new Promise((resolve) = > {
      http.get('http://127.0.0.1:9222/json', res => {
        res.addListener('data', data => {
          try {
            const result = JSON.parse(data.toString());
            const tempWsList = {};

            result.forEach(info= > {
              if (typeof wsList[info.id] === 'undefined') { tempWsList[info.id] = info.webSocketDebuggerUrl; wsList[info.id] = info.webSocketDebuggerUrl; }});if (Object.keys(tempWsList).length ! = =0) { resolve(tempWsList); }}catch (e) {
            console.error(e); }}); }); }); }; setInterval((a)= > {
    getWsList().then(list= > {
      Object.values(list).forEach(wsUrl= > {
        const client = new WebSocket(wsUrl);
        client.on('message', data => {
          if (data.indexOf('"method":"Inspector.targetCrashed"')! = =- 1) {
            if(! crashStaus) { crashStaus =true;
              console.log('crash!!! '); }}}); })}); },1000);
};
Copy the code

This code needs to be explained:

if(! crashStaus) { crashStaus =true;
  console.log('crash!!! ');
}
Copy the code

Because my requirement is that if any process crashes, I shut down the entire service and restart it. So if multiple processes crash at the same time. My code only goes once, I don’t want it to go multiple times. This is for my needs here, you can change the code according to their own needs.

reference

Probe into Webkit remote Debugging protocol

Chrome remote debugging protocol analysis and combat

The author information

  • Author: Black-Hole
  • Blog: bugs.cc/
  • Github: github.com/BlackHole1/
  • Twitter: twitter.com/Free_BlackH…
  • Email: [email protected]

other

Division I (Philharmonic) recruitment, interested partners can cast resume ah.

Flexible working, fruit of the day, nice colleagues, 965, group construction, five insurances and one housing fund…

Location: Shanghai Pusoft Building