<|start_filename|>lib/index.js<|end_filename|>
'use strict';

var app = require('electron').app;
var Promise = require('bluebird');
var express = require('express');
var isAbsoluteUrl = require('is-absolute-url');
var bodyParser = require('body-parser');
var pool = require('./pool');
var stringScheme = require('./stringScheme');
var util = require('util');



function readPrintingOptions(req) {
  var orientation = req.get('x-pdf-orientation');
  var noBackgrounds = req.get('x-pdf-no-backgrounds');
  var margins = req.get('x-pdf-margins');
  var pageSize = req.get('x-pdf-pageSize');

  var landscape = orientation ? orientation.toLowerCase() === 'landscape' : false;
  var marginsType = margins && {
    none: 1,
    minimum: 2
  }[margins.toLowerCase()] || 0; // default = 0

  return {
    landscape: landscape,
    pageSize: pageSize,
    printBackground: !noBackgrounds,
    marginsType: marginsType
  };
}


function printToPdf(url, options) {
  return pool.loadUrl(url, function (window) {
    // !!! Make sure to return a promise here that only resolves when the window
    // can be released again
    return Promise.fromNode(function (callback) {
      window.webContents.printToPDF(options, callback);
    });
  });
}

app.on('ready', function() {

  function printAndRespond(url, req, res, next) {
    var options = readPrintingOptions(req);
    return printToPdf(url, options)
      .then(function (pdf) {
        res.contentType('application/pdf').send(pdf);
      }, next);
  }

  express()
    .get('/*', function (req, res, next) {
      var url = req.url.replace(/^\//, '');
      if (!isAbsoluteUrl(url)) {
        return next();
      }

      printAndRespond(url, req, res, next);
    })
    .post('/', bodyParser.text({ type: '*/*' }), function (req, res, next) {
      Promise.using(stringScheme.withString(req.body || ''), function (url) {
        return printAndRespond(url, req, res, next);
      });
    })
    .use(function (err, req, res, next) { //eslint-disable-line no-unused-vars
      if (err instanceof pool.StatusError) {
        return res.status(400)
          .send(util.format('Url responded with %d', err.code));
      }

      if (err instanceof pool.CefError) {
        return res.status(400)
          .send(util.format('Request failed "%s"', err.code));
      }

      console.error(err.stack);
      res.sendStatus(500);
    })
    .use(function (req, res) {
      res.sendStatus(404);
    })
    .listen(3000, function () {
      console.log('listening');
    });

});

app.on('before-quit', function (event) {
  // Prevent the app from quitting when the last window closes
  event.preventDefault();
});


<|start_filename|>lib/stringScheme.js<|end_filename|>
'use strict';

var Promise = require('bluebird');
var uuid = require('node-uuid');
var util = require('util');

var SCHEME_NAME = 'string';

function memoize(fn, context) {
  var valuePromise;
  return function () {
    if (!valuePromise) {
      valuePromise = fn.apply(context || this, arguments);
    }
    return valuePromise;
  };
}


var strings = {};

var ensureInit = memoize(function init() {
  // (must be loaded after app 'ready' event)
  var protocol = require('electron').protocol;
  var registerProtocol = Promise.promisify(protocol.registerStringProtocol, protocol);

  return registerProtocol(SCHEME_NAME, function(request) {
    var string = strings[request.url];
    if (!string) {
      return new protocol.RequestErrorJob(500);
    }
    return {
      mimeType: 'text/html',
      data: string
    };
  });
});



function withString(string) {
  var id = uuid.v4();
  var url = util.format('%s://%s', SCHEME_NAME, id);
  strings[url] = string;
  return ensureInit()
    .return(url)
    .disposer(function (toDisposeUrl) {
      delete strings[toDisposeUrl];
    });
}

exports.withString = withString;


<|start_filename|>lib/CefError.js<|end_filename|>
'use strict';

var util = require('util');

var codes = {
  '-3': 'ERR_ABORTED',
  '-10': 'ERR_ACCESS_DENIED',
  '-108': 'ERR_ADDRESS_INVALID',
  '-109': 'ERR_ADDRESS_UNREACHABLE',
  '-400': 'ERR_CACHE_MISS',
  '-202': 'ERR_CERT_AUTHORITY_INVALID',
  '-200': 'ERR_CERT_COMMON_NAME_INVALID',
  '-203': 'ERR_CERT_CONTAINS_ERRORS',
  '-201': 'ERR_CERT_DATE_INVALID',
  '-208': 'ERR_CERT_END',
  '-207': 'ERR_CERT_INVALID',
  '-204': 'ERR_CERT_NO_REVOCATION_MECHANISM',
  '-206': 'ERR_CERT_REVOKED',
  '-205': 'ERR_CERT_UNABLE_TO_CHECK_REVOCATION',
  '-103': 'ERR_CONNECTION_ABORTED',
  '-100': 'ERR_CONNECTION_CLOSED',
  '-104': 'ERR_CONNECTION_FAILED',
  '-102': 'ERR_CONNECTION_REFUSED',
  '-101': 'ERR_CONNECTION_RESET',
  '-301': 'ERR_DISALLOWED_URL_SCHEME',
  '-324': 'ERR_EMPTY_RESPONSE',
  '-2': 'ERR_FAILED',
  '-6': 'ERR_FILE_NOT_FOUND',
  '-8': 'ERR_FILE_TOO_BIG',
  '-501': 'ERR_INSECURE_RESPONSE',
  '-106': 'ERR_INTERNET_DISCONNECTED',
  '-4': 'ERR_INVALID_ARGUMENT',
  '-321': 'ERR_INVALID_CHUNKED_ENCODING',
  '-5': 'ERR_INVALID_HANDLE',
  '-320': 'ERR_INVALID_RESPONSE',
  '-300': 'ERR_INVALID_URL',
  '-322': 'ERR_METHOD_NOT_SUPPORTED',
  '-105': 'ERR_NAME_NOT_RESOLVED',
  '-112': 'ERR_NO_SSL_VERSIONS_ENABLED',
  '-11': 'ERR_NOT_IMPLEMENTED',
  '-325': 'ERR_RESPONSE_HEADERS_TOO_BIG',
  '-110': 'ERR_SSL_CLIENT_AUTH_CERT_NEEDED',
  '-107': 'ERR_SSL_PROTOCOL_ERROR',
  '-114': 'ERR_SSL_RENEGOTIATION_REQUESTED',
  '-113': 'ERR_SSL_VERSION_OR_CIPHER_MISMATCH',
  '-7': 'ERR_TIMED_OUT',
  '-310': 'ERR_TOO_MANY_REDIRECTS',
  '-111': 'ERR_TUNNEL_CONNECTION_FAILED',
  '-9': 'ERR_UNEXPECTED',
  '-323': 'ERR_UNEXPECTED_PROXY_AUTH',
  '-302': 'ERR_UNKNOWN_URL_SCHEME',
  '-312': 'ERR_UNSAFE_PORT',
  '-311': 'ERR_UNSAFE_REDIRECT'
};

function CefError(code) {
  this.name = 'CefError';
  this.code = codes[code] || null;
  this.message = util.format('CEF error "%s"', this.code);
  Error.captureStackTrace(this, CefError);
}
util.inherits(CefError, Error);

module.exports = CefError;


<|start_filename|>Dockerfile<|end_filename|>
FROM iojs:3.0

WORKDIR /opt

RUN apt-get update && \
  apt-get install -y libgconf2-4 libxtst6 libnss3 libasound2 xvfb dbus-x11 libgtk2.0-common libxss1
RUN npm install -g electron-prebuilt

COPY ./package.json /opt/package.json
RUN npm install

COPY ./run.sh /opt/run.sh
COPY ./lib/ /opt/lib/

CMD [ "sh", "/opt/run.sh" ]


<|start_filename|>lib/pool.js<|end_filename|>
'use strict';

var BrowserWindow = require('electron').BrowserWindow;
var Promise = require('bluebird');
var genericPool = require('generic-pool');
var util = require('util');
var CefError = require('./CefError');
var urlUtil = require('url');

function StatusError (code) {
  this.name = 'StatusError';
  this.code = code;
  this.message = util.format('Bad status %d', code);
  Error.captureStackTrace(this, StatusError);
}
util.inherits(StatusError, Error);

function waitUntilLoaded (window, timeout) {
  var onStopLoading;
  return Promise.fromNode(function (callback) {
    onStopLoading = function () {
      callback(null, window);
    };
    window.webContents.on('did-stop-loading', onStopLoading);

  })
    .timeout(timeout || 60000)
    .finally(function () {
      // clean up so we can reuse the window
      window.webContents.removeListener('did-stop-loading', onStopLoading);
    })
    .catch(Promise.TimeoutError, function (err) {
      window.webContents.stop();
      throw err;
    });
}

function normalizeUrl (url) {
  if (typeof url !== 'string') {
    return url;
  }
  return urlUtil.format(urlUtil.parse(url));
}

function sameUrl (url1, url2) {
  return normalizeUrl(url1) === normalizeUrl(url2);
}

function isGoodStatus (code) {
  return code === 200;
}

function loadUrlInWindow (window, url) {
  url = normalizeUrl(url);
  var error = null;
  var onResponseDetails = function (_1, _2, _3, originalUrl, statusCode) {
    if (sameUrl(originalUrl, url) && !isGoodStatus(statusCode)) {
      error = error || new StatusError(statusCode);
      window.webContents.stop();
    }
  };
  var onResponseFailLoad = function (event, code, msg, originalUrl) {
    if (sameUrl(originalUrl, url)) {
      error = error || new CefError(code);
      window.webContents.stop();
    }
  };
  window.webContents.on('did-get-response-details', onResponseDetails);
  window.webContents.on('did-fail-load', onResponseFailLoad);

  window.loadURL(url);
  return waitUntilLoaded(window)
    .then(function (loadedWindow) {
      if (error) {
        throw error;
      }
      return loadedWindow;
    })
    .finally(function () {
      window.webContents.removeListener('did-get-response-details', onResponseDetails);
      window.webContents.removeListener('did-fail-load', onResponseFailLoad);
    });
}

var windowPool = new genericPool.Pool({
  name: 'windows',
  create: function (callback) {
    callback(null, new BrowserWindow({
      width: 800,
      height: 600,
      show: false,
      'skip-taskbar': true,
      'node-integration': false,
      'web-preferences': {
        'page-visibility': true
      }
    }));
  },
  validate: function (window) {
    return !window.webContents.isCrashed();
  },
  destroy: function (window) {
    window.destroy();
  },
  max: 5
});
var acquireWindow = Promise.promisify(windowPool.acquire, windowPool);

function loadUrl (url, doWork) {
  return acquireWindow()
    .then(function (window) {
      return loadUrlInWindow(window, url)
        .then(function () {
          return doWork(window);
        })
        .finally(function () {
          windowPool.release(window);
        });
    });
}

exports.loadUrl = loadUrl;
exports.CefError = CefError;
exports.StatusError = StatusError;
