renderscreenshot 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 866ece0d8812f0d70751db71d9746530c4e4b0f95a39624f569672b93688137b
4
- data.tar.gz: a6d705614f59526337dc55e852075ed21030f66e2a71c17a94895db00386993a
3
+ metadata.gz: 31b7d11d24a55a5e7ffae6e8ba80c81433d20b81ae82d95866eb56acb3e8613f
4
+ data.tar.gz: d3a1dc0bb1a01470d761ff05873fdaa9d8ae402ac56c9f0349705a09d685a08b
5
5
  SHA512:
6
- metadata.gz: f8a11440d6feb0739526fe945732ed2c12e0e0130476c48b6c96d544d98fa70b41c1a9d982e621a42046f42cf0d2bd4b8ffe2d1ef2c618163318a8a4e3b3355d
7
- data.tar.gz: 11cc424b00ae6727181f580118644a01aa2bb29ddf7554d8a8768e7ebf6bdf410cf862b8728abee757d6d484f30d76fd7f0973ff8e4b31a349249579f1885d48
6
+ metadata.gz: 380f44cd4faee08b39073403dcff73167a02df5acc368b426402d412da18503dbe3b8a03c477bfa2053916f5d5820a019458aff83fd7598bb908aab453c22869
7
+ data.tar.gz: b756575692352f742ba194cfc653cdcd95c883e114bae180053fb3d4c983e67b3cd0a6f217d14dcb75e24030b1e0e511b8fcbed05342af95196e3ebb84f12ad7
data/CHANGELOG.md CHANGED
@@ -5,6 +5,33 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.1] - 2026-10-18
9
+
10
+ ### Added
11
+
12
+ - `TakeOptions#cache_available` sets `cache.available` (GET: `cache_available`).
13
+ The API reuses a cached screenshot only when this is set.
14
+ - `TakeOptions#to_query_params` returns the flat GET parameters as a hash.
15
+
16
+ ### Fixed
17
+
18
+ - `pdf_print_background` now sends `pdf.print_background`. Before, it sent
19
+ `pdf.background`, which the API ignores.
20
+ - `pdf_margin_top`/`_right`/`_bottom`/`_left` now send flat `pdf.margin_top`,
21
+ `pdf.margin_right`, `pdf.margin_bottom` and `pdf.margin_left`. Before, they
22
+ sent a nested `pdf.margin` hash, which the API drops. `pdf_margin` still
23
+ sends `pdf.margin`, and the per-side values can be combined with it.
24
+ - `device` is now sent as the top-level `device` parameter, which POST and GET
25
+ both read. Before, it was sent as `viewport.device`.
26
+ - `generate_url` now uses the GET parameter names (`selector`, `timeout`,
27
+ `block_cookies`, `pdf_paper`, ...). Before, it flattened the builder's own
28
+ names (`element`, `wait_for`, `block_cookie_banners`, ...), which GET does
29
+ not read. Options that GET cannot express are left out of the URL.
30
+ - `to_query_string` covers every GET parameter, and its pairs are sorted and
31
+ encoded like Rails `to_query`.
32
+ - Docs: `wait_for_timeout` is in seconds (the API clamps it to 1-60), not
33
+ milliseconds. The devices example reads `viewport.width`/`viewport.height`.
34
+
8
35
  ## [1.0.0] - 2026-02-24
9
36
 
10
37
  ### Added
data/README.md CHANGED
@@ -120,6 +120,7 @@ options = RenderScreenshot::TakeOptions
120
120
  .wait_for('networkidle')
121
121
  .delay(500)
122
122
  .wait_for_selector('.loaded')
123
+ .wait_for_timeout(30) # seconds; the API clamps it to 1-60
123
124
  # Blocking
124
125
  .block_ads
125
126
  .block_trackers
@@ -188,13 +189,40 @@ options = RenderScreenshot::TakeOptions
188
189
  .url('https://example.com')
189
190
  .preset('og_card')
190
191
 
191
- # URL expires in 24 hours
192
+ # URL expires in 24 hours (the API accepts at most 30 days)
192
193
  signed_url = client.generate_url(options, expires_at: Time.now + 86400)
