-
Notifications
You must be signed in to change notification settings - Fork 269
Handle CORS requests for only defined agent routes #457
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"agents": patch | ||
--- | ||
|
||
Update `routeAgentRequest()` to handle preflight requests only for defined routes. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1577,65 +1577,97 @@ export type AgentContext = DurableObjectState; | |
*/ | ||
export type AgentOptions<Env> = PartyServerOptions<Env> & { | ||
/** | ||
* Whether to enable CORS for the Agent | ||
* Whether to enable CORS for the Agent. | ||
* Implement `HeadersInit` to enable CORS and override the default CORS header fields. | ||
*/ | ||
cors?: boolean | HeadersInit | undefined; | ||
cors?: boolean | HeadersInit; | ||
}; | ||
|
||
/** | ||
* Route a request to the appropriate Agent | ||
* Route a request to the appropriate Agent. | ||
* | ||
* An Agent is mapped to the route `/agents/[agent_namespace]/[agent_name]` (including sub-routes) for all methods, where: | ||
* - `agent_namespace`: The kebab-case string of the Agent namespace (example: MyAgent maps to `my-agent`). | ||
* - `agent_name`: The name of the Agent instance. | ||
* | ||
* If `options.cors` is `true` or satisfies `HeadersInit`, it will handle preflight requests to the agent route and set CORS header fields on all resolved responses. | ||
* The header fields when `options.cors` is `true` are: | ||
* - Access-Control-Allow-Credentials: true | ||
* - Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS | ||
* - Access-Control-Allow-Origin: * | ||
* - Access-Control-Max-Age: 86400 | ||
* | ||
* @param request Request to route | ||
* @param env Environment containing Agent bindings | ||
* @param options Routing options | ||
* @returns Response from the Agent or undefined if no route matched | ||
* @returns Response from the Agent or null if no route matched | ||
*/ | ||
export async function routeAgentRequest<Env>( | ||
request: Request, | ||
env: Env, | ||
options?: AgentOptions<Env> | ||
options: AgentOptions<Env> = {} | ||
) { | ||
const corsHeaders = | ||
options?.cors === true | ||
? { | ||
"Access-Control-Allow-Credentials": "true", | ||
"Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS", | ||
"Access-Control-Allow-Origin": "*", | ||
"Access-Control-Max-Age": "86400" | ||
let corsEnabled: boolean; | ||
let corsHeaders: HeadersInit; | ||
if (options.cors === true) { | ||
corsEnabled = true; | ||
corsHeaders = { | ||
"Access-Control-Allow-Credentials": "true", | ||
"Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS", | ||
"Access-Control-Allow-Origin": "*", | ||
"Access-Control-Max-Age": "86400" | ||
}; | ||
} else if (typeof options.cors === "object" || Array.isArray(options.cors)) { | ||
// options.cors satisfies HeadersInit. | ||
|
||
corsEnabled = true; | ||
corsHeaders = options.cors; | ||
} else { | ||
corsEnabled = false; | ||
corsHeaders = {}; | ||
} | ||
|
||
const response = await routePartykitRequest(request, env as any, { | ||
prefix: "agents", | ||
jurisdiction: options.jurisdiction, | ||
locationHint: options.locationHint, | ||
// Preflight request with `Upgrade` header field don't exist. | ||
onBeforeConnect: options.onBeforeConnect as any, | ||
onBeforeRequest: async (req, lobby) => { | ||
let resolvedRequest = req; | ||
if (options.onBeforeRequest !== undefined) { | ||
const reqOrRes = await options.onBeforeRequest(req, lobby as any); | ||
if (reqOrRes instanceof Response) { | ||
return reqOrRes; | ||
} | ||
: options?.cors; | ||
if (reqOrRes instanceof Request) { | ||
resolvedRequest = reqOrRes; | ||
} | ||
} | ||
|
||
if (request.method === "OPTIONS") { | ||
if (corsHeaders) { | ||
return new Response(null, { | ||
headers: corsHeaders | ||
}); | ||
} | ||
console.warn( | ||
"Received an OPTIONS request, but cors was not enabled. Pass `cors: true` or `cors: { ...custom cors headers }` to routeAgentRequest to enable CORS." | ||
); | ||
if (resolvedRequest.method === "OPTIONS") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't we want to do this check before calling onBeforeRequest? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a user I would assume |
||
if (corsEnabled) { | ||
return new Response(null, { | ||
headers: corsHeaders | ||
}); | ||
} | ||
console.warn( | ||
"Received an OPTIONS request, but cors was not enabled. Pass `cors: true` or `cors: { ...custom cors headers }` to routeAgentRequest to enable CORS." | ||
); | ||
} | ||
}, | ||
props: options.props | ||
}); | ||
|
||
if (response === null) { | ||
return null; | ||
} | ||
|
||
let response = await routePartykitRequest( | ||
request, | ||
env as Record<string, unknown>, | ||
{ | ||
prefix: "agents", | ||
...(options as PartyServerOptions<Record<string, unknown>>) | ||
if (request.headers.get("Upgrade")?.toLowerCase() !== "websocket") { | ||
const headersEntries = new Headers(corsHeaders).entries(); | ||
for (const [fieldName, fieldValue] of headersEntries) { | ||
response.headers.set(fieldName, fieldValue); | ||
} | ||
); | ||
|
||
if ( | ||
response && | ||
corsHeaders && | ||
request.headers.get("upgrade")?.toLowerCase() !== "websocket" && | ||
request.headers.get("Upgrade")?.toLowerCase() !== "websocket" | ||
) { | ||
response = new Response(response.body, { | ||
headers: { | ||
...response.headers, | ||
...corsHeaders | ||
} | ||
}); | ||
} | ||
return response; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can replace these
any
if needed but it's something that needs to be fixed inpartyserver
imoThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cloudflare/partykit#278
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's put it back the way it was for now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As in use
Record
overany
?