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 +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
|