diff --git a/lib/Dhalang.rb b/lib/Dhalang.rb index 4ac1c64..ca8ac5b 100644 --- a/lib/Dhalang.rb +++ b/lib/Dhalang.rb @@ -5,7 +5,8 @@ module Dhalang require_relative 'Dhalang/url_utils' require_relative 'Dhalang/file_utils' require_relative 'Dhalang/error' - require_relative 'Dhalang/puppeteer' + require_relative 'Dhalang/configuration' + require_relative 'Dhalang/node_script_invoker' require 'uri' require 'tempfile' require 'shellwords' diff --git a/lib/Dhalang/configuration.rb b/lib/Dhalang/configuration.rb new file mode 100644 index 0000000..7d25627 --- /dev/null +++ b/lib/Dhalang/configuration.rb @@ -0,0 +1,87 @@ +module Dhalang + # Groups Puppeteer and Dhalang configuration. + class Configuration + NODE_MODULES_PATH = Dir.pwd + '/node_modules/'.freeze + USER_OPTIONS = { + navigationTimeout: 10000, + printToPDFTimeout: 0, # unlimited + navigationWaitUntil: 'load', + navigationWaitForSelector: '', + navigationWaitForXPath: '', + userAgent: '', + isHeadless: true, + viewPort: '', + httpAuthenticationCredentials: '', + isAutoHeight: false, + chromeOptions: [] + }.freeze + DEFAULT_PDF_OPTIONS = { + scale: 1, + displayHeaderFooter: false, + headerTemplate: '', + footerTemplate: '', + headerTemplateFile: '', + footerTemplateFile: '', + printBackground: true, + landscape: false, + pageRanges: '', + format: 'A4', + width: '', + height: '', + margin: { top: 36, right: 36, bottom: 20, left: 36 }, + preferCSSPageSize: true, + omitBackground: false + }.freeze + DEFAULT_SCREENSHOT_OPTIONS = { + fullPage: true, + clip: nil, + omitBackground: false + }.freeze + DEFAULT_JPEG_OPTIONS = { + quality: 100 + }.freeze + + private_constant :NODE_MODULES_PATH + private_constant :USER_OPTIONS + private_constant :DEFAULT_PDF_OPTIONS + private_constant :DEFAULT_SCREENSHOT_OPTIONS + private_constant :DEFAULT_JPEG_OPTIONS + + private attr_accessor :page_url + private attr_accessor :temp_file_path + private attr_accessor :temp_file_extension + private attr_accessor :user_options + private attr_accessor :pdf_options + private attr_accessor :screenshot_options + private attr_accessor :jpeg_options + + # @param [Hash] custom_options Changes that should override default. + # @param [String] page_url Url for Puppeteer to visit. + # @param [String] temp_file_path Absolute path of temp file to write results of scripts towards. + # Can be nil for scripts using stdout. + # @param [String] temp_file_extension Extension of temp file. Can be nil for scripts using stdout. + def initialize(custom_options, page_url, temp_file_path = nil, temp_file_extension = nil) + self.page_url = page_url + self.temp_file_path = temp_file_path + self.temp_file_extension = temp_file_extension + self.user_options = USER_OPTIONS.map { |key, default_value| [key, custom_options.fetch(key, default_value)] } + self.pdf_options = DEFAULT_PDF_OPTIONS.map { |key, default_value| [key, custom_options.fetch(key, default_value)] } + self.screenshot_options = DEFAULT_SCREENSHOT_OPTIONS.map { |key, default_value| [key, custom_options.fetch(key, default_value)] } + self.jpeg_options = DEFAULT_JPEG_OPTIONS.map { |key, default_value| [key, custom_options.fetch(key, default_value)] } + end + + # Returns configuration as JSON string. + def json + return { + webPageUrl: page_url, + tempFilePath: temp_file_path, + puppeteerPath: NODE_MODULES_PATH, + imageType: temp_file_extension, + userOptions: user_options.to_h, + pdfOptions: pdf_options.to_h, + screenshotOptions: screenshot_options.to_h, + jpegOptions: jpeg_options.to_h + }.to_json + end + end +end \ No newline at end of file diff --git a/lib/Dhalang/node_script_invoker.rb b/lib/Dhalang/node_script_invoker.rb new file mode 100644 index 0000000..b649164 --- /dev/null +++ b/lib/Dhalang/node_script_invoker.rb @@ -0,0 +1,28 @@ +module Dhalang + class NodeScriptInvoker + + # Executes JS script under given script_path by launching a new Node process. + # + # @param [String] script_path Absolute path of the JS script to execute. + # @param [Configuration] configuration Configuration to use by j. + def self.execute_script(script_path, configuration) + command = create_node_command(script_path, configuration) + Open3.popen2e(command) do |_stdin, stdouterr, wait| + return nil if wait.value.success? + + output = stdouterr.read.strip + output = nil if output == '' + message = output || "Exited with status #{wait.value.exitstatus}" + raise DhalangError, message + end + end + + # Returns a [String] with the node command to invoke the provided script with the configuration. + # + # @param [String] script_path Absolute path of JS script to invoke. + # @param [Object] configuration JSON with options to use for Puppeteer. + private_class_method def self.create_node_command(script_path, configuration) + "node #{script_path} #{Shellwords.escape(configuration.json)}" + end + end +end diff --git a/lib/Dhalang/puppeteer.rb b/lib/Dhalang/puppeteer.rb deleted file mode 100644 index 70dbb38..0000000 --- a/lib/Dhalang/puppeteer.rb +++ /dev/null @@ -1,97 +0,0 @@ -module Dhalang - # Contains common logic for interacting with Puppeteer. - class Puppeteer - NODE_MODULES_PATH = Dir.pwd + '/node_modules/'.freeze - private_constant :NODE_MODULES_PATH - - USER_OPTIONS = { - navigationTimeout: 10000, - printToPDFTimeout: 0, # unlimited - navigationWaitUntil: 'load', - navigationWaitForSelector: '', - navigationWaitForXPath: '', - userAgent: '', - isHeadless: true, - viewPort: '', - httpAuthenticationCredentials: '', - isAutoHeight: false, - chromeOptions: [] - } - private_constant :USER_OPTIONS - - DEFAULT_PDF_OPTIONS = { - scale: 1, - displayHeaderFooter: false, - headerTemplate: '', - footerTemplate: '', - headerTemplateFile: '', - footerTemplateFile: '', - printBackground: true, - landscape: false, - pageRanges: '', - format: 'A4', - width: '', - height: '', - margin: { top: 36, right: 36, bottom: 20, left: 36 }, - preferCSSPageSize: true, - omitBackground: false - } - private_constant :DEFAULT_PDF_OPTIONS - - DEFAULT_SCREENSHOT_OPTIONS = { - fullPage: true, - clip: nil, - omitBackground: false - } - private_constant :DEFAULT_SCREENSHOT_OPTIONS - - DEFAULT_JPEG_OPTIONS = { - quality: 100 - } - private_constant :DEFAULT_JPEG_OPTIONS - - - # Launches a new Node process, executing the (Puppeteer) script under the given script_path. - # - # @param [String] page_url The url to pass to the goTo method of Puppeteer. - # @param [String] script_path The absolute path of the JS script to execute. - # @param [String] temp_file_path The absolute path of the temp file to use to write any actions from Puppeteer. - # @param [String] temp_file_extension The extension of the temp file. - # @param [Object] options Set of options to use, configurable by the user. - def self.visit(page_url, script_path, temp_file_path, temp_file_extension, options) - configuration = create_configuration(page_url, script_path, temp_file_path, temp_file_extension, options) - - command = "node #{script_path} #{Shellwords.escape(configuration)}" - - Open3.popen2e(command) do |_stdin, stdouterr, wait| - return nil if wait.value.success? - - output = stdouterr.read.strip - output = nil if output == '' - message = output || "Exited with status #{wait.value.exitstatus}" - raise DhalangError, message - end - end - - - # Returns a JSON string with the configuration to use within the Puppeteer script. - # - # @param [String] page_url The url to pass to the goTo method of Puppeteer. - # @param [String] script_path The absolute path of the JS script to execute. - # @param [String] temp_file_path The absolute path of the temp file to use to write any actions from Puppeteer. - # @param [String] temp_file_extension The extension of the temp file. - # @param [Hash] options Set of options to use, configurable by the user. - private_class_method def self.create_configuration(page_url, script_path, temp_file_path, temp_file_extension, options) - { - webPageUrl: page_url, - tempFilePath: temp_file_path, - puppeteerPath: NODE_MODULES_PATH, - imageType: temp_file_extension, - userOptions: USER_OPTIONS.map { |option, value| [option, options.has_key?(option) ? options[option] : value]}.to_h, - pdfOptions: DEFAULT_PDF_OPTIONS.map { |option, value| [option, options.has_key?(option) ? options[option] : value] }.to_h, - screenshotOptions: DEFAULT_SCREENSHOT_OPTIONS.map { |option, value| [option, options.has_key?(option) ? options[option] : value] }.to_h, - jpegOptions: DEFAULT_JPEG_OPTIONS.map { |option, value| [option, options.has_key?(option) ? options[option] : value] }.to_h - }.to_json - end - end -end diff --git a/lib/PDF.rb b/lib/PDF.rb index 7aef851..69412b6 100644 --- a/lib/PDF.rb +++ b/lib/PDF.rb @@ -1,8 +1,8 @@ module Dhalang # Allows consumers of this library to create PDFs with Puppeteer. class PDF - PUPPETEER_SCRIPT_PATH = File.expand_path('../js/pdf-generator.js', __FILE__).freeze - private_constant :PUPPETEER_SCRIPT_PATH + SCRIPT_PATH = File.expand_path('../js/pdf-generator.js', __FILE__).freeze + private_constant :SCRIPT_PATH # Captures the full webpage under the given url as PDF. # @@ -43,7 +43,8 @@ def self.get_from_html(html, options = {}) private_class_method def self.get(url, options) temp_file = FileUtils.create_temp_file("pdf") begin - Puppeteer.visit(url, PUPPETEER_SCRIPT_PATH, temp_file.path, "pdf", options) + configuration = Configuration.new(options, url, temp_file.path, "pdf") + NodeScriptInvoker.execute_script(SCRIPT_PATH, configuration) binary_pdf_content = FileUtils.read_binary(temp_file.path) ensure FileUtils.delete(temp_file) diff --git a/lib/Screenshot.rb b/lib/Screenshot.rb index 68f7092..4e0625e 100644 --- a/lib/Screenshot.rb +++ b/lib/Screenshot.rb @@ -1,9 +1,9 @@ module Dhalang # Allows consumers of this library to take screenshots with Puppeteer. class Screenshot - PUPPETEER_SCRIPT_PATH = File.expand_path('../js/screenshot-generator.js', __FILE__).freeze + SCRIPT_PATH = File.expand_path('../js/screenshot-generator.js', __FILE__).freeze IMAGE_TYPES = [:jpeg, :png, :webp].freeze - private_constant :PUPPETEER_SCRIPT_PATH + private_constant :SCRIPT_PATH private_constant :IMAGE_TYPES # DEPRECATED: Please use `get_from_url(url, :jpeg)` instead. @@ -44,7 +44,8 @@ def self.get_from_url(url, image_type, options = {}) temp_file = FileUtils.create_temp_file(image_type) begin - Puppeteer.visit(url, PUPPETEER_SCRIPT_PATH, temp_file.path, image_type, options) + configuration = Configuration.new(options, url, temp_file.path, image_type) + NodeScriptInvoker.execute_script(SCRIPT_PATH, configuration) binary_image_content = FileUtils.read_binary(temp_file.path) ensure FileUtils.delete(temp_file) diff --git a/lib/js/screenshot-generator.js b/lib/js/screenshot-generator.js index 3ed6aa1..7062b4a 100644 --- a/lib/js/screenshot-generator.js +++ b/lib/js/screenshot-generator.js @@ -20,7 +20,6 @@ const createScreenshot = async () => { ...configuration.screenshotOptions }); } catch (error) { - console.error(error.message); process.exit(1); } finally { if (browser) {