1- import { fromZodError } from "zod-validation-error" ;
2- import type { ActionFunctionArgs } from "@remix-run/server-runtime" ;
31import { json } from "@remix-run/server-runtime" ;
4- import { TriggerTaskRequestBody } from "@trigger.dev/core/v3" ;
2+ import { generateJWT as internal_generateJWT , TriggerTaskRequestBody } from "@trigger.dev/core/v3" ;
3+ import { TaskRun } from "@trigger.dev/database" ;
54import { z } from "zod" ;
65import { env } from "~/env.server" ;
7- import { authenticateApiRequest } from "~/services/apiAuth.server" ;
6+ import { AuthenticatedEnvironment } from "~/services/apiAuth.server" ;
87import { logger } from "~/services/logger.server" ;
9- import { parseRequestJsonAsync } from "~/utils/parseRequestJson .server" ;
8+ import { createActionApiRoute } from "~/services/routeBuiilders/apiBuilder .server" ;
109import { ServiceValidationError } from "~/v3/services/baseService.server" ;
1110import { OutOfEntitlementError , TriggerTaskService } from "~/v3/services/triggerTask.server" ;
12- import { startActiveSpan } from "~/v3/tracer.server" ;
1311
1412const ParamsSchema = z . object ( {
1513 taskId : z . string ( ) ,
@@ -20,115 +18,125 @@ export const HeadersSchema = z.object({
2018 "trigger-version" : z . string ( ) . nullish ( ) ,
2119 "x-trigger-span-parent-as-link" : z . coerce . number ( ) . nullish ( ) ,
2220 "x-trigger-worker" : z . string ( ) . nullish ( ) ,
21+ "x-trigger-client" : z . string ( ) . nullish ( ) ,
2322 traceparent : z . string ( ) . optional ( ) ,
2423 tracestate : z . string ( ) . optional ( ) ,
2524} ) ;
2625
27- export async function action ( { request, params } : ActionFunctionArgs ) {
28- // Ensure this is a POST request
29- if ( request . method . toUpperCase ( ) !== "POST" ) {
30- return { status : 405 , body : "Method Not Allowed" } ;
31- }
32-
33- logger . debug ( "TriggerTask action" , { headers : Object . fromEntries ( request . headers ) } ) ;
34-
35- // Next authenticate the request
36- const authenticationResult = await authenticateApiRequest ( request ) ;
37-
38- if ( ! authenticationResult ) {
39- return json ( { error : "Invalid or Missing API key" } , { status : 401 } ) ;
40- }
41-
42- const contentLength = request . headers . get ( "content-length" ) ;
43-
44- if ( ! contentLength || parseInt ( contentLength ) > env . TASK_PAYLOAD_MAXIMUM_SIZE ) {
45- return json ( { error : "Request body too large" } , { status : 413 } ) ;
46- }
26+ const { action, loader } = createActionApiRoute (
27+ {
28+ headers : HeadersSchema ,
29+ params : ParamsSchema ,
30+ body : TriggerTaskRequestBody ,
31+ allowJWT : true ,
32+ maxContentLength : env . TASK_PAYLOAD_MAXIMUM_SIZE ,
33+ authorization : {
34+ action : "write" ,
35+ resource : ( params ) => ( { tasks : params . taskId } ) ,
36+ superScopes : [ "write:tasks" , "admin" ] ,
37+ } ,
38+ corsStrategy : "all" ,
39+ } ,
40+ async ( { body, headers, params, authentication } ) => {
41+ const {
42+ "idempotency-key" : idempotencyKey ,
43+ "trigger-version" : triggerVersion ,
44+ "x-trigger-span-parent-as-link" : spanParentAsLink ,
45+ traceparent,
46+ tracestate,
47+ "x-trigger-worker" : isFromWorker ,
48+ "x-trigger-client" : triggerClient ,
49+ } = headers ;
50+
51+ const service = new TriggerTaskService ( ) ;
52+
53+ try {
54+ const traceContext =
55+ traceparent && isFromWorker /// If the request is from a worker, we should pass the trace context
56+ ? { traceparent, tracestate }
57+ : undefined ;
58+
59+ logger . debug ( "Triggering task" , {
60+ taskId : params . taskId ,
61+ idempotencyKey,
62+ triggerVersion,
63+ headers,
64+ options : body . options ,
65+ isFromWorker,
66+ traceContext,
67+ } ) ;
68+
69+ const run = await service . call ( params . taskId , authentication . environment , body , {
70+ idempotencyKey : idempotencyKey ?? undefined ,
71+ triggerVersion : triggerVersion ?? undefined ,
72+ traceContext,
73+ spanParentAsLink : spanParentAsLink === 1 ,
74+ } ) ;
75+
76+ if ( ! run ) {
77+ return json ( { error : "Task not found" } , { status : 404 } ) ;
78+ }
4779
48- const rawHeaders = Object . fromEntries ( request . headers ) ;
80+ const $responseHeaders = await responseHeaders (
81+ run ,
82+ authentication . environment ,
83+ triggerClient
84+ ) ;
4985
50- const headers = HeadersSchema . safeParse ( rawHeaders ) ;
86+ return json (
87+ {
88+ id : run . friendlyId ,
89+ } ,
90+ {
91+ headers : $responseHeaders ,
92+ }
93+ ) ;
94+ } catch ( error ) {
95+ if ( error instanceof ServiceValidationError ) {
96+ return json ( { error : error . message } , { status : 422 } ) ;
97+ } else if ( error instanceof OutOfEntitlementError ) {
98+ return json ( { error : error . message } , { status : 422 } ) ;
99+ } else if ( error instanceof Error ) {
100+ return json ( { error : error . message } , { status : 400 } ) ;
101+ }
51102
52- if ( ! headers . success ) {
53- return json ( { error : "Invalid headers" } , { status : 400 } ) ;
103+ return json ( { error : "Something went wrong" } , { status : 500 } ) ;
104+ }
54105 }
55-
56- const {
57- "idempotency-key" : idempotencyKey ,
58- "trigger-version" : triggerVersion ,
59- "x-trigger-span-parent-as-link" : spanParentAsLink ,
60- traceparent,
61- tracestate,
62- "x-trigger-worker" : isFromWorker ,
63- } = headers . data ;
64-
65- const { taskId } = ParamsSchema . parse ( params ) ;
66-
67- // Now parse the request body
68- const anyBody = await parseRequestJsonAsync ( request , { taskId } ) ;
69-
70- const body = await startActiveSpan ( "TriggerTaskRequestBody.safeParse()" , async ( span ) => {
71- return TriggerTaskRequestBody . safeParse ( anyBody ) ;
106+ ) ;
107+
108+ async function responseHeaders (
109+ run : TaskRun ,
110+ environment : AuthenticatedEnvironment ,
111+ triggerClient ?: string | null
112+ ) : Promise < Record < string , string > > {
113+ const claimsHeader = JSON . stringify ( {
114+ sub : run . runtimeEnvironmentId ,
115+ pub : true ,
72116 } ) ;
73117
74- if ( ! body . success ) {
75- return json (
76- { error : fromZodError ( body . error , { prefix : "Invalid trigger call" } ) . toString ( ) } ,
77- { status : 400 }
78- ) ;
79- }
80-
81- const service = new TriggerTaskService ( ) ;
82-
83- try {
84- const traceContext =
85- traceparent && isFromWorker /// If the request is from a worker, we should pass the trace context
86- ? { traceparent, tracestate }
87- : undefined ;
88-
89- logger . debug ( "Triggering task" , {
90- taskId,
91- idempotencyKey,
92- triggerVersion,
93- headers : Object . fromEntries ( request . headers ) ,
94- options : body . data . options ,
95- isFromWorker,
96- traceContext,
118+ if ( triggerClient === "browser" ) {
119+ const claims = {
120+ sub : run . runtimeEnvironmentId ,
121+ pub : true ,
122+ scopes : [ `read:runs:${ run . friendlyId } ` ] ,
123+ } ;
124+
125+ const jwt = await internal_generateJWT ( {
126+ secretKey : environment . apiKey ,
127+ payload : claims ,
128+ expirationTime : "1h" ,
97129 } ) ;
98130
99- const run = await service . call ( taskId , authenticationResult . environment , body . data , {
100- idempotencyKey : idempotencyKey ?? undefined ,
101- triggerVersion : triggerVersion ?? undefined ,
102- traceContext,
103- spanParentAsLink : spanParentAsLink === 1 ,
104- } ) ;
105-
106- if ( ! run ) {
107- return json ( { error : "Task not found" } , { status : 404 } ) ;
108- }
109-
110- return json (
111- {
112- id : run . friendlyId ,
113- } ,
114- {
115- headers : {
116- "x-trigger-jwt-claims" : JSON . stringify ( {
117- sub : authenticationResult . environment . id ,
118- pub : true ,
119- } ) ,
120- } ,
121- }
122- ) ;
123- } catch ( error ) {
124- if ( error instanceof ServiceValidationError ) {
125- return json ( { error : error . message } , { status : 422 } ) ;
126- } else if ( error instanceof OutOfEntitlementError ) {
127- return json ( { error : error . message } , { status : 422 } ) ;
128- } else if ( error instanceof Error ) {
129- return json ( { error : error . message } , { status : 400 } ) ;
130- }
131-
132- return json ( { error : "Something went wrong" } , { status : 500 } ) ;
131+ return {
132+ "x-trigger-jwt-claims" : claimsHeader ,
133+ "x-trigger-jwt" : jwt ,
134+ } ;
133135 }
136+
137+ return {
138+ "x-trigger-jwt-claims" : claimsHeader ,
139+ } ;
134140}
141+
142+ export { action , loader } ;
0 commit comments