193
194
 
194
195
  # Use in HTML
195
196
  # <img src="#{signed_url}" />
196
197
  ```
197
198
 
199
+ Signing needs your public key ID (`rs_pub_*`) and secret key (`rs_secret_*`):
200
+ pass `signing_key:` and `public_key_id:` to `Client.new` or to `generate_url`.
201
+
202
+ A signed URL is a `GET /v1/screenshot` request, so it carries only the options
203
+ that GET reads as query parameters: `url`, `html`, `preset`, `device`, `width`,
204
+ `height`, `scale`, `full_page`, `element` (sent as `selector`), `format`,
205
+ `quality`, `delay`, `wait_for_timeout` (sent as `timeout`), `click`, `hide`,
206
+ `remove`, `block_ads`, `block_cookie_banners` (sent as `block_cookies`),
207
+ `dark_mode`, `user_agent`, `locale`, `timezone`, `cache_ttl`, `cache_refresh`,
208
+ `cache_available` and the `pdf_*` options. Other options (for example `mobile`,
209
+ `wait_for`, `wait_for_selector`, `block_trackers`, network and storage options)
210
+ are left out of the URL. Use `take` or `take_json` (POST) for those.
211
+ `TakeOptions#to_query_params` and `#to_query_string` give the same mapping.
212
+
213
+ ### Cache Reuse
214
+
215
+ The API serves a previous screenshot (0 credits) only when the request sets
216
+ `cache_available`. Otherwise every request renders a new screenshot.
217
+
218
+ ```ruby
219
+ options = RenderScreenshot::TakeOptions
220
+ .url('https://example.com')
221
+ .preset('og_card')
222
+ .cache_available # reuse a render with the same options
223
+ .cache_ttl(86_400) # look back up to 24 hours (seconds)
224
+ ```
225
+
198
226
  ### Cache Management
199
227
 
200
228
  ```ruby
@@ -231,7 +259,7 @@ preset = client.preset('og_card')
231
259
 
232
260
  # List all devices
233
261
  devices = client.devices
234
- devices.each { |d| puts "#{d['id']}: #{d['name']} (#{d['width']}x#{d['height']})" }
262
+ devices.each { |d| puts "#{d['id']}: #{d['name']} (#{d['viewport']['width']}x#{d['viewport']['height']})" }
235
263
  ```
236
264
 
237
265
  ### Webhook Verification
@@ -311,10 +339,13 @@ Error properties:
311
339
  | **Page** | `inject_script`, `inject_style`, `click`, `hide`, `remove` |
312
340
  | **Browser** | `dark_mode`, `reduced_motion`, `media_type`, `user_agent`, `timezone`, `locale`, `geolocation` |
313
341
  | **Network** | `headers`, `cookies`, `auth_basic`, `auth_bearer`, `bypass_csp` |
314
- | **Cache** | `cache_ttl`, `cache_refresh` |
342
+ | **Cache** | `cache_ttl`, `cache_refresh`, `cache_available` |
315
343
  | **PDF** | `pdf_paper_size`, `pdf_width`, `pdf_height`, `pdf_landscape`, `pdf_margin`, `pdf_margin_top`, `pdf_margin_right`, `pdf_margin_bottom`, `pdf_margin_left`, `pdf_scale`, `pdf_print_background`, `pdf_page_ranges`, `pdf_header`, `pdf_footer`, `pdf_fit_one_page`, `pdf_prefer_css_page_size` |
316
344
  | **Storage** | `storage_enabled`, `storage_path`, `storage_acl` |
317
345
 
346
+ Units: `delay` is in milliseconds (max 10000). `wait_for_timeout` is in seconds
347
+ (the API clamps it to 1-60). `cache_ttl` is in seconds (3600-2592000).
348
+
318
349
  ## Development
319
350
 
320
351
  After checking out the repo:
@@ -349,7 +380,7 @@ The hooks include:
349
380
 
350
381
  ## Contributing
351
382
 
352
- Bug reports and pull requests are welcome on GitHub at https://github.com/renderscreenshot/renderscreenshot-ruby.
383
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Render-Screenshot/rs-ruby.
353
384
 
