189 lines
5.6 KiB
JavaScript
189 lines
5.6 KiB
JavaScript
|
/* global window,MathJax */
|
||
|
const spawn = require('child_process').spawn;
|
||
|
const fs = require('fs');
|
||
|
const tmp = require('tmp');
|
||
|
const user = require('./user');
|
||
|
|
||
|
/* eslint-disable no-var, prefer-arrow-callback, func-names */
|
||
|
function waitForJavaScript() {
|
||
|
if (window.MathJax) {
|
||
|
// Amazon EC2: fix TeX font detection
|
||
|
MathJax.Hub.Register.StartupHook('HTML-CSS Jax Startup', function () {
|
||
|
var htmlCss = MathJax.OutputJax['HTML-CSS'];
|
||
|
htmlCss.Font.checkWebFont = function (check, font, callback) {
|
||
|
if (check.time(callback)) {
|
||
|
return;
|
||
|
}
|
||
|
if (check.total === 0) {
|
||
|
htmlCss.Font.testFont(font);
|
||
|
setTimeout(check, 200);
|
||
|
} else {
|
||
|
callback(check.STATUS.OK);
|
||
|
}
|
||
|
};
|
||
|
});
|
||
|
MathJax.Hub.Queue(function () {
|
||
|
window.status = 'done';
|
||
|
});
|
||
|
} else {
|
||
|
setTimeout(function () {
|
||
|
window.status = 'done';
|
||
|
}, 2000);
|
||
|
}
|
||
|
}
|
||
|
/* eslint-disable no-var, prefer-arrow-callback, func-names */
|
||
|
|
||
|
const authorizedPageSizes = [
|
||
|
'A3',
|
||
|
'A4',
|
||
|
'Legal',
|
||
|
'Letter',
|
||
|
];
|
||
|
|
||
|
const readJson = (str) => {
|
||
|
try {
|
||
|
return JSON.parse(str);
|
||
|
} catch (e) {
|
||
|
return {};
|
||
|
}
|
||
|
};
|
||
|
|
||
|
exports.generate = (req, res) => {
|
||
|
let wkhtmltopdfError = '';
|
||
|
Promise.all([
|
||
|
user.checkSponsor(req.query.idToken),
|
||
|
user.checkMonetize(req.query.token),
|
||
|
])
|
||
|
.then(([isSponsor, isMonetize]) => {
|
||
|
if (!isSponsor && !isMonetize) {
|
||
|
throw new Error('unauthorized');
|
||
|
}
|
||
|
return new Promise((resolve, reject) => {
|
||
|
tmp.file((err, filePath, fd, cleanupCallback) => {
|
||
|
if (err) {
|
||
|
reject(err);
|
||
|
} else {
|
||
|
resolve({
|
||
|
filePath,
|
||
|
cleanupCallback,
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
})
|
||
|
.then(({ filePath, cleanupCallback }) => new Promise((resolve, reject) => {
|
||
|
let finished = false;
|
||
|
|
||
|
function onError(err) {
|
||
|
finished = true;
|
||
|
cleanupCallback();
|
||
|
reject(err);
|
||
|
}
|
||
|
const options = readJson(req.query.options);
|
||
|
const params = [];
|
||
|
|
||
|
// Margins
|
||
|
const marginTop = parseInt(`${options.marginTop}`, 10);
|
||
|
params.push('-T', isNaN(marginTop) ? 25 : marginTop);
|
||
|
const marginRight = parseInt(`${options.marginRight}`, 10);
|
||
|
params.push('-R', isNaN(marginRight) ? 25 : marginRight);
|
||
|
const marginBottom = parseInt(`${options.marginBottom}`, 10);
|
||
|
params.push('-B', isNaN(marginBottom) ? 25 : marginBottom);
|
||
|
const marginLeft = parseInt(`${options.marginLeft}`, 10);
|
||
|
params.push('-L', isNaN(marginLeft) ? 25 : marginLeft);
|
||
|
|
||
|
// Header
|
||
|
if (options.headerCenter) {
|
||
|
params.push('--header-center', `${options.headerCenter}`);
|
||
|
}
|
||
|
if (options.headerLeft) {
|
||
|
params.push('--header-left', `${options.headerLeft}`);
|
||
|
}
|
||
|
if (options.headerRight) {
|
||
|
params.push('--header-right', `${options.headerRight}`);
|
||
|
}
|
||
|
if (options.headerFontName) {
|
||
|
params.push('--header-font-name', `${options.headerFontName}`);
|
||
|
}
|
||
|
if (options.headerFontSize) {
|
||
|
params.push('--header-font-size', `${options.headerFontSize}`);
|
||
|
}
|
||
|
|
||
|
// Footer
|
||
|
if (options.footerCenter) {
|
||
|
params.push('--footer-center', `${options.footerCenter}`);
|
||
|
}
|
||
|
if (options.footerLeft) {
|
||
|
params.push('--footer-left', `${options.footerLeft}`);
|
||
|
}
|
||
|
if (options.footerRight) {
|
||
|
params.push('--footer-right', `${options.footerRight}`);
|
||
|
}
|
||
|
if (options.footerFontName) {
|
||
|
params.push('--footer-font-name', `${options.footerFontName}`);
|
||
|
}
|
||
|
if (options.footerFontSize) {
|
||
|
params.push('--footer-font-size', `${options.footerFontSize}`);
|
||
|
}
|
||
|
|
||
|
// Page size
|
||
|
params.push('--page-size', authorizedPageSizes.indexOf(options.pageSize) === -1 ? 'A4' : options.pageSize);
|
||
|
|
||
|
// Use a temp file as wkhtmltopdf can't access /dev/stdout on Amazon EC2 for some reason
|
||
|
const binPath = process.env.WKHTMLTOPDF_PATH || 'wkhtmltopdf';
|
||
|
params.push('--run-script', `${waitForJavaScript.toString()}waitForJavaScript()`);
|
||
|
params.push('--window-status', 'done');
|
||
|
const wkhtmltopdf = spawn(binPath, params.concat('-', filePath), {
|
||
|
stdio: [
|
||
|
'pipe',
|
||
|
'ignore',
|
||
|
'pipe',
|
||
|
],
|
||
|
});
|
||
|
let timeoutId = setTimeout(function () {
|
||
|
timeoutId = null;
|
||
|
wkhtmltopdf.kill();
|
||
|
}, 50000);
|
||
|
wkhtmltopdf.on('error', onError);
|
||
|
wkhtmltopdf.stdin.on('error', onError);
|
||
|
wkhtmltopdf.stderr.on('data', (data) => {
|
||
|
wkhtmltopdfError += `${data}`;
|
||
|
});
|
||
|
wkhtmltopdf.on('close', (code) => {
|
||
|
if (!finished) {
|
||
|
clearTimeout(timeoutId);
|
||
|
if (!timeoutId) {
|
||
|
cleanupCallback();
|
||
|
reject(new Error('timeout'));
|
||
|
} else if (code) {
|
||
|
cleanupCallback();
|
||
|
reject();
|
||
|
} else {
|
||
|
res.set('Content-Type', 'application/pdf');
|
||
|
const readStream = fs.createReadStream(filePath);
|
||
|
readStream.on('open', () => readStream.pipe(res));
|
||
|
readStream.on('close', () => cleanupCallback());
|
||
|
readStream.on('error', () => {
|
||
|
cleanupCallback();
|
||
|
reject();
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
req.pipe(wkhtmltopdf.stdin);
|
||
|
}))
|
||
|
.catch((err) => {
|
||
|
const message = err && err.message;
|
||
|
if (message === 'unauthorized') {
|
||
|
res.statusCode = 401;
|
||
|
res.end('Unauthorized.');
|
||
|
} else if (message === 'timeout') {
|
||
|
res.statusCode = 408;
|
||
|
res.end('Request timeout.');
|
||
|
} else {
|
||
|
res.statusCode = 400;
|
||
|
res.end(wkhtmltopdfError || 'Unknown error.');
|
||
|
}
|
||
|
});
|
||
|
};
|