transloadit 3.0.2 → 3.1.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: bbcbc1941ade757af1bd9f57068863464f25062415f2b894715992e905528ac7
4
- data.tar.gz: 740dd8bcc2311d47fb133ae5c771f05b36835f77f00843bab4d9808d0c4a23e5
3
+ metadata.gz: 6d73cc9f7b8291b8c4d208ec3f4bdd624a115bc31de35803b7651f7f86d09dee
4
+ data.tar.gz: d5298dfe9ba4df6723e087267f6a1ee1c96ab890c4888d1116a3bfba53d9a2eb
5
5
  SHA512:
6
- metadata.gz: bb2ff7fae5d6e9a0370c07880123e4a88743b13d163800d7a62b2b859f010dac589600b4a9673d0903b633e04af05ce74027c617550aa11bf0274898576a6d60
7
- data.tar.gz: 5bebda5ba59fffdd478a82aad4f348bc58feb31b619f58530aebf3fca75313c458c599cf8f435171cd424a9a49185a39ab4d75a77ab0d3502cded2ffe81d0dae
6
+ metadata.gz: c789384262485d41d6d4a6c188f2c860efc155721197f026980c80cfc6aa61cda8018ea9a346200885fdd2f663ad2a7cfdad8df89299ed5c07209e4e1b240023
7
+ data.tar.gz: e19fc6c2f00aebd20639fbacfc787da14999d4900b7b53cb0cf5f19776160fffbe385863636c88c3cb842d5d98622478f4b5808997aaad054105e5c42cef5925
@@ -1,9 +1,12 @@
1
1
  name: CI
2
2
  on:
3
3
  push:
4
- branches: [ main ]
4
+ branches:
5
+ - main
5
6
  pull_request:
6
- branches: [ main ]
7
+ types:
8
+ - opened
9
+ - synchronize
7
10
  jobs:
8
11
  ci:
9
12
  runs-on: ubuntu-latest
@@ -13,15 +16,19 @@ jobs:
13
16
  - 3.0
14
17
  - 3.1
15
18
  - 3.2
19
+ - 3.3
16
20
  - jruby
17
- # - jruby-head
18
21
  - truffleruby
19
- # - truffleruby-head
22
+ fail-fast: false
20
23
  steps:
21
- - uses: actions/checkout@v2
24
+ - uses: actions/checkout@v3
25
+ - uses: actions/setup-node@v4
26
+ with:
27
+ node-version: 20
28
+ - run: npm install -g tsx
22
29
  - uses: ruby/setup-ruby@v1
23
30
  with:
24
31
  ruby-version: ${{ matrix.ruby }}
25
32
  bundler-cache: true
26
33
  - run: bundle exec standardrb
27
- - run: COVERAGE=false bundle exec rake test
34
+ - run: COVERAGE=0 TEST_NODE_PARITY=1 bundle exec rake test
data/.gitignore CHANGED
@@ -13,3 +13,5 @@ transloadit-*.gem
13
13
 
14
14
  .DS_Store
15
15
  env.sh
16
+ .env
17
+ vendor/bundle/
@@ -0,0 +1,13 @@
1
+ {
2
+ "folders": [
3
+ {
4
+ "path": ".."
5
+ }
6
+ ],
7
+ "settings": {
8
+ "workbench.colorCustomizations": {
9
+ "titleBar.activeForeground": "#ffffff",
10
+ "titleBar.activeBackground": "#cc0000"
11
+ },
12
+ }
13
+ }
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ### 3.1.0 / 2024-11-24
2
+
3
+ - Add Smart CDN signature support via `signed_smart_cdn_url` method (kvz)
4
+
1
5
  ### 3.0.2 / 2024-03-01
2
6
 
