Merge pull request #169 from bckmnn/dev/puppeteer

replace phantom js with puppeteer
This commit is contained in:
banglashi
2021-03-24 21:12:47 +01:00
committed by GitHub
15 changed files with 7666 additions and 172 deletions

View File

@@ -21,6 +21,20 @@ RUN cd audiowaveform/build/ && cmake -D ENABLE_TESTS=0 -D BUILD_STATIC=1 ..
RUN cd audiowaveform/build/ && make
RUN cd audiowaveform/build/ && make install
# install chromium
RUN apk add --no-cache \
chromium \
nss \
freetype \
freetype-dev \
harfbuzz \
ca-certificates \
ttf-freefont
# Tell Puppeteer to skip installing Chrome. We'll be using the installed package.
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
# install other requirements
RUN apk add graphicsmagick ffmpeg ffmpeg-dev ghostscript

View File

@@ -25,7 +25,7 @@
"redis_mock": true,
"redis_host": "localhost",
"phantom_api_secret": "very_secret_phantom_password",
"export_api_secret": "very_secret_export_password",
"mail_provider": "smtp",
"mail_smtp_host": "your.smtp.host",

58
helpers/exporter.js Normal file
View File

@@ -0,0 +1,58 @@
'use strict';
const db = require('../models/db');
const config = require('config');
const puppeteer = require('puppeteer');
const os = require('os');
module.exports = {
// type = "pdf" or "png"
takeScreenshot: function(space,type,on_success,on_error) {
var spaceId = space._id;
var space_url = config.get("endpoint")+"/api/spaces/"+spaceId+"/html";
var export_path = os.tmpdir()+"/"+spaceId+"."+type;
var timeout = 5000;
if (type=="pdf") timeout = 30000;
space_url += "?api_token="+config.get("export_api_secret");
console.log("[space-screenshot] url: "+space_url);
console.log("[space-screenshot] export_path: "+export_path);
(async () => {
let browser;
let page;
try {
browser = await puppeteer.launch(
{
headless: true,
args: ['--disable-dev-shm-usage', '--no-sandbox']
}
);
page = await browser.newPage();
page.setDefaultTimeout(timeout);
await page.setJavaScriptEnabled(false);
await page.goto(space_url, {waitUntil: 'networkidle2'});
await page.emulateMediaType('screen');
if (type=="pdf") {
let margin = 2;
await page.pdf({path: export_path, printBackground: true, width: space.width+margin+'px', height: space.height+margin+'px' });
}else{
await page.screenshot({path: export_path, printBackground: true});
}
await browser.close();
on_success(export_path);
} catch (error) {
console.error(error);
console.error("[space-screenshot] puppeteer abnormal exit for url "+space_url);
on_error();
}
})();
}
};

View File

@@ -1,70 +0,0 @@
'use strict';
const db = require('../models/db');
const config = require('config');
const phantom = require('node-phantom-simple');
const os = require('os');
module.exports = {
// type = "pdf" or "png"
takeScreenshot: function(space,type,on_success,on_error) {
var spaceId = space._id;
var space_url = config.get("endpoint")+"/api/spaces/"+spaceId+"/html";
var export_path = os.tmpdir()+"/"+spaceId+"."+type;
var timeout = 5000;
if (type=="pdf") timeout = 30000;
space_url += "?api_token="+config.get("phantom_api_secret");
console.log("[space-screenshot] url: "+space_url);
console.log("[space-screenshot] export_path: "+export_path);
var on_success_called = false;
var on_exit = function(exit_code) {
if (exit_code>0) {
console.error("phantom abnormal exit for url "+space_url);
if (!on_success_called && on_error) {
on_error();
}
}
};
phantom.create({ path: require('phantomjs-prebuilt').path }, function (err, browser) {
if (err) {
console.error(err);
} else {
return browser.createPage(function (err, page) {
console.log("page created, opening ",space_url);
if (type=="pdf") {
var psz = {
width: space.width+"px",
height: space.height+"px"
};
page.set('paperSize', psz);
}
page.set('settings.resourceTimeout',timeout);
page.set('settings.javascriptEnabled',false);
return page.open(space_url, function (err,status) {
page.render(export_path, function() {
on_success_called = true;
if (on_success) {
on_success(export_path);
}
page.close();
browser.exit();
});
});
});
}
}, {
onExit: on_exit
});
}
};

View File

