Skip to content

Commit de551fd

Browse files
committed
Create node-smartcdn-sig.ts
1 parent 68bc07c commit de551fd

File tree

1 file changed

+91
-0
lines changed

1 file changed

+91
-0
lines changed

tests/node-smartcdn-sig.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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,
27+
auth_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+
if (!auth_key) throw new Error('auth_key is required')
36+
if (!auth_secret) throw new Error('auth_secret is required')
37+
38+
const workspaceSlug = encodeURIComponent(workspace)
39+
const templateSlug = encodeURIComponent(template)
40+
const inputField = encodeURIComponent(input)
41+
42+
const expireAt = expire_at_ms ?? Date.now() + 60 * 60 * 1000 // 1 hour default
43+
44+
const queryParams: Record<string, string[]> = {}
45+
46+
// Handle url_params
47+
Object.entries(url_params).forEach(([key, value]) => {
48+
if (value === null || value === undefined) return
49+
if (Array.isArray(value)) {
50+
value.forEach((val) => {
51+
if (val === null || val === undefined) return
52+
;(queryParams[key] ||= []).push(String(val))
53+
})
54+
} else {
55+
queryParams[key] = [String(value)]
56+
}
57+
})
58+
59+
queryParams.auth_key = [auth_key]
60+
queryParams.exp = [String(expireAt)]
61+
62+
// Sort parameters to ensure consistent ordering
63+
const sortedParams = Object.entries(queryParams)
64+
.sort()
65+
.map(([key, values]) =>
66+
values.map((v) => `${encodeURIComponent(key)}=${encodeURIComponent(v)}`)
67+
)
68+
.flat()
69+
.join('&')
70+
71+
const stringToSign = `${workspaceSlug}/${templateSlug}/${inputField}?${sortedParams}`
72+
const signature = createHmac('sha256', auth_secret)
73+
.update(stringToSign)
74+
.digest('hex')
75+
76+
const finalParams = `${sortedParams}&sig=${encodeURIComponent(
77+
`sha256:${signature}`
78+
)}`
79+
return `https://${workspaceSlug}.tlcdn.com/${templateSlug}/${inputField}?${finalParams}`
80+
}
81+
82+
// Read JSON from stdin
83+
let jsonInput = ''
84+
process.stdin.on('data', (chunk) => {
85+
jsonInput += chunk
86+
})
87+
88+
process.stdin.on('end', () => {
89+
const params = JSON.parse(jsonInput)
90+
console.log(signSmartCDNUrl(params))
91+
})

0 commit comments

Comments
 (0)