@transloadit/convex 0.0.3 → 0.0.4
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 +114 -134
- package/dist/client/index.d.ts +24 -13
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +14 -3
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +2 -2
- package/dist/component/_generated/dataModel.d.ts +1 -1
- package/dist/component/_generated/server.d.ts +1 -1
- package/dist/component/apiUtils.d.ts +26 -6
- package/dist/component/apiUtils.d.ts.map +1 -1
- package/dist/component/apiUtils.js +48 -38
- package/dist/component/apiUtils.js.map +1 -1
- package/dist/component/lib.d.ts +7 -9
- package/dist/component/lib.d.ts.map +1 -1
- package/dist/component/lib.js +74 -18
- package/dist/component/lib.js.map +1 -1
- package/dist/component/schema.d.ts +4 -6
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +0 -7
- package/dist/component/schema.js.map +1 -1
- package/dist/debug/index.d.ts +19 -0
- package/dist/debug/index.d.ts.map +1 -0
- package/dist/debug/index.js +49 -0
- package/dist/debug/index.js.map +1 -0
- package/dist/react/index.d.ts +201 -3
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +674 -94
- package/dist/react/index.js.map +1 -1
- package/dist/shared/assemblyUrls.d.ts +10 -0
- package/dist/shared/assemblyUrls.d.ts.map +1 -0
- package/dist/shared/assemblyUrls.js +26 -0
- package/dist/shared/assemblyUrls.js.map +1 -0
- package/dist/shared/errors.d.ts +7 -0
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/shared/errors.js +10 -0
- package/dist/shared/errors.js.map +1 -0
- package/dist/shared/pollAssembly.d.ts +12 -0
- package/dist/shared/pollAssembly.d.ts.map +1 -0
- package/dist/shared/pollAssembly.js +50 -0
- package/dist/shared/pollAssembly.js.map +1 -0
- package/dist/shared/resultTypes.d.ts +37 -0
- package/dist/shared/resultTypes.d.ts.map +1 -0
- package/dist/shared/resultTypes.js +2 -0
- package/dist/shared/resultTypes.js.map +1 -0
- package/dist/shared/resultUtils.d.ts +4 -0
- package/dist/shared/resultUtils.d.ts.map +1 -0
- package/dist/shared/resultUtils.js +69 -0
- package/dist/shared/resultUtils.js.map +1 -0
- package/dist/shared/tusUpload.d.ts +13 -0
- package/dist/shared/tusUpload.d.ts.map +1 -0
- package/dist/shared/tusUpload.js +32 -0
- package/dist/shared/tusUpload.js.map +1 -0
- package/dist/test/index.d.ts +4 -4
- package/dist/test/nodeModules.d.ts +2 -0
- package/dist/test/nodeModules.d.ts.map +1 -0
- package/dist/test/nodeModules.js +19 -0
- package/dist/test/nodeModules.js.map +1 -0
- package/package.json +36 -6
- package/src/client/index.ts +73 -7
- package/src/component/_generated/api.ts +2 -2
- package/src/component/_generated/dataModel.ts +1 -1
- package/src/component/_generated/server.ts +1 -1
- package/src/component/apiUtils.test.ts +166 -2
- package/src/component/apiUtils.ts +96 -64
- package/src/component/lib.test.ts +170 -4
- package/src/component/lib.ts +113 -25
- package/src/component/schema.ts +0 -10
- package/src/debug/index.ts +84 -0
- package/src/react/index.test.tsx +340 -0
- package/src/react/index.tsx +1089 -179
- package/src/react/uploadWithTus.test.tsx +192 -0
- package/src/shared/assemblyUrls.test.ts +71 -0
- package/src/shared/assemblyUrls.ts +59 -0
- package/src/shared/errors.ts +23 -0
- package/src/shared/pollAssembly.ts +65 -0
- package/src/shared/resultTypes.ts +44 -0
- package/src/shared/resultUtils.test.ts +29 -0
- package/src/shared/resultUtils.ts +71 -0
- package/src/shared/tusUpload.ts +59 -0
- package/src/test/index.ts +1 -1
- package/src/test/nodeModules.ts +19 -0
package/src/client/index.ts
CHANGED
|
@@ -1,11 +1,71 @@
|
|
|
1
|
-
import type { AssemblyStatus } from "@transloadit/
|
|
2
|
-
import type { AssemblyInstructionsInput } from "@transloadit/
|
|
1
|
+
import type { AssemblyStatus } from "@transloadit/zod/v3/assemblyStatus";
|
|
2
|
+
import type { AssemblyInstructionsInput } from "@transloadit/zod/v3/template";
|
|
3
3
|
import { actionGeneric, mutationGeneric, queryGeneric } from "convex/server";
|
|
4
4
|
import { type Infer, v } from "convex/values";
|
|
5
|
-
import type { ComponentApi } from "../component/_generated/component.
|
|
6
|
-
import type { RunActionCtx, RunMutationCtx, RunQueryCtx } from "./types.
|
|
5
|
+
import type { ComponentApi } from "../component/_generated/component.ts";
|
|
6
|
+
import type { RunActionCtx, RunMutationCtx, RunQueryCtx } from "./types.ts";
|
|
7
7
|
|
|
8
|
-
export {
|
|
8
|
+
export {
|
|
9
|
+
assemblyStatusErrCodeSchema,
|
|
10
|
+
assemblyStatusOkCodeSchema,
|
|
11
|
+
assemblyStatusResultsSchema,
|
|
12
|
+
assemblyStatusSchema,
|
|
13
|
+
isAssemblyBusy,
|
|
14
|
+
isAssemblyBusyStatus,
|
|
15
|
+
isAssemblyErrorStatus,
|
|
16
|
+
isAssemblyOkStatus,
|
|
17
|
+
isAssemblySysError,
|
|
18
|
+
isAssemblyTerminal,
|
|
19
|
+
isAssemblyTerminalError,
|
|
20
|
+
isAssemblyTerminalOk,
|
|
21
|
+
isAssemblyTerminalOkStatus,
|
|
22
|
+
} from "@transloadit/zod/v3/assemblyStatus";
|
|
23
|
+
export type {
|
|
24
|
+
ParsedWebhookRequest,
|
|
25
|
+
VerifiedWebhookRequest,
|
|
26
|
+
WebhookActionArgs,
|
|
27
|
+
} from "../component/apiUtils.ts";
|
|
28
|
+
export {
|
|
29
|
+
buildWebhookQueueArgs,
|
|
30
|
+
handleWebhookRequest,
|
|
31
|
+
parseAndVerifyTransloaditWebhook,
|
|
32
|
+
parseTransloaditWebhook,
|
|
33
|
+
} from "../component/apiUtils.ts";
|
|
34
|
+
export type {
|
|
35
|
+
NormalizedAssemblyUrls,
|
|
36
|
+
TransloaditAssembly,
|
|
37
|
+
} from "../shared/assemblyUrls.ts";
|
|
38
|
+
export {
|
|
39
|
+
ASSEMBLY_STATUS_COMPLETED,
|
|
40
|
+
ASSEMBLY_STATUS_UPLOADING,
|
|
41
|
+
getAssemblyStage,
|
|
42
|
+
isAssemblyCompletedStatus,
|
|
43
|
+
isAssemblyUploadingStatus,
|
|
44
|
+
normalizeAssemblyUploadUrls,
|
|
45
|
+
parseAssemblyFields,
|
|
46
|
+
parseAssemblyResults,
|
|
47
|
+
parseAssemblyStatus,
|
|
48
|
+
parseAssemblyUrls,
|
|
49
|
+
} from "../shared/assemblyUrls.ts";
|
|
50
|
+
export { pollAssembly } from "../shared/pollAssembly.ts";
|
|
51
|
+
export type {
|
|
52
|
+
ImageResizeResult,
|
|
53
|
+
ResultByRobot,
|
|
54
|
+
ResultForRobot,
|
|
55
|
+
StoreResult,
|
|
56
|
+
TransloaditResult,
|
|
57
|
+
VideoEncodeResult,
|
|
58
|
+
VideoThumbsResult,
|
|
59
|
+
} from "../shared/resultTypes.ts";
|
|
60
|
+
export {
|
|
61
|
+
getResultOriginalKey,
|
|
62
|
+
getResultUrl,
|
|
63
|
+
} from "../shared/resultUtils.ts";
|
|
64
|
+
export type {
|
|
65
|
+
TusMetadataOptions,
|
|
66
|
+
TusUploadConfig,
|
|
67
|
+
} from "../shared/tusUpload.ts";
|
|
68
|
+
export { buildTusUploadConfig } from "../shared/tusUpload.ts";
|
|
9
69
|
export type { AssemblyStatus, AssemblyInstructionsInput };
|
|
10
70
|
|
|
11
71
|
export interface TransloaditConfig {
|
|
@@ -74,6 +134,9 @@ export const vCreateAssemblyArgs = v.object({
|
|
|
74
134
|
userId: v.optional(v.string()),
|
|
75
135
|
});
|
|
76
136
|
|
|
137
|
+
/**
|
|
138
|
+
* @deprecated Prefer `makeTransloaditAPI` or `Transloadit` for new code.
|
|
139
|
+
*/
|
|
77
140
|
export class TransloaditClient {
|
|
78
141
|
declare component: TransloaditComponent;
|
|
79
142
|
declare config: TransloaditConfig;
|
|
@@ -172,6 +235,9 @@ export class TransloaditClient {
|
|
|
172
235
|
|
|
173
236
|
export class Transloadit extends TransloaditClient {}
|
|
174
237
|
|
|
238
|
+
/**
|
|
239
|
+
* @deprecated Prefer `new Transloadit(...)` or `makeTransloaditAPI(...)`.
|
|
240
|
+
*/
|
|
175
241
|
export function createTransloadit(
|
|
176
242
|
component: TransloaditComponent,
|
|
177
243
|
config?: Partial<TransloaditConfig>,
|
|
@@ -207,11 +273,12 @@ export function makeTransloaditAPI(
|
|
|
207
273
|
payload: v.any(),
|
|
208
274
|
rawBody: v.optional(v.string()),
|
|
209
275
|
signature: v.optional(v.string()),
|
|
210
|
-
verifySignature: v.optional(v.boolean()),
|
|
211
276
|
},
|
|
212
277
|
returns: v.object({
|
|
213
278
|
assemblyId: v.string(),
|
|
214
279
|
resultCount: v.number(),
|
|
280
|
+
ok: v.optional(v.string()),
|
|
281
|
+
status: v.optional(v.string()),
|
|
215
282
|
}),
|
|
216
283
|
handler: async (ctx, args) => {
|
|
217
284
|
return ctx.runAction(component.lib.handleWebhook, {
|
|
@@ -225,7 +292,6 @@ export function makeTransloaditAPI(
|
|
|
225
292
|
payload: v.any(),
|
|
226
293
|
rawBody: v.optional(v.string()),
|
|
227
294
|
signature: v.optional(v.string()),
|
|
228
|
-
verifySignature: v.optional(v.boolean()),
|
|
229
295
|
},
|
|
230
296
|
returns: v.object({
|
|
231
297
|
assemblyId: v.string(),
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type * as apiUtils from "../apiUtils.
|
|
12
|
-
import type * as lib from "../lib.
|
|
11
|
+
import type * as apiUtils from "../apiUtils.ts";
|
|
12
|
+
import type * as lib from "../lib.ts";
|
|
13
13
|
|
|
14
14
|
import type { ApiFromModules, FilterApi, FunctionReference } from "convex/server";
|
|
15
15
|
import { anyApi, componentsGeneric } from "convex/server";
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
internalMutationGeneric,
|
|
29
29
|
internalQueryGeneric,
|
|
30
30
|
} from "convex/server";
|
|
31
|
-
import type { DataModel } from "./dataModel.
|
|
31
|
+
import type { DataModel } from "./dataModel.ts";
|
|
32
32
|
|
|
33
33
|
export const query: QueryBuilder<DataModel, "public"> = queryGeneric;
|
|
34
34
|
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { createHmac } from "node:crypto";
|
|
2
|
-
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { describe, expect, test, vi } from "vitest";
|
|
3
3
|
import {
|
|
4
4
|
buildTransloaditParams,
|
|
5
|
+
buildWebhookQueueArgs,
|
|
6
|
+
handleWebhookRequest,
|
|
7
|
+
parseAndVerifyTransloaditWebhook,
|
|
5
8
|
parseTransloaditWebhook,
|
|
6
9
|
signTransloaditParams,
|
|
7
10
|
verifyWebhookSignature,
|
|
8
|
-
} from "./apiUtils.
|
|
11
|
+
} from "./apiUtils.ts";
|
|
9
12
|
|
|
10
13
|
describe("apiUtils", () => {
|
|
11
14
|
test("buildTransloaditParams requires templateId or steps", () => {
|
|
@@ -74,4 +77,165 @@ describe("apiUtils", () => {
|
|
|
74
77
|
"Missing transloadit payload",
|
|
75
78
|
);
|
|
76
79
|
});
|
|
80
|
+
|
|
81
|
+
test("parseAndVerifyTransloaditWebhook verifies signature", async () => {
|
|
82
|
+
const payload = { ok: "ASSEMBLY_COMPLETED", assembly_id: "asm_123" };
|
|
83
|
+
const rawBody = JSON.stringify(payload);
|
|
84
|
+
const secret = "webhook-secret";
|
|
85
|
+
const digest = createHmac("sha384", secret).update(rawBody).digest("hex");
|
|
86
|
+
const formData = new FormData();
|
|
87
|
+
formData.append("transloadit", rawBody);
|
|
88
|
+
formData.append("signature", `sha384:${digest}`);
|
|
89
|
+
|
|
90
|
+
const request = new Request("http://localhost", {
|
|
91
|
+
method: "POST",
|
|
92
|
+
body: formData,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const parsed = await parseAndVerifyTransloaditWebhook(request, {
|
|
96
|
+
authSecret: secret,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(parsed.payload).toEqual(payload);
|
|
100
|
+
expect(parsed.verified).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("parseAndVerifyTransloaditWebhook rejects invalid signature", async () => {
|
|
104
|
+
const payload = { ok: "ASSEMBLY_COMPLETED", assembly_id: "asm_123" };
|
|
105
|
+
const formData = new FormData();
|
|
106
|
+
formData.append("transloadit", JSON.stringify(payload));
|
|
107
|
+
formData.append("signature", "sha384:bad");
|
|
108
|
+
|
|
109
|
+
const request = new Request("http://localhost", {
|
|
110
|
+
method: "POST",
|
|
111
|
+
body: formData,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await expect(
|
|
115
|
+
parseAndVerifyTransloaditWebhook(request, {
|
|
116
|
+
authSecret: "secret",
|
|
117
|
+
}),
|
|
118
|
+
).rejects.toThrow("Invalid Transloadit webhook signature");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("buildWebhookQueueArgs returns webhook payload args", async () => {
|
|
122
|
+
const payload = { ok: "ASSEMBLY_COMPLETED", assembly_id: "asm_123" };
|
|
123
|
+
const rawBody = JSON.stringify(payload);
|
|
124
|
+
const secret = "webhook-secret";
|
|
125
|
+
const digest = createHmac("sha384", secret).update(rawBody).digest("hex");
|
|
126
|
+
const formData = new FormData();
|
|
127
|
+
formData.append("transloadit", rawBody);
|
|
128
|
+
formData.append("signature", `sha384:${digest}`);
|
|
129
|
+
|
|
130
|
+
const request = new Request("http://localhost", {
|
|
131
|
+
method: "POST",
|
|
132
|
+
body: formData,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const args = await buildWebhookQueueArgs(request, { authSecret: secret });
|
|
136
|
+
expect(args.payload).toEqual(payload);
|
|
137
|
+
expect(args.rawBody).toBe(rawBody);
|
|
138
|
+
expect(args.signature).toBe(`sha384:${digest}`);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("buildWebhookQueueArgs can skip verification", async () => {
|
|
142
|
+
const payload = { ok: "ASSEMBLY_COMPLETED", assembly_id: "asm_123" };
|
|
143
|
+
const rawBody = JSON.stringify(payload);
|
|
144
|
+
const formData = new FormData();
|
|
145
|
+
formData.append("transloadit", rawBody);
|
|
146
|
+
|
|
147
|
+
const request = new Request("http://localhost", {
|
|
148
|
+
method: "POST",
|
|
149
|
+
body: formData,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const args = await buildWebhookQueueArgs(request, {
|
|
153
|
+
authSecret: "secret",
|
|
154
|
+
requireSignature: false,
|
|
155
|
+
});
|
|
156
|
+
expect(args.payload).toEqual(payload);
|
|
157
|
+
expect(args.rawBody).toBe(rawBody);
|
|
158
|
+
expect(args.signature).toBeUndefined();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("handleWebhookRequest queues webhook by default", async () => {
|
|
162
|
+
const payload = { ok: "ASSEMBLY_COMPLETED", assembly_id: "asm_123" };
|
|
163
|
+
const rawBody = JSON.stringify(payload);
|
|
164
|
+
const formData = new FormData();
|
|
165
|
+
formData.append("transloadit", rawBody);
|
|
166
|
+
formData.append("signature", "sha384:abc");
|
|
167
|
+
|
|
168
|
+
const request = new Request("http://localhost", {
|
|
169
|
+
method: "POST",
|
|
170
|
+
body: formData,
|
|
171
|
+
});
|
|
172
|
+
const runAction = vi.fn().mockResolvedValue(null);
|
|
173
|
+
|
|
174
|
+
const response = await handleWebhookRequest(request, {
|
|
175
|
+
runAction,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(runAction).toHaveBeenCalledWith({
|
|
179
|
+
payload,
|
|
180
|
+
rawBody,
|
|
181
|
+
signature: "sha384:abc",
|
|
182
|
+
});
|
|
183
|
+
expect(response.status).toBe(202);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("handleWebhookRequest supports sync mode with verification", async () => {
|
|
187
|
+
const payload = { ok: "ASSEMBLY_COMPLETED", assembly_id: "asm_123" };
|
|
188
|
+
const rawBody = JSON.stringify(payload);
|
|
189
|
+
const secret = "webhook-secret";
|
|
190
|
+
const digest = createHmac("sha384", secret).update(rawBody).digest("hex");
|
|
191
|
+
const formData = new FormData();
|
|
192
|
+
formData.append("transloadit", rawBody);
|
|
193
|
+
formData.append("signature", `sha384:${digest}`);
|
|
194
|
+
|
|
195
|
+
const request = new Request("http://localhost", {
|
|
196
|
+
method: "POST",
|
|
197
|
+
body: formData,
|
|
198
|
+
});
|
|
199
|
+
const runAction = vi.fn().mockResolvedValue(null);
|
|
200
|
+
|
|
201
|
+
const response = await handleWebhookRequest(request, {
|
|
202
|
+
mode: "sync",
|
|
203
|
+
runAction,
|
|
204
|
+
requireSignature: true,
|
|
205
|
+
authSecret: secret,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
expect(runAction).toHaveBeenCalledWith({
|
|
209
|
+
payload,
|
|
210
|
+
rawBody,
|
|
211
|
+
signature: `sha384:${digest}`,
|
|
212
|
+
});
|
|
213
|
+
expect(response.status).toBe(204);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("handleWebhookRequest honors a custom response status", async () => {
|
|
217
|
+
const payload = { ok: "ASSEMBLY_COMPLETED", assembly_id: "asm_123" };
|
|
218
|
+
const rawBody = JSON.stringify(payload);
|
|
219
|
+
const formData = new FormData();
|
|
220
|
+
formData.append("transloadit", rawBody);
|
|
221
|
+
formData.append("signature", "sha384:abc");
|
|
222
|
+
|
|
223
|
+
const request = new Request("http://localhost", {
|
|
224
|
+
method: "POST",
|
|
225
|
+
body: formData,
|
|
226
|
+
});
|
|
227
|
+
const runAction = vi.fn().mockResolvedValue(null);
|
|
228
|
+
|
|
229
|
+
const response = await handleWebhookRequest(request, {
|
|
230
|
+
runAction,
|
|
231
|
+
responseStatus: 299,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
expect(runAction).toHaveBeenCalledWith({
|
|
235
|
+
payload,
|
|
236
|
+
rawBody,
|
|
237
|
+
signature: "sha384:abc",
|
|
238
|
+
});
|
|
239
|
+
expect(response.status).toBe(299);
|
|
240
|
+
});
|
|
77
241
|
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
1
|
+
import { signParams, verifyWebhookSignature } from "@transloadit/utils";
|
|
2
|
+
import type { AssemblyStatusResults } from "@transloadit/zod/v3/assemblyStatus";
|
|
3
|
+
import type { AssemblyInstructionsInput } from "@transloadit/zod/v3/template";
|
|
4
|
+
import { transloaditError } from "../shared/errors.ts";
|
|
3
5
|
|
|
4
6
|
export interface TransloaditAuthConfig {
|
|
5
7
|
authKey: string;
|
|
@@ -26,7 +28,10 @@ export function buildTransloaditParams(
|
|
|
26
28
|
options: BuildParamsOptions,
|
|
27
29
|
): BuildParamsResult {
|
|
28
30
|
if (!options.templateId && !options.steps) {
|
|
29
|
-
throw
|
|
31
|
+
throw transloaditError(
|
|
32
|
+
"createAssembly",
|
|
33
|
+
"Provide either templateId or steps to create an Assembly",
|
|
34
|
+
);
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
const auth: Record<string, string> = {
|
|
@@ -62,40 +67,11 @@ export function buildTransloaditParams(
|
|
|
62
67
|
};
|
|
63
68
|
}
|
|
64
69
|
|
|
65
|
-
async function hmacHex(
|
|
66
|
-
algorithm: "SHA-384" | "SHA-1",
|
|
67
|
-
key: string,
|
|
68
|
-
data: string,
|
|
69
|
-
): Promise<string> {
|
|
70
|
-
if (!globalThis.crypto?.subtle) {
|
|
71
|
-
throw new Error("Web Crypto is required to sign Transloadit payloads");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const encoder = new TextEncoder();
|
|
75
|
-
const cryptoKey = await globalThis.crypto.subtle.importKey(
|
|
76
|
-
"raw",
|
|
77
|
-
encoder.encode(key),
|
|
78
|
-
{ name: "HMAC", hash: { name: algorithm } },
|
|
79
|
-
false,
|
|
80
|
-
["sign"],
|
|
81
|
-
);
|
|
82
|
-
const signature = await globalThis.crypto.subtle.sign(
|
|
83
|
-
"HMAC",
|
|
84
|
-
cryptoKey,
|
|
85
|
-
encoder.encode(data),
|
|
86
|
-
);
|
|
87
|
-
const bytes = new Uint8Array(signature);
|
|
88
|
-
return Array.from(bytes)
|
|
89
|
-
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
90
|
-
.join("");
|
|
91
|
-
}
|
|
92
|
-
|
|
93
70
|
export async function signTransloaditParams(
|
|
94
71
|
paramsString: string,
|
|
95
72
|
authSecret: string,
|
|
96
73
|
): Promise<string> {
|
|
97
|
-
|
|
98
|
-
return `sha384:${signature}`;
|
|
74
|
+
return signParams(paramsString, authSecret, "sha384");
|
|
99
75
|
}
|
|
100
76
|
|
|
101
77
|
export type ParsedWebhookRequest = {
|
|
@@ -104,6 +80,10 @@ export type ParsedWebhookRequest = {
|
|
|
104
80
|
signature?: string;
|
|
105
81
|
};
|
|
106
82
|
|
|
83
|
+
export type VerifiedWebhookRequest = ParsedWebhookRequest & {
|
|
84
|
+
verified: boolean;
|
|
85
|
+
};
|
|
86
|
+
|
|
107
87
|
export async function parseTransloaditWebhook(
|
|
108
88
|
request: Request,
|
|
109
89
|
): Promise<ParsedWebhookRequest> {
|
|
@@ -112,7 +92,7 @@ export async function parseTransloaditWebhook(
|
|
|
112
92
|
const signature = formData.get("signature");
|
|
113
93
|
|
|
114
94
|
if (typeof rawPayload !== "string") {
|
|
115
|
-
throw
|
|
95
|
+
throw transloaditError("webhook", "Missing transloadit payload");
|
|
116
96
|
}
|
|
117
97
|
|
|
118
98
|
return {
|
|
@@ -122,44 +102,96 @@ export async function parseTransloaditWebhook(
|
|
|
122
102
|
};
|
|
123
103
|
}
|
|
124
104
|
|
|
125
|
-
function
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
105
|
+
export async function parseAndVerifyTransloaditWebhook(
|
|
106
|
+
request: Request,
|
|
107
|
+
options: {
|
|
108
|
+
authSecret: string;
|
|
109
|
+
requireSignature?: boolean;
|
|
110
|
+
},
|
|
111
|
+
): Promise<VerifiedWebhookRequest> {
|
|
112
|
+
const parsed = await parseTransloaditWebhook(request);
|
|
113
|
+
const authSecret = options.authSecret;
|
|
114
|
+
if (!authSecret) {
|
|
115
|
+
throw transloaditError(
|
|
116
|
+
"webhook",
|
|
117
|
+
"Missing authSecret for webhook verification",
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const verified = await verifyWebhookSignature({
|
|
121
|
+
rawBody: parsed.rawBody,
|
|
122
|
+
signatureHeader: parsed.signature,
|
|
123
|
+
authSecret,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (options.requireSignature ?? true) {
|
|
127
|
+
if (!verified) {
|
|
128
|
+
throw transloaditError(
|
|
129
|
+
"webhook",
|
|
130
|
+
"Invalid Transloadit webhook signature",
|
|
131
|
+
);
|
|
132
|
+
}
|
|
130
133
|
}
|
|
131
|
-
return mismatch === 0;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export async function verifyWebhookSignature(options: {
|
|
135
|
-
rawBody: string;
|
|
136
|
-
signatureHeader?: string;
|
|
137
|
-
authSecret: string;
|
|
138
|
-
}): Promise<boolean> {
|
|
139
|
-
if (!options.signatureHeader) return false;
|
|
140
134
|
|
|
141
|
-
|
|
142
|
-
|
|
135
|
+
return { ...parsed, verified };
|
|
136
|
+
}
|
|
143
137
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
138
|
+
export async function buildWebhookQueueArgs(
|
|
139
|
+
request: Request,
|
|
140
|
+
options: {
|
|
141
|
+
authSecret: string;
|
|
142
|
+
requireSignature?: boolean;
|
|
143
|
+
},
|
|
144
|
+
): Promise<ParsedWebhookRequest> {
|
|
145
|
+
if (options.requireSignature === false) {
|
|
146
|
+
return parseTransloaditWebhook(request);
|
|
147
|
+
}
|
|
147
148
|
|
|
148
|
-
const
|
|
149
|
-
|
|
149
|
+
const parsed = await parseAndVerifyTransloaditWebhook(request, options);
|
|
150
|
+
return {
|
|
151
|
+
payload: parsed.payload,
|
|
152
|
+
rawBody: parsed.rawBody,
|
|
153
|
+
signature: parsed.signature,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
150
156
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
157
|
+
export type WebhookActionArgs = {
|
|
158
|
+
payload: unknown;
|
|
159
|
+
rawBody?: string;
|
|
160
|
+
signature?: string;
|
|
161
|
+
};
|
|
154
162
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
163
|
+
export async function handleWebhookRequest(
|
|
164
|
+
request: Request,
|
|
165
|
+
options: {
|
|
166
|
+
mode?: "queue" | "sync";
|
|
167
|
+
runAction: (args: WebhookActionArgs) => Promise<unknown>;
|
|
168
|
+
requireSignature?: boolean;
|
|
169
|
+
authSecret?: string;
|
|
170
|
+
responseStatus?: number;
|
|
171
|
+
},
|
|
172
|
+
): Promise<Response> {
|
|
173
|
+
const mode = options.mode ?? "queue";
|
|
174
|
+
const requireSignature = options.requireSignature ?? false;
|
|
175
|
+
|
|
176
|
+
const parsed = requireSignature
|
|
177
|
+
? await parseAndVerifyTransloaditWebhook(request, {
|
|
178
|
+
authSecret: options.authSecret ?? "",
|
|
179
|
+
requireSignature: true,
|
|
180
|
+
})
|
|
181
|
+
: await parseTransloaditWebhook(request);
|
|
182
|
+
|
|
183
|
+
await options.runAction({
|
|
184
|
+
payload: parsed.payload,
|
|
185
|
+
rawBody: parsed.rawBody,
|
|
186
|
+
signature: parsed.signature,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const status = options.responseStatus ?? (mode === "sync" ? 204 : 202);
|
|
190
|
+
return new Response(null, { status });
|
|
161
191
|
}
|
|
162
192
|
|
|
193
|
+
export { verifyWebhookSignature };
|
|
194
|
+
|
|
163
195
|
export type AssemblyResult = AssemblyStatusResults[string][number];
|
|
164
196
|
|
|
165
197
|
export type AssemblyResultRecord = {
|