354
385
  ## License
355
386
 
@@ -9,7 +9,7 @@ module RenderScreenshot
9
9
  attr_reader :http
10
10
 
11
11
  # Initialize a new client
12
- # @param api_key [String] API key (rs_live_*, rs_test_*)
12
+ # @param api_key [String] API key (rs_live_*)
13
13
  # @param base_url [String, nil] Custom base URL
14
14
  # @param timeout [Integer, nil] Request timeout in seconds
15
15
  # @param signing_key [String, nil] Secret key for signed URLs (rs_secret_*)
@@ -64,11 +64,12 @@ module RenderScreenshot
64
64
  )
65
65
  end
66
66
 
67
- config = options.is_a?(TakeOptions) ? options.to_h : options
67
+ take_options = options.is_a?(TakeOptions) ? options : TakeOptions.from(options || {})
68
68
  expires = expires_at.is_a?(Time) ? expires_at.to_i : expires_at
69
69
 
70
- # Build query params in alphabetical order (required for signature verification)
71
- params = build_signed_params(config, key_id, expires)
70
+ # Signed URLs carry only the parameters GET /v1/screenshot reads.
71
+ # Pairs are sorted and encoded like Rails `to_query` (required for signature verification).
72
+ params = take_options.to_query_params.merge('expires' => expires.to_s, 'key_id' => key_id)
72
73
  query_string = params.sort.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
73
74
 
74
75
  # Sign with HMAC-SHA256 using the secret key
@@ -154,48 +155,5 @@ module RenderScreenshot
154
155
  raise ValidationError.invalid_request('Options must be a TakeOptions or Hash')
155
156
  end
156
157
  end
157
-
158
- def build_signed_params(config, key_id, expires)
159
- params = {}
160
-
161
- # Add expires first (alphabetically before key_id)
162
- params['expires'] = expires
163
-
164
- # Add key_id (public identifier)
165
- params['key_id'] = key_id
166
-
167
- # Add URL or html
168
- params['url'] = config[:url] if config[:url]
169
- params['html'] = config[:html] if config[:html]
170
-
171
- # Add other params
172
- flat_params = flatten_config(config)
173
- flat_params.each do |key, value|
174
- next if %w[url html].include?(key)
175
-
176
- params[key] = value
177
- end
178
-
179
- params
180
- end
181
-
182
- def flatten_config(config)
183
- result = {}
184
- config.each do |key, value|
185
- next if value.nil?
186
-
187
- str_key = key.to_s
188
- if value.is_a?(Hash)
189
- value.each do |sub_key, sub_value|
190
- result["#{str_key}_#{sub_key}"] = sub_value unless sub_value.nil?
191
- end
192
- elsif value.is_a?(Array)
193
- result[str_key] = value.join(',')
194
- else
195
- result[str_key] = value
196
- end
197
- end
198
- result
199
- end
200
158
  end
201
159
  end
@@ -72,6 +72,7 @@ module RenderScreenshot
72
72
  with(wait_for_selector: selector)
73
73
  end
74
74
 
75
+ # Maximum wait time in seconds (the API clamps it to 1-60)
75
76
  def wait_for_timeout(value)
76
77
  with(wait_for_timeout: value)
77
78
  end
@@ -192,6 +193,12 @@ module RenderScreenshot
192
193
  with(cache_refresh: value)
193
194
  end
194
195
 
196
+ # Serve a previous screenshot with the same options when one exists.
197
+ # The API reuses a cached render only when this is set.
198
+ def cache_available(value = true)
199
+ with(cache_available: value)
200
+ end
201
+
195
202
  # PDF methods
196
203
  def pdf_paper_size(value)
197
204
  with(pdf_paper_size: value)
@@ -280,13 +287,13 @@ module RenderScreenshot
280
287
  result = {}
281
288
 
282
289
  # Top-level params
283
- %i[url html preset].each do |key|
290
+ %i[url html preset device].each do |key|
284
291
  result[key] = config[key] if config.key?(key)
285
292
  end
