transloadit 3.0.2 → 3.1.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: 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