withCors function

Applies options around inner; answers preflight OPTIONS itself.

The building block behind the cors option of GenkitRouter.serve and GenkitRouter.handleHttpRequest, for HTTP framework adapters (e.g. asShelfHandler in package:genkit_shelf):

final response = await withCors(cors, request, (request) async =>
    await router.handle(request) ?? notFound());

Implementation

Future<GenkitHttpResponse> withCors(
  CorsOptions options,
  GenkitHttpRequest request,
  GenkitHttpHandler inner,
) async {
  final allowAny = options.allowedOrigins.contains('*');
  final origin = request.headers['origin'];
  final String? allowOrigin;
  if (allowAny) {
    allowOrigin = '*';
  } else if (origin != null && options.allowedOrigins.contains(origin)) {
    allowOrigin = origin;
  } else {
    allowOrigin = null;
  }
  final headers = <String, String>{
    // With a restricted list the response depends on the request origin, so
    // caches must key on it. That includes responses without CORS headers (no
    // or a disallowed `Origin`): cached, they would be handed to an allowed
    // origin and the browser would block them.
    if (!allowAny) 'vary': 'Origin',
    if (allowOrigin != null) ...{
      'access-control-allow-origin': allowOrigin,
      if (options.exposedHeaders.isNotEmpty)
        'access-control-expose-headers': options.exposedHeaders.join(', '),
    },
  };

  if (request.method == 'OPTIONS') {
    return GenkitHttpResponse(
      statusCode: 204,
      headers: {
        ...headers,
        if (allowOrigin != null) ...{
          'access-control-allow-methods': 'POST, OPTIONS',
          if (options.allowedHeaders.isNotEmpty)
            'access-control-allow-headers': options.allowedHeaders.join(', '),
        },
      },
    );
  }

  final response = await inner(request);
  if (headers.isEmpty) return response;
  final existingVary = response.headers['vary'];
  final vary = headers['vary'];
  return GenkitHttpResponse(
    statusCode: response.statusCode,
    headers: {
      ...response.headers,
      ...headers,
      // Append to an existing `Vary` (e.g. `Accept-Encoding`) instead of
      // clobbering it.
      if (vary != null && existingVary != null)
        'vary': _appendVary(existingVary, vary),
    },
    body: response.body,
  );
}