Как заставить Puppeteer работать с приложением ReactJS на стороне клиента - PullRequest
3 голосов
/ 06 марта 2019

Я довольно новичок в React и занимаюсь разработкой приложения, которое будет делать реальные снимки экрана веб-страницы, а приложение может рисовать и добавлять рисунки поверх снятого снимка экрана. Первоначально я использовал html2canvas и domToImage для создания снимков экрана на стороне клиента, но он не отображает изображение точно так, как показано на веб-странице.

Пользователь Reddit / pamblam0 предложил мне заглянуть в Google Puppeteer. Как это работает, так это то, что он запускает браузер безголового хрома, который переходит к моему приложению реакции на localhost, а затем легко получает скриншот всей этой страницы. Моя проблема, однако, в том, что кукловод не играет хорошо в приложении реакции. Это дает мне ошибку ws, которая, как объяснено в поиске Google, может быть исправлена ​​простой установкой ws (что, кстати, не работает).

Что происходит сейчас, мой сценарист-кукловод работает над моим приложением реакции. Из того, что я понимаю, это не работает с клиентским приложением (я могу ошибаться). Я хочу, чтобы каждый раз, когда я нажимал кнопку в моем приложении реагирования, кукловод должен выполнять и возвращать строку base64, которая затем будет передаваться компоненту в моем приложении реагирования.

Вот что я сделал до сих пор.

puppeteerApp.js

const puppeteer = require('puppeteer');

const takeScreenshot = async () => {
    puppeteer.launch().then(async browser => {
        const page = await browser.newPage();
        const options = {
            path: 'saved_images/webshot.png',
            encoding: 'base64'
        }
        await page.goto('http://localhost:3000/', { waitUntil: 'networkidle2' });
        const elem = await page.$('iframe').then(async (iframe) => {
            return await iframe.screenshot(options)
        });

        await browser.close()
    });
}

takeScreenshot();

Код от приложения реакции. App.js

import React, { Component } from 'react';
import ScreenshotsContainer from './containers/ScreenshotsContainer/ScreenshotsContainer'
import ImageContainer from './containers/ImageContainer/ImageContainer';
import html2canvas from 'html2canvas';
import domtoimage from 'dom-to-image';
import Button from './components/UI/Button/Button'
import classes from './App.module.css';
import { CSSTransition } from 'react-transition-group'
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';


class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
      imgURIArray: [],
      img: null,
      showImageContainer: false,
      showScreenshotContainer: false,
      selectedImageURI: null,
      showSaveAnimation: false,
      showNotify: false
    }
  }


  storeImageToArrayHandler = (imgURI) => {
    if (imgURI !== "") {
      this.setState({ imgURIArray: [...this.state.imgURIArray, imgURI] }, () => {
        this.setState({ showImageContainer: !this.state.showImageContainer })
      })
    }
  }

  getScreenshotHandler = () => {
   //use puppeteer here!!!
  }



  getSelectedImageFromContainerHandler(selectedImageURI) {
    this.setState({
      selectedImageURI: selectedImageURI,
      showImageContainer: !this.state.showImageContainer
    })

  }

  showImageContainerHandler(showImageContainer) {
    this.setState({ showImageContainer: showImageContainer })
  }

  showScreenshotContainerHandler = () => {
    this.setState({ showScreenshotContainer: !this.state.showScreenshotContainer })
  }
  notify = (submitSuccessful, msg) => {
    let message = msg ? msg : ""
    submitSuccessful ?
      toast.success(message, {
        autoClose: 3000,
        position: toast.POSITION.TOP_CENTER
      })
      :
      toast.error(message, {
        position: toast.POSITION.TOP_CENTER
      });

  }
  render() {
    let buttonOps = (
      <CSSTransition
        in={!this.state.showScreenshotContainer}
        appear={true}
        timeout={300}
        classNames="fade"
      >
        <div className={classes.optionButtons}>
          <Button icon={"fas fa-camera"} type={"button-success"} gridClass={""} buttonName={""} style={{ width: "100%", height: "70px" }} onClick={() => this.getScreenshotHandler} />
          <Button icon={"fas fa-images"} type={"button-primary "} gridClass={""} buttonName={""} style={{ width: "100%", height: "70px" }} onClick={() => this.showScreenshotContainerHandler} />
        </div>
      </CSSTransition>
    )

    return (
      <div>
        {
          this.state.showImageContainer ?
            <div>
              <ImageContainer
                img={this.state.img}
                showImageContainer={showImageContainer => this.showImageContainerHandler(showImageContainer)}
                storeImageToArrayHandler={imgURI => this.storeImageToArrayHandler(imgURI)}
                notify={(submitSuccessful, msg) => this.notify(submitSuccessful, msg)}
              />
            </div>
            : null
        }
        <CSSTransition
          in={this.state.showScreenshotContainer}
          appear={true}
          timeout={300}
          classNames="slide"
          unmountOnExit
          onExited={() => {
            this.setState({ showScreenshotContainer: false })
          }}
        >
          <ScreenshotsContainer
            imgURIArray={this.state.imgURIArray}
            getSelectedImageFromContainerHandler={imgURI => this.getSelectedImageFromContainerHandler(imgURI)}
            showScreenshotContainerHandler={() => this.showScreenshotContainerHandler}
            notify={(submitSuccessful, msg) => this.notify(submitSuccessful, msg)}
          />

        </CSSTransition>
        {this.state.showImageContainer ? null : buttonOps}
        {/* <button onClick={this.notify}>Notify !</button> */}
        <ToastContainer />

      </div >
    );
  }
}

