cloudimage 0.2.0 → 0.6.0

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: abb64d67ac6a4a0d7e87cd0758c94497b1c31a96f4dcfc9a4d10d2b66e4460b6
4
- data.tar.gz: f12f312eea0653ee858bcc0c124a842a574ee6e63b820832c93f275687d463e3
3
+ metadata.gz: c02d5fa30289aa70eb6b0d94e13f31130e5c1918ffb18d45197bb0b62cda26de
4
+ data.tar.gz: ce5da501f8200577f7c9003dba581b59f88628c99a03d54fa662ed0f336737fd
5
5
  SHA512:
6
- metadata.gz: ec3ede4d88539362085fe46726453aa9fca339bb875a2071c897d4797a093aef6caec2805e2927f363d0ea27f21ef319634513fc855e748b76366399840cb6aa
7
- data.tar.gz: d292ff52e7d4219bdbf3a0668c22f9c26da414bc187246eeb97caf550c80762a54462f5752da08312f94233caa87c5a09c156519e94093e1e3d0ee833cf4093b
6
+ metadata.gz: f698aefba14fee30249e29e431cf53d1e25440c97c08731ac4c050ba0bc58a95eeeac93143301fdd7cc31d7e96a4cdedac653aa832b61adf2f8c2a7681748d52
7
+ data.tar.gz: 3a58f037be0329dfbdfd4c1b0d0a71848caaf7d9c42fc1e9425d5f4c43015b81ee60cec29ef84cbf3318a6c195af851d82ab042d12166e7e98ae16bacff5bf2b
@@ -1,4 +1,38 @@
1
- ## master
1
+ # Changelog
2
+
3
+ ## [v0.5.0](https://github.com/scaleflex/cloudimage-rb/tree/v0.5.0) (2020-08-02)
4
+
5
+ [Full Changelog](https://github.com/scaleflex/cloudimage-rb/compare/v0.4.0...v0.5.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Invalidation [\#24](https://github.com/scaleflex/cloudimage-rb/pull/24) ([janklimo](https://github.com/janklimo))
10
+ - Add support for custom CNAMEs [\#21](https://github.com/scaleflex/cloudimage-rb/pull/21) ([janklimo](https://github.com/janklimo))
11
+
12
+ ## [v0.4.0](https://github.com/scaleflex/cloudimage-rb/tree/v0.4.0) (2020-07-19)
13
+
14
+ [Full Changelog](https://github.com/scaleflex/cloudimage-rb/compare/v0.3.0...v0.4.0)
15
+
16
+ **Implemented enhancements:**
17
+
18
+ - Add support for aliases [\#20](https://github.com/scaleflex/cloudimage-rb/pull/20) ([janklimo](https://github.com/janklimo))
19
+
20
+ ## [v0.3.0](https://github.com/scaleflex/cloudimage-rb/tree/v0.3.0) (2020-07-09)
21
+
22
+ [Full Changelog](https://github.com/scaleflex/cloudimage-rb/compare/v0.2.1...v0.3.0)
23
+
24
+ **Implemented enhancements:**
25
+
26
+ - Introduce URL sealing [\#10](https://github.com/scaleflex/cloudimage-rb/pull/10) ([janklimo](https://github.com/janklimo))
27
+
28
+ **Merged pull requests:**
29
+
30
+ - Use changelog generation [\#16](https://github.com/scaleflex/cloudimage-rb/pull/16) ([janklimo](https://github.com/janklimo))
31
+ - Add test coverage with SimpleCov [\#9](https://github.com/scaleflex/cloudimage-rb/pull/9) ([janklimo](https://github.com/janklimo))
32
+
33
+ ## 0.2.1 (2020-06-29)
34
+
35
+ - Include `force_download` param.
2
36
 
3
37
  ## 0.2.0 (2020-06-28)
4
38
 
@@ -19,3 +53,6 @@
19
53
 
20
54
  - Set up Github actions for CI.
21
55
  [#1](https://github.com/scaleflex/cloudimage-rb/pull/1) (@janklimo)
56
+
57
+
58
+ \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
data/README.md CHANGED
@@ -1,9 +1,8 @@
1
1
  # cloudimage
2
2
 
3
- ![](https://github.com/scaleflex/cloudimage-rb/workflows/Build/badge.svg)
3
+ [![Gem Version](https://badge.fury.io/rb/cloudimage.svg)](https://badge.fury.io/rb/cloudimage) ![Build status](https://github.com/scaleflex/cloudimage-rb/workflows/Build/badge.svg)
4
4
 
5
- `cloudimage` is the official Ruby API wrapper for
6
- [Cloudimage's API](https://docs.cloudimage.io/go/cloudimage-documentation-v7/en/introduction).
5
+ `cloudimage` is the official Ruby API wrapper for [Cloudimage's API][docs].
7
6
 
8
7
  Supports Ruby `2.4` and above, `JRuby`, and `TruffleRuby`.
9
8
 
@@ -12,14 +11,20 @@ Supports Ruby `2.4` and above, `JRuby`, and `TruffleRuby`.
12
11
  - [Usage](#usage)
13
12
  - [Hash of params](#hash-of-params)
14
13
  - [Chainable helpers](#chainable-helpers)
15
- - [Aliases](#aliases)
14
+ - [Method aliases](#method-aliases)
16
15
  - [Custom helpers](#custom-helpers)
16
+ - [URL aliases](#url-aliases)
17
+ - [`srcset` generation](#srcset-generation)
18
+ - [CNAME](#cname)
19
+ - [Optional API version](#optional-api-version)
17
20
  - [Security](#security)
21
+ - [URL signature](#url-signature)
22
+ - [URL sealing](#url-sealing)
23
+ - [Invalidation API](#invalidation-api)
18
24
  - [Development](#development)
19
- - [TODOs](#todos)
20
25
  - [Contributing](#contributing)
21
26
  - [License](#license)
22
- - [Code of Conduct](#code-of-conduct)
27
+ - [Showcase](#showcase)
23
28
 
24
29
  ## Installation
25
30
 
@@ -39,7 +44,7 @@ Or install it yourself as:
39
44
 
40
45
  ## Usage
41
46
 
42
- The only requirement to get started is your customer token. You can
47
+ The most common way to use Cloudimage is by means of your customer token. You can
43
48
  find it within your Admin interface:
44
49
 
45
50
  ![token](docs/token.png)
@@ -53,12 +58,16 @@ client = Cloudimage::Client.new(token: 'mysecrettoken')
53
58
 
54
59
  Cloudimage client accepts the following options:
55
60
 
56
- | Option | Required? | Additional info |
57
- | ------------------ | --------- | --------------------------------------------------- |
58
- | `token` | Yes | |
59
- | `salt` | No | See [Security](#security). |
60
- | `signature_length` | No | Integer value in the range `6..40`. Defaults to 18. |
61
- | `api_version` | No | Defaults to the current stable version. |
61
+ | Option | Type | Additional info |
62
+ | --------------------- | ------- | ------------------------------------------------------------------------------ |
63
+ | `token` | string | Required if `cname` is missing. |
64
+ | `cname` | string | Required if `token` is missing. See [CNAME](#cname). |
65
+ | `salt` | string | Optional. See [Security](#security). |
66
+ | `signature_length` | integer | Optional. Integer value in the range `6..40`. Defaults to 18. |
67
+ | `sign_urls` | boolean | Optional. Defaults to `true`. See [Security](#security). |
68
+ | `aliases` | hash | Optional. See [URL aliases](#url-aliases). |
69
+ | `api_key` | string | Optional. See [Invalidation API](#invalidation-api). |
70
+ | `include_api_version` | boolean | Optional. Defaults to true. See [Optional API version](#optional-api-version). |
62
71
 
63
72
  Calling `path` on the client object returns an instance of `Cloudimage::URI`.
64
73
  It accepts path to the image as a string and we we will use it to build
@@ -101,7 +110,7 @@ uri.heigth(200).to_url
101
110
  This is useful for catching typos and identifying deprecated methods in
102
111
  case Cloudimage's API changes.
103
112
 
104
- ### Aliases
113
+ ### Method aliases
105
114
 
106
115
  The gem comes with a handful of useful aliases. Consult
107
116
  [`Cloudimage::Params`](lib/cloudimage/params.rb) module for their full list.
@@ -119,8 +128,76 @@ need to accept arguments and will be translated into `param=1` in the final URL.
119
128
  For a list of custom helpers available to you, please consult
120
129
  [`Cloudimage::CustomHelpers`](lib/cloudimage/custom_helpers.rb) module.
121
130
 
131
+ ### URL aliases
132
+
133
+ Specify [aliases][aliases] to automatically replace parts of path with defined values.
134
+ Aliases is a hash which maps strings to be replaced with values to be used instead.
135
+
136
+ ```ruby
137
+ my_alias = 'https://store.s3-us-west-2.amazonaws.com/uploads'
138
+ client = Cloudimage::Client.new(token: 'token', aliases: { my_alias => '_uploads_' })
139
+ client.path('https://store.s3-us-west-2.amazonaws.com/uploads/image.jpg').to_url
140
+ # => "https://token.cloudimg.io/v7/_uploads_/image.jpg"
141
+ ```
142
+
143
+ [URL prefix][url-prefix] is just another form of URL alias. Simply make the target value an empty string:
144
+
145
+ ```ruby
146
+ prefix = 'https://store.s3-us-west-2.amazonaws.com/uploads/'
147
+ client = Cloudimage::Client.new(token: 'token', aliases: { prefix => '' })
148
+ client.path('https://store.s3-us-west-2.amazonaws.com/uploads/image.jpg').to_url
149
+ # => "https://token.cloudimg.io/v7/image.jpg"
150
+ ```
151
+
152
+ You don't need to specify an alias if the input to `path` is a URL that
153
+ already matches the base of the generated URL:
154
+
155
+ ```ruby
156
+ client = Cloudimage::Client.new(token: 'token')
157
+ client.path('https://token.cloudimg.io/v7/image.jpg').to_url(w: 200)
158
+ => "https://token.cloudimg.io/v7/image.jpg?w=200"
159
+ ```
160
+
161
+ ### `srcset` generation
162
+
163
+ Use the provided `to_srcset` method which also accepts any additional params to
164
+ be applied to the `srcset` URLs:
165
+
166
+ ```ruby
167
+ client = Cloudimage::Client.new(token: 'token')
168
+ client.path('/assets/image.jpg').to_srcset(blur: 5)
169
+ # => "https://token.cloudimg.io/v7/assets/image.jpg?blur=5&w=100 100w, https://token.cloudimg.io/v7/assets/image.jpg?blur=5&w=170 170w, https://token.cloudimg.io/v7/assets/image.jpg?blur=5&w=280 280w, https://token.cloudimg.io/v7/assets/image.jpg?blur=5&w=470 470w, https://token.cloudimg.io/v7/assets/image.jpg?blur=5&w=780 780w, https://token.cloudimg.io/v7/assets/image.jpg?blur=5&w=1300 1300w, https://token.cloudimg.io/v7/assets/image.jpg?blur=5&w=2170 2170w, https://token.cloudimg.io/v7/assets/image.jpg?blur=5&w=3620 3620w, https://token.cloudimg.io/v7/assets/image.jpg?blur=5&w=5760 5760w"
170
+ ```
171
+
172
+ A growth factor is applied to exponentially distribute widths between 100 and 5760 pixels.
173
+ See [`Cloudimage::Srcset`](/lib/cloudimage/srcset.rb) for implementation details.
174
+
175
+ ### CNAME
176
+
177
+ If you have a custom CNAME configured for your account, you can
178
+ use it to initialize the client:
179
+
180
+ ```ruby
181
+ client = Cloudimage::Client.new(cname: 'img.klimo.io')
182
+ client.path('/assets/image.jpg').to_url
183
+ # => 'https://img.klimo.io/v7/assets/image.jpg'
184
+ ```
185
+
186
+ ### Optional API version
187
+
188
+ If your account is configured to work without the API version component in the URL,
189
+ you can configure client not to include it in the generated URL:
190
+
191
+ ```ruby
192
+ client = Cloudimage::Client.new(cname: 'img.klimo.io', include_api_version: false)
193
+ client.path('/assets/image.jpg').to_url
194
+ # => "https://img.klimo.io/assets/image.jpg"
195
+ ```
196
+
122
197
  ### Security
123
198
 
199
+ #### URL signature
200
+
124
201
  If `salt` is defined, all URLs will be signed.
125
202
 
126
203
  You can control the length of the generated signature by specifying `signature_length`
@@ -133,6 +210,67 @@ uri.w(200).h(400).to_url
133
210
  # => "https://mysecrettoken.cloudimg.io/v7/assets/image.png?h=400&w=200&ci_sign=79cfbc458b"
134
211
  ```
135
212
 
213
+ #### URL sealing
214
+
215
+ Whereas URL signatures let you protect your URL from any kind of
216
+ tampering, URL sealing protects the params you specify while making
217
+ it possible to append additional params on the fly.
218
+
219
+ This is useful when working with Cloudimage's [responsive frontend libraries][responsive-images].
220
+ A common use case would be sealing your watermark but letting the
221
+ React client request the best possible width.
222
+
223
+ To seal your URLs, initialize client with `salt` and set
224
+ `sign_urls` to `false`. `signature_length` setting is applied
225
+ to control the length of the generated `ci_seal` value.
226
+
227
+ Use the `seal_params` helper to specify which params to seal
228
+ as a list of arguments. These could be symbols or strings.
229
+
230
+ ```ruby
231
+ client = Cloudimage::Client.new(token: 'demoseal', salt: 'test', sign_urls: false)
232
+
233
+ client
234
+ .path('/sample.li/birds.jpg')
235
+ .f('bright:10,contrast:20')
236
+ .w(300)
237
+ .h(400)
238
+ .seal_params(:w, :f)
239
+ .to_url
240
+ # => "https://demoseal.cloudimg.io/v7/sample.li/birds.jpg?ci_eqs=Zj1icmlnaHQlM0ExMCUyQ2NvbnRyYXN0JTNBMjAmdz0zMDA&ci_seal=67dd8cc44f6ba44ee5&h=400"
241
+
242
+ # Alternative approach:
243
+ client
244
+ .path('/sample.li/birds.jpg')
245
+ .to_url(f: 'bright:10,contrast:20', w: 300, h: 400, seal_params: [:w, :f])
246
+ # => "https://demoseal.cloudimg.io/v7/sample.li/birds.jpg?ci_eqs=Zj1icmlnaHQlM0ExMCUyQ2NvbnRyYXN0JTNBMjAmdz0zMDA&ci_seal=67dd8cc44f6ba44ee5&h=400"
247
+ ```
248
+
249
+ This approach protects `w` and `f` values from being edited but
250
+ makes it possible to freely modify the value of `h`.
251
+
252
+ ### Invalidation API
253
+
254
+ To access invalidation API you'll need to initialize client with
255
+ an API key.
256
+
257
+ The provided helper methods accept any number of strings:
258
+
259
+ ```ruby
260
+ client = Cloudimage::Client.new(token: 'token', api_key: 'key')
261
+
262
+ # Invalidate original
263
+ client.invalidate_original('/v7/image.jpg')
264
+
265
+ # Invalidate URLs
266
+ client.invalidate_urls('/v7/image.jpg?w=200', '/v7/image.jpg?h=300')
267
+
268
+ # Invalidate all
269
+ client.invalidate_all
270
+ ```
271
+
272
+ Consult the [invalidation API docs][invalidation-docs] for further details.
273
+
136
274
  ## Development
137
275
 
138
276
  After checking out the repo, run `bin/setup` to install dependencies.
@@ -140,27 +278,29 @@ Then, run `bundle exec rake` to run the tests. You can also run
140
278
  `bin/console` for an interactive prompt that will allow you to
141
279
  experiment.
142
280
 
143
- ### TODOs
144
-
145
- - URL sealing
146
- - Add support for custom CNAMEs
147
- - `srcset` generation
148
- - Purge cache API
149
-
150
281
  ## Contributing
151
282
 
152
283
  Bug reports and pull requests are welcome. This project is intended
153
284
  to be a safe, welcoming space for collaboration, and contributors
154
- are expected to adhere to the
155
- [code of conduct](https://github.com/scaleflex/cloudimage-rb/blob/master/CODE_OF_CONDUCT.md).
285
+ are expected to adhere to the [code of conduct][code-of-conduct].
156
286
 
157
287
  ## License
158
288
 
159
- The gem is available as open source under the terms of the
160
- [MIT License](https://opensource.org/licenses/MIT).
289
+ [MIT](https://opensource.org/licenses/MIT)
290
+
291
+ ## Showcase
292
+
293
+ Among others, `cloudimage` is used to power the following apps:
294
+
295
+ - [Robin PRO][robin-pro] - Fast, beautiful, mobile-friendly image galleries for Shopify stores.
161
296
 
162
- ## Code of Conduct
297
+ Using this gem in your app? Let us know in [this issue](https://github.com/scaleflex/cloudimage-rb/issues/8)
298
+ so that we can feature it.
163
299
 
164
- Everyone interacting with the project's codebase, issues, and pull
165
- requests is expected to follow the
166
- [code of conduct](https://github.com/scaleflex/cloudimage-rb/blob/master/CODE_OF_CONDUCT.md).
300
+ [aliases]: https://docs.cloudimage.io/go/cloudimage-documentation-v7/en/domains-urls/aliases
301
+ [code-of-conduct]: https://github.com/scaleflex/cloudimage-rb/blob/master/CODE_OF_CONDUCT.md
302
+ [docs]: https://docs.cloudimage.io/go/cloudimage-documentation-v7/en/introduction
303
+ [invalidation-docs]: https://docs.cloudimage.io/go/cloudimage-documentation-v7/en/caching-acceleration/invalidation-api
304
+ [responsive-images]: https://docs.cloudimage.io/go/cloudimage-documentation-v7/en/responsive-images
305
+ [robin-pro]: https://apps.shopify.com/robin-pro-image-gallery
306
+ [url-prefix]: https://docs.cloudimage.io/go/cloudimage-documentation-v7/en/domains-urls/origin-url-prefix
@@ -2,5 +2,4 @@
2
2
 
3
3
  require 'addressable/uri'
4
4
 
5
- require_relative 'cloudimage/version'
6
5
  require_relative 'cloudimage/client'
@@ -1,25 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'uri'
4
+ require_relative 'invalidation'
4
5
 
5
6
  module Cloudimage
6
7
  class InvalidConfig < StandardError; end
7
8
 
8
9
  class Client
10
+ include Invalidation
11
+
9
12
  attr_reader :config
10
13
 
11
14
  API_VERSION = 'v7'
12
15
  DEFAULT_SIGNATURE_LENGTH = 18
13
16
 
14
17
  def initialize(**options)
15
- @config = {}
16
- @config[:token] = options[:token]
17
- @config[:salt] = options[:salt]
18
- @config[:signature_length] =
19
- options[:signature_length] || DEFAULT_SIGNATURE_LENGTH
20
- @config[:api_version] = API_VERSION
21
-
22
- ensure_valid_config
18
+ @config = set_config_defaults(options)
19
+ validate_config
23
20
  end
24
21
 
25
22
  def path(path)
@@ -28,18 +25,31 @@ module Cloudimage
28
25
 
29
26
  private
30
27
 
31
- def ensure_valid_config
32
- ensure_valid_token
33
- ensure_valid_signature_length
28
+ def set_config_defaults(options)
29
+ options.tap do |config|
30
+ config[:signature_length] =
31
+ options[:signature_length] || DEFAULT_SIGNATURE_LENGTH
32
+ config[:api_version] = API_VERSION
33
+ config[:include_api_version] =
34
+ options[:include_api_version].nil? ? true : false
35
+ config[:sign_urls] = options[:sign_urls].nil? ? true : false
36
+ config[:aliases] = options[:aliases] || {}
37
+ end
38
+ end
39
+
40
+ def validate_config
41
+ validate_site_config
42
+ validate_signature_length
34
43
  end
35
44
 
36
- def ensure_valid_token
37
- return unless config[:token].nil?
45
+ def validate_site_config
46
+ return unless config[:token].nil? && config[:cname].nil?
38
47
 
39
- raise InvalidConfig, 'Please specify your Cloudimage customer token.'
48
+ raise InvalidConfig,
49
+ 'Please specify your customer token or a custom CNAME.'
40
50
  end
41
51
 
42
- def ensure_valid_signature_length
52
+ def validate_signature_length
43
53
  return if config[:salt].nil?
44
54
  return if (6..40).cover? config[:signature_length]
45
55
 
@@ -5,5 +5,11 @@ module Cloudimage
5
5
  def positionable_crop(origin_x:, origin_y:, width:, height:)
6
6
  tl_px(origin_x, origin_y).br_px(origin_x + width, origin_y + height)
7
7
  end
8
+
9
+ def seal_params(*query_params)
10
+ # URI#query_values returns hash where keys are strings.
11
+ sealed_params.merge(query_params.map(&:to_s))
12
+ self
13
+ end
8
14
  end
9
15
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+
6
+ module Cloudimage
7
+ module Invalidation
8
+ ENDPOINT = ::URI.parse('https://api.cloudimage.com/invalidate')
9
+
10
+ %i[original urls all].each do |type|
11
+ define_method "invalidate_#{type}" do |*paths|
12
+ validate_api_key
13
+
14
+ body = {
15
+ scope: type,
16
+ }
17
+
18
+ body[:urls] = paths if paths.any?
19
+
20
+ send_request(body)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def validate_api_key
27
+ return if config[:api_key]
28
+
29
+ raise InvalidConfig, 'API key is required to perform cache invalidation.'
30
+ end
31
+
32
+ def headers
33
+ {
34
+ 'X-Client-Key': config[:api_key],
35
+ 'Content-Type': 'application/json',
36
+ }
37
+ end
38
+
39
+ def send_request(body)
40
+ http = Net::HTTP.new(ENDPOINT.host, ENDPOINT.port)
41
+ http.use_ssl = true
42
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
43
+
44
+ request = Net::HTTP::Post.new(ENDPOINT.path, headers)
45
+ request.body = body.to_json
46
+
47
+ http.request(request)
48
+ end
49
+ end
50
+ end
@@ -26,6 +26,7 @@ module Cloudimage
26
26
  contrast
27
27
  doc_page
28
28
  f
29
+ force_download
29
30
  force_format
30
31
  func
31
32
  gravity
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cloudimage
4
+ module Refinements
5
+ unless {}.respond_to?(:slice)
6
+ refine Hash do
7
+ def slice(*keys)
8
+ keys.each_with_object({}) do |k, acc|
9
+ acc[k] = self[k] if key?(k)
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ unless ''.respond_to?(:delete_prefix)
16
+ refine String do
17
+ def delete_prefix(prefix)
18
+ sub(/\A#{prefix}/, '')
19
+ end
20
+ end
21
+ end
22
+
23
+ unless Kernel.respond_to?(:yield_self)
24
+ refine Kernel do
25
+ def yield_self
26
+ unless block_given?
27
+ return ::Enumerator.new(1) do |yielder|
28
+ yielder.yield(self)
29
+ end
30
+ end
31
+
32
+ yield(self)
33
+ end
34
+ end
35
+ end
36
+
37
+ unless Kernel.respond_to?(:then)
38
+ refine Kernel do
39
+ def then
40
+ return yield_self unless block_given?
41
+
42
+ yield_self(&::Proc.new)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require 'base64'
5
+
6
+ require_relative 'refinements'
7
+
8
+ module Cloudimage
9
+ class Security
10
+ using Refinements
11
+
12
+ attr_reader :uri, :config
13
+
14
+ def initialize(uri, **config)
15
+ @uri = uri
16
+ @config = config
17
+ end
18
+
19
+ def sign_url(request_uri)
20
+ query = uri.query_values || {}
21
+ uri.query_values = query.merge(ci_sign: signature(request_uri))
22
+ end
23
+
24
+ def seal_url(path, sealed_params)
25
+ query = uri.query_values || {}
26
+ sealed_query = query.slice(*sealed_params)
27
+ query.keep_if { |k, _| !sealed_query.key?(k) }
28
+ eqs = eqs(sealed_query)
29
+ query[:ci_eqs] = eqs unless eqs.empty?
30
+ query[:ci_seal] = seal(path, eqs)
31
+ uri.query_values = query
32
+ end
33
+
34
+ private
35
+
36
+ def signature(request_uri)
37
+ digest = Digest::SHA1.hexdigest(config[:salt] + request_uri)
38
+ trim(digest, config[:signature_length])
39
+ end
40
+
41
+ def eqs(query_params)
42
+ uri = Addressable::URI.new
43
+ uri.query_values = query_params
44
+ Base64.urlsafe_encode64(uri.query, padding: false)
45
+ end
46
+
47
+ def seal(path, eqs)
48
+ digest = Digest::SHA1.hexdigest(path + eqs + config[:salt])
49
+ trim(digest, config[:signature_length])
50
+ end
51
+
52
+ def trim(signature, length)
53
+ signature[0, length]
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cloudimage
4
+ module Srcset
5
+ # 5760 is 3x 1920 (the largest common screen width):
6
+ # https://gs.statcounter.com/screen-resolution-stats
7
+ SRCSET_RANGE = (100..5_760).freeze
8
+ SRCSET_GROWTH_FACTOR = 1.67
9
+
10
+ def to_srcset(**extra_params)
11
+ srcset_widths
12
+ .map { |width| "#{to_url(**extra_params, w: width)} #{width}w" }
13
+ .join(', ')
14
+ end
15
+
16
+ private
17
+
18
+ def srcset_widths
19
+ current = SRCSET_RANGE.begin
20
+
21
+ [].tap do |widths|
22
+ loop do
23
+ widths << current
24
+ current = (current * SRCSET_GROWTH_FACTOR).round(-1)
25
+
26
+ break if current >= SRCSET_RANGE.end && widths << SRCSET_RANGE.end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,31 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'digest'
3
+ require 'set'
4
4
 
5
5
  require_relative 'params'
6
6
  require_relative 'custom_helpers'
7
+ require_relative 'security'
8
+ require_relative 'srcset'
9
+ require_relative 'refinements'
7
10
 
8
11
  module Cloudimage
9
12
  class URI
13
+ using Refinements
14
+
10
15
  include Params
11
16
  include CustomHelpers
17
+ include Srcset
12
18
 
13
- attr_reader :uri, :params, :config
19
+ attr_reader :path, :uri, :params, :config, :sealed_params
14
20
 
15
21
  def initialize(path, **config)
16
22
  @config = config
17
23
  @params = {}
18
- @uri = build_uri_from(path)
24
+ @sealed_params = Set.new
25
+ @path = transform(path)
26
+ @uri = build_uri
19
27
  end
20
28
 
21
29
  PARAMS.each do |param|
22
30
  define_method param do |*args|
23
- @params[param] = if args.any?
24
- args.join(',')
25
- else
26
- # Flag params don't need to pass in arguments.
27
- @params[param] = 1
28
- end
31
+ # Flag params don't need to pass in arguments.
32
+ params[param] = args.any? ? args.join(',') : 1
29
33
  self
30
34
  end
31
35
  end
@@ -36,43 +40,72 @@ module Cloudimage
36
40
 
37
41
  def to_url(**extra_params)
38
42
  set_uri_params(**extra_params)
39
- sign_url
43
+ secure_url
44
+ uri.to_s
40
45
  end
41
46
 
42
47
  private
43
48
 
44
- def base_url
49
+ def site
50
+ return "https://#{config[:cname]}" if config[:cname]
51
+
45
52
  "https://#{config[:token]}.cloudimg.io"
46
53
  end
47
54
 
48
- def base_url_with_api_version
49
- "#{base_url}/#{config[:api_version]}"
55
+ def api_version
56
+ "/#{config[:api_version]}"
57
+ end
58
+
59
+ def transform(path)
60
+ path
61
+ .then { |input| input.start_with?('/') ? input : "/#{input}" }
62
+ .then(&method(:apply_aliases))
63
+ end
64
+
65
+ def apply_aliases(path)
66
+ config[:aliases][default_alias] = ''
67
+
68
+ path.dup.tap do |input|
69
+ config[:aliases].each do |source, target|
70
+ input.sub!(source, target)
71
+ end
72
+ end
73
+ end
74
+
75
+ def default_alias
76
+ config[:include_api_version] ? "#{site}#{api_version}/" : "#{site}/"
77
+ end
78
+
79
+ def request_uri
80
+ uri.request_uri.delete_prefix(api_version)
50
81
  end
51
82
 
52
- def build_uri_from(path)
53
- formatted_path = path.start_with?('/') ? path : "/#{path}"
54
- Addressable::URI.parse(base_url_with_api_version + formatted_path)
83
+ def build_uri
84
+ if config[:include_api_version]
85
+ Addressable::URI.parse(site + api_version + path)
86
+ else
87
+ Addressable::URI.parse(site + path)
88
+ end
55
89
  end
56
90
 
57
91
  def set_uri_params(**extra_params)
92
+ seal_params(*extra_params.delete(:seal_params))
58
93
  url_params = params.merge(**extra_params)
59
94
  return unless url_params.any?
60
95
 
61
96
  uri.query_values = url_params
62
97
  end
63
98
 
64
- def sign_url
65
- url = uri.to_s
66
-
67
- return url if config[:salt].nil?
99
+ def secure_url
100
+ return uri.to_s if config[:salt].nil?
68
101
 
69
- url + "#{uri.query_values ? '&' : '?'}ci_sign=#{signature}"
70
- end
102
+ security = Security.new(uri, **config)
71
103
 
72
- def signature
73
- path = uri.to_s.sub(base_url_with_api_version, '')
74
- digest = Digest::SHA1.hexdigest(config[:salt] + path)
75
- digest[0..(config[:signature_length] - 1)]
104
+ if config[:sign_urls]
105
+ security.sign_url(request_uri)
106
+ else
107
+ security.seal_url(path, sealed_params)
108
+ end
76
109
  end
77
110
  end
78
111
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloudimage
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Klimo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-28 00:00:00.000000000 Z
11
+ date: 2020-08-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -24,6 +24,34 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: github_changelog_generator
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.15.2
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.15.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.13'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.13'
27
55
  description: Fast and easy image resizing, transformation, and acceleration in the
28
56
  Cloud.
29
57
  email:
@@ -40,9 +68,12 @@ files:
40
68
  - lib/cloudimage.rb
41
69
  - lib/cloudimage/client.rb
42
70
  - lib/cloudimage/custom_helpers.rb
71
+ - lib/cloudimage/invalidation.rb
43
72
  - lib/cloudimage/params.rb
73
+ - lib/cloudimage/refinements.rb
74
+ - lib/cloudimage/security.rb
75
+ - lib/cloudimage/srcset.rb
44
76
  - lib/cloudimage/uri.rb
45
- - lib/cloudimage/version.rb
46
77
  homepage: https://github.com/scaleflex/cloudimage-rb
47
78
  licenses:
48
79
  - MIT
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Cloudimage
4
- VERSION = '0.2.0'
5
- end