@transloadit/utils 4.5.1 → 4.6.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.
package/README.md CHANGED
@@ -10,8 +10,11 @@ npm install @transloadit/utils
10
10
 
11
11
  ## Web / Edge usage
12
12
 
13
+ Everything in the root export runs on WebCrypto, so it works in browsers (secure origins only:
14
+ `https://` or `localhost`), edge runtimes, and Node.
15
+
13
16
  ```ts
14
- import { signParams, verifyWebhookSignature } from '@transloadit/utils'
17
+ import { getSignedSmartCdnUrl, signParams, verifyWebhookSignature } from '@transloadit/utils'
15
18
 
16
19
  const signature = await signParams(paramsString, authSecret)
17
20
  const verified = await verifyWebhookSignature({
@@ -19,6 +22,13 @@ const verified = await verifyWebhookSignature({
19
22
  signatureHeader,
20
23
  authSecret,
21
24
  })
25
+ const url = await getSignedSmartCdnUrl({
26
+ workspace,
27
+ template,
28
+ input,
29
+ authKey,
30
+ authSecret,
31
+ })
22
32
  ```
23
33
 
24
34
  ## Node usage
@@ -55,9 +65,12 @@ for (const source of imageCandidates.sources) {
55
65
 
56
66
  ## API
57
67
 
58
- - `signParams(paramsString, authSecret, algorithm?)`: WebCrypto-based HMAC signature for params.
68
+ - `signParams(paramsString, authSecret, algorithm?)`: WebCrypto-based HMAC signature for params
69
+ (`sha1`, `sha256`, `sha384`, `sha512`).
59
70
  - `verifyWebhookSignature({ rawBody, signatureHeader, authSecret })`: validates webhook signatures.
71
+ - `getSignedSmartCdnUrl(options)`: async, WebCrypto-based Smart CDN URL signer. Byte-identical to
72
+ the Node variant below.
60
73
  - `signParamsSync(paramsString, authSecret, algorithm?)`: Node-only sync signature helper.
61
- - `getSignedSmartCdnUrl(options)`: Node-only Smart CDN URL signer.
74
+ - `getSignedSmartCdnUrl(options)` from `@transloadit/utils/node`: synchronous Smart CDN URL signer.
62
75
  - `getSignedSmartCdnImageCandidates(options)`: deterministic structured, signed AVIF and WebP
63
76
  candidates plus the original fallback URL.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- export type SignatureAlgorithm = 'sha1' | 'sha256' | 'sha384';
1
+ import type { SmartCdnUrlOptions } from './smartCdn.ts';
2
+ export type SignatureAlgorithm = 'sha1' | 'sha256' | 'sha384' | 'sha512';
3
+ export type { SmartCdnUrlOptions } from './smartCdn.ts';
2
4
  export * from './assemblyInstructionsCompiler.ts';
3
5
  export declare const signParams: (paramsString: string, authSecret: string, algorithm?: SignatureAlgorithm) => Promise<string>;
4
6
  export type VerifyWebhookSignatureOptions = {
@@ -7,4 +9,9 @@ export type VerifyWebhookSignatureOptions = {
7
9
  authSecret: string;
8
10
  };
9
11
  export declare const verifyWebhookSignature: (options: VerifyWebhookSignatureOptions) => Promise<boolean>;
12
+ /**
13
+ * Signs a Smart CDN URL with WebCrypto, so it works in browsers, edge runtimes and Node alike.
14
+ * Produces the same URL as the synchronous `getSignedSmartCdnUrl` from `@transloadit/utils/node`.
15
+ */
16
+ export declare const getSignedSmartCdnUrl: (opts: SmartCdnUrlOptions) => Promise<string>;
10
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAE7D,cAAc,mCAAmC,CAAA;AAiDjD,eAAO,MAAM,UAAU,GACrB,cAAc,MAAM,EACpB,YAAY,MAAM,EAClB,YAAW,kBAA6B,KACvC,OAAO,CAAC,MAAM,CAOhB,CAAA;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,OAAO,EAAE,MAAM,CAAA;IACf,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,eAAO,MAAM,sBAAsB,GACjC,SAAS,6BAA6B,KACrC,OAAO,CAAC,OAAO,CAkBjB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAIvD,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAExE,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAEvD,cAAc,mCAAmC,CAAA;AAqDjD,eAAO,MAAM,UAAU,GACrB,cAAc,MAAM,EACpB,YAAY,MAAM,EAClB,YAAW,kBAA6B,KACvC,OAAO,CAAC,MAAM,CAOhB,CAAA;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,OAAO,EAAE,MAAM,CAAA;IACf,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,eAAO,MAAM,sBAAsB,GACjC,SAAS,6BAA6B,KACrC,OAAO,CAAC,OAAO,CAkBjB,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAAU,MAAM,kBAAkB,KAAG,OAAO,CAAC,MAAM,CAInF,CAAA"}
package/dist/index.js CHANGED
@@ -1,14 +1,17 @@
1
+ import { finishSmartCdnUrl, prepareSmartCdnUrl } from "./smartCdn.js";
1
2
  export * from "./assemblyInstructionsCompiler.js";
2
3
  const algorithmMap = {
3
4
  sha1: 'SHA-1',
4
5
  sha256: 'SHA-256',
5
6
  sha384: 'SHA-384',
7
+ sha512: 'SHA-512',
6
8
  };
7
- const isSignatureAlgorithm = (value) => value === 'sha1' || value === 'sha256' || value === 'sha384';
9
+ const isSignatureAlgorithm = (value) => value === 'sha1' || value === 'sha256' || value === 'sha384' || value === 'sha512';
8
10
  const getSubtle = () => {
9
11
  const subtle = globalThis.crypto?.subtle;
10
12
  if (!subtle) {
11
- throw new Error('Web Crypto is required to sign Transloadit payloads');
13
+ // Browsers only expose crypto.subtle on secure origins (https:// or localhost).
14
+ throw new Error('Web Crypto is required to sign Transloadit payloads; browsers only provide crypto.subtle on secure origins (https:// or localhost)');
12
15
  }
13
16
  return subtle;
14
17
  };
@@ -55,3 +58,12 @@ export const verifyWebhookSignature = async (options) => {
55
58
  const expected = await hmacHex(normalized, options.authSecret, options.rawBody);
56
59
  return safeCompare(expected, signature);
57
60
  };
61
+ /**
62
+ * Signs a Smart CDN URL with WebCrypto, so it works in browsers, edge runtimes and Node alike.
63
+ * Produces the same URL as the synchronous `getSignedSmartCdnUrl` from `@transloadit/utils/node`.
64
+ */
65
+ export const getSignedSmartCdnUrl = async (opts) => {
66
+ const prepared = prepareSmartCdnUrl(opts);
67
+ const signature = await hmacHex('sha256', opts.authSecret, prepared.stringToSign);
68
+ return finishSmartCdnUrl(prepared, signature);
69
+ };
package/dist/node.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import type { SignatureAlgorithm } from './index.ts';
2
+ import type { SmartCdnUrlOptions } from './smartCdn.ts';
2
3
  export type { SignatureAlgorithm } from './index.ts';
4
+ export type { SmartCdnUrlOptions } from './smartCdn.ts';
3
5
  export type SignatureAlgorithmInput = SignatureAlgorithm | (string & {});
4
6
  /** Image formats supported by the responsive-image Built-in. */
5
7
  export type SmartCdnImageFormat = 'avif' | 'png' | 'webp';
@@ -38,38 +40,8 @@ export interface SmartCdnImageCandidatesOptions {
38
40
  /** Workspace slug. */
39
41
  workspace: string;
40
42
  }
41
- export type SmartCdnUrlOptions = {
42
- /**
43
- * Workspace slug.
44
- */
45
- workspace: string;
46
- /**
47
- * Template slug or template ID.
48
- */
49
- template: string;
50
- /**
51
- * Input value that is provided as `${fields.input}` in the template.
52
- */
53
- input: string;
54
- /**
55
- * Additional parameters for the URL query string.
56
- */
57
- urlParams?: Record<string, boolean | number | string | (boolean | number | string)[]>;
58
- /**
59
- * Expiration timestamp of the signature in milliseconds since UNIX epoch.
60
- * Defaults to 1 hour from now.
61
- */
62
- expiresAt?: number;
63
- /**
64
- * Transloadit auth key used to sign the URL.
65
- */
66
- authKey: string;
67
- /**
68
- * Transloadit auth secret used to sign the URL.
69
- */
70
- authSecret: string;
71
- };
72
43
  export declare const signParamsSync: (paramsString: string, authSecret: string, algorithm?: SignatureAlgorithmInput) => string;
44
+ /** Synchronous Smart CDN URL signer (Node). The root export has an async WebCrypto twin. */
73
45
  export declare const getSignedSmartCdnUrl: (opts: SmartCdnUrlOptions) => string;
74
46
  /**
75
47
  * Builds deterministic signed Smart CDN candidates for server-rendered `<picture>` elements.
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAIpD,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD,MAAM,MAAM,uBAAuB,GAAG,kBAAkB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAExE,gEAAgE;AAChE,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAA;AAEzD,oEAAoE;AACpE,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;CACd;AAED,2DAA2D;AAC3D,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,SAAS,sBAAsB,EAAE,CAAA;IAC7C,MAAM,EAAE,mBAAmB,CAAA;IAC3B,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,wDAAwD;AACxD,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAA;CACxC;AAED,8EAA8E;AAC9E,MAAM,WAAW,8BAA8B;IAC7C,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAA;IACf,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAA;IAClB,uFAAuF;IACvF,SAAS,EAAE,MAAM,CAAA;IACjB,yEAAyE;IACzE,OAAO,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;IAChE,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAA;IACb,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oFAAoF;IACpF,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IACzB,sBAAsB;IACtB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,CAAA;IACrF;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;OAEG;IACH,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAkED,eAAO,MAAM,cAAc,GACzB,cAAc,MAAM,EACpB,YAAY,MAAM,EAClB,YAAW,uBAAkC,KAC5C,MAKF,CAAA;AAED,eAAO,MAAM,oBAAoB,GAAI,MAAM,kBAAkB,KAAG,MA8B/D,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAC9C,IAAI,EAAE,8BAA8B,GACnC,uBAAuB,CAsDzB"}
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AACpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAMvD,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AACpD,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAEvD,MAAM,MAAM,uBAAuB,GAAG,kBAAkB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAExE,gEAAgE;AAChE,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAA;AAEzD,oEAAoE;AACpE,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;CACd;AAED,2DAA2D;AAC3D,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,SAAS,sBAAsB,EAAE,CAAA;IAC7C,MAAM,EAAE,mBAAmB,CAAA;IAC3B,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,wDAAwD;AACxD,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAA;CACxC;AAED,8EAA8E;AAC9E,MAAM,WAAW,8BAA8B;IAC7C,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAA;IACf,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAA;IAClB,uFAAuF;IACvF,SAAS,EAAE,MAAM,CAAA;IACjB,yEAAyE;IACzE,OAAO,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;IAChE,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAA;IACb,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oFAAoF;IACpF,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IACzB,sBAAsB;IACtB,SAAS,EAAE,MAAM,CAAA;CAClB;AAkED,eAAO,MAAM,cAAc,GACzB,cAAc,MAAM,EACpB,YAAY,MAAM,EAClB,YAAW,uBAAkC,KAC5C,MAKF,CAAA;AAED,4FAA4F;AAC5F,eAAO,MAAM,oBAAoB,GAAI,MAAM,kBAAkB,KAAG,MAM/D,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAC9C,IAAI,EAAE,8BAA8B,GACnC,uBAAuB,CAsDzB"}
package/dist/node.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createHmac } from 'node:crypto';
2
+ import { finishSmartCdnUrl, prepareSmartCdnUrl } from "./smartCdn.js";
2
3
  const defaultSmartCdnImageFormats = {
3
4
  avif: 45,
4
5
  webp: 75,
@@ -57,35 +58,13 @@ export const signParamsSync = (paramsString, authSecret, algorithm = 'sha384') =
57
58
  .digest('hex');
58
59
  return `${algorithm}:${signature}`;
59
60
  };
61
+ /** Synchronous Smart CDN URL signer (Node). The root export has an async WebCrypto twin. */
60
62
  export const getSignedSmartCdnUrl = (opts) => {
61
- if (opts.workspace == null || opts.workspace === '')
62
- throw new TypeError('workspace is required');
63
- if (opts.template == null || opts.template === '')
64
- throw new TypeError('template is required');
65
- if (opts.input == null)
66
- throw new TypeError('input is required');
67
- const workspaceSlug = encodeURIComponent(opts.workspace);
68
- const templateSlug = encodeURIComponent(opts.template);
69
- const inputField = encodeURIComponent(opts.input);
70
- const expiresAt = opts.expiresAt || Date.now() + 60 * 60 * 1000;
71
- const queryParams = new URLSearchParams();
72
- for (const [key, value] of Object.entries(opts.urlParams || {})) {
73
- if (Array.isArray(value)) {
74
- for (const val of value) {
75
- queryParams.append(key, `${val}`);
76
- }
77
- }
78
- else {
79
- queryParams.append(key, `${value}`);
80
- }
81
- }
82
- queryParams.set('auth_key', opts.authKey);
83
- queryParams.set('exp', `${expiresAt}`);
84
- queryParams.sort();
85
- const stringToSign = `${workspaceSlug}/${templateSlug}/${inputField}?${queryParams}`;
86
- const signature = createHmac('sha256', opts.authSecret).update(stringToSign).digest('hex');
87
- queryParams.set('sig', `sha256:${signature}`);
88
- return `https://${workspaceSlug}.tlcdn.com/${templateSlug}/${inputField}?${queryParams}`;
63
+ const prepared = prepareSmartCdnUrl(opts);
64
+ const signature = createHmac('sha256', opts.authSecret)
65
+ .update(prepared.stringToSign)
66
+ .digest('hex');
67
+ return finishSmartCdnUrl(prepared, signature);
89
68
  };
90
69
  /**
91
70
  * Builds deterministic signed Smart CDN candidates for server-rendered `<picture>` elements.
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Smart CDN URL building shared by the synchronous Node signer (`@transloadit/utils/node`) and the
3
+ * asynchronous WebCrypto signer (`@transloadit/utils`). Only the HMAC differs between the two, so
4
+ * the string-to-sign and the final URL are assembled here and cannot drift apart.
5
+ */
6
+ export type SmartCdnUrlOptions = {
7
+ /**
8
+ * Workspace slug.
9
+ */
10
+ workspace: string;
11
+ /**
12
+ * Template slug or template ID.
13
+ */
14
+ template: string;
15
+ /**
16
+ * Input value that is provided as `${fields.input}` in the template.
17
+ */
18
+ input: string;
19
+ /**
20
+ * Additional parameters for the URL query string.
21
+ */
22
+ urlParams?: Record<string, boolean | number | string | (boolean | number | string)[]>;
23
+ /**
24
+ * Expiration timestamp of the signature in milliseconds since UNIX epoch.
25
+ * Defaults to 1 hour from now.
26
+ */
27
+ expiresAt?: number;
28
+ /**
29
+ * Transloadit auth key used to sign the URL.
30
+ */
31
+ authKey: string;
32
+ /**
33
+ * Transloadit auth secret used to sign the URL.
34
+ */
35
+ authSecret: string;
36
+ };
37
+ /** A Smart CDN URL with everything but its signature in place. */
38
+ export interface PreparedSmartCdnUrl {
39
+ /** `workspace/template/input?sortedQuery`, the message the auth secret signs with HMAC-SHA256. */
40
+ stringToSign: string;
41
+ /** URL-encoded path segments and the sorted query (without `sig`). */
42
+ parts: {
43
+ workspaceSlug: string;
44
+ templateSlug: string;
45
+ inputField: string;
46
+ queryParams: URLSearchParams;
47
+ };
48
+ }
49
+ /** Validates the options and assembles the string to sign; the caller supplies the HMAC. */
50
+ export declare const prepareSmartCdnUrl: (opts: SmartCdnUrlOptions) => PreparedSmartCdnUrl;
51
+ /** Appends the `sig` parameter and returns the final `https://{workspace}.tlcdn.com/…` URL. */
52
+ export declare const finishSmartCdnUrl: ({ parts }: PreparedSmartCdnUrl, signatureHex: string) => string;
53
+ //# sourceMappingURL=smartCdn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smartCdn.d.ts","sourceRoot":"","sources":["../src/smartCdn.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,CAAA;IACrF;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;OAEG;IACH,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,kEAAkE;AAClE,MAAM,WAAW,mBAAmB;IAClC,kGAAkG;IAClG,YAAY,EAAE,MAAM,CAAA;IACpB,sEAAsE;IACtE,KAAK,EAAE;QACL,aAAa,EAAE,MAAM,CAAA;QACrB,YAAY,EAAE,MAAM,CAAA;QACpB,UAAU,EAAE,MAAM,CAAA;QAClB,WAAW,EAAE,eAAe,CAAA;KAC7B,CAAA;CACF;AAED,4FAA4F;AAC5F,eAAO,MAAM,kBAAkB,GAAI,MAAM,kBAAkB,KAAG,mBA6B7D,CAAA;AAED,+FAA+F;AAC/F,eAAO,MAAM,iBAAiB,GAAI,WAAW,mBAAmB,EAAE,cAAc,MAAM,KAAG,MAIxF,CAAA"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Smart CDN URL building shared by the synchronous Node signer (`@transloadit/utils/node`) and the
3
+ * asynchronous WebCrypto signer (`@transloadit/utils`). Only the HMAC differs between the two, so
4
+ * the string-to-sign and the final URL are assembled here and cannot drift apart.
5
+ */
6
+ /** Validates the options and assembles the string to sign; the caller supplies the HMAC. */
7
+ export const prepareSmartCdnUrl = (opts) => {
8
+ if (opts.workspace == null || opts.workspace === '')
9
+ throw new TypeError('workspace is required');
10
+ if (opts.template == null || opts.template === '')
11
+ throw new TypeError('template is required');
12
+ if (opts.input == null)
13
+ throw new TypeError('input is required');
14
+ const workspaceSlug = encodeURIComponent(opts.workspace);
15
+ const templateSlug = encodeURIComponent(opts.template);
16
+ const inputField = encodeURIComponent(opts.input);
17
+ const expiresAt = opts.expiresAt || Date.now() + 60 * 60 * 1000;
18
+ const queryParams = new URLSearchParams();
19
+ for (const [key, value] of Object.entries(opts.urlParams || {})) {
20
+ if (Array.isArray(value)) {
21
+ for (const val of value) {
22
+ queryParams.append(key, `${val}`);
23
+ }
24
+ }
25
+ else {
26
+ queryParams.append(key, `${value}`);
27
+ }
28
+ }
29
+ queryParams.set('auth_key', opts.authKey);
30
+ queryParams.set('exp', `${expiresAt}`);
31
+ queryParams.sort();
32
+ return {
33
+ stringToSign: `${workspaceSlug}/${templateSlug}/${inputField}?${queryParams}`,
34
+ parts: { workspaceSlug, templateSlug, inputField, queryParams },
35
+ };
36
+ };
37
+ /** Appends the `sig` parameter and returns the final `https://{workspace}.tlcdn.com/…` URL. */
38
+ export const finishSmartCdnUrl = ({ parts }, signatureHex) => {
39
+ const { workspaceSlug, templateSlug, inputField, queryParams } = parts;
40
+ queryParams.set('sig', `sha256:${signatureHex}`);
41
+ return `https://${workspaceSlug}.tlcdn.com/${templateSlug}/${inputField}?${queryParams}`;
42
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@transloadit/utils",
3
- "version": "4.5.1",
3
+ "version": "4.6.0",
4
4
  "description": "Transloadit shared utilities",
5
5
  "type": "module",
6
6
  "license": "MIT",