@@ -320,5 +320,6 @@
"follow_present": "Folgen",
"mute_present": "Entfolgen",
"follow_present_help": "Wenn jemand den Space präsentiert, folgen die anderen Mitglieder automatisch der Präsentation. Mit diesem Knopf lässt sich das an- oder ausschalten.",
"media": "Media"
}
"media": "Media",
"tool_edit_text": "Text bearbeiten"
}

View File

@@ -86,7 +86,7 @@ module.exports = (req, res, next) => {
// space is private
// special permission for screenshot/pdf export from backend
if (req.query['api_token'] && req.query['api_token'] == config.get('phantom_api_secret')) {
if (req.query['api_token'] && req.query['api_token'] == config.get('export_api_secret')) {
finalizeReq(space, "viewer");
return;
}

7466
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -30,10 +30,8 @@
"mock-aws-s3": "^2.6.0",
"moment": "^2.19.3",
"morgan": "^1.9.1",
"node-phantom-simple": "2.2.4",
"node-server-screenshot": "^0.2.1",
"nodemailer": "^4.6.7",
"phantomjs-prebuilt": "^2.1.16",
"puppeteer": "8.0.0",
"read-chunk": "^2.1.0",
"request": "^2.88.0",
"sanitize-html": "^1.11.1",

View File

@@ -23,6 +23,9 @@ function vec2_angle(v) {
function render_vector_drawing(a, padding) {
var shape = a.shape || "";
var path = [];
if(typeof a.control_points == 'string'){
a.control_points = JSON.parse(a.control_points);
}
var p = a.control_points[0];
if (!p) return "";

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,7 @@ const db = require('../../models/db');
var mailer = require('../../helpers/mailer');
var uploader = require('../../helpers/uploader');
var space_render = require('../../helpers/space-render');
var phantom = require('../../helpers/phantom');
var exporter = require('../../helpers/exporter');
var async = require('async');
var moment = require('moment');
@@ -51,7 +51,7 @@ router.get('/png', function(req, res, next) {
if (!req.space.thumbnail_updated_at || req.space.thumbnail_updated_at < req.space.updated_at || !req.space.thumbnail_url) {
db.Space.update({ thumbnail_updated_at: triggered }, {where : {"_id": req.space._id }});
phantom.takeScreenshot(req.space, "png", function(local_path) {
exporter.takeScreenshot(req.space, "png", function(local_path) {
var localResizedFilePath = local_path + ".thumb.jpg";
gm(local_path).resize(640, 480).quality(70.0).autoOrient().write(localResizedFilePath, function(err) {
@@ -109,7 +109,7 @@ function make_export_filename(space, extension) {
router.get('/pdf', function(req, res, next) {
var s3_filename = make_export_filename(req.space, "pdf");
phantom.takeScreenshot(req.space, "pdf", function(local_path) {
exporter.takeScreenshot(req.space, "pdf", function(local_path) {
uploader.uploadFile(s3_filename, "application/pdf", local_path, function(err, url) {
res.status(201).json({
url: url

View File

@@ -9,7 +9,7 @@ var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer');
var uploader = require('../../helpers/uploader');
var space_render = require('../../helpers/space-render');
var phantom = require('../../helpers/phantom');
var exporter = require('../../helpers/exporter');
var async = require('async');
var fs = require('fs');

View File

@@ -10,7 +10,7 @@ var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer');
var uploader = require('../../helpers/uploader');
var space_render = require('../../helpers/space-render');
var phantom = require('../../helpers/phantom');
var exporter = require('../../helpers/exporter');
var payloadConverter = require('../../helpers/artifact_converter');
var slug = require('slug');

View File

@@ -1,10 +1,10 @@
"use strict";
const puppeteer = require('puppeteer');
var config = require('config');
require('../../models/db');
var fs = require('fs');
var phantom = require('node-phantom-simple');
var md5 = require('md5');
var express = require('express');
@@ -19,40 +19,39 @@ function website_to_png(url,on_success,on_error) {
console.log("[webgrabber] url: "+url);
console.log("[webgrabber] export_path: "+export_path);
var on_success_called = false;
var on_exit = function(exit_code) {
if (exit_code>0) {
console.log("[phantom-webgrabber] abnormal exit for url "+url);
if (!on_success_called && on_error) {
on_error();
}
}
};
fs.stat(export_path, function(err, stat) {
if (!err) {
// file exists
console.log("[webgrabber] serving cached snapshot of url: "+url);
on_success(export_path);
} else {
phantom.create({ path: require('phantomjs-prebuilt').path }, function (err, browser) {
return browser.createPage(function (err, page) {
page.set('settings.resourceTimeout',timeout);
page.set('settings.javascriptEnabled',false);
return page.open(url, function(err, status) {
console.log("[webgrabber] status: "+status);
page.render(export_path, function() {
on_success_called = true;
on_success(export_path);
browser.exit();
});
});
});
}, {
onExit: on_exit
});
(async () => {
let browser;
let page;
try {
browser = await puppeteer.launch(
{
headless: true,
args: ['--disable-dev-shm-usage', '--no-sandbox']
}
);
page = await browser.newPage();
page.setDefaultTimeout(timeout);
await page.setJavaScriptEnabled(false);
await page.goto(url, {waitUntil: 'networkidle2'});
await page.emulateMedia('screen');
await page.screenshot({path: export_path, printBackground: true});
await browser.close();
on_success(export_path);
} catch (error) {
console.error(error);
console.log("[webgrabber] puppeteer abnormal exit for url "+url);
on_error();
}
})();
}
});
}

View File

@@ -3,12 +3,17 @@
@import "unicode";
@-webkit-keyframes appear {
0% { opacity: 0;}
30% { opacity: 0;}
100% { opacity: 1; }
0% {
opacity: 0;
}
30% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.avatar {
background-color: $blue;
font-family: $main-font;
@@ -26,7 +31,9 @@
#user-root {
margin-left: 5px !important;
padding: 0px !important;
.btn {margin-right: 10px; }
.btn {
margin-right: 10px;
}
}
#sidebar.folder-sidebar {
@@ -70,7 +77,9 @@
li {
display: inline-block;
line-height: 44px;
&.active span {color: $light; }
&.active span {
color: $light;
}
span {
color: $medium;
display: inline-block;
@@ -82,8 +91,6 @@
}
}
#folder {
position: absolute;
height: 100%;
@@ -137,7 +144,7 @@
z-index: initial;
}
.folder .icon {
.folder .icon {
color: $yellow;
opacity: 1;
}
@@ -150,7 +157,7 @@
width: 100%;
}
> .icon {
margin-left: -15px
margin-left: -15px;
}
}
@@ -176,30 +183,30 @@
// margin-top: -18px;
margin-right: -60px;
// margin-bottom: -18px;
@include transition( all 0.125s ease-in-out);
@include transition(all 0.125s ease-in-out);
.icon {
left: 0px;
top: 0px;
line-height: 60px;
font-size: 13px;
position: absolute;
@include scale(0,0);
@include transition( all 0.125s ease-in-out);
@include scale(0, 0);
@include transition(all 0.125s ease-in-out);
@include opacity(0.25);
}
&:hover {
margin-right: -12px;
.icon {
@include scale(1,1);
@include scale(1, 1);
}
}
}
}
#folder-header {
background-color: rgba(41,41,41,0.95);
background-color: rgba(245,245,245,0.95);
background-color: rgba(41, 41, 41, 0.95);
background-color: rgba(245, 245, 245, 0.95);
}
#folder-wrapper {
@@ -212,15 +219,22 @@
margin: auto;
}
#folder-grid .item {
float: left;
width: 100%;
@media screen and (min-width: 640px) {width: 50%; }
@media screen and (min-width: 800px) {width: 33.333333%; }
@media screen and (min-width: 1000px) {width: 25%; }
@media screen and (min-width: 1200px) {width: 20%; }
@media screen and (min-width: 640px) {
width: 50%;
}
@media screen and (min-width: 800px) {
width: 33.333333%;
}
@media screen and (min-width: 1000px) {
width: 25%;
}
@media screen and (min-width: 1200px) {
width: 20%;
}
}
.compact-hidden {
@@ -256,8 +270,8 @@
.item {
display: inline-block;
text-align: left;
padding-right: $folder-gutter*2;
padding-bottom: $folder-gutter*2;
padding-right: $folder-gutter * 2;
padding-bottom: $folder-gutter * 2;
margin-bottom: $folder-gutter;
position: relative;
@@ -265,36 +279,38 @@
cursor: pointer;
.folder-drop {display:none; }
.folder-drop {
display: none;
}
&.appear > a,
&.creating > a {
opacity: 0;
-webkit-animation: appear 0.25s ease-out 0.05s;
-moz-animation: appear 0.25s ease-out 0.05s;
-ms-animation: appear 0.25s ease-out 0.05s;
-o-animation: appear 0.25s ease-out 0.05s;
animation: appear 0.25s ease-out 0.05s;
-moz-animation: appear 0.25s ease-out 0.05s;
-ms-animation: appear 0.25s ease-out 0.05s;
-o-animation: appear 0.25s ease-out 0.05s;
animation: appear 0.25s ease-out 0.05s;
-webkit-animation-fill-mode: forwards;
-moz-animation-fill-mode: forwards;
-ms-animation-fill-mode: forwards;
-o-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-moz-animation-fill-mode: forwards;
-ms-animation-fill-mode: forwards;
-o-animation-fill-mode: forwards;
animation-fill-mode: forwards;
}
&.deleting > a {
-webkit-animation: disappear 0.25s ease-out 0.05s;
-moz-animation: disappear 0.25s ease-out 0.05s;
-ms-animation: disappear 0.25s ease-out 0.05s;
-o-animation: disappear 0.25s ease-out 0.05s;
animation: disappear 0.25s ease-out 0.05s;
-moz-animation: disappear 0.25s ease-out 0.05s;
-ms-animation: disappear 0.25s ease-out 0.05s;
-o-animation: disappear 0.25s ease-out 0.05s;
animation: disappear 0.25s ease-out 0.05s;
-webkit-animation-fill-mode: forwards;
-moz-animation-fill-mode: forwards;
-ms-animation-fill-mode: forwards;
-o-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-moz-animation-fill-mode: forwards;
-ms-animation-fill-mode: forwards;
-o-animation-fill-mode: forwards;
animation-fill-mode: forwards;
}
&.tag-important > a {
@@ -308,7 +324,7 @@
&.dropping {
.folder-drop {
display:block;
display: block;
}
}
@@ -349,7 +365,13 @@
background-color: $darker;
position: absolute;
bottom: 2px;
-webkit-mask-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0,0,0,0)), to(rgba(0,0,0,0.1)));
-webkit-mask-image: -webkit-gradient(
linear,
left top,
left bottom,
from(rgba(0, 0, 0, 0)),
to(rgba(0, 0, 0, 0.1))
);
background-color: black;
pointer-events: none;
}
@@ -360,7 +382,7 @@
.fav-toggle {
margin-right: -12px;
.icon {
@include scale(1,1);
@include scale(1, 1);
@include opacity(1);
}
}
@@ -384,7 +406,7 @@
> a {
/* aspect ratio without spacer image */
&:before{
&:before {
content: "";
display: block;
padding-top: 100%; /* initial ratio of 1:1*/
@@ -392,9 +414,11 @@
}
background-color: white;
border-radius: $radius*2;
border-radius: $radius * 2;
&:active { opacity: 0.95 !important; }
&:active {
opacity: 0.95 !important;
}
box-shadow: 0 0 30px 1px rgba(0, 0, 0, 0.15);
border: 1px solid black;
@@ -406,7 +430,7 @@
font-weight: 400;
display: block;
text-decoration: none;
border-radius: $radius*2;
border-radius: $radius * 2;
position: relative;
.item-thumbnail {
@@ -420,8 +444,9 @@
overflow: hidden;
background-color: transparent;
border-top-left-radius: $radius*2;
border-top-right-radius: $radius*2;
background-size: cover;
border-top-left-radius: $radius * 2;
border-top-right-radius: $radius * 2;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
@@ -431,7 +456,7 @@
.item-title {
display: block;
border-top: 1px solid rgba(0,0,0,0.05);
border-top: 1px solid rgba(0, 0, 0, 0.05);
background-color: white;
font-size: 18px;
width: 100%;
@@ -439,15 +464,15 @@
left: 0px;
bottom: 0px;
position: absolute;
border-bottom-left-radius: $radius*2;
border-bottom-right-radius: $radius*2;
border-bottom-left-radius: $radius * 2;
border-bottom-right-radius: $radius * 2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: $dark;
text-align: left;
}
.thumbnail-loading {
position: absolute;
z-index: 0;
@@ -464,11 +489,11 @@
}
.item-meta {
border-top: 2px solid rgba(0,0,0,0.025);
border-radius: 2*$radius;
border-top: 2px solid rgba(0, 0, 0, 0.025);
border-radius: 2 * $radius;
border-top-left-radius: 0;
border-top-right-radius: 0;
@include clearfix;
color: $medium;
position: absolute;
@@ -493,8 +518,8 @@
left: 0px;
bottom: 0px;
position: relative;
border-bottom-left-radius: $radius*2;
border-bottom-right-radius: $radius*2;
border-bottom-left-radius: $radius * 2;
border-bottom-right-radius: $radius * 2;
//white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -509,7 +534,7 @@
position: absolute;
bottom: 0;
left: 0;
right: $folder-gutter*2;
right: $folder-gutter * 2;
color: $medium;
font-size: 11px;
font-family: $main-font;