grover 0.14.1 → 1.0.2

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: b599a5462dc8008f92d9ea95c949d8de0cfd0786792b303c1e78f22a104a503d
4
- data.tar.gz: bb4dbd2d6d336e88f71668853b2d9819e314a87b3b55c7c4f211f3d94ddcfe25
3
+ metadata.gz: afca67d672e63071ee66a65dc2d2a65e728eb06715b2c59b43cf44dce587ab1c
4
+ data.tar.gz: 50313e3201d5f1bbbf8e6d9c92147c3ddc373fbdd51145ee3315a616b69b303c
5
5
  SHA512:
6
- metadata.gz: cc644ef00aa06d6ebb2e146165fe300f94c73749cffaa6e4e409d78e6ed5d376421a4163137557f551ea90ef984a52bda42f5db01bad1f81d03c5fb209ed5863
7
- data.tar.gz: 872dd3bde76e74f201d0dc52f46614434bdb52ea7ef43deb205783241c54a4c0f107f69812a9fa70f3af1e25a46edb3ac6e13e7d17bc51ba6f3b2223c0f69f92
6
+ metadata.gz: f8aa164f8b52ef008c70fe7ff4918194859db0be10cee102556cc67d49fe9cf5035ee181f9dac64dce87dfa7b7295effe090b851504dd9774e33a23ecab7e15e
7
+ data.tar.gz: cd5b46accc2bbbdc5f3d149d62099c4d47c36744aa877876966680e687bfa0fc69f9ad6045c0a4b9a1e3f135c90279ffd595d526f373daddda02a5fb42db754d
data/lib/grover.rb CHANGED
@@ -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
@@ -11,7 +11,7 @@ try {
11
11
  process.stdout.write("[\"ok\"]\n");
12
12
 
13
13
  const _processPage = (async (convertAction, urlOrHtml, options) => {
14
- let browser;
14
+ let browser, errors = [];
15
15
  try {
16
16
  const launchParams = {
17
17
  args: process.env.GROVER_NO_SANDBOX === 'true' ? ['--no-sandbox', '--disable-setuid-sandbox'] : []
@@ -66,12 +66,76 @@ 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
+
127
+ const raiseOnRequestFailure = options.raiseOnRequestFailure; delete options.raiseOnRequestFailure;
128
+ if (raiseOnRequestFailure) {
129
+ page.on('requestfinished', (request) => {
130
+ if (request.response() && !request.response().ok() && !request.redirectChain().length > 0) {
131
+ errors.push(request);
132
+ }
133
+ });
134
+ page.on('requestfailed', (request) => {
135
+ errors.push(request);
136
+ });
137
+ }
138
+
75
139
  const waitUntil = options.waitUntil; delete options.waitUntil;
76
140
  if (urlOrHtml.match(/^http/i)) {
77
141
  // Request is for a URL, so request it
@@ -91,13 +155,19 @@ const _processPage = (async (convertAction, urlOrHtml, options) => {
91
155
  await page.goto(displayUrl || 'http://example.com', requestOptions);
92
156
  }
93
157
 
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);
158
+ // add styles (if provided)
159
+ const styleTagOptions = options.styleTagOptions; delete options.styleTagOptions;
160
+ if (Array.isArray(styleTagOptions)) {
161
+ for (const styleTagOption of styleTagOptions) {
162
+ await page.addStyleTag(styleTagOption);
163
+ }
164
+ }
165
+
166
+ // add scripts (if provided)
167
+ const scriptTagOptions = options.scriptTagOptions; delete options.scriptTagOptions;
168
+ if (Array.isArray(scriptTagOptions)) {
169
+ for (const scriptTagOption of scriptTagOptions) {
170
+ await page.addScriptTag(scriptTagOption);
101
171
  }
102
172
  }
103
173
 
@@ -111,7 +181,49 @@ const _processPage = (async (convertAction, urlOrHtml, options) => {
111
181
  const waitForSelector = options.waitForSelector; delete options.waitForSelector;
112
182
  const waitForSelectorOptions = options.waitForSelectorOptions; delete options.waitForSelectorOptions;
113
183
  if (waitForSelector !== undefined) {
114
- await page.waitForSelector(waitForSelector, waitForSelectorOptions)
184
+ await page.waitForSelector(waitForSelector, waitForSelectorOptions);
185
+ }
186
+
187
+ // If specified, wait for function
188
+ const waitForFunction = options.waitForFunction; delete options.waitForFunction;
189
+ const waitForFunctionOptions = options.waitForFunctionOptions; delete options.waitForFunctionOptions;
190
+ if (waitForFunction !== undefined) {
191
+ await page.waitForFunction(waitForFunction, waitForFunctionOptions);
192
+ }
193
+
194
+ // If specified, wait for timeout
195
+ const waitForTimeout = options.waitForTimeout; delete options.waitForTimeout;
196
+ if (waitForTimeout !== undefined) {
197
+ await page.waitForTimeout(waitForTimeout);
198
+ }
199
+
200
+ // If specified, focus on the specified selector
201
+ const focusSelector = options.focus; delete options.focus;
202
+ if (focusSelector !== undefined) {
203
+ await page.focus(focusSelector);
204
+ }
205
+
206
+ // If specified, hover on the specified selector
207
+ const hoverSelector = options.hover; delete options.hover;
208
+ if (hoverSelector !== undefined) {
209
+ await page.hover(hoverSelector);
210
+ }
211
+
212
+ if (errors.length > 0) {
213
+ function RequestFailedError(errors) {
214
+ this.name = "RequestFailedError";
215
+ this.message = errors.map(e => {
216
+ if (e.failure()) {
217
+ return e.failure().errorText + " at " + e.url();
218
+ } else if (e.response() && e.response().status()) {
219
+ return e.response().status() + " " + e.url();
220
+ } else {
221
+ return "UnknownError " + e.url()
222
+ }
223
+ }).join("\n");
224
+ }
225
+ RequestFailedError.prototype = Error.prototype;
226
+ throw new RequestFailedError(errors);
115
227
  }
116
228
 
117
229
  // If we're running puppeteer in headless mode, return the converted PDF
@@ -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
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.14.1'
4
+ VERSION = '1.0.2'
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.14.1
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Bromwich
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-16 00:00:00.000000000 Z
11
+ date: 2021-07-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: combine_pdf
@@ -181,7 +181,7 @@ homepage: https://github.com/Studiosity/grover
181
181
  licenses:
182
182
  - MIT
183
183
  metadata: {}
184
- post_install_message:
184
+ post_install_message:
185
185
  rdoc_options: []
186
186
  require_paths:
187
187
  - lib
@@ -199,8 +199,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
199
199
  - !ruby/object:Gem::Version
200
200
  version: '0'
201
201
  requirements: []
202
- rubygems_version: 3.2.3
203
- signing_key:
202
+ rubygems_version: 3.0.6
203
+ signing_key:
204
204
  specification_version: 4
205
205
  summary: A Ruby gem to transform HTML into PDF, PNG or JPEG by wrapping the NodeJS
206
206
  Google Puppeteer driver for Chromium