grover 0.13.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7084b165657179f7954b473653ff58cb634e0444177c95978168f1a481c2a423
4
- data.tar.gz: aa4497878b0d4eb55b0cd5703011db42c9d8f79fd65da906ce770956f59f032e
3
+ metadata.gz: ec752a494049fb8bfeeb78aec6a5a553f3c6502e869163492556d2bf386b7340
4
+ data.tar.gz: 8886bbe696f7f6b8510e8cf209c334c67540e560741162e503fb2505da18cc8c
5
5
  SHA512:
6
- metadata.gz: 95f0001bdba9e31e8025f0e4c693d7d2b60c20c3e51c11b91e8ed9934dfad160f0733db6ed40344b42a4c20a59d5990a346195d08539c7098311f5de095d7b7a
7
- data.tar.gz: 14a75499dd49dfa219b3dd914f2cb82601a0151a06c325805f3419a2836a228707cf1e63e9bfbecb0253be850b25134d1b8e2fbb6fe12a769b007c3c77f2f13a
6
+ metadata.gz: 1f0e2c7d80edc1a037b863ecc14cc117671d030602d1ff243114efd81ccae8e3dbbf31bc98a61b3f63158c68eb87097aaa7feb86215eaff007e336defe9efd5e
7
+ data.tar.gz: 97aa4ad8721ea2f689af1cb9cbcbeb429c663f35942ebcfcd5007c6e51df66def829965dac16a4a0ec002d93885c8a88a2515784abb024fb097edac922cbcea8
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018-2020 Studiosity
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/lib/grover.rb CHANGED
@@ -33,8 +33,8 @@ class Grover
33
33
  # see https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagepdfoptions
34
34
  #
35
35
  def initialize(url, options = {})
36
- @url = url
37
- @options = OptionsBuilder.new(options, url)
36
+ @url = url.to_s
37
+ @options = OptionsBuilder.new(options, @url)
38
38
  @root_path = @options.delete 'root_path'
39
39
  @front_cover_path = @options.delete 'front_cover_path'
40
40
  @back_cover_path = @options.delete 'back_cover_path'
@@ -50,6 +50,15 @@ class Grover
50
50
  processor.convert :pdf, @url, normalized_options(path: path)
51
51
  end
52
52
 
53
+ #
54
+ # Request URL with provided options and render HTML
55
+ #
56
+ # @return [String] The resulting HTML string
57
+ #
58
+ def to_html
59
+ processor.convert :content, @url, normalized_options(path: nil)
60
+ end
61
+
53
62
  #
54
63
  # Request URL with provided options and create screenshot
55
64
  #
@@ -135,7 +144,7 @@ class Grover
135
144
  end
136
145
 
137
146
  def normalized_options(path:)
138
- normalized_options = Utils.normalize_object @options
147
+ normalized_options = Utils.normalize_object @options, excluding: ['extraHTTPHeaders']
139
148
  normalized_options['path'] = path if path.is_a? ::String
140
149
  normalized_options
141
150
  end
@@ -5,13 +5,14 @@ class Grover
5
5
  # Configuration of the options for Grover HTML to PDF conversion
6
6
  #
7
7
  class Configuration
8
- attr_accessor :options, :meta_tag_prefix, :ignore_path,
8
+ attr_accessor :options, :meta_tag_prefix, :ignore_path, :root_url,
9
9
  :use_pdf_middleware, :use_png_middleware, :use_jpeg_middleware
10
10
 
11
11
  def initialize
12
12
  @options = {}
13
13
  @meta_tag_prefix = 'grover-'
14
14
  @ignore_path = nil
15
+ @root_url = nil
15
16
  @use_pdf_middleware = true
16
17
  @use_png_middleware = false
17
18
  @use_jpeg_middleware = false
