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 +4 -4
- data/.github/workflows/ci.yml +13 -6
- data/.gitignore +2 -0
- data/.vscode/ruby-sdk.code-workspace +13 -0
- data/CHANGELOG.md +4 -0
- data/Makefile +18 -0
- data/README.md +103 -41
- data/lib/transloadit/assembly.rb +2 -2
- data/lib/transloadit/step.rb +1 -1
- data/lib/transloadit/template.rb +1 -1
- data/lib/transloadit/version.rb +1 -1
- data/lib/transloadit.rb +54 -1
- data/test/test_helper.rb +1 -1
- data/test/unit/transloadit/node-smartcdn-sig.ts +89 -0
- data/test/unit/transloadit/test_assembly.rb +1 -1
- data/test/unit/transloadit/test_smart_cdn.rb +169 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6d73cc9f7b8291b8c4d208ec3f4bdd624a115bc31de35803b7651f7f86d09dee
|
4
|
+
data.tar.gz: d5298dfe9ba4df6723e087267f6a1ee1c96ab890c4888d1116a3bfba53d9a2eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c789384262485d41d6d4a6c188f2c860efc155721197f026980c80cfc6aa61cda8018ea9a346200885fdd2f663ad2a7cfdad8df89299ed5c07209e4e1b240023
|
7
|
+
data.tar.gz: e19fc6c2f00aebd20639fbacfc787da14999d4900b7b53cb0cf5f19776160fffbe385863636c88c3cb842d5d98622478f4b5808997aaad054105e5c42cef5925
|
data/.github/workflows/ci.yml
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
name: CI
|
2
2
|
on:
|
3
3
|
push:
|
4
|
-
branches:
|
4
|
+
branches:
|
5
|
+
- main
|
5
6
|
pull_request:
|
6
|
-
|
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
|
-
|
22
|
+
fail-fast: false
|
20
23
|
steps:
|
21
|
-
- uses: actions/checkout@
|
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=
|
34
|
+
- run: COVERAGE=0 TEST_NODE_PARITY=1 bundle exec rake test
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
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
|
-
|
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 => '
|
39
|
-
: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 => '
|
53
|
-
: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 => '
|
64
|
-
:secret => '
|
65
|
-
: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 => '
|
151
|
-
: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 => '
|
182
|
-
: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 => '
|
212
|
-
:secret => '
|
211
|
+
:key => 'MY_TRANSLOADIT_KEY',
|
212
|
+
:secret => 'MY_TRANSLOADIT_SECRET'
|
213
213
|
)
|
214
214
|
|
215
215
|
transloadit.assembly(
|
216
|
-
: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 => '
|
235
|
-
: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 => '
|
256
|
-
: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 => '
|
275
|
-
: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 '
|
284
|
+
assembly.get 'MY_ASSEMBLY_ID'
|
285
285
|
|
286
286
|
# replays a specific assembly
|
287
|
-
response = assembly.replay '
|
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 '
|
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 => '
|
308
|
-
: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 => '
|
335
|
-
: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 '
|
344
|
+
template.get 'MY_TEMPLATE_ID'
|
345
345
|
|
346
346
|
# updates the template whose id is specified.
|
347
347
|
template.update(
|
348
|
-
'
|
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 '
|
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 => '
|
374
|
-
: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.
|
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 => '
|
397
|
-
: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
|
+
```
|
data/lib/transloadit/assembly.rb
CHANGED
@@ -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
|
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
|
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
|
data/lib/transloadit/step.rb
CHANGED
@@ -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
|
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
|
data/lib/transloadit/template.rb
CHANGED
@@ -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
|
7
|
+
# for further information on Templates and their parameters.
|
8
8
|
#
|
9
9
|
class Transloadit::Template < Transloadit::ApiModel
|
10
10
|
#
|
data/lib/transloadit/version.rb
CHANGED
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
|
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
@@ -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
|
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
|
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:
|
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.
|
259
|
+
rubygems_version: 3.5.22
|
256
260
|
signing_key:
|
257
261
|
specification_version: 4
|
258
262
|
summary: Official Ruby gem for Transloadit
|