286
293
 
287
294
  # Viewport group
288
295
  viewport = {}
289
- { width: :width, height: :height, scale: :scale, mobile: :mobile, device: :device }.each do |config_key, api_key|
296
+ { width: :width, height: :height, scale: :scale, mobile: :mobile }.each do |config_key, api_key|
290
297
  viewport[api_key] = config[config_key] if config.key?(config_key)
291
298
  end
292
299
  result[:viewport] = viewport unless viewport.empty?
@@ -357,6 +364,7 @@ module RenderScreenshot
357
364
  cache = {}
358
365
  cache[:ttl] = config[:cache_ttl] if config[:cache_ttl]
359
366
  cache[:refresh] = config[:cache_refresh] if config.key?(:cache_refresh)
367
+ cache[:available] = config[:cache_available] if config.key?(:cache_available)
360
368
  result[:cache] = cache unless cache.empty?
361
369
 
362
370
  # PDF group
@@ -366,25 +374,18 @@ module RenderScreenshot
366
374
  pdf[:height] = config[:pdf_height] if config[:pdf_height]
367
375
  pdf[:landscape] = config[:pdf_landscape] if config.key?(:pdf_landscape)
368
376
  pdf[:scale] = config[:pdf_scale] if config[:pdf_scale]
369
- pdf[:background] = config[:pdf_print_background] if config.key?(:pdf_print_background)
377
+ pdf[:print_background] = config[:pdf_print_background] if config.key?(:pdf_print_background)
370
378
  pdf[:page_ranges] = config[:pdf_page_ranges] if config[:pdf_page_ranges]
371
379
  pdf[:header] = config[:pdf_header] if config[:pdf_header]
372
380
  pdf[:footer] = config[:pdf_footer] if config[:pdf_footer]
373
381
  pdf[:fit_one_page] = config[:pdf_fit_one_page] if config.key?(:pdf_fit_one_page)
374
382
  pdf[:prefer_css_page_size] = config[:pdf_prefer_css_page_size] if config.key?(:pdf_prefer_css_page_size)
375
383
 
376
- # PDF margins - supports uniform margin (string like "2cm") or individual margins (hash)
377
- if config[:pdf_margin]
378
- # Uniform margin specified - use directly (string or hash)
379
- pdf[:margin] = config[:pdf_margin]
380
- else
381
- # Build margin hash from individual margin settings
382
- margin = {}
383
- margin[:top] = config[:pdf_margin_top] if config[:pdf_margin_top]
384
- margin[:right] = config[:pdf_margin_right] if config[:pdf_margin_right]
385
- margin[:bottom] = config[:pdf_margin_bottom] if config[:pdf_margin_bottom]
386
- margin[:left] = config[:pdf_margin_left] if config[:pdf_margin_left]
387
- pdf[:margin] = margin unless margin.empty?
384
+ # PDF margins: a uniform `margin` plus optional per-side overrides
385
+ pdf[:margin] = config[:pdf_margin] if config[:pdf_margin]
386
+ %i[top right bottom left].each do |side|
387
+ key = :"pdf_margin_#{side}"
388
+ pdf[:"margin_#{side}"] = config[key] if config[key]
388
389
  end
389
390
  result[:pdf] = pdf unless pdf.empty?
390
391
 
@@ -398,39 +399,44 @@ module RenderScreenshot
398
399
  result
399
400
  end
400
401
 