3
7
  - Upgrade signature algorithm to more secure SHA-384 [#69](https://github.com/transloadit/ruby-sdk/pull/69) (@aduh95)
data/Makefile ADDED
@@ -0,0 +1,18 @@
1
+ .PHONY: all test fix
2
+
3
+ all: fix test
4
+
5
+ # Run tests
6
+ test:
7
+ bundle exec rake test
8
+
9
+ # Fix code formatting
10
+ fix:
11
+ bundle exec standardrb --fix
12
+
13
+ # Install dependencies
14
+ install:
15
+ bundle install
16
+
17
+ # Run both fix and test
18
+ check: fix test
data/README.md CHANGED
@@ -11,7 +11,7 @@ A **Ruby** Integration for [Transloadit](https://transloadit.com)'s file uploadi
11
11
 
12
12
  This is a **Ruby** SDK to make it easy to talk to the [Transloadit](https://transloadit.com) REST API.
13
13
 
14
- *If you run Ruby on Rails and are looking to integrate with the browser for file uploads, checkout the [rails-sdk](https://github.com/transloadit/rails-sdk).*
14
+ _If you run Ruby on Rails and are looking to integrate with the browser for file uploads, checkout the [rails-sdk](https://github.com/transloadit/rails-sdk)._
15
15
 
16
16
  ## Install
17
17
 
@@ -29,14 +29,14 @@ $ irb -rubygems
29
29
  => true
30
30
  ```
31
31
 
32
- Then create a Transloadit instance, which will maintain your
33
- [authentication credentials](https://transloadit.com/accounts/credentials)
32
+ Then create a Transloadit instance, which will maintain your
33
+ [authentication credentials](https://transloadit.com/accounts/credentials)
34
34
  and allow us to make requests to [the API](https://transloadit.com/docs/api/).
35
35
 
36
36
  ```ruby
37
37
  transloadit = Transloadit.new(
38
- :key => 'YOUR_TRANSLOADIT_KEY',
39
- :secret => 'YOUR_TRANSLOADIT_SECRET'
38
+ :key => 'MY_TRANSLOADIT_KEY',
39
+ :secret => 'MY_TRANSLOADIT_SECRET'
40
40
  )
41
41
  ```
42
42
 
@@ -49,8 +49,8 @@ and store the result on [Amazon S3](https://aws.amazon.com/s3/).
49
49
  require 'transloadit'
50
50
 
51
51
  transloadit = Transloadit.new(
52
- :key => 'YOUR_TRANSLOADIT_KEY',
53
- :secret => 'YOUR_TRANSLOADIT_SECRET'
52
+ :key => 'MY_TRANSLOADIT_KEY',
53
+ :secret => 'MY_TRANSLOADIT_SECRET'
54
54
  )
55
55
 
56
56
  # First, we create two steps: one to resize the image to 320x240, and another to
@@ -60,9 +60,9 @@ resize = transloadit.step 'resize', '/image/resize',
60
60
  :height => 240
61
61
 
62
62
  store = transloadit.step 'store', '/s3/store',
63
- :key => 'YOUR_AWS_KEY',
64
- :secret => 'YOUR_AWS_SECRET',
65
- :bucket => 'YOUR_S3_BUCKET'
63
+ :key => 'MY_AWS_KEY',
64
+ :secret => 'MY_AWS_SECRET',
65
+ :bucket => 'MY_S3_BUCKET'
66
66
 
67
67
  # Now that we have the steps, we create an assembly (which is just a request to
68
68
  # process a file or set of files) and let Transloadit do the rest.
@@ -125,7 +125,7 @@ API at the time the <dfn>Assembly</dfn> was created. You have to explicitly ask
125
125
  # reloads the response's contents from the REST API
126
126
  response.reload!
127
127
 
128
- # reloads once per second until all processing is finished, up to number of
128
+ # reloads once per second until all processing is finished, up to number of
129
129
  # times specified in :tries option, otherwise will raise ReloadLimitReached
130
130
  response.reload_until_finished! tries: 300 # default is 600
131
131
  ```
@@ -147,8 +147,8 @@ than one file in the same request. You can also pass a single <dfn>Step</dfn> fo
147
147
  require 'transloadit'
148
148
 
149
149
  transloadit = Transloadit.new(
150
- :key => 'YOUR_TRANSLOADIT_KEY',
151
- :secret => 'YOUR_TRANSLOADIT_SECRET'
150
+ :key => 'MY_TRANSLOADIT_KEY',
151
+ :secret => 'MY_TRANSLOADIT_SECRET'
152
152
  )
153
153
 
154
154
  assembly = transloadit.assembly(steps: store)
@@ -160,7 +160,7 @@ response = assembly.create!(
160
160
  )
161
161
  ```
162
162
 
163
- You can also pass an array of files to the `create!` method.
163
+ You can also pass an array of files to the `create!` method.
164
164
  Just unpack the array using the splat `*` operator.
165
165
 
166
166
  ```ruby
@@ -178,8 +178,8 @@ simply need to `use` other <dfn>Steps</dfn>. Following
178
178
  require 'transloadit'
179
179
 
180
180
  transloadit = Transloadit.new(
181
- :key => 'YOUR_TRANSLOADIT_KEY',
182
- :secret => 'YOUR_TRANSLOADIT_SECRET'
181
+ :key => 'MY_TRANSLOADIT_KEY',
182
+ :secret => 'MY_TRANSLOADIT_SECRET'
183
183
  )
184
184
 
185
185
  encode = transloadit.step 'encode', '/video/encode', { ... }
@@ -208,17 +208,17 @@ for recurring encoding tasks. In order to use these do the following:
208
208
  require 'transloadit'
209
209
 
210
210
  transloadit = Transloadit.new(
211
- :key => 'YOUR_TRANSLOADIT_KEY',
212
- :secret => 'YOUR_TRANSLOADIT_SECRET'
211
+ :key => 'MY_TRANSLOADIT_KEY',
212
+ :secret => 'MY_TRANSLOADIT_SECRET'
213
213
  )
214
214
 
215
215
  transloadit.assembly(
216
- :template_id => 'YOUR_TEMPLATE_ID'
216
+ :template_id => 'MY_TEMPLATE_ID'
217
217
  ).create! open('/PATH/TO/FILE.mpg')
218
218
  ```
219
219
 
220
220
  You can use your steps together with this template and even use variables.
221
- The [Transloadit documentation](https://transloadit.com/docs/#passing-variables-into-a-template)
221
+ The [Transloadit documentation](https://transloadit.com/docs/#passing-variables-into-a-template)
222
222
  has some nice examples for that.
223
223
 
224
224
  ### 5. Using fields
@@ -231,8 +231,8 @@ to the upload itself. You can use fields like the following:
231
231
  require 'transloadit'
232
232
 
233
233
  transloadit = Transloadit.new(
234
- :key => 'YOUR_TRANSLOADIT_KEY',
235
- :secret => 'YOUR_TRANSLOADIT_SECRET'
234
+ :key => 'MY_TRANSLOADIT_KEY',
235
+ :secret => 'MY_TRANSLOADIT_SECRET'
236
236
  )
237
237
 
238
238
  transloadit.assembly(
@@ -252,8 +252,8 @@ a Notify URL for the <dfn>Assembly</dfn>.
252
252
  require 'transloadit'
253
253
 
254
254
  transloadit = Transloadit.new(
255
- :key => 'YOUR_TRANSLOADIT_KEY',
256
- :secret => 'YOUR_TRANSLOADIT_SECRET'
255
+ :key => 'MY_TRANSLOADIT_KEY',
256
+ :secret => 'MY_TRANSLOADIT_SECRET'
257
257
  )
258
258
 
259
259
  transloadit.assembly(
@@ -271,8 +271,8 @@ Transloadit also provides methods to retrieve/replay <dfn>Assemblies</dfn> and t
271
271
  require 'transloadit'
272
272
 
273
273
  transloadit = Transloadit.new(
274
- :key => 'YOUR_TRANSLOADIT_KEY',
275
- :secret => 'YOUR_TRANSLOADIT_SECRET'
274
+ :key => 'MY_TRANSLOADIT_KEY',
275
+ :secret => 'MY_TRANSLOADIT_SECRET'
276
276
  )
277
277
 
278
278
  assembly = transloadit.assembly
@@ -281,10 +281,10 @@ assembly = transloadit.assembly
281
281
  assembly.list
282
282
 
283
283
  # returns a specific assembly
284
- assembly.get 'YOUR_ASSEMBLY_ID'
284
+ assembly.get 'MY_ASSEMBLY_ID'
285
285
 
286
286
  # replays a specific assembly
287
- response = assembly.replay 'YOUR_ASSEMBLY_ID'
287
+ response = assembly.replay 'MY_ASSEMBLY_ID'
288
288
  # should return true if assembly is replaying and false otherwise.
289
289
  response.replaying?
290
290
 
@@ -292,7 +292,7 @@ response.replaying?
292
292
  assembly.get_notifications
293
293
 
294
294
  # replays an assembly notification
295
- assembly.replay_notification 'YOUR_ASSEMBLY_ID'
295
+ assembly.replay_notification 'MY_ASSEMBLY_ID'
296
296
  ```
297
297
 
298
298
  ### 8. Templates
@@ -304,8 +304,8 @@ for recurring encoding tasks. Here's how you would create a <dfn>Template</dfn>:
304
304
  require 'transloadit'
305
305
 
306
306
  transloadit = Transloadit.new(
307
- :key => 'YOUR_TRANSLOADIT_KEY',
308
- :secret => 'YOUR_TRANSLOADIT_SECRET'
307
+ :key => 'MY_TRANSLOADIT_KEY',
308
+ :secret => 'MY_TRANSLOADIT_SECRET'
309
309
  )
310
310
 
311
311
  template = transloadit.template
@@ -331,8 +331,8 @@ There are also some other methods to retrieve, update and delete a <dfn>Template
331
331
  require 'transloadit'
332
332
 
333
333
  transloadit = Transloadit.new(
334
- :key => 'YOUR_TRANSLOADIT_KEY',
335
- :secret => 'YOUR_TRANSLOADIT_SECRET'
334
+ :key => 'MY_TRANSLOADIT_KEY',
335
+ :secret => 'MY_TRANSLOADIT_SECRET'
336
336
  )
337
337
 
338
338
  template = transloadit.template
@@ -341,11 +341,11 @@ template = transloadit.template
341
341
  template.list
342
342
 
343
343
  # returns a specific template.
344
- template.get 'YOUR_TEMPLATE_ID'
344
+ template.get 'MY_TEMPLATE_ID'
345
345
 
346
346
  # updates the template whose id is specified.
347
347
  template.update(
348
- 'YOUR_TEMPLATE_ID',
348
+ 'MY_TEMPLATE_ID',
349
349
  :name => 'CHANGED_TEMPLATE_NAME',
350
350
  :template => {
351
351
  :steps => {
@@ -358,7 +358,7 @@ template.update(
358
358
  )
359
359
 
360
360
  # deletes a specific template
361
- template.delete 'YOUR_TEMPLATE_ID'
361
+ template.delete 'MY_TEMPLATE_ID'
362
362
  ```
363
363
 
364
364
  ### 9. Getting Bill reports
@@ -370,8 +370,8 @@ you can use the `bill` method passing the required month and year like the follo
370
370
  require 'transloadit'
371
371
 
372
372
  transloadit = Transloadit.new(
373
- :key => 'YOUR_TRANSLOADIT_KEY',
374
- :secret => 'YOUR_TRANSLOADIT_SECRET'
373
+ :key => 'MY_TRANSLOADIT_KEY',
374
+ :secret => 'MY_TRANSLOADIT_SECRET'
375
375
  )
376
376
 
377
377
  # returns bill report for February, 2016.
@@ -380,7 +380,48 @@ transloadit.bill(2, 2016)
380
380
 
381
381
  Not specifying the `month` or `year` would default to the current month or year.
382
382
 
383
- ### 10. Rate limits
383
+ ### 10. Signing Smart CDN URLs
384
+
385
+ You can generate signed [Smart CDN](https://transloadit.com/services/content-delivery/) URLs using your Transloadit instance:
386
+
387
+ ```ruby
388
+ require 'transloadit'
389
+
390
+ transloadit = Transloadit.new(
391
+ :key => 'MY_TRANSLOADIT_KEY',
392
+ :secret => 'MY_TRANSLOADIT_SECRET'
393
+ )
394
+
395
+ # Generate a signed URL using instance credentials
396
+ url = transloadit.signed_smart_cdn_url(
397
+ workspace: "MY_WORKSPACE",
398
+ template: "MY_TEMPLATE",
399
+ input: "avatars/jane.jpg"
400
+ )
401
+
402
+ # Add URL parameters
403
+ url = transloadit.signed_smart_cdn_url(
404
+ workspace: "MY_WORKSPACE",
405
+ template: "MY_TEMPLATE",
406
+ input: "avatars/jane.jpg",
407
+ url_params: {
408
+ width: 100,
409
+ height: 200
410
+ }
411
+ )
412
+
413
+ # Set expiration time
414
+ url = transloadit.signed_smart_cdn_url(
415
+ workspace: "MY_WORKSPACE",
416
+ template: "MY_TEMPLATE",
417
+ input: "avatars/jane.jpg",
418
+ expire_at_ms: 1732550672867 # Specific timestamp
419
+ )
420
+ ```
421
+
422
+ The generated URL will be signed using your Transloadit credentials and can be used to access files through the Smart CDN in a secure manner.
423
+
424
+ ### 11. Rate limits
384
425
 
385
426
  Transloadit enforces rate limits to guarantee that no customers are adversely affected by the usage
386
427
  of any given customer. See [Rate Limiting](https://transloadit.com/docs/api/#rate-limiting).
@@ -393,8 +434,8 @@ To change the number of attempts that will be made when creating an <dfn>Assembl
393
434
  require 'transloadit'
394
435
 
395
436
  transloadit = Transloadit.new(
396
- :key => 'YOUR_TRANSLOADIT_KEY',
397
- :secret => 'YOUR_TRANSLOADIT_SECRET'
437
+ :key => 'MY_TRANSLOADIT_KEY',
438
+ :secret => 'MY_TRANSLOADIT_SECRET'
398
439
  )
399
440
 
400
441
  # would make one extra attempt after a failed attempt.
@@ -423,3 +464,24 @@ Please see [ci.yml](https://github.com/transloadit/ruby-sdk/tree/main/.github/wo
423
464
  ### Ruby 2.x
424
465
 
425
466
  If you still need support for Ruby 2.x, 2.0.1 is the last version that supports it.
467
+
468
+ ## Contributing
469
+
470
+ ### Running tests
471
+
472
+ ```bash
473
+ bundle install
474
+ bundle exec rake test
475
+ ```
476
+
477
+ To also test parity against the Node.js reference implementation, run:
478
+
479
+ ```bash
480
+ TEST_NODE_PARITY=1 bundle exec rake test
481
+ ```
482
+
483
+ To disable coverage reporting, run:
484
+
485
+ ```bash
486
+ COVERAGE=0 bundle exec rake test
487
+ ```
@@ -4,7 +4,7 @@ require "transloadit"
4
4
  # Represents an Assembly API ready to make calls to the REST API endpoints.
5
5
  #
6
6
  # See the Transloadit {documentation}[https://transloadit.com/docs/api-docs/#assembly-api]
7
- # for futher information on Assemblies and available endpoints.
7
+ # for further information on Assemblies and available endpoints.
8
8
  #
9
9
  class Transloadit::Assembly < Transloadit::ApiModel
10
10
  DEFAULT_TRIES = 3
@@ -23,7 +23,7 @@ class Transloadit::Assembly < Transloadit::ApiModel
23
23
  # at which point Transloadit will process and store the files according to the
24
24
  # rules in the Assembly.
25
25
  # See the Transloadit {documentation}[https://transloadit.com/docs/building-assembly-instructions]
26
- # for futher information on Assemblies and their parameters.
26
+ # for further information on Assemblies and their parameters.
27
27
  #
28
28
  # Accepts as many IO objects as you wish to process in the assembly.
29
29
  # The last argument is an optional Hash
@@ -6,7 +6,7 @@ require "transloadit"
6
6
  # +options+ specific to the chosen robot.
7
7
  #
8
8
  # See the Transloadit {documentation}[https://transloadit.com/docs/building-assembly-instructions]
9
- # for futher information on robot types and their parameters.
9
+ # for further information on robot types and their parameters.
10
10
  #
11
11
  class Transloadit::Step
12
12
  # @return [String] the name to give the step
@@ -4,7 +4,7 @@ require "transloadit"
4
4
  # Represents a Template API ready to interact with its corresponding REST API.
5
5
  #
6
6
  # See the Transloadit {documentation}[https://transloadit.com/docs/api-docs/#template-api]
7
- # for futher information on Templates and their parameters.
7
+ # for further information on Templates and their parameters.
8
8
  #
9
9
  class Transloadit::Template < Transloadit::ApiModel
10
10
  #
@@ -1,3 +1,3 @@
1
1
  class Transloadit
2
- VERSION = "3.0.2"
2
+ VERSION = "3.1.0"
3
3
  end
data/lib/transloadit.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  require "multi_json"
2
2
  require "date"
3
+ require "json"
4
+ require "openssl"
5
+ require "uri"
6
+ require "cgi"
3
7
 
4
8
  #
5
9
  # Implements the Transloadit REST API in Ruby. Check the {file:README.md README}
@@ -11,6 +15,7 @@ class Transloadit
11
15
  autoload :Exception, "transloadit/exception"
12
16
  autoload :Request, "transloadit/request"
13
17
  autoload :Response, "transloadit/response"
18
+ autoload :SmartCDN, "transloadit/smart_cdn"
14
19
  autoload :Step, "transloadit/step"
15
20
  autoload :Template, "transloadit/template"
16
21
  autoload :VERSION, "transloadit/version"
@@ -83,7 +88,7 @@ class Transloadit
83
88
  # Creates a Transloadit::Template instance ready to interact with its corresponding REST API.
84
89
  #
85
90
  # See the Transloadit {documentation}[https://transloadit.com/docs/api-docs/#template-api]
86
- # for futher information on Templates and available endpoints.
91
+ # for further information on Templates and available endpoints.
87
92
  #
88
93
  def template(options = {})
89
94
  Transloadit::Template.new(self, options)
@@ -130,6 +135,54 @@ class Transloadit
130
135
  MultiJson.dump(to_hash)
131
136
  end
132
137
 
138
+ # @param workspace [String] Workspace slug
139
+ # @param template [String] Template slug or template ID
140
+ # @param input [String] Input value that is provided as `${fields.input}` in the template
141
+ # @param url_params [Hash] Additional parameters for the URL query string (optional)
142
+ # @param expire_at_ms [Integer] Expiration time as Unix timestamp in milliseconds (optional)
143
+ # @return [String] Signed Smart CDN URL
144
+ def signed_smart_cdn_url(
145
+ workspace:,
146
+ template:,
147
+ input:,
148
+ expire_at_ms: nil,
149
+ url_params: {}
150
+ )
151
+ raise ArgumentError, "workspace is required" if workspace.nil? || workspace.empty?
152
+ raise ArgumentError, "template is required" if template.nil? || template.empty?
153
+ raise ArgumentError, "input is required" if input.nil?
154
+
155
+ workspace_slug = CGI.escape(workspace)
156
+ template_slug = CGI.escape(template)
157
+ input_field = CGI.escape(input)
158
+
159
+ expire_at = expire_at_ms || (Time.now.to_i * 1000 + 60 * 60 * 1000) # 1 hour default
160
+
161
+ query_params = {}
162
+ url_params.each do |key, value|
163
+ next if value.nil?
164
+ Array(value).each do |val|
165
+ next if val.nil?
166
+ (query_params[key.to_s] ||= []) << val.to_s
167
+ end
168
+ end
169
+
170
+ query_params["auth_key"] = [key]
171
+ query_params["exp"] = [expire_at.to_s]
172
+
173
+ # Sort parameters to ensure consistent ordering
174
+ sorted_params = query_params.sort.flat_map do |key, values|
175
+ values.map { |v| "#{CGI.escape(key)}=#{CGI.escape(v)}" }
176
+ end.join("&")
177
+
178
+ string_to_sign = "#{workspace_slug}/#{template_slug}/#{input_field}?#{sorted_params}"
179
+
180
+ signature = OpenSSL::HMAC.hexdigest("sha256", secret, string_to_sign)
181
+
182
+ final_params = "#{sorted_params}&sig=#{CGI.escape("sha256:#{signature}")}"
183
+ "https://#{workspace_slug}.tlcdn.com/#{template_slug}/#{input_field}?#{final_params}"
184
+ end
185
+
133
186
  private
134
187
 
135
188
  #
data/test/test_helper.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  $:.unshift File.dirname(__FILE__)
2
2
  $:.unshift File.expand_path("../../lib", __FILE__)
3
3
 
4
- if ENV["COVERAGE"] != "false"
4
+ if ENV["COVERAGE"] != "0"
5
5
  require "simplecov"
6
6
  SimpleCov.start { add_filter "/test/" }
7
7
  end
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env tsx
2
+ // Reference Smart CDN (https://transloadit.com/services/content-delivery/) Signature implementation
3
+ // And CLI tester to see if our SDK's implementation
4
+ // matches Node's
5
+
6
+ /// <reference types="node" />
7
+
8
+ import { createHash, createHmac } from 'crypto'
9
+
10
+ interface SmartCDNParams {
11
+ workspace: string
12
+ template: string
13
+ input: string
14
+ expire_at_ms?: number
15
+ auth_key?: string
16
+ auth_secret?: string
17
+ url_params?: Record<string, any>
18
+ }
19
+
20
+ function signSmartCDNUrl(params: SmartCDNParams): string {
21
+ const {
22
+ workspace,
23
+ template,
24
+ input,
25
+ expire_at_ms,
26
+ auth_key = 'my-key',
27
+ auth_secret = 'my-secret',
28
+ url_params = {},
29
+ } = params
30
+
31
+ if (!workspace) throw new Error('workspace is required')
32
+ if (!template) throw new Error('template is required')
33
+ if (input === null || input === undefined)
34
+ throw new Error('input must be a string')
35
+
36
+ const workspaceSlug = encodeURIComponent(workspace)
37
+ const templateSlug = encodeURIComponent(template)
38
+ const inputField = encodeURIComponent(input)
39
+
40
+ const expireAt = expire_at_ms ?? Date.now() + 60 * 60 * 1000 // 1 hour default
41
+
42
+ const queryParams: Record<string, string[]> = {}
43
+
44
+ // Handle url_params
45
+ Object.entries(url_params).forEach(([key, value]) => {
46
+ if (value === null || value === undefined) return
47
+ if (Array.isArray(value)) {
48
+ value.forEach((val) => {
49
+ if (val === null || val === undefined) return
50
+ ;(queryParams[key] ||= []).push(String(val))
51
+ })
52
+ } else {
53
+ queryParams[key] = [String(value)]
54
+ }
55
+ })
56
+
57
+ queryParams.auth_key = [auth_key]
58
+ queryParams.exp = [String(expireAt)]
59
+
60
+ // Sort parameters to ensure consistent ordering
61
+ const sortedParams = Object.entries(queryParams)
62
+ .sort()
63
+ .map(([key, values]) =>
64
+ values.map((v) => `${encodeURIComponent(key)}=${encodeURIComponent(v)}`)
65
+ )
66
+ .flat()
67
+ .join('&')
68
+
69
+ const stringToSign = `${workspaceSlug}/${templateSlug}/${inputField}?${sortedParams}`
70
+ const signature = createHmac('sha256', auth_secret)
71
+ .update(stringToSign)
72
+ .digest('hex')
73
+
74
+ const finalParams = `${sortedParams}&sig=${encodeURIComponent(
75
+ `sha256:${signature}`
76
+ )}`
77
+ return `https://${workspaceSlug}.tlcdn.com/${templateSlug}/${inputField}?${finalParams}`
78
+ }
79
+
80
+ // Read JSON from stdin
81
+ let jsonInput = ''
82
+ process.stdin.on('data', (chunk) => {
83
+ jsonInput += chunk
84
+ })
85
+
86
+ process.stdin.on('end', () => {
87
+ const params = JSON.parse(jsonInput)
88
+ console.log(signSmartCDNUrl(params))
89
+ })
@@ -286,7 +286,7 @@ describe Transloadit::Assembly do
286
286
  end
287
287
 
288
288
  describe "when replaying assembly notification" do
289
- it "must replay notification of sepcified assembly" do
289
+ it "must replay notification of specified assembly" do
290
290
  VCR.use_cassette "replay_assembly_notification" do
291
291
  response = @assembly.replay_notification "2ea5d21063ad11e6bc93e53395ce4e7d"
292
292
 
@@ -0,0 +1,169 @@
1
+ require "test_helper"
2
+ require "json"
3
+ require "open3"
4
+
5
+ describe Transloadit do
6
+ before do
7
+ @auth_key = "my-key"
8
+ @auth_secret = "my-secret"
9
+ @transloadit = Transloadit.new(key: @auth_key, secret: @auth_secret)
10
+ @workspace = "my-app"
11
+ @template = "test-smart-cdn"
12
+ @input = "inputs/prinsengracht.jpg"
13
+ @expire_at = 1732550672867
14
+ end
15
+
16
+ def run_node_script(params)
17
+ return unless ENV["TEST_NODE_PARITY"] == "1"
18
+ script_path = File.expand_path("./node-smartcdn-sig", __dir__)
19
+ json_input = JSON.dump(params)
20
+ stdout, stderr, status = Open3.capture3("tsx #{script_path}", stdin_data: json_input)
21
+ raise "Node script failed: #{stderr}" unless status.success?
22
+ stdout.strip
23
+ end
24
+
25
+ describe "#signed_smart_cdn_url" do
26
+ it "requires workspace" do
27
+ assert_raises ArgumentError, "workspace is required" do
28
+ @transloadit.signed_smart_cdn_url(
29
+ workspace: nil,
30
+ template: @template,
31
+ input: @input
32
+ )
33
+ end
34
+
35
+ assert_raises ArgumentError, "workspace is required" do
36
+ @transloadit.signed_smart_cdn_url(
37
+ workspace: "",
38
+ template: @template,
39
+ input: @input
40
+ )
41
+ end
42
+ end
43
+
44
+ it "requires template" do
45
+ assert_raises ArgumentError, "template is required" do
46
+ @transloadit.signed_smart_cdn_url(
47
+ workspace: @workspace,
48
+ template: nil,
49
+ input: @input
50
+ )
51
+ end
52
+
53
+ assert_raises ArgumentError, "template is required" do
54
+ @transloadit.signed_smart_cdn_url(
55
+ workspace: @workspace,
56
+ template: "",
57
+ input: @input
58
+ )
59
+ end
60
+ end
61
+
62
+ it "requires input" do
63
+ assert_raises ArgumentError, "input is required" do
64
+ @transloadit.signed_smart_cdn_url(
65
+ workspace: @workspace,
66
+ template: @template,
67
+ input: nil
68
+ )
69
+ end
70
+ end
71
+
72
+ it "allows empty input string" do
73
+ params = {
74
+ workspace: @workspace,
75
+ template: @template,
76
+ input: "",
77
+ expire_at_ms: @expire_at
78
+ }
79
+ expected_url = "https://my-app.tlcdn.com/test-smart-cdn/?auth_key=my-key&exp=1732550672867&sig=sha256%3Ad5e13df4acde8d4aaa0f34534489e54098b5128c54392600ed96dd77669a533e"
80
+
81
+ url = @transloadit.signed_smart_cdn_url(**params)
82
+ assert_equal expected_url, url
83
+
84
+ if (node_url = run_node_script(params.merge(auth_key: "my-key", auth_secret: "my-secret")))
85
+ assert_equal expected_url, node_url
86
+ end
87
+ end
88
+
89
+ it "uses instance credentials" do
90
+ params = {
91
+ workspace: @workspace,
92
+ template: @template,
93
+ input: @input,
94
+ expire_at_ms: @expire_at
95
+ }
96
+ expected_url = "https://my-app.tlcdn.com/test-smart-cdn/inputs%2Fprinsengracht.jpg?auth_key=my-key&exp=1732550672867&sig=sha256%3A8620fc2a22aec6081cde730b7f3f29c0d8083f58a68f62739e642b3c03709139"
97
+
98
+ url = @transloadit.signed_smart_cdn_url(**params)
99
+ assert_equal expected_url, url
100
+
101
+ if (node_url = run_node_script(params.merge(auth_key: "my-key", auth_secret: "my-secret")))
102
+ assert_equal expected_url, node_url
103
+ end
104
+ end
105
+
106
+ it "includes empty width parameter" do
107
+ params = {
108
+ workspace: @workspace,
109
+ template: @template,
110
+ input: @input,
111
+ expire_at_ms: @expire_at,
112
+ url_params: {
113
+ width: "",
114
+ height: 200
115
+ }
116
+ }
117
+ expected_url = "https://my-app.tlcdn.com/test-smart-cdn/inputs%2Fprinsengracht.jpg?auth_key=my-key&exp=1732550672867&height=200&width=&sig=sha256%3Aebf562722c504839db97165e657583f74192ac4ab580f1a0dd67d3d868b4ced3"
118
+
119
+ url = @transloadit.signed_smart_cdn_url(**params)
120
+ assert_equal expected_url, url
121
+
122
+ if (node_url = run_node_script(params.merge(auth_key: "my-key", auth_secret: "my-secret")))
123
+ assert_equal expected_url, node_url
124
+ end
125
+ end
126
+
127
+ it "handles nil values in parameters" do
128
+ params = {
129
+ workspace: @workspace,
130
+ template: @template,
131
+ input: @input,
132
+ expire_at_ms: @expire_at,
133
+ url_params: {
134
+ width: nil,
135
+ height: 200
136
+ }
137
+ }
138
+ expected_url = "https://my-app.tlcdn.com/test-smart-cdn/inputs%2Fprinsengracht.jpg?auth_key=my-key&exp=1732550672867&height=200&sig=sha256%3Ad6897a0cb527a14eaab13c54b06f53527797c553d8b7e5d0b1a5df237212f083"
139
+
140
+ url = @transloadit.signed_smart_cdn_url(**params)
141
+ assert_equal expected_url, url
142
+
143
+ if (node_url = run_node_script(params.merge(auth_key: "my-key", auth_secret: "my-secret")))
144
+ assert_equal expected_url, node_url
145
+ end
146
+ end
147
+
148
+ it "handles array values in parameters" do
149
+ params = {
150
+ workspace: @workspace,
151
+ template: @template,
152
+ input: @input,
153
+ expire_at_ms: @expire_at,
154
+ url_params: {
155
+ tags: ["landscape", "amsterdam", nil, ""],
156
+ height: 200
157
+ }
158
+ }
159
+ expected_url = "https://my-app.tlcdn.com/test-smart-cdn/inputs%2Fprinsengracht.jpg?auth_key=my-key&exp=1732550672867&height=200&tags=landscape&tags=amsterdam&tags=&sig=sha256%3Aff46eb0083d64b250b2e4510380e333f67da855b2401493dee7a706a47957d3f"
160
+
161
+ url = @transloadit.signed_smart_cdn_url(**params)
162
+ assert_equal expected_url, url
163
+
164
+ if (node_url = run_node_script(params.merge(auth_key: "my-key", auth_secret: "my-secret")))
165
+ assert_equal expected_url, node_url
166
+ end
167
+ end
168
+ end
169
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: transloadit
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Touset
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-03-01 00:00:00.000000000 Z
12
+ date: 1980-01-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rest-client
@@ -177,9 +177,11 @@ files:
177
177
  - ".github/workflows/ci.yml"
178
178
  - ".gitignore"
179
179
  - ".standard.yml"
180
+ - ".vscode/ruby-sdk.code-workspace"
180
181
  - CHANGELOG.md
181
182
  - Gemfile
182
183
  - LICENSE
184
+ - Makefile
183
185
  - README.md
184
186
  - Rakefile
185
187
  - examples/README.md
@@ -226,10 +228,12 @@ files:
226
228
  - test/fixtures/cassettes/update_template.yml
227
229
  - test/test_helper.rb
228
230
  - test/unit/test_transloadit.rb
231
+ - test/unit/transloadit/node-smartcdn-sig.ts
229
232
  - test/unit/transloadit/test_api.rb
230
233
  - test/unit/transloadit/test_assembly.rb
231
234
  - test/unit/transloadit/test_request.rb
232
235
  - test/unit/transloadit/test_response.rb
236
+ - test/unit/transloadit/test_smart_cdn.rb
233
237
  - test/unit/transloadit/test_step.rb
234
238
  - test/unit/transloadit/test_template.rb
235
239
  - transloadit.gemspec
@@ -252,7 +256,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
252
256
  - !ruby/object:Gem::Version
253
257
  version: 2.2.0
254
258
  requirements: []
255
- rubygems_version: 3.5.3
259
+ rubygems_version: 3.5.22
256
260
  signing_key:
257
261
  specification_version: 4
258
262
  summary: Official Ruby gem for Transloadit