@@ -66,12 +66,64 @@ const _processPage = (async (convertAction, urlOrHtml, options) => {
66
66
  requestOptions.timeout = timeout;
67
67
  }
68
68
 
69
+ // Setup user agent (if provided)
70
+ const userAgent = options.userAgent; delete options.userAgent;
71
+ if (userAgent !== undefined) {
72
+ await page.setUserAgent(userAgent);
73
+ }
74
+
69
75
  // Setup viewport options (if provided)
70
76
  const viewport = options.viewport; delete options.viewport;
71
77
  if (viewport !== undefined) {
72
78
  await page.setViewport(viewport);
73
79
  }
74
80
 
81
+ // If specified, emulate the media type
82
+ const emulateMedia = options.emulateMedia; delete options.emulateMedia;
83
+ if (emulateMedia !== undefined) {
84
+ if (typeof page.emulateMediaType === 'function') {
85
+ await page.emulateMediaType(emulateMedia);
86
+ } else {
87
+ await page.emulateMedia(emulateMedia);
88
+ }
89
+ }
90
+
91
+ // Emulate the media features, if specified
92
+ const mediaFeatures = options.mediaFeatures; delete options.mediaFeatures;
93
+ if (Array.isArray(mediaFeatures)) {
94
+ page.emulateMediaFeatures(mediaFeatures);
95
+ }
96
+
97
+ // Emulate timezone (if provided)
98
+ const timezone = options.timezone; delete options.timezone;
99
+ if (timezone !== undefined) {
100
+ await page.emulateTimezone(timezone);
101
+ }
102
+
103
+ // Emulate vision deficiency (if provided)
104
+ const visionDeficiency = options.visionDeficiency; delete options.visionDeficiency;
105
+ if (visionDeficiency !== undefined) {
106
+ page.emulateVisionDeficiency(type);
107
+ }
108
+
109
+ // Bypass CSP (content security policy), if provided
110
+ const bypassCSP = options.bypassCSP; delete options.bypassCSP;
111
+ if (bypassCSP !== undefined) {
112
+ page.setBypassCSP(bypassCSP);
113
+ }
114
+
115
+ // Add extra HTTP headers (if provided)
116
+ const extraHTTPHeaders = options.extraHTTPHeaders; delete options.extraHTTPHeaders;
117
+ if (extraHTTPHeaders !== undefined) {
118
+ page.setExtraHTTPHeaders(extraHTTPHeaders);
119
+ }
120
+
121
+ // Set geolocation (if provided)
122
+ const geolocation = options.geolocation; delete options.geolocation;
123
+ if (geolocation !== undefined) {
124
+ page.setGeolocation(geolocation);
125
+ }
126
+
75
127
  const waitUntil = options.waitUntil; delete options.waitUntil;
76
128
  if (urlOrHtml.match(/^http/i)) {
77
129
  // Request is for a URL, so request it
@@ -82,7 +134,7 @@ const _processPage = (async (convertAction, urlOrHtml, options) => {
82
134
  requestOptions.waitUntil = waitUntil || 'networkidle0';
83
135
  await page.setRequestInterception(true);
84
136
  page.once('request', request => {
85
- request.respond({ body: urlOrHtml });
137
+ request.respond({ body: urlOrHtml === '' ? ' ' : urlOrHtml });
86
138
  // Reset the request interception
87
139
  // (we only want to intercept the first request - ie our HTML)
88
140
  page.on('request', request => request.continue());
@@ -91,13 +143,19 @@ const _processPage = (async (convertAction, urlOrHtml, options) => {
91
143
  await page.goto(displayUrl || 'http://example.com', requestOptions);
92
144
  }
93
145
 
94
- // If specified, emulate the media type
95
- const emulateMedia = options.emulateMedia; delete options.emulateMedia;
96
- if (emulateMedia !== undefined) {
97
- if (typeof page.emulateMediaType == 'function') {
98
- await page.emulateMediaType(emulateMedia);
99
- } else {
100
- await page.emulateMedia(emulateMedia);
146
+ // add styles (if provided)
147
+ const styleTagOptions = options.styleTagOptions; delete options.styleTagOptions;
148
+ if (Array.isArray(styleTagOptions)) {
149
+ for (const styleTagOption of styleTagOptions) {
150
+ await page.addStyleTag(styleTagOption);
151
+ }
152
+ }
153
+
154
+ // add scripts (if provided)
155
+ const scriptTagOptions = options.scriptTagOptions; delete options.scriptTagOptions;
156
+ if (Array.isArray(scriptTagOptions)) {
157
+ for (const scriptTagOption of scriptTagOptions) {
158
+ await page.addScriptTag(scriptTagOption);
101
159
  }
102
160
  }
103
161
 
@@ -111,7 +169,25 @@ const _processPage = (async (convertAction, urlOrHtml, options) => {
111
169
  const waitForSelector = options.waitForSelector; delete options.waitForSelector;
112
170
  const waitForSelectorOptions = options.waitForSelectorOptions; delete options.waitForSelectorOptions;
113
171
  if (waitForSelector !== undefined) {
114
- await page.waitForSelector(waitForSelector, waitForSelectorOptions)
172
+ await page.waitForSelector(waitForSelector, waitForSelectorOptions);
173
+ }
174
+
175
+ // If specified, wait for timeout
176
+ const waitForTimeout = options.waitForTimeout; delete options.waitForTimeout;
177
+ if (waitForTimeout !== undefined) {
178
+ await page.waitForTimeout(waitForTimeout);
179
+ }
180
+
181
+ // If specified, focus on the specified selector
182
+ const focusSelector = options.focus; delete options.focus;
183
+ if (focusSelector !== undefined) {
184
+ await page.focus(focusSelector);
185
+ }
186
+
187
+ // If specified, hover on the specified selector
188
+ const hoverSelector = options.hover; delete options.hover;
189
+ if (hoverSelector !== undefined) {
190
+ await page.hover(hoverSelector);
115
191
  }
116
192
 
117
193
  // If we're running puppeteer in headless mode, return the converted PDF
@@ -10,11 +10,14 @@ class Grover
10
10
  # @see https://github.com/pdfkit/pdfkit
11
11
  #
12
12
  class Middleware # rubocop:disable Metrics/ClassLength
13
- def initialize(app)
13
+ def initialize(app, *args)
14
14
  @app = app
15
15
  @pdf_request = false
16
16
  @png_request = false
17
17
  @jpeg_request = false
18
+
19
+ @root_url =
20
+ args.last.is_a?(Hash) && args.last.key?(:root_url) ? args.last[:root_url] : Grover.configuration.root_url
18
21
  end
19
22
 
20
23
  def call(env)
@@ -34,13 +34,13 @@ class Grover
34
34
  def fix_boolean_options!
35
35
  fix_options!(
36
36
  'display_header_footer', 'full_page', 'landscape', 'omit_background', 'prefer_css_page_size',
37
- 'print_background', 'viewport.has_touch', 'viewport.is_landscape', 'viewport.is_mobile'
37
+ 'print_background', 'viewport.has_touch', 'viewport.is_landscape', 'viewport.is_mobile', 'bypass_csp'
38
38
  ) { |value| !FALSE_VALUES.include?(value) }
39
39
  end
40
40
 
41
41
  def fix_integer_options!
42
42
  fix_options!(
43
- 'viewport.height', 'viewport.width',
43
+ 'viewport.height', 'viewport.width', 'wait_for_timeout',
44
44
  &:to_i
45
45
  )
46
46
  end
@@ -48,6 +48,7 @@ class Grover
48
48
  def fix_float_options!
49
49
  fix_options!(
50
50
  'clip.height', 'clip.width', 'clip.x', 'clip.y', 'quality', 'scale', 'viewport.device_scale_factor',
51
+ 'geolocation.latitude', 'geolocation.longitude',
51
52
  &:to_f
52
53
  )
53
54
  end
@@ -19,6 +19,7 @@ class Grover
19
19
  ensure_packages_are_initiated
20
20
  result = call_js_method method, url_or_html, options
21
21
  return unless result
22
+ return result if result.is_a?(String)
22
23
 
23
24
  result['data'].pack('C*')
24
25
  ensure
@@ -76,7 +77,7 @@ class Grover
76
77
  @package_json ||= JSON.parse(File.read(package_json_path))
77
78
  end
78
79
 
79
- def call_js_method(method, url_or_html, options) # rubocop:disable Metrics/MethodLength
80
+ def call_js_method(method, url_or_html, options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
80
81
  stdin.puts JSON.dump([method, url_or_html, options])
81
82
  input = stdout.gets
82
83
  raise Errno::EPIPE, "Can't read from worker" if input.nil?
@@ -90,6 +91,8 @@ class Grover
90
91
  else
91
92
  raise Grover::JavaScript.const_get(error_class, false), message
92
93
  end
94
+ rescue JSON::ParserError
95
+ raise Grover::Error, 'Malformed worker response'
93
96
  rescue Errno::EPIPE, IOError
94
97
  raise Grover::Error, "Worker process failed:\n#{stderr.read}"
95
98
  end
data/lib/grover/utils.rb CHANGED
@@ -6,7 +6,9 @@ class Grover
6
6
  #
7
7
  class Utils
8
8
  ACRONYMS = {
9
- 'css' => 'CSS'
9
+ 'css' => 'CSS',
10
+ 'csp' => 'CSP',
11
+ 'http' => 'HTTP'
10
12
  }.freeze
11
13
  private_constant :ACRONYMS
12
14
 
@@ -41,11 +43,12 @@ class Grover
41
43
  # Copied from active support
42
44
  # @see active_support/core_ext/hash/keys.rb
43
45
  #
44
- def self.deep_transform_keys_in_object(object, &block)
46
+ def self.deep_transform_keys_in_object(object, excluding: [], &block) # rubocop:disable Metrics/MethodLength
45
47
  case object
46
48
  when Hash
47
49
  object.each_with_object({}) do |(key, value), result|
48
- result[yield(key)] = deep_transform_keys_in_object(value, &block)
50
+ new_key = yield(key)
51
+ result[new_key] = excluding.include?(new_key) ? value : deep_transform_keys_in_object(value, &block)
49
52
  end
50
53
  when Array
51
54
  object.map { |e| deep_transform_keys_in_object(e, &block) }
@@ -80,8 +83,8 @@ class Grover
80
83
  #
81
84
  # Recursively normalizes hash objects with camelized string keys
82
85
  #
83
- def self.normalize_object(object)
84
- deep_transform_keys_in_object(object) { |k| normalize_key(k) }
86
+ def self.normalize_object(object, excluding: [])
87
+ deep_transform_keys_in_object(object, excluding: excluding) { |k| normalize_key(k) }
85
88
  end
86
89
 
87
90
  #
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Grover
4
- VERSION = '0.13.1'
4
+ VERSION = '1.0.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grover
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Bromwich
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-29 00:00:00.000000000 Z
11
+ date: 2021-03-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: combine_pdf
@@ -163,6 +163,7 @@ executables: []
163
163
  extensions: []
164
164
  extra_rdoc_files: []
165
165
  files:
166
+ - LICENSE
166
167
  - lib/active_support_ext/object/deep_dup.rb
167
168
  - lib/active_support_ext/object/duplicable.rb
168
169
  - lib/grover.rb
@@ -191,7 +192,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
191
192
  version: 2.5.0
192
193
  - - "<"
193
194
  - !ruby/object:Gem::Version
194
- version: 2.8.0
195
+ version: 3.1.0
195
196
  required_rubygems_version: !ruby/object:Gem::Requirement
196
197
  requirements:
197
198
  - - ">="