diff --git a/.changeset/slimy-kings-confess.md b/.changeset/slimy-kings-confess.md new file mode 100644 index 0000000000..e9940c5ab8 --- /dev/null +++ b/.changeset/slimy-kings-confess.md @@ -0,0 +1,5 @@ +--- +'graphql-yoga': patch +--- + +Add content-length: 0 header if 204 is returned by OPTIONS request diff --git a/packages/graphql-yoga/src/plugins/useCORS.ts b/packages/graphql-yoga/src/plugins/useCORS.ts index 2f60856069..77593c2663 100644 --- a/packages/graphql-yoga/src/plugins/useCORS.ts +++ b/packages/graphql-yoga/src/plugins/useCORS.ts @@ -146,6 +146,12 @@ export function useCORS>( if (request.method.toUpperCase() === 'OPTIONS') { const response = new fetchAPI.Response(null, { status: 204, + // Safari (and potentially other browsers) need content-length 0, + // for 204 or they just hang waiting for a body + // see: https://github.com/expressjs/cors/blob/master/lib/index.js#L176 + headers: { + 'Content-Length': '0', + }, }) endResponse(response) } diff --git a/packages/graphql-yoga/src/plugins/useCors.spec.ts b/packages/graphql-yoga/src/plugins/useCors.spec.ts index e5cb3f8bcc..1a2da21720 100644 --- a/packages/graphql-yoga/src/plugins/useCors.spec.ts +++ b/packages/graphql-yoga/src/plugins/useCors.spec.ts @@ -1,7 +1,39 @@ import { Request } from '@whatwg-node/fetch' +import { createSchema } from '../schema.js' +import { createYoga } from '../server.js' +import { YogaInitialContext } from '../types.js' import { getCORSHeadersByRequestAndOptions, CORSOptions } from './useCORS.js' describe('CORS', () => { + describe('OPTIONS call', () => { + it('should respond with correct status & headers', async () => { + const schemaFactory = async (ctx: YogaInitialContext) => { + return createSchema({ + typeDefs: /* GraphQL */ ` + type Query { + foo: String + } + `, + resolvers: { + Query: { + foo: () => 'bar', + }, + }, + }) + } + const yoga = createYoga({ + schema: schemaFactory, + }) + let result = await yoga.fetch('http://yoga/graphql', { + method: 'OPTIONS', + headers: { + 'Content-Type': 'application/json', + }, + }) + expect(result.status).toEqual(204) + expect(result.headers.get('Content-Length')).toEqual('0') + }) + }) describe('No origins specified', () => { const corsOptionsWithNoOrigins = {} it('should return the wildcard if no origin is sent with header', () => {