401
- # Convert to flat query string for GET requests
402
- def to_query_string
403
- params = []
404
-
405
- # Map config keys to query param names
406
- query_mappings = {
407
- url: 'url',
408
- html: 'html',
409
- preset: 'preset',
410
- width: 'width',
411
- height: 'height',
412
- device: 'device',
413
- scale: 'scale',
414
- full_page: 'full_page',
415
- element: 'selector',
416
- format: 'format',
417
- quality: 'quality',
418
- delay: 'delay',
419
- wait_for_timeout: 'timeout',
420
- block_ads: 'block_ads',
421
- block_cookie_banners: 'block_cookies',
422
- dark_mode: 'dark_mode',
423
- cache_ttl: 'cache_ttl'
424
- }
425
-
426
- query_mappings.each do |config_key, param_name|
427
- next unless config.key?(config_key)
402
+ # GET /v1/screenshot query parameter names, keyed by config key
403
+ QUERY_PARAM_NAMES = {
404
+ url: 'url', html: 'html', preset: 'preset', device: 'device',
405
+ width: 'width', height: 'height', scale: 'scale',
406
+ full_page: 'full_page', element: 'selector', format: 'format', quality: 'quality',
407
+ delay: 'delay', wait_for_timeout: 'timeout',
408
+ click: 'click', hide: 'hide', remove: 'remove',
409
+ block_ads: 'block_ads', block_cookie_banners: 'block_cookies',
410
+ dark_mode: 'dark_mode', user_agent: 'user_agent', locale: 'locale', timezone: 'timezone',
411
+ cache_ttl: 'cache_ttl', cache_refresh: 'cache_refresh', cache_available: 'cache_available',
412
+ pdf_paper_size: 'pdf_paper', pdf_width: 'pdf_width', pdf_height: 'pdf_height',
413
+ pdf_landscape: 'pdf_landscape', pdf_scale: 'pdf_scale', pdf_fit_one_page: 'pdf_fit_one_page',
414
+ pdf_margin: 'pdf_margin', pdf_margin_top: 'pdf_margin_top', pdf_margin_right: 'pdf_margin_right',
415
+ pdf_margin_bottom: 'pdf_margin_bottom', pdf_margin_left: 'pdf_margin_left',
416
+ pdf_print_background: 'pdf_print_background', pdf_prefer_css_page_size: 'pdf_prefer_css_page_size',
417
+ pdf_page_ranges: 'pdf_page_ranges', pdf_header: 'pdf_header', pdf_footer: 'pdf_footer'
418
+ }.freeze
419
+
420
+ # Convert to flat query parameters for GET requests and signed URLs.
421
+ #
422
+ # Uses the parameter names that GET /v1/screenshot reads. Options with no
423
+ # GET equivalent (for example mobile, wait_for, wait_for_selector,
424
+ # block_trackers, block_urls, media_type, network, storage and page
425
+ # injection options) are left out. Use take / take_json (POST) for those.
426
+ # @return [Hash{String => String}]
427
+ def to_query_params
428
+ QUERY_PARAM_NAMES.each_with_object({}) do |(config_key, param_name), params|
429
+ next unless config.key?(config_key) && !config[config_key].nil?
428
430
 
429
431
  value = config[config_key]
430
- params << "#{param_name}=#{CGI.escape(value.to_s)}"
432
+ params[param_name] = value.is_a?(Array) ? value.join(',') : value.to_s
431
433
  end
434
+ end
432
435
 
433
- params.join('&')
436
+ # Convert to a flat query string for GET requests.
437
+ # Pairs are sorted and values are form-encoded like Rails `to_query`.
438
+ def to_query_string
439
+ to_query_params.sort.map { |k, v| "#{CGI.escape(k)}=#{CGI.escape(v)}" }.join('&')
434
440
  end
435
441
 
436
442
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RenderScreenshot
4
- VERSION = '1.0.0'
4
+ VERSION = '1.0.1'
5
5
  end
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: renderscreenshot
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
- - Michael Brooks
7
+ - Simon Chiu
8
8
  bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
@@ -32,7 +32,7 @@ dependencies:
32
32
  description: A developer-friendly screenshot API for capturing web pages programmatically.
33
33
  Create social cards, link previews, documentation screenshots, and more.
34
34
  email:
35
- - michael@renderscreenshot.com
35
+ - simon@renderscreenshot.com
36
36
  executables: []
37
37
  extensions: []
38
38
  extra_rdoc_files: []
@@ -78,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
78
78
  - !ruby/object:Gem::Version
79
79
  version: '0'
80
80
  requirements: []
81
- rubygems_version: 3.7.2
81
+ rubygems_version: 4.0.20
82
82
  specification_version: 4
83
83
  summary: Official Ruby SDK for RenderScreenshot API
84
84
  test_files: []