export default App;

Любая помощь будет оценена. Спасибо!

1 Ответ

5 голосов
/ 07 марта 2019

Ваше приложение React.js работает на стороне клиента (в браузере). Puppeteer не может работать внутри этой среды, так как вы не можете запустить полноценный браузер внутри браузера.

Вам нужен сервер, который сделает это за вас. Вы можете предложить конечную точку HTTP (вариант 1) или выставить веб-сокет своего кукловода (вариант 2):

Вариант 1. Укажите конечную точку HTTP

Для этого параметра вы настраиваете сервер, который обрабатывает входящий запрос и запускает задачу (делает снимок экрана) для вас:

server.js

const puppeteer = require('puppeteer');
const express = require('express');

const app = express();

app.get('/screenshot', async (req, res) => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto(req.query.url); // URL is given by the "user" (your client-side application)
    const screenshotBuffer = await page.screenshot();

    // Respond with the image
    res.writeHead(200, {
        'Content-Type': 'image/png',
        'Content-Length': screenshotBuffer.length
    });
    res.end(screenshotBuffer);

    await browser.close();
})

app.listen(4000);

Запустите приложение с node server.js, и теперь вы можете передать URL на ваш сервер и получить скриншот с вашего сервера: http://localhost:4000/screenshot?url=https://example.com/

Ответ от сервера может затем использоваться в качестве источника элемента изображения в вашем приложении.

Вариант 2: Предоставление Websocket кукловода клиенту

Вы также можете управлять браузером (который работает на сервере) со стороны клиента, открывая Websocket.

Для этого вам нужно открыть Websocket вашего сервера следующим образом:

server.js

const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch();
    const browserWSEndpoint = browser.wsEndpoint();
    browser.disconnect(); // Disconnect from the browser, but don't close it
    console.log(browserWSEndpoint); // Communicate the Websocket URL to the client-side
    // example output: ws://127.0.0.1:55620/devtools/browser/e62ec4c8-1f05-42a1-86ce-7b8dd3403f91
})();

Теперь вы можете управлять браузером (работающим на сервере) с клиентской стороны с помощью кукловода для клиента. В этом сценарии вы теперь можете подключиться к браузеру через puppeteer.connect и сделать снимок экрана таким образом.

Я бы настоятельно рекомендовал использовать вариант 1, так как в варианте 2 вы полностью открываете свой работающий браузер клиенту. Даже с опцией 1 вам все равно придется обрабатывать ввод данных пользователем, таймауты, ошибки навигации и т. Д.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...