From 528759db7cbbc0d28b213f18c24ab4ea3108c0d0 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 12 Feb 2025 18:20:54 +0000 Subject: [PATCH 1/8] WIP with webhook SDK function and types --- apps/webapp/app/models/projectAlert.server.ts | 1 + packages/core/src/v3/schemas/index.ts | 1 + packages/core/src/v3/schemas/webhooks.ts | 141 +++++++++++++++ packages/trigger-sdk/src/v3/webhooks.ts | 167 ++++++++++++++++++ 4 files changed, 310 insertions(+) create mode 100644 packages/core/src/v3/schemas/webhooks.ts create mode 100644 packages/trigger-sdk/src/v3/webhooks.ts diff --git a/apps/webapp/app/models/projectAlert.server.ts b/apps/webapp/app/models/projectAlert.server.ts index cd3af4004f..d2ab0be1d1 100644 --- a/apps/webapp/app/models/projectAlert.server.ts +++ b/apps/webapp/app/models/projectAlert.server.ts @@ -4,6 +4,7 @@ import { EncryptedSecretValueSchema } from "~/services/secrets/secretStore.serve export const ProjectAlertWebhookProperties = z.object({ secret: EncryptedSecretValueSchema, url: z.string(), + version: z.string().optional().default("v1"), }); export type ProjectAlertWebhookProperties = z.infer; diff --git a/packages/core/src/v3/schemas/index.ts b/packages/core/src/v3/schemas/index.ts index 6f8b74e64e..ba94a4a16b 100644 --- a/packages/core/src/v3/schemas/index.ts +++ b/packages/core/src/v3/schemas/index.ts @@ -10,3 +10,4 @@ export * from "./eventFilter.js"; export * from "./openTelemetry.js"; export * from "./config.js"; export * from "./build.js"; +export * from "./webhooks.js"; diff --git a/packages/core/src/v3/schemas/webhooks.ts b/packages/core/src/v3/schemas/webhooks.ts new file mode 100644 index 0000000000..5fa934d05b --- /dev/null +++ b/packages/core/src/v3/schemas/webhooks.ts @@ -0,0 +1,141 @@ +import { z } from "zod"; +import { MachinePresetName, TaskRunError } from "./common.js"; +import { RunStatus } from "./api.js"; +import { RuntimeEnvironmentTypeSchema } from "../../schemas/api.js"; + +const AlertWebhookRunFailedObject = z.object({ + task: z.object({ + id: z.string(), + filePath: z.string(), + exportName: z.string(), + version: z.string(), + sdkVersion: z.string(), + cliVersion: z.string(), + }), + run: z.object({ + id: z.string(), + number: z.number(), + status: RunStatus, + createdAt: z.coerce.date(), + startedAt: z.coerce.date().optional(), + completedAt: z.coerce.date().optional(), + isTest: z.boolean(), + idempotencyKey: z.string(), + tags: z.array(z.string()), + error: TaskRunError, + machine: MachinePresetName, + dashboardUrl: z.string(), + }), + environment: z.object({ + id: z.string(), + type: RuntimeEnvironmentTypeSchema, + slug: z.string(), + }), + organization: z.object({ + id: z.string(), + slug: z.string(), + name: z.string(), + }), + project: z.object({ + id: z.string(), + ref: z.string(), + slug: z.string(), + name: z.string(), + }), +}); +export type AlertWebhookRunFailedObject = z.infer; + +export const DeployError = z.object({ + name: z.string(), + message: z.string(), + stack: z.string().optional(), + stderr: z.string().optional(), +}); +export type DeployError = z.infer; + +export const AlertWebhookDeploymentObject = z.discriminatedUnion("success", [ + z.object({ + success: z.literal(true), + deployment: z.object({ + id: z.string(), + status: z.string(), + version: z.string(), + shortCode: z.string(), + deployedAt: z.coerce.date(), + }), + tasks: z.array( + z.object({ + id: z.string(), + filePath: z.string(), + exportName: z.string(), + triggerSource: z.string(), + }) + ), + environment: z.object({ + id: z.string(), + type: RuntimeEnvironmentTypeSchema, + slug: z.string(), + }), + organization: z.object({ + id: z.string(), + slug: z.string(), + name: z.string(), + }), + project: z.object({ + id: z.string(), + ref: z.string(), + slug: z.string(), + name: z.string(), + }), + }), + z.object({ + success: z.literal(false), + deployment: z.object({ + id: z.string(), + status: z.string(), + version: z.string(), + shortCode: z.string(), + failedAt: z.coerce.date(), + }), + environment: z.object({ + id: z.string(), + type: RuntimeEnvironmentTypeSchema, + slug: z.string(), + }), + organization: z.object({ + id: z.string(), + slug: z.string(), + name: z.string(), + }), + project: z.object({ + id: z.string(), + ref: z.string(), + slug: z.string(), + name: z.string(), + }), + error: DeployError, + }), +]); + +export type AlertWebhookDeploymentObject = z.infer; + +const commonProperties = { + id: z.string(), + created: z.coerce.date(), + webhookVersion: z.string(), +}; + +export const Webhook = z.discriminatedUnion("type", [ + z.object({ + ...commonProperties, + type: z.literal("alert.run.failed"), + object: AlertWebhookRunFailedObject, + }), + z.object({ + ...commonProperties, + type: z.literal("alert.deployment.finished"), + object: AlertWebhookDeploymentObject, + }), +]); + +export type Webhook = z.infer; diff --git a/packages/trigger-sdk/src/v3/webhooks.ts b/packages/trigger-sdk/src/v3/webhooks.ts new file mode 100644 index 0000000000..4e5d95e16b --- /dev/null +++ b/packages/trigger-sdk/src/v3/webhooks.ts @@ -0,0 +1,167 @@ +import { Webhook } from "@trigger.dev/core/v3"; +import { subtle } from "crypto"; + +/** + * The type of error thrown when a webhook fails to parse or verify + */ +export class WebhookError extends Error { + constructor(message: string) { + super(message); + this.name = "WebhookError"; + } +} + +/** Header name used for webhook signatures */ +const SIGNATURE_HEADER_NAME = "x-trigger-signature-hmacsha256"; + +/** + * Options for constructing a webhook event + */ +type ConstructEventOptions = { + /** Raw payload as string or Buffer */ + payload: string | Buffer; + /** Signature header as string, Buffer, or string array */ + header: string | Buffer | Array; +}; + +/** + * Interface describing the webhook utilities + */ +interface Webhooks { + /** + * Constructs and validates a webhook event from an incoming request + * @param request - Either a Request object or ConstructEventOptions containing the payload and signature + * @param secret - Secret key used to verify the webhook signature + * @returns Promise resolving to a validated AlertWebhook object + * @throws {WebhookError} If validation fails or payload can't be parsed + * + * @example + * // Using with Request object + * const event = await webhooks.constructEvent(request, "webhook_secret"); + * + * @example + * // Using with manual options + * const event = await webhooks.constructEvent({ + * payload: rawBody, + * header: signatureHeader + * }, "webhook_secret"); + */ + constructEvent(request: ConstructEventOptions | Request, secret: string): Promise; + + /** Header name used for webhook signatures */ + SIGNATURE_HEADER_NAME: string; +} + +/** + * Webhook utilities for handling incoming webhook requests + */ +export const webhooks: Webhooks = { + constructEvent, + SIGNATURE_HEADER_NAME, +}; + +async function constructEvent( + request: ConstructEventOptions | Request, + secret: string +): Promise { + let payload: string; + let signature: string; + + if (request instanceof Request) { + if (!secret) { + throw new WebhookError("Secret is required when passing a Request object"); + } + + const signatureHeader = request.headers.get(SIGNATURE_HEADER_NAME); + if (!signatureHeader) { + throw new WebhookError("No signature header found"); + } + signature = signatureHeader; + + payload = await request.text(); + } else { + payload = request.payload.toString(); + + if (Array.isArray(request.header)) { + throw new WebhookError("Signature header cannot be an array"); + } + signature = request.header.toString(); + } + + // Verify the signature + const isValid = await verifySignature(payload, signature, secret); + + if (!isValid) { + throw new WebhookError("Invalid signature"); + } + + // Parse and validate the payload + try { + const jsonPayload = JSON.parse(payload); + const parsedPayload = Webhook.parse(jsonPayload); + return parsedPayload; + } catch (error) { + if (error instanceof Error) { + throw new WebhookError(`Webhook parsing failed: ${error.message}`); + } + throw new WebhookError("Webhook parsing failed"); + } +} + +/** + * Verifies the signature of a webhook payload + * @param payload - Raw payload string to verify + * @param signature - Expected signature to check against + * @param secret - Secret key used to generate the signature + * @returns Promise resolving to boolean indicating if signature is valid + * @throws {WebhookError} If signature verification process fails + * + * @example + * const isValid = await verifySignature( + * '{"event": "test"}', + * "abc123signature", + * "webhook_secret" + * ); + */ +async function verifySignature( + payload: string, + signature: string, + secret: string +): Promise { + try { + // Convert the payload and secret to buffers + const hashPayload = Buffer.from(payload, "utf-8"); + const hmacSecret = Buffer.from(secret, "utf-8"); + + // Import the secret key + const key = await subtle.importKey( + "raw", + hmacSecret, + { name: "HMAC", hash: "SHA-256" }, + false, + ["sign", "verify"] + ); + + // Calculate the expected signature + const actualSignature = await subtle.sign("HMAC", key, hashPayload); + const actualSignatureHex = Buffer.from(actualSignature).toString("hex"); + + // Compare signatures using timing-safe comparison + return timingSafeEqual(signature, actualSignatureHex); + } catch (error) { + throw new WebhookError("Signature verification failed"); + } +} + +// Timing-safe comparison to prevent timing attacks +function timingSafeEqual(a: string, b: string): boolean { + if (a.length !== b.length) { + return false; + } + + let result = 0; + for (let i = 0; i < a.length; i++) { + result |= a.charCodeAt(i) ^ b.charCodeAt(i); + } + return result === 0; +} From 109f0144165dec9187f75f582dc3f63dc8e01eee Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 12 Feb 2025 18:26:29 +0000 Subject: [PATCH 2/8] JSDocs added to the schema --- packages/core/src/v3/schemas/webhooks.ts | 76 +++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/packages/core/src/v3/schemas/webhooks.ts b/packages/core/src/v3/schemas/webhooks.ts index 5fa934d05b..9ab5ac0b89 100644 --- a/packages/core/src/v3/schemas/webhooks.ts +++ b/packages/core/src/v3/schemas/webhooks.ts @@ -2,85 +2,142 @@ import { z } from "zod"; import { MachinePresetName, TaskRunError } from "./common.js"; import { RunStatus } from "./api.js"; import { RuntimeEnvironmentTypeSchema } from "../../schemas/api.js"; +import { OutOfMemoryError } from "../errors.js"; +/** Represents a failed run alert webhook payload */ const AlertWebhookRunFailedObject = z.object({ + /** Task information */ task: z.object({ + /** Unique identifier for the task */ id: z.string(), + /** File path where the task is defined */ filePath: z.string(), + /** Name of the exported task function */ exportName: z.string(), + /** Version of the task */ version: z.string(), + /** Version of the SDK used */ sdkVersion: z.string(), + /** Version of the CLI used */ cliVersion: z.string(), }), + /** Run information */ run: z.object({ + /** Unique identifier for the run */ id: z.string(), + /** Run number */ number: z.number(), + /** Current status of the run */ status: RunStatus, + /** When the run was created */ createdAt: z.coerce.date(), + /** When the run started executing */ startedAt: z.coerce.date().optional(), + /** When the run finished executing */ completedAt: z.coerce.date().optional(), + /** Whether this is a test run */ isTest: z.boolean(), + /** Idempotency key for the run */ idempotencyKey: z.string(), + /** Associated tags */ tags: z.array(z.string()), + /** Error information */ error: TaskRunError, - machine: MachinePresetName, + /** Whether the run was an out-of-memory error */ + isOutOfMemoryError: z.boolean(), + /** Machine preset used for the run */ + machine: z.string(), + /** URL to view the run in the dashboard */ dashboardUrl: z.string(), }), + /** Environment information */ environment: z.object({ + /** Environment ID */ id: z.string(), + /** Environment type */ type: RuntimeEnvironmentTypeSchema, + /** Environment slug */ slug: z.string(), }), + /** Organization information */ organization: z.object({ + /** Organization ID */ id: z.string(), + /** Organization slug */ slug: z.string(), + /** Organization name */ name: z.string(), }), + /** Project information */ project: z.object({ + /** Project ID */ id: z.string(), + /** Project reference */ ref: z.string(), + /** Project slug */ slug: z.string(), + /** Project name */ name: z.string(), }), }); export type AlertWebhookRunFailedObject = z.infer; +/** Represents a deployment error */ export const DeployError = z.object({ + /** Error name */ name: z.string(), + /** Error message */ message: z.string(), + /** Error stack trace */ stack: z.string().optional(), + /** Standard error output */ stderr: z.string().optional(), }); export type DeployError = z.infer; +/** Represents a deployment alert webhook payload */ export const AlertWebhookDeploymentObject = z.discriminatedUnion("success", [ + /** Successful deployment */ z.object({ success: z.literal(true), deployment: z.object({ + /** Deployment ID */ id: z.string(), + /** Deployment status */ status: z.string(), + /** Deployment version */ version: z.string(), + /** Short code identifier */ shortCode: z.string(), + /** When the deployment completed */ deployedAt: z.coerce.date(), }), + /** Deployed tasks */ tasks: z.array( z.object({ + /** Task ID */ id: z.string(), + /** File path where the task is defined */ filePath: z.string(), + /** Name of the exported task function */ exportName: z.string(), + /** Source of the trigger */ triggerSource: z.string(), }) ), + /** Environment information */ environment: z.object({ id: z.string(), type: RuntimeEnvironmentTypeSchema, slug: z.string(), }), + /** Organization information */ organization: z.object({ id: z.string(), slug: z.string(), name: z.string(), }), + /** Project information */ project: z.object({ id: z.string(), ref: z.string(), @@ -88,49 +145,66 @@ export const AlertWebhookDeploymentObject = z.discriminatedUnion("success", [ name: z.string(), }), }), + /** Failed deployment */ z.object({ success: z.literal(false), deployment: z.object({ + /** Deployment ID */ id: z.string(), + /** Deployment status */ status: z.string(), + /** Deployment version */ version: z.string(), + /** Short code identifier */ shortCode: z.string(), + /** When the deployment failed */ failedAt: z.coerce.date(), }), + /** Environment information */ environment: z.object({ id: z.string(), type: RuntimeEnvironmentTypeSchema, slug: z.string(), }), + /** Organization information */ organization: z.object({ id: z.string(), slug: z.string(), name: z.string(), }), + /** Project information */ project: z.object({ id: z.string(), ref: z.string(), slug: z.string(), name: z.string(), }), + /** Error information */ error: DeployError, }), ]); export type AlertWebhookDeploymentObject = z.infer; +/** Common properties for all webhooks */ const commonProperties = { + /** Webhook ID */ id: z.string(), + /** When the webhook was created */ created: z.coerce.date(), + /** Version of the webhook */ webhookVersion: z.string(), }; +/** Represents all possible webhook types */ export const Webhook = z.discriminatedUnion("type", [ + /** Run failed alert webhook */ z.object({ ...commonProperties, type: z.literal("alert.run.failed"), object: AlertWebhookRunFailedObject, }), + /** Deployment finished alert webhook */ z.object({ ...commonProperties, type: z.literal("alert.deployment.finished"), From 8b7181708d68ec466e3c5056c8b8b4c659311970 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 12 Feb 2025 20:19:33 +0000 Subject: [PATCH 3/8] Webhooks are working --- .../app/routes/internal.webhooks.tester.ts | 50 ++ .../alerts/createAlertChannel.server.ts | 1 + .../v3/services/alerts/deliverAlert.server.ts | 577 ++++++++---------- .../app/v3/services/completeAttempt.server.ts | 2 +- packages/core/src/v3/schemas/webhooks.ts | 176 +++--- packages/trigger-sdk/src/v3/index.ts | 1 + packages/trigger-sdk/src/v3/webhooks.ts | 4 + 7 files changed, 409 insertions(+), 402 deletions(-) create mode 100644 apps/webapp/app/routes/internal.webhooks.tester.ts diff --git a/apps/webapp/app/routes/internal.webhooks.tester.ts b/apps/webapp/app/routes/internal.webhooks.tester.ts new file mode 100644 index 0000000000..f49d12e600 --- /dev/null +++ b/apps/webapp/app/routes/internal.webhooks.tester.ts @@ -0,0 +1,50 @@ +import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; +import { webhooks } from "@trigger.dev/sdk/v3"; +import { WebhookError } from "@trigger.dev/sdk/v3"; +import { logger } from "~/services/logger.server"; + +/* + This route is for testing our webhooks +*/ +export async function action({ request }: ActionFunctionArgs) { + // Make sure this is a POST request + if (request.method !== "POST") { + return json({ error: "[Webhook Internal Test] Method not allowed" }, { status: 405 }); + } + + const clonedRequest = request.clone(); + const rawBody = await clonedRequest.text(); + logger.log("[Webhook Internal Test] Raw body:", { rawBody }); + + try { + // Construct and verify the webhook event + const event = await webhooks.constructEvent(request, process.env.INTERNAL_TEST_WEBHOOK_SECRET!); + + // Handle the webhook event + logger.log("[Webhook Internal Test] Received verified webhook:", event); + + // Process the event based on its type + switch (event.type) { + default: + logger.log(`[Webhook Internal Test] Unhandled event type: ${event.type}`); + } + + // Return a success response + return json({ received: true }, { status: 200 }); + } catch (err) { + // Handle webhook errors + if (err instanceof WebhookError) { + logger.error("[Webhook Internal Test] Webhook error:", { message: err.message }); + return json({ error: err.message }, { status: 400 }); + } + + if (err instanceof Error) { + logger.error("[Webhook Internal Test] Error processing webhook:", { message: err.message }); + return json({ error: err.message }, { status: 400 }); + } + + // Handle other errors + logger.error("[Webhook Internal Test] Error processing webhook:", { err }); + return json({ error: "Internal server error" }, { status: 500 }); + } +} diff --git a/apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts b/apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts index edc42d3891..b2bbb42398 100644 --- a/apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts +++ b/apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts @@ -100,6 +100,7 @@ export class CreateAlertChannelService extends BaseService { return { url: channel.url, secret: await encryptSecret(env.ENCRYPTION_KEY, channel.secret ?? nanoid()), + version: "v2", }; case "SLACK": return { diff --git a/apps/webapp/app/v3/services/alerts/deliverAlert.server.ts b/apps/webapp/app/v3/services/alerts/deliverAlert.server.ts index 51832bcd4d..d476949a4f 100644 --- a/apps/webapp/app/v3/services/alerts/deliverAlert.server.ts +++ b/apps/webapp/app/v3/services/alerts/deliverAlert.server.ts @@ -6,7 +6,14 @@ import { WebAPIRateLimitedError, WebAPIRequestError, } from "@slack/web-api"; -import { TaskRunError, createJsonErrorObject } from "@trigger.dev/core/v3"; +import { + Webhook, + TaskRunError, + createJsonErrorObject, + RunFailedWebhook, + DeploymentFailedWebhook, + DeploymentSuccessWebhook, +} from "@trigger.dev/core/v3"; import assertNever from "assert-never"; import { subtle } from "crypto"; import { Prisma, prisma, PrismaClientOrTransaction } from "~/db.server"; @@ -31,6 +38,9 @@ import { BaseService } from "../baseService.server"; import { generateFriendlyId } from "~/v3/friendlyIdentifiers"; import { ProjectAlertType } from "@trigger.dev/database"; import { alertsRateLimiter } from "~/v3/alertsRateLimiter.server"; +import { v3RunPath } from "~/utils/pathBuilder"; +import { isOOMError } from "../completeAttempt.server"; +import { ApiRetrieveRunPresenter } from "~/presenters/v3/ApiRetrieveRunPresenter.server"; type FoundAlert = Prisma.Result< typeof prisma.projectAlert, @@ -193,43 +203,7 @@ export class DeliverAlertService extends BaseService { switch (alert.type) { case "TASK_RUN_ATTEMPT": { - if (alert.taskRunAttempt) { - const parseError = TaskRunError.safeParse(alert.taskRunAttempt.error); - - let taskRunError: TaskRunError; - - if (!parseError.success) { - logger.error("[DeliverAlert] Attempt: Failed to parse task run error", { - issues: parseError.error.issues, - taskAttemptError: alert.taskRunAttempt.error, - }); - - taskRunError = { - type: "STRING_ERROR" as const, - raw: "No error on task", - }; - } else { - taskRunError = parseError.data; - } - - await sendAlertEmail({ - email: "alert-attempt", - to: emailProperties.data.email, - taskIdentifier: alert.taskRunAttempt.taskRun.taskIdentifier, - fileName: alert.taskRunAttempt.backgroundWorkerTask.filePath, - exportName: alert.taskRunAttempt.backgroundWorkerTask.exportName, - version: alert.taskRunAttempt.backgroundWorker.version, - environment: alert.environment.slug, - error: createJsonErrorObject(taskRunError), - attemptLink: `${env.APP_ORIGIN}/projects/v3/${alert.project.externalRef}/runs/${alert.taskRunAttempt.taskRun.friendlyId}`, - organization: alert.project.organization.title, - }); - } else { - logger.error("[DeliverAlert] Task run attempt not found", { - alert, - }); - } - + logger.error("[DeliverAlert] Task run attempt alerts are deprecated, not sending anything"); break; } case "TASK_RUN": { @@ -332,102 +306,110 @@ export class DeliverAlertService extends BaseService { switch (alert.type) { case "TASK_RUN_ATTEMPT": { - if (alert.taskRunAttempt) { - const taskRunError = TaskRunError.safeParse(alert.taskRunAttempt.error); - - if (!taskRunError.success) { - logger.error("[DeliverAlert] Attempt: Failed to parse task run error", { - issues: taskRunError.error.issues, - taskAttemptError: alert.taskRunAttempt.error, - }); - - return; - } - - const error = createJsonErrorObject(taskRunError.data); - - const payload = { - task: { - id: alert.taskRunAttempt.taskRun.taskIdentifier, - filePath: alert.taskRunAttempt.backgroundWorkerTask.filePath, - exportName: alert.taskRunAttempt.backgroundWorkerTask.exportName, - }, - attempt: { - id: alert.taskRunAttempt.friendlyId, - number: alert.taskRunAttempt.number, - startedAt: alert.taskRunAttempt.startedAt, - status: alert.taskRunAttempt.status, - }, - run: { - id: alert.taskRunAttempt.taskRun.friendlyId, - isTest: alert.taskRunAttempt.taskRun.isTest, - createdAt: alert.taskRunAttempt.taskRun.createdAt, - idempotencyKey: alert.taskRunAttempt.taskRun.idempotencyKey, - }, - environment: { - id: alert.environment.id, - type: alert.environment.type, - slug: alert.environment.slug, - }, - organization: { - id: alert.project.organizationId, - slug: alert.project.organization.slug, - name: alert.project.organization.title, - }, - project: { - id: alert.project.id, - ref: alert.project.externalRef, - slug: alert.project.slug, - name: alert.project.name, - }, - error, - }; - - await this.#deliverWebhook(payload, webhookProperties.data); - } else { - logger.error("[DeliverAlert] Task run attempt not found", { - alert, - }); - } - + logger.error("[DeliverAlert] Task run attempt alerts are deprecated, not sending anything"); break; } case "TASK_RUN": { if (alert.taskRun) { const error = this.#getRunError(alert); - const payload = { - task: { - id: alert.taskRun.taskIdentifier, - fileName: alert.taskRun.lockedBy?.filePath ?? "Unknown", - exportName: alert.taskRun.lockedBy?.exportName ?? "Unknown", - }, - run: { - id: alert.taskRun.friendlyId, - isTest: alert.taskRun.isTest, - createdAt: alert.taskRun.createdAt, - idempotencyKey: alert.taskRun.idempotencyKey, - }, - environment: { - id: alert.environment.id, - type: alert.environment.type, - slug: alert.environment.slug, - }, - organization: { - id: alert.project.organizationId, - slug: alert.project.organization.slug, - name: alert.project.organization.title, - }, - project: { - id: alert.project.id, - ref: alert.project.externalRef, - slug: alert.project.slug, - name: alert.project.name, - }, - error, - }; + switch (webhookProperties.data.version) { + case "v1": { + const payload = { + task: { + id: alert.taskRun.taskIdentifier, + fileName: alert.taskRun.lockedBy?.filePath ?? "Unknown", + exportName: alert.taskRun.lockedBy?.exportName ?? "Unknown", + }, + run: { + id: alert.taskRun.friendlyId, + isTest: alert.taskRun.isTest, + createdAt: alert.taskRun.createdAt, + idempotencyKey: alert.taskRun.idempotencyKey, + }, + environment: { + id: alert.environment.id, + type: alert.environment.type, + slug: alert.environment.slug, + }, + organization: { + id: alert.project.organizationId, + slug: alert.project.organization.slug, + name: alert.project.organization.title, + }, + project: { + id: alert.project.id, + ref: alert.project.externalRef, + slug: alert.project.slug, + name: alert.project.name, + }, + error, + }; - await this.#deliverWebhook(payload, webhookProperties.data); + await this.#deliverWebhook(payload, webhookProperties.data); + break; + } + case "v2": { + const payload: RunFailedWebhook = { + id: alert.id, + created: alert.createdAt, + webhookVersion: "v1", + type: "alert.run.failed", + object: { + task: { + id: alert.taskRun.taskIdentifier, + filePath: alert.taskRun.lockedBy?.filePath ?? "Unknown", + exportName: alert.taskRun.lockedBy?.exportName ?? "Unknown", + version: alert.taskRun.taskVersion ?? "Unknown", + sdkVersion: alert.taskRun.sdkVersion ?? "Unknown", + cliVersion: alert.taskRun.cliVersion ?? "Unknown", + }, + run: { + id: alert.taskRun.friendlyId, + number: alert.taskRun.number, + status: ApiRetrieveRunPresenter.apiStatusFromRunStatus(alert.taskRun.status), + createdAt: alert.taskRun.createdAt, + startedAt: alert.taskRun.startedAt ?? undefined, + completedAt: alert.taskRun.completedAt ?? undefined, + isTest: alert.taskRun.isTest, + idempotencyKey: alert.taskRun.idempotencyKey ?? undefined, + tags: alert.taskRun.runTags, + error, + isOutOfMemoryError: isOOMError(error), + machine: alert.taskRun.machinePreset ?? "Unknown", + dashboardUrl: `${env.APP_ORIGIN}${v3RunPath( + alert.project.organization, + alert.project, + alert.taskRun + )}`, + }, + environment: { + id: alert.environment.id, + type: alert.environment.type, + slug: alert.environment.slug, + }, + organization: { + id: alert.project.organizationId, + slug: alert.project.organization.slug, + name: alert.project.organization.title, + }, + project: { + id: alert.project.id, + ref: alert.project.externalRef, + slug: alert.project.slug, + name: alert.project.name, + }, + }, + }; + + await this.#deliverWebhook(payload, webhookProperties.data); + + break; + } + default: { + throw new Error(`Unknown webhook version: ${webhookProperties.data.version}`); + } + } } else { logger.error("[DeliverAlert] Task run not found", { alert, @@ -450,34 +432,80 @@ export class DeliverAlertService extends BaseService { return; } - const payload = { - deployment: { - id: alert.workerDeployment.friendlyId, - status: alert.workerDeployment.status, - version: alert.workerDeployment.version, - shortCode: alert.workerDeployment.shortCode, - failedAt: alert.workerDeployment.failedAt ?? new Date(), - }, - environment: { - id: alert.environment.id, - type: alert.environment.type, - slug: alert.environment.slug, - }, - organization: { - id: alert.project.organizationId, - slug: alert.project.organization.slug, - name: alert.project.organization.title, - }, - project: { - id: alert.project.id, - ref: alert.project.externalRef, - slug: alert.project.slug, - name: alert.project.name, - }, - error: preparedError, - }; + switch (webhookProperties.data.version) { + case "v1": { + const payload = { + deployment: { + id: alert.workerDeployment.friendlyId, + status: alert.workerDeployment.status, + version: alert.workerDeployment.version, + shortCode: alert.workerDeployment.shortCode, + failedAt: alert.workerDeployment.failedAt ?? new Date(), + }, + environment: { + id: alert.environment.id, + type: alert.environment.type, + slug: alert.environment.slug, + }, + organization: { + id: alert.project.organizationId, + slug: alert.project.organization.slug, + name: alert.project.organization.title, + }, + project: { + id: alert.project.id, + ref: alert.project.externalRef, + slug: alert.project.slug, + name: alert.project.name, + }, + error: preparedError, + }; - await this.#deliverWebhook(payload, webhookProperties.data); + await this.#deliverWebhook(payload, webhookProperties.data); + break; + } + case "v2": { + const payload: DeploymentFailedWebhook = { + id: alert.id, + created: alert.createdAt, + webhookVersion: "v1", + type: "alert.deployment.failed", + object: { + deployment: { + id: alert.workerDeployment.friendlyId, + status: alert.workerDeployment.status, + version: alert.workerDeployment.version, + shortCode: alert.workerDeployment.shortCode, + failedAt: alert.workerDeployment.failedAt ?? new Date(), + }, + environment: { + id: alert.environment.id, + type: alert.environment.type, + slug: alert.environment.slug, + }, + organization: { + id: alert.project.organizationId, + slug: alert.project.organization.slug, + name: alert.project.organization.title, + }, + project: { + id: alert.project.id, + ref: alert.project.externalRef, + slug: alert.project.slug, + name: alert.project.name, + }, + error: preparedError, + }, + }; + + await this.#deliverWebhook(payload, webhookProperties.data); + + break; + } + default: { + throw new Error(`Unknown webhook version: ${webhookProperties.data.version}`); + } + } } else { logger.error("[DeliverAlert] Worker deployment not found", { alert, @@ -488,40 +516,92 @@ export class DeliverAlertService extends BaseService { } case "DEPLOYMENT_SUCCESS": { if (alert.workerDeployment) { - const payload = { - deployment: { - id: alert.workerDeployment.friendlyId, - status: alert.workerDeployment.status, - version: alert.workerDeployment.version, - shortCode: alert.workerDeployment.shortCode, - deployedAt: alert.workerDeployment.deployedAt ?? new Date(), - }, - tasks: - alert.workerDeployment.worker?.tasks.map((task) => ({ - id: task.slug, - filePath: task.filePath, - exportName: task.exportName, - triggerSource: task.triggerSource, - })) ?? [], - environment: { - id: alert.environment.id, - type: alert.environment.type, - slug: alert.environment.slug, - }, - organization: { - id: alert.project.organizationId, - slug: alert.project.organization.slug, - name: alert.project.organization.title, - }, - project: { - id: alert.project.id, - ref: alert.project.externalRef, - slug: alert.project.slug, - name: alert.project.name, - }, - }; + switch (webhookProperties.data.version) { + case "v1": { + const payload = { + deployment: { + id: alert.workerDeployment.friendlyId, + status: alert.workerDeployment.status, + version: alert.workerDeployment.version, + shortCode: alert.workerDeployment.shortCode, + deployedAt: alert.workerDeployment.deployedAt ?? new Date(), + }, + tasks: + alert.workerDeployment.worker?.tasks.map((task) => ({ + id: task.slug, + filePath: task.filePath, + exportName: task.exportName, + triggerSource: task.triggerSource, + })) ?? [], + environment: { + id: alert.environment.id, + type: alert.environment.type, + slug: alert.environment.slug, + }, + organization: { + id: alert.project.organizationId, + slug: alert.project.organization.slug, + name: alert.project.organization.title, + }, + project: { + id: alert.project.id, + ref: alert.project.externalRef, + slug: alert.project.slug, + name: alert.project.name, + }, + }; + + await this.#deliverWebhook(payload, webhookProperties.data); + break; + } + case "v2": { + const payload: DeploymentSuccessWebhook = { + id: alert.id, + created: alert.createdAt, + webhookVersion: "v1", + type: "alert.deployment.success", + object: { + deployment: { + id: alert.workerDeployment.friendlyId, + status: alert.workerDeployment.status, + version: alert.workerDeployment.version, + shortCode: alert.workerDeployment.shortCode, + deployedAt: alert.workerDeployment.deployedAt! ?? new Date(), + }, + tasks: + alert.workerDeployment.worker?.tasks.map((task) => ({ + id: task.slug, + filePath: task.filePath, + exportName: task.exportName, + triggerSource: task.triggerSource, + })) ?? [], + environment: { + id: alert.environment.id, + type: alert.environment.type, + slug: alert.environment.slug, + }, + organization: { + id: alert.project.organizationId, + slug: alert.project.organization.slug, + name: alert.project.organization.title, + }, + project: { + id: alert.project.id, + ref: alert.project.externalRef, + slug: alert.project.slug, + name: alert.project.name, + }, + }, + }; + + await this.#deliverWebhook(payload, webhookProperties.data); - await this.#deliverWebhook(payload, webhookProperties.data); + break; + } + default: { + throw new Error(`Unknown webhook version: ${webhookProperties.data.version}`); + } + } } else { logger.error("[DeliverAlert] Worker deployment not found", { alert, @@ -582,126 +662,7 @@ export class DeliverAlertService extends BaseService { switch (alert.type) { case "TASK_RUN_ATTEMPT": { - if (alert.taskRunAttempt) { - // Find existing storage by the run ID - const storage = await this._prisma.projectAlertStorage.findFirst({ - where: { - alertChannelId: alert.channel.id, - alertType: alert.type, - storageId: alert.taskRunAttempt.taskRunId, - }, - }); - - const storageData = storage - ? ProjectAlertSlackStorage.safeParse(storage.storageData) - : undefined; - - const thread_ts = - storageData && storageData.success ? storageData.data.message_ts : undefined; - - const taskRunError = TaskRunError.safeParse(alert.taskRunAttempt.error); - - if (!taskRunError.success) { - logger.error("[DeliverAlert] Attempt: Failed to parse task run error", { - issues: taskRunError.error.issues, - taskAttemptError: alert.taskRunAttempt.error, - }); - - return; - } - - const error = createJsonErrorObject(taskRunError.data); - - const exportName = alert.taskRunAttempt.backgroundWorkerTask.exportName; - const version = alert.taskRunAttempt.backgroundWorker.version; - const environment = alert.environment.slug; - const taskIdentifier = alert.taskRunAttempt.backgroundWorkerTask.slug; - const timestamp = alert.taskRunAttempt.completedAt ?? new Date(); - const runId = alert.taskRunAttempt.taskRun.friendlyId; - const attemptNumber = alert.taskRunAttempt.number; - - const message = await this.#postSlackMessage(integration, { - thread_ts, - channel: slackProperties.data.channelId, - text: `Task error in ${alert.taskRunAttempt.backgroundWorkerTask.exportName} [${alert.taskRunAttempt.backgroundWorker.version}.${alert.environment.slug}]`, - blocks: [ - { - type: "section", - text: { - type: "mrkdwn", - text: `:rotating_light: Error in *${exportName}* __`, - }, - }, - { - type: "section", - text: { - type: "mrkdwn", - text: this.#wrapInCodeBlock(error.stackTrace ?? error.message), - }, - }, - { - type: "context", - elements: [ - { - type: "mrkdwn", - text: `${runId}.${attemptNumber} | ${taskIdentifier} | ${version}.${environment} | ${alert.project.name}`, - }, - ], - }, - { - type: "divider", - }, - { - type: "actions", - elements: [ - { - type: "button", - text: { - type: "plain_text", - text: "Investigate", - }, - url: `${env.APP_ORIGIN}/projects/v3/${alert.project.externalRef}/runs/${alert.taskRunAttempt.taskRun.friendlyId}`, - }, - ], - }, - ], - }); - - // Upsert the storage - if (message.ts) { - if (storage) { - await this._prisma.projectAlertStorage.update({ - where: { - id: storage.id, - }, - data: { - storageData: { - message_ts: message.ts, - }, - }, - }); - } else { - await this._prisma.projectAlertStorage.create({ - data: { - alertChannelId: alert.channel.id, - alertType: alert.type, - storageId: alert.taskRunAttempt.taskRunId, - storageData: { - message_ts: message.ts, - }, - projectId: alert.project.id, - }, - }); - } - } - } else { - logger.error("[DeliverAlert] Task run attempt not found", { - alert, - }); - } - + logger.error("[DeliverAlert] Task run attempt alerts are deprecated, not sending anything"); break; } case "TASK_RUN": { @@ -945,7 +906,7 @@ export class DeliverAlertService extends BaseService { } } - async #deliverWebhook(payload: any, webhook: ProjectAlertWebhookProperties) { + async #deliverWebhook(payload: T, webhook: ProjectAlertWebhookProperties) { const rawPayload = JSON.stringify(payload); const hashPayload = Buffer.from(rawPayload, "utf-8"); diff --git a/apps/webapp/app/v3/services/completeAttempt.server.ts b/apps/webapp/app/v3/services/completeAttempt.server.ts index cc4472f1e7..235ae87352 100644 --- a/apps/webapp/app/v3/services/completeAttempt.server.ts +++ b/apps/webapp/app/v3/services/completeAttempt.server.ts @@ -738,7 +738,7 @@ async function findAttempt(prismaClient: PrismaClientOrTransaction, friendlyId: }); } -function isOOMError(error: TaskRunError) { +export function isOOMError(error: TaskRunError) { if (error.type === "INTERNAL_ERROR") { if ( error.code === "TASK_PROCESS_OOM_KILLED" || diff --git a/packages/core/src/v3/schemas/webhooks.ts b/packages/core/src/v3/schemas/webhooks.ts index 9ab5ac0b89..c583740d88 100644 --- a/packages/core/src/v3/schemas/webhooks.ts +++ b/packages/core/src/v3/schemas/webhooks.ts @@ -1,8 +1,7 @@ import { z } from "zod"; -import { MachinePresetName, TaskRunError } from "./common.js"; -import { RunStatus } from "./api.js"; import { RuntimeEnvironmentTypeSchema } from "../../schemas/api.js"; -import { OutOfMemoryError } from "../errors.js"; +import { RunStatus } from "./api.js"; +import { TaskRunError } from "./common.js"; /** Represents a failed run alert webhook payload */ const AlertWebhookRunFailedObject = z.object({ @@ -38,7 +37,7 @@ const AlertWebhookRunFailedObject = z.object({ /** Whether this is a test run */ isTest: z.boolean(), /** Idempotency key for the run */ - idempotencyKey: z.string(), + idempotencyKey: z.string().optional(), /** Associated tags */ tags: z.array(z.string()), /** Error information */ @@ -95,96 +94,78 @@ export const DeployError = z.object({ }); export type DeployError = z.infer; -/** Represents a deployment alert webhook payload */ -export const AlertWebhookDeploymentObject = z.discriminatedUnion("success", [ - /** Successful deployment */ - z.object({ - success: z.literal(true), - deployment: z.object({ - /** Deployment ID */ - id: z.string(), - /** Deployment status */ - status: z.string(), - /** Deployment version */ - version: z.string(), - /** Short code identifier */ - shortCode: z.string(), - /** When the deployment completed */ - deployedAt: z.coerce.date(), - }), - /** Deployed tasks */ - tasks: z.array( - z.object({ - /** Task ID */ - id: z.string(), - /** File path where the task is defined */ - filePath: z.string(), - /** Name of the exported task function */ - exportName: z.string(), - /** Source of the trigger */ - triggerSource: z.string(), - }) - ), - /** Environment information */ - environment: z.object({ - id: z.string(), - type: RuntimeEnvironmentTypeSchema, - slug: z.string(), - }), - /** Organization information */ - organization: z.object({ - id: z.string(), - slug: z.string(), - name: z.string(), - }), - /** Project information */ - project: z.object({ - id: z.string(), - ref: z.string(), - slug: z.string(), - name: z.string(), - }), +const deploymentCommonProperties = { + /** Environment information */ + environment: z.object({ + id: z.string(), + type: RuntimeEnvironmentTypeSchema, + slug: z.string(), }), - /** Failed deployment */ - z.object({ - success: z.literal(false), - deployment: z.object({ - /** Deployment ID */ - id: z.string(), - /** Deployment status */ - status: z.string(), - /** Deployment version */ - version: z.string(), - /** Short code identifier */ - shortCode: z.string(), - /** When the deployment failed */ - failedAt: z.coerce.date(), - }), - /** Environment information */ - environment: z.object({ - id: z.string(), - type: RuntimeEnvironmentTypeSchema, - slug: z.string(), - }), - /** Organization information */ - organization: z.object({ - id: z.string(), - slug: z.string(), - name: z.string(), - }), - /** Project information */ - project: z.object({ + /** Organization information */ + organization: z.object({ + id: z.string(), + slug: z.string(), + name: z.string(), + }), + /** Project information */ + project: z.object({ + id: z.string(), + ref: z.string(), + slug: z.string(), + name: z.string(), + }), +}; + +const deploymentDeploymentCommonProperties = { + /** Deployment ID */ + id: z.string(), + /** Deployment status */ + status: z.string(), + /** Deployment version */ + version: z.string(), + /** Short code identifier */ + shortCode: z.string(), +}; + +/** Represents a successful deployment alert webhook payload */ +export const AlertWebhookDeploymentSuccessObject = z.object({ + ...deploymentCommonProperties, + deployment: z.object({ + ...deploymentDeploymentCommonProperties, + /** When the deployment completed */ + deployedAt: z.coerce.date(), + }), + /** Deployed tasks */ + tasks: z.array( + z.object({ + /** Task ID */ id: z.string(), - ref: z.string(), - slug: z.string(), - name: z.string(), - }), - /** Error information */ - error: DeployError, + /** File path where the task is defined */ + filePath: z.string(), + /** Name of the exported task function */ + exportName: z.string(), + /** Source of the trigger */ + triggerSource: z.string(), + }) + ), +}); + +/** Represents a failed deployment alert webhook payload */ +export const AlertWebhookDeploymentFailedObject = z.object({ + ...deploymentCommonProperties, + deployment: z.object({ + ...deploymentDeploymentCommonProperties, + /** When the deployment failed */ + failedAt: z.coerce.date(), }), -]); + /** Error information */ + error: DeployError, +}); -export type AlertWebhookDeploymentObject = z.infer; +export type AlertWebhookDeploymentSuccessObject = z.infer< + typeof AlertWebhookDeploymentSuccessObject +>; +export type AlertWebhookDeploymentFailedObject = z.infer; /** Common properties for all webhooks */ const commonProperties = { @@ -204,12 +185,21 @@ export const Webhook = z.discriminatedUnion("type", [ type: z.literal("alert.run.failed"), object: AlertWebhookRunFailedObject, }), - /** Deployment finished alert webhook */ + /** Deployment success alert webhook */ + z.object({ + ...commonProperties, + type: z.literal("alert.deployment.success"), + object: AlertWebhookDeploymentSuccessObject, + }), + /** Deployment failed alert webhook */ z.object({ ...commonProperties, - type: z.literal("alert.deployment.finished"), - object: AlertWebhookDeploymentObject, + type: z.literal("alert.deployment.failed"), + object: AlertWebhookDeploymentFailedObject, }), ]); export type Webhook = z.infer; +export type RunFailedWebhook = Extract; +export type DeploymentSuccessWebhook = Extract; +export type DeploymentFailedWebhook = Extract; diff --git a/packages/trigger-sdk/src/v3/index.ts b/packages/trigger-sdk/src/v3/index.ts index b70ddfc265..49b1d49627 100644 --- a/packages/trigger-sdk/src/v3/index.ts +++ b/packages/trigger-sdk/src/v3/index.ts @@ -12,6 +12,7 @@ export * from "./tags.js"; export * from "./metadata.js"; export * from "./timeout.js"; export * from "./waitUntil.js"; +export * from "./webhooks.js"; export type { Context }; import type { Context } from "./shared.js"; diff --git a/packages/trigger-sdk/src/v3/webhooks.ts b/packages/trigger-sdk/src/v3/webhooks.ts index 4e5d95e16b..9714f7567a 100644 --- a/packages/trigger-sdk/src/v3/webhooks.ts +++ b/packages/trigger-sdk/src/v3/webhooks.ts @@ -129,6 +129,10 @@ async function verifySignature( secret: string ): Promise { try { + if (!secret) { + throw new WebhookError("Secret is required for signature verification"); + } + // Convert the payload and secret to buffers const hashPayload = Buffer.from(payload, "utf-8"); const hmacSecret = Buffer.from(secret, "utf-8"); From b09d875425ba82b205e32d602b82ab1293337306 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 12 Feb 2025 20:25:26 +0000 Subject: [PATCH 4/8] Expanded the alert docs --- docs/troubleshooting-alerts.mdx | 67 +++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/docs/troubleshooting-alerts.mdx b/docs/troubleshooting-alerts.mdx index 2ee194bc19..2bbd8e8233 100644 --- a/docs/troubleshooting-alerts.mdx +++ b/docs/troubleshooting-alerts.mdx @@ -3,6 +3,13 @@ title: "Alerts" description: "Get alerted when runs or deployments fail, or when deployments succeed." --- +We support receiving alerts for the following events: +- Run fails +- Deployment fails +- Deployment succeeds + +## How to setup alerts + @@ -27,3 +34,63 @@ Click on the triple dot menu on the right side of the table row and select "Disa + + +## Alert webhooks + +For the alert webhooks you can use the SDK to parse them. Here is an example of how to parse the webhook payload in Remix: + +```ts +import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; +import { webhooks, WebhookError } from "@trigger.dev/sdk/v3"; + +export async function action({ request }: ActionFunctionArgs) { + // Make sure this is a POST request + if (request.method !== "POST") { + return json({ error: "Method not allowed" }, { status: 405 }); + } + + try { + // Construct and verify the webhook event + // This secret can be found on your Alerts page when you create a webhook alert + const event = await webhooks.constructEvent(request, process.env.ALERT_WEBHOOK_SECRET!); + + // Process the event based on its type + switch (event.type) { + case "alert.run.failed": { + console.log("[Webhook Internal Test] Run failed alert webhook received", { event }); + break; + } + case "alert.deployment.success": { + console.log("[Webhook Internal Test] Deployment success alert webhook received", { event }); + break; + } + case "alert.deployment.failed": { + console.log("[Webhook Internal Test] Deployment failed alert webhook received", { event }); + break; + } + default: { + console.log("[Webhook Internal Test] Unhandled webhook type", { event }); + } + } + + // Return a success response + return json({ received: true }, { status: 200 }); + } catch (err) { + // Handle webhook errors + if (err instanceof WebhookError) { + console.error("Webhook error:", { message: err.message }); + return json({ error: err.message }, { status: 400 }); + } + + if (err instanceof Error) { + console.error("Error processing webhook:", { message: err.message }); + return json({ error: err.message }, { status: 400 }); + } + + // Handle other errors + console.error("Error processing webhook:", { err }); + return json({ error: "Internal server error" }, { status: 500 }); + } +} +``` From 07f026882297c74d4453bf7ac37ae0481e979858 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 13 Feb 2025 13:23:03 +0000 Subject: [PATCH 5/8] Remove duplicate export of waitUntil.js --- packages/trigger-sdk/src/v3/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/trigger-sdk/src/v3/index.ts b/packages/trigger-sdk/src/v3/index.ts index 49b1d49627..f83254b8c9 100644 --- a/packages/trigger-sdk/src/v3/index.ts +++ b/packages/trigger-sdk/src/v3/index.ts @@ -11,7 +11,6 @@ export * from "./idempotencyKeys.js"; export * from "./tags.js"; export * from "./metadata.js"; export * from "./timeout.js"; -export * from "./waitUntil.js"; export * from "./webhooks.js"; export type { Context }; From 035245ef67d810946b00aa7211a9f89878f93683 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 13 Feb 2025 13:27:25 +0000 Subject: [PATCH 6/8] Use uncrypto --- packages/trigger-sdk/package.json | 1 + .../trigger-sdk/src/imports/uncrypto-cjs.cts | 5 + packages/trigger-sdk/src/imports/uncrypto.ts | 5 + packages/trigger-sdk/src/v3/webhooks.ts | 2 +- pnpm-lock.yaml | 239 ++++++++++++++++-- 5 files changed, 235 insertions(+), 17 deletions(-) create mode 100644 packages/trigger-sdk/src/imports/uncrypto-cjs.cts create mode 100644 packages/trigger-sdk/src/imports/uncrypto.ts diff --git a/packages/trigger-sdk/package.json b/packages/trigger-sdk/package.json index 7566597c01..02eded22ba 100644 --- a/packages/trigger-sdk/package.json +++ b/packages/trigger-sdk/package.json @@ -56,6 +56,7 @@ "slug": "^6.0.0", "terminal-link": "^3.0.0", "ulid": "^2.3.0", + "uncrypto": "^0.1.3", "uuid": "^9.0.0", "ws": "^8.11.0" }, diff --git a/packages/trigger-sdk/src/imports/uncrypto-cjs.cts b/packages/trigger-sdk/src/imports/uncrypto-cjs.cts new file mode 100644 index 0000000000..a9e17c27e5 --- /dev/null +++ b/packages/trigger-sdk/src/imports/uncrypto-cjs.cts @@ -0,0 +1,5 @@ +// @ts-ignore +const { subtle } = require("uncrypto"); + +// @ts-ignore +module.exports.subtle = subtle; diff --git a/packages/trigger-sdk/src/imports/uncrypto.ts b/packages/trigger-sdk/src/imports/uncrypto.ts new file mode 100644 index 0000000000..8e4a594949 --- /dev/null +++ b/packages/trigger-sdk/src/imports/uncrypto.ts @@ -0,0 +1,5 @@ +// @ts-ignore +import { subtle } from "uncrypto"; + +// @ts-ignore +export { subtle }; diff --git a/packages/trigger-sdk/src/v3/webhooks.ts b/packages/trigger-sdk/src/v3/webhooks.ts index 9714f7567a..049cb1f5c3 100644 --- a/packages/trigger-sdk/src/v3/webhooks.ts +++ b/packages/trigger-sdk/src/v3/webhooks.ts @@ -1,5 +1,5 @@ import { Webhook } from "@trigger.dev/core/v3"; -import { subtle } from "crypto"; +import { subtle } from "../imports/uncrypto.js"; /** * The type of error thrown when a webhook fails to parse or verify diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3b74e01681..f97aae3e5f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -410,7 +410,7 @@ importers: version: 8.6.6 '@uiw/react-codemirror': specifier: ^4.19.5 - version: 4.19.5(@babel/runtime@7.24.5)(@codemirror/autocomplete@6.4.0)(@codemirror/language@6.3.2)(@codemirror/lint@6.4.2)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/theme-one-dark@6.1.0)(@codemirror/view@6.7.2)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0) + version: 4.19.5(@babel/runtime@7.26.7)(@codemirror/autocomplete@6.4.0)(@codemirror/language@6.3.2)(@codemirror/lint@6.4.2)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/theme-one-dark@6.1.0)(@codemirror/view@6.7.2)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0) '@unkey/cache': specifier: ^1.5.0 version: 1.5.0 @@ -774,7 +774,7 @@ importers: version: 10.4.13(postcss@8.4.44) babel-loader: specifier: ^9.1.3 - version: 9.1.3(@babel/core@7.24.5)(webpack@5.88.2) + version: 9.1.3(@babel/core@7.26.8)(webpack@5.88.2) babel-preset-react-app: specifier: ^10.0.1 version: 10.0.1 @@ -1535,6 +1535,9 @@ importers: ulid: specifier: ^2.3.0 version: 2.3.0 + uncrypto: + specifier: ^0.1.3 + version: 0.1.3 uuid: specifier: ^9.0.0 version: 9.0.0 @@ -3329,6 +3332,15 @@ packages: '@babel/highlight': 7.24.7 picocolors: 1.0.1 + /@babel/code-frame@7.26.2: + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + dev: true + /@babel/compat-data@7.22.9: resolution: {integrity: sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==} engines: {node: '>=6.9.0'} @@ -3336,6 +3348,12 @@ packages: /@babel/compat-data@7.25.4: resolution: {integrity: sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==} engines: {node: '>=6.9.0'} + dev: false + + /@babel/compat-data@7.26.8: + resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} + engines: {node: '>=6.9.0'} + dev: true /@babel/core@7.22.17: resolution: {integrity: sha512-2EENLmhpwplDux5PSsZnSbnSkB3tZ6QTksgO25xwEL7pIDcNOMhF5v/s6RzwjMZzZzw9Ofc30gHv5ChCC8pifQ==} @@ -3380,6 +3398,31 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color + dev: false + + /@babel/core@7.26.8: + resolution: {integrity: sha512-l+lkXCHS6tQEc5oUpK28xBOZ6+HwaH7YwoYQbLFiYb4nS2/l1tKnZEtEWkD0GuiYdvArf9qBS0XlQGXzPMsNqQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.8 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.8) + '@babel/helpers': 7.26.7 + '@babel/parser': 7.26.8 + '@babel/template': 7.26.8 + '@babel/traverse': 7.26.8 + '@babel/types': 7.26.8 + '@types/gensync': 1.0.4 + convert-source-map: 2.0.0 + debug: 4.3.7 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true /@babel/eslint-parser@7.21.8(@babel/core@7.22.17)(eslint@8.31.0): resolution: {integrity: sha512-HLhI+2q+BP3sf78mFUZNCGc10KEmoUqtUT1OCdMZsN+qr4qFeLUod62/zAnF3jNQstwyasDkZnVXwfK2Bml7MQ==} @@ -3421,6 +3464,18 @@ packages: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 + dev: false + + /@babel/generator@7.26.8: + resolution: {integrity: sha512-ef383X5++iZHWAXX0SXQR6ZyQhw/0KtTkrTz61WXRhFM6dhpHulO/RJz79L8S6ugZHJkOOkUrUdxgdF2YiPFnA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/parser': 7.26.8 + '@babel/types': 7.26.8 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.0.2 + dev: true /@babel/helper-annotate-as-pure@7.22.5: resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} @@ -3456,6 +3511,18 @@ packages: browserslist: 4.23.3 lru-cache: 5.1.1 semver: 6.3.1 + dev: false + + /@babel/helper-compilation-targets@7.26.5: + resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.4 + lru-cache: 5.1.1 + semver: 6.3.1 + dev: true /@babel/helper-create-class-features-plugin@7.21.8(@babel/core@7.22.17): resolution: {integrity: sha512-+THiN8MqiH2AczyuZrnrKL6cAxFRRQDKW9h1YkBvbgKmAm6mwiacig1qT73DHIWMGo40GRnsEfN3LA+E6NtmSw==} @@ -3623,6 +3690,17 @@ packages: '@babel/types': 7.24.7 transitivePeerDependencies: - supports-color + dev: false + + /@babel/helper-module-imports@7.25.9: + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/traverse': 7.26.8 + '@babel/types': 7.26.8 + transitivePeerDependencies: + - supports-color + dev: true /@babel/helper-module-transforms@7.22.17(@babel/core@7.22.17): resolution: {integrity: sha512-XouDDhQESrLHTpnBtCKExJdyY4gJCdrvH2Pyv8r8kovX2U8G0dRUOT45T9XlbLtuu9CLXP15eusnkprhoPV5iQ==} @@ -3650,6 +3728,21 @@ packages: '@babel/traverse': 7.25.6 transitivePeerDependencies: - supports-color + dev: false + + /@babel/helper-module-transforms@7.26.0(@babel/core@7.26.8): + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.26.8 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.26.8 + transitivePeerDependencies: + - supports-color + dev: true /@babel/helper-optimise-call-expression@7.18.6: resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} @@ -3730,6 +3823,7 @@ packages: '@babel/types': 7.24.7 transitivePeerDependencies: - supports-color + dev: false /@babel/helper-skip-transparent-expression-wrappers@7.20.0: resolution: {integrity: sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==} @@ -3764,11 +3858,20 @@ packages: /@babel/helper-string-parser@7.24.8: resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} engines: {node: '>=6.9.0'} + dev: false + + /@babel/helper-string-parser@7.25.9: + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} /@babel/helper-validator-identifier@7.24.7: resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} + /@babel/helper-validator-identifier@7.25.9: + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + /@babel/helper-validator-option@7.22.15: resolution: {integrity: sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==} engines: {node: '>=6.9.0'} @@ -3776,6 +3879,12 @@ packages: /@babel/helper-validator-option@7.24.8: resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==} engines: {node: '>=6.9.0'} + dev: false + + /@babel/helper-validator-option@7.25.9: + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + dev: true /@babel/helper-wrap-function@7.20.5: resolution: {integrity: sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==} @@ -3805,6 +3914,15 @@ packages: dependencies: '@babel/template': 7.25.0 '@babel/types': 7.25.6 + dev: false + + /@babel/helpers@7.26.7: + resolution: {integrity: sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.26.8 + '@babel/types': 7.26.8 + dev: true /@babel/highlight@7.22.13: resolution: {integrity: sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==} @@ -3860,6 +3978,14 @@ packages: hasBin: true dependencies: '@babel/types': 7.25.6 + dev: false + + /@babel/parser@7.26.8: + resolution: {integrity: sha512-TZIQ25pkSoaKEYYaHbbxkfL36GNsQ6iFiBbeuzAkLnXayKR1yP1zFe+NxuZWWsUyvt8icPU9CCq0sgWGXR1GEw==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.26.8 /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.18.6(@babel/core@7.22.17): resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==} @@ -4872,6 +4998,13 @@ packages: dependencies: regenerator-runtime: 0.14.1 + /@babel/runtime@7.26.7: + resolution: {integrity: sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.14.1 + dev: false + /@babel/template@7.22.15: resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} engines: {node: '>=6.9.0'} @@ -4895,6 +5028,16 @@ packages: '@babel/code-frame': 7.24.7 '@babel/parser': 7.25.6 '@babel/types': 7.25.6 + dev: false + + /@babel/template@7.26.8: + resolution: {integrity: sha512-iNKaX3ZebKIsCvJ+0jd6embf+Aulaa3vNBqZ41kM7iTWjx5qzWKXGHiJUW3+nTpQ18SG11hdF8OAzKrpXkb96Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.8 + '@babel/types': 7.26.8 + dev: true /@babel/traverse@7.22.17: resolution: {integrity: sha512-xK4Uwm0JnAMvxYZxOVecss85WxTEIbTa7bnGyf/+EgCL5Zt3U7htUpEOWv9detPlamGKuRzCqw74xVglDWpPdg==} @@ -4944,6 +5087,22 @@ packages: globals: 11.12.0 transitivePeerDependencies: - supports-color + dev: false + + /@babel/traverse@7.26.8: + resolution: {integrity: sha512-nic9tRkjYH0oB2dzr/JoGIm+4Q6SuYeLEiIiZDwBscRMYFJ+tMAz98fuel9ZnbXViA2I0HVSSRRK8DW5fjXStA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.8 + '@babel/parser': 7.26.8 + '@babel/template': 7.26.8 + '@babel/types': 7.26.8 + debug: 4.3.7 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true /@babel/types@7.24.0: resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} @@ -4968,6 +5127,14 @@ packages: '@babel/helper-string-parser': 7.24.8 '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 + dev: false + + /@babel/types@7.26.8: + resolution: {integrity: sha512-eUuWapzEGWFEpHFxgEaBG8e3n6S8L3MSu0oda755rOfabWPnh0Our1AozNFVUxGFIhbKgd1ksprsoDGMinTOTA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 /@balena/dockerignore@1.0.2: resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} @@ -16674,6 +16841,10 @@ packages: '@types/node': 18.19.20 dev: true + /@types/gensync@1.0.4: + resolution: {integrity: sha512-C3YYeRQWp2fmq9OryX+FoDy8nXS6scQ7dPptD8LnFDAUNcKWJjXQKDNJD3HVm+kOUsXhTOkpi69vI4EuAr95bA==} + dev: true + /@types/gradient-string@1.1.2: resolution: {integrity: sha512-zIet2KvHr2dkOCPI5ggQQ+WJVyfBSFaqK9sNelhgDjlE2K3Fu2muuPJwu5aKM3xoWuc3WXudVEMUwI1QWhykEQ==} dependencies: @@ -17288,7 +17459,7 @@ packages: '@codemirror/view': 6.7.2 dev: false - /@uiw/react-codemirror@4.19.5(@babel/runtime@7.24.5)(@codemirror/autocomplete@6.4.0)(@codemirror/language@6.3.2)(@codemirror/lint@6.4.2)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/theme-one-dark@6.1.0)(@codemirror/view@6.7.2)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0): + /@uiw/react-codemirror@4.19.5(@babel/runtime@7.26.7)(@codemirror/autocomplete@6.4.0)(@codemirror/language@6.3.2)(@codemirror/lint@6.4.2)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/theme-one-dark@6.1.0)(@codemirror/view@6.7.2)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-ZCHh8d7beXbF8/t7F1+yHht6A9Y6CdKeOkZq4A09lxJEnyTQrj1FMf2zvfaqc7K23KNjkTCtSlbqKKbVDgrWaw==} peerDependencies: '@babel/runtime': '>=7.11.0' @@ -17299,7 +17470,7 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' dependencies: - '@babel/runtime': 7.24.5 + '@babel/runtime': 7.26.7 '@codemirror/commands': 6.1.3 '@codemirror/state': 6.2.0 '@codemirror/theme-one-dark': 6.1.0 @@ -17590,7 +17761,7 @@ packages: /@vue/compiler-core@3.4.38: resolution: {integrity: sha512-8IQOTCWnLFqfHzOGm9+P8OPSEDukgg3Huc92qSG49if/xI2SAwLHQO2qaPQbjCWPBcQoO1WYfXfTACUrWV3c5A==} dependencies: - '@babel/parser': 7.25.6 + '@babel/parser': 7.26.8 '@vue/shared': 3.4.38 entities: 4.5.0 estree-walker: 2.0.2 @@ -17605,7 +17776,7 @@ packages: /@vue/compiler-sfc@3.4.38: resolution: {integrity: sha512-s5QfZ+9PzPh3T5H4hsQDJtI8x7zdJaew/dCGgqZ2630XdzaZ3AD8xGZfBqpT8oaD/p2eedd+pL8tD5vvt5ZYJQ==} dependencies: - '@babel/parser': 7.25.6 + '@babel/parser': 7.26.8 '@vue/compiler-core': 3.4.38 '@vue/compiler-dom': 3.4.38 '@vue/compiler-ssr': 3.4.38 @@ -18598,14 +18769,14 @@ packages: /b4a@1.6.6: resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==} - /babel-loader@9.1.3(@babel/core@7.24.5)(webpack@5.88.2): + /babel-loader@9.1.3(@babel/core@7.26.8)(webpack@5.88.2): resolution: {integrity: sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==} engines: {node: '>= 14.15.0'} peerDependencies: '@babel/core': ^7.12.0 webpack: '>=5' dependencies: - '@babel/core': 7.24.5 + '@babel/core': 7.26.8 find-cache-dir: 4.0.0 schema-utils: 4.0.1 webpack: 5.88.2(@swc/core@1.3.26)(esbuild@0.15.18) @@ -18893,6 +19064,17 @@ packages: node-releases: 2.0.18 update-browserslist-db: 1.1.0(browserslist@4.23.3) + /browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001699 + electron-to-chromium: 1.5.98 + node-releases: 2.0.19 + update-browserslist-db: 1.1.2(browserslist@4.24.4) + dev: true + /buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} @@ -19089,6 +19271,10 @@ packages: /caniuse-lite@1.0.30001655: resolution: {integrity: sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==} + /caniuse-lite@1.0.30001699: + resolution: {integrity: sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==} + dev: true + /capnp-ts@0.7.0: resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==} dependencies: @@ -20617,6 +20803,10 @@ packages: /electron-to-chromium@1.5.13: resolution: {integrity: sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==} + /electron-to-chromium@1.5.98: + resolution: {integrity: sha512-bI/LbtRBxU2GzK7KK5xxFd2y9Lf9XguHooPYbcXWy6wUoT8NMnffsvRhPmSeUHLSDKAEtKuTaEtK4Ms15zkIEA==} + dev: true + /email-reply-parser@1.8.0: resolution: {integrity: sha512-hiie/4vNxT5NYBux8m/jZccUQdtQqiq5ytwJwbA9QIkTWbTNPQasJXfsfdetZo23hBn4ZJleRDyOA5nV4+ssSQ==} engines: {node: '>= 10.0.0'} @@ -25726,6 +25916,10 @@ packages: /node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + /node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + dev: true + /nodemailer@6.9.16: resolution: {integrity: sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==} engines: {node: '>=6.0.0'} @@ -26265,14 +26459,14 @@ packages: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: - yocto-queue: 1.0.0 + yocto-queue: 1.1.1 dev: true /p-limit@5.0.0: resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} engines: {node: '>=18'} dependencies: - yocto-queue: 1.0.0 + yocto-queue: 1.1.1 dev: true /p-limit@6.2.0: @@ -26729,6 +26923,10 @@ packages: /picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + /picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + dev: true + /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -31211,6 +31409,10 @@ packages: through: 2.3.8 dev: false + /uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + dev: false + /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -31398,6 +31600,17 @@ packages: escalade: 3.2.0 picocolors: 1.0.1 + /update-browserslist-db@1.1.2(browserslist@4.24.4): + resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 + dev: true + /uploadthing@7.1.0(next@14.2.15)(tailwindcss@3.4.1): resolution: {integrity: sha512-l1bRHs+q/YLx3XwBav98t4Bl1wLWaskhPEwopxtYgiRrxX5nW3uUuSP0RJ9eKwx0+6ZhHWxHDvShf7ZLledqmQ==} engines: {node: '>=18.13.0'} @@ -32857,15 +33070,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - /yocto-queue@1.0.0: - resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} - engines: {node: '>=12.20'} - dev: true - /yocto-queue@1.1.1: resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} engines: {node: '>=12.20'} - dev: false /youch@3.3.3: resolution: {integrity: sha512-qSFXUk3UZBLfggAW3dJKg0BMblG5biqSF8M34E06o5CSsZtH92u9Hqmj2RzGiHDi64fhe83+4tENFP2DB6t6ZA==} From 4f2109ff53fded8d6956c0c8473a382c78f261da Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 13 Feb 2025 13:31:03 +0000 Subject: [PATCH 7/8] =?UTF-8?q?Don=E2=80=99t=20rate=20limit=20webhooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/webapp/app/v3/services/alerts/deliverAlert.server.ts | 6 ++++-- .../v3/services/alerts/performDeploymentAlerts.server.ts | 1 + .../app/v3/services/alerts/performTaskRunAlerts.server.ts | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/webapp/app/v3/services/alerts/deliverAlert.server.ts b/apps/webapp/app/v3/services/alerts/deliverAlert.server.ts index d476949a4f..3c8d95cd5e 100644 --- a/apps/webapp/app/v3/services/alerts/deliverAlert.server.ts +++ b/apps/webapp/app/v3/services/alerts/deliverAlert.server.ts @@ -36,7 +36,7 @@ import { commonWorker } from "~/v3/commonWorker.server"; import { FINAL_ATTEMPT_STATUSES } from "~/v3/taskStatus"; import { BaseService } from "../baseService.server"; import { generateFriendlyId } from "~/v3/friendlyIdentifiers"; -import { ProjectAlertType } from "@trigger.dev/database"; +import { ProjectAlertChannelType, ProjectAlertType } from "@trigger.dev/database"; import { alertsRateLimiter } from "~/v3/alertsRateLimiter.server"; import { v3RunPath } from "~/utils/pathBuilder"; import { isOOMError } from "../completeAttempt.server"; @@ -1068,6 +1068,7 @@ export class DeliverAlertService extends BaseService { static async createAndSendAlert( { channelId, + channelType, projectId, environmentId, alertType, @@ -1075,6 +1076,7 @@ export class DeliverAlertService extends BaseService { taskRunId, }: { channelId: string; + channelType: ProjectAlertChannelType; projectId: string; environmentId: string; alertType: ProjectAlertType; @@ -1083,7 +1085,7 @@ export class DeliverAlertService extends BaseService { }, db: PrismaClientOrTransaction ) { - if (taskRunId) { + if (taskRunId && channelType !== "WEBHOOK") { try { const result = await alertsRateLimiter.check(channelId); diff --git a/apps/webapp/app/v3/services/alerts/performDeploymentAlerts.server.ts b/apps/webapp/app/v3/services/alerts/performDeploymentAlerts.server.ts index 7d8a71c586..fd390477b6 100644 --- a/apps/webapp/app/v3/services/alerts/performDeploymentAlerts.server.ts +++ b/apps/webapp/app/v3/services/alerts/performDeploymentAlerts.server.ts @@ -49,6 +49,7 @@ export class PerformDeploymentAlertsService extends BaseService { await DeliverAlertService.createAndSendAlert( { channelId: alertChannel.id, + channelType: alertChannel.type, projectId: deployment.projectId, environmentId: deployment.environmentId, alertType, diff --git a/apps/webapp/app/v3/services/alerts/performTaskRunAlerts.server.ts b/apps/webapp/app/v3/services/alerts/performTaskRunAlerts.server.ts index 8b88a3f9db..6712392d08 100644 --- a/apps/webapp/app/v3/services/alerts/performTaskRunAlerts.server.ts +++ b/apps/webapp/app/v3/services/alerts/performTaskRunAlerts.server.ts @@ -49,6 +49,7 @@ export class PerformTaskRunAlertsService extends BaseService { await DeliverAlertService.createAndSendAlert( { channelId: alertChannel.id, + channelType: alertChannel.type, projectId: run.projectId, environmentId: run.runtimeEnvironmentId, alertType: "TASK_RUN", From d03344446aa7784f8ead3ea25488ca1d6cda71ca Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Thu, 13 Feb 2025 13:35:35 +0000 Subject: [PATCH 8/8] Create slow-olives-fix.md --- .changeset/slow-olives-fix.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .changeset/slow-olives-fix.md diff --git a/.changeset/slow-olives-fix.md b/.changeset/slow-olives-fix.md new file mode 100644 index 0000000000..7193075c87 --- /dev/null +++ b/.changeset/slow-olives-fix.md @@ -0,0 +1,16 @@ +--- +"@trigger.dev/sdk": patch +--- + +You can add Alerts in the dashboard. One of these is a webhook, which this change greatly improves. + +The main change is that there's now an SDK function to verify and parse them (similar to Stripe SDK). + +```ts +const event = await webhooks.constructEvent(request, process.env.ALERT_WEBHOOK_SECRET!); +``` + +If the signature you provide matches the one from the dashboard when you create the webhook, you will get a nicely typed object back for these three types: +- "alert.run.failed" +- "alert.deployment.success" +- "alert.deployment.failed"