From 60563dbdee8dddfada69fa3365c67b0aa320c4b0 Mon Sep 17 00:00:00 2001 From: demolaf Date: Thu, 4 Jun 2026 12:48:42 +0100 Subject: [PATCH 1/6] feat: add RunFunctionsOptions to allow x-powered-by header customization --- lib/firebase_functions.dart | 2 +- lib/src/server.dart | 26 ++++++++++++++++++++++++-- test/unit/server_test.dart | 19 +++++++++++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/lib/firebase_functions.dart b/lib/firebase_functions.dart index fa6c873..2023e1f 100644 --- a/lib/firebase_functions.dart +++ b/lib/firebase_functions.dart @@ -123,7 +123,7 @@ export 'src/remote_config/remote_config.dart'; // Experimental: Scheduler triggers (not yet supported in production or emulator) export 'src/scheduler/scheduler.dart'; // Core runtime -export 'src/server.dart' show fireUp, runFunctions; +export 'src/server.dart' show fireUp, runFunctions, RunFunctionsOptions; // Experimental: Storage triggers (emulator only) export 'src/storage/storage.dart'; // Experimental: Task queue triggers (not yet supported in production or emulator) diff --git a/lib/src/server.dart b/lib/src/server.dart index 5744621..60609dc 100644 --- a/lib/src/server.dart +++ b/lib/src/server.dart @@ -30,6 +30,20 @@ import 'logger/logger.dart'; /// Callback type for the user's function registration code. typedef FunctionsRunner = FutureOr Function(Firebase firebase); +/// Runtime configuration options for [runFunctions]. +class RunFunctionsOptions { + const RunFunctionsOptions({ + this.poweredByHeader = 'Dart with package:shelf', + }); + + /// Value for the `x-powered-by` response header. + /// + /// Defaults to `'Dart with package:shelf'`. Pass `null` to omit the header + /// entirely. This applies to all responses, including internally-generated + /// shelf error responses. + final String? poweredByHeader; +} + /// Starts the Firebase Functions runtime. /// /// This is the main entry point for a Firebase Functions application. @@ -64,7 +78,10 @@ Future fireUp(List args, FunctionsRunner runner) => /// }); /// } /// ``` -Future runFunctions(FunctionsRunner runner) async { +Future runFunctions( + FunctionsRunner runner, { + RunFunctionsOptions options = const RunFunctionsOptions(), +}) async { final firebase = createFirebaseInternal(); final env = firebase.$env; final projectId = env.projectId; @@ -95,7 +112,12 @@ Future runFunctions(FunctionsRunner runner) async { }); // Start HTTP server - await shelf_io.serve(handler, InternetAddress.anyIPv4, env.port); + await shelf_io.serve( + handler, + InternetAddress.anyIPv4, + env.port, + poweredByHeader: options.poweredByHeader, + ); }); } diff --git a/test/unit/server_test.dart b/test/unit/server_test.dart index c881b5b..a7cf530 100644 --- a/test/unit/server_test.dart +++ b/test/unit/server_test.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import 'package:firebase_functions/firebase_functions.dart' + show RunFunctionsOptions; import 'package:firebase_functions/src/server.dart'; import 'package:shelf/shelf.dart'; import 'package:test/test.dart'; @@ -51,6 +53,23 @@ void main() { }); }); + group('RunFunctionsOptions', () { + test('defaults to shelf powered-by header', () { + const opts = RunFunctionsOptions(); + expect(opts.poweredByHeader, 'Dart with package:shelf'); + }); + + test('accepts null to remove the header', () { + const opts = RunFunctionsOptions(poweredByHeader: null); + expect(opts.poweredByHeader, isNull); + }); + + test('accepts a custom header value', () { + const opts = RunFunctionsOptions(poweredByHeader: 'MyApp/1.0'); + expect(opts.poweredByHeader, 'MyApp/1.0'); + }); + }); + group('corsHeadersFor', () { test('returns asterisk when allowedOrigins contains asterisk', () { final request = Request('GET', Uri.parse('http://localhost/test')); From 87cca604de222d5454b0e711d9f95444f09907f6 Mon Sep 17 00:00:00 2001 From: demolaf Date: Thu, 4 Jun 2026 13:00:43 +0100 Subject: [PATCH 2/6] chore: lint errors --- lib/firebase_functions.dart | 2 +- lib/src/server.dart | 4 +-- test/e2e/tests/https_onrequest_tests.dart | 36 ++++++++++------------- test/unit/server_test.dart | 2 -- 4 files changed, 18 insertions(+), 26 deletions(-) diff --git a/lib/firebase_functions.dart b/lib/firebase_functions.dart index 2023e1f..aac4c3b 100644 --- a/lib/firebase_functions.dart +++ b/lib/firebase_functions.dart @@ -123,7 +123,7 @@ export 'src/remote_config/remote_config.dart'; // Experimental: Scheduler triggers (not yet supported in production or emulator) export 'src/scheduler/scheduler.dart'; // Core runtime -export 'src/server.dart' show fireUp, runFunctions, RunFunctionsOptions; +export 'src/server.dart' show RunFunctionsOptions, fireUp, runFunctions; // Experimental: Storage triggers (emulator only) export 'src/storage/storage.dart'; // Experimental: Task queue triggers (not yet supported in production or emulator) diff --git a/lib/src/server.dart b/lib/src/server.dart index 60609dc..ac944e4 100644 --- a/lib/src/server.dart +++ b/lib/src/server.dart @@ -32,9 +32,7 @@ typedef FunctionsRunner = FutureOr Function(Firebase firebase); /// Runtime configuration options for [runFunctions]. class RunFunctionsOptions { - const RunFunctionsOptions({ - this.poweredByHeader = 'Dart with package:shelf', - }); + const RunFunctionsOptions({this.poweredByHeader = 'Dart with package:shelf'}); /// Value for the `x-powered-by` response header. /// diff --git a/test/e2e/tests/https_onrequest_tests.dart b/test/e2e/tests/https_onrequest_tests.dart index 599f054..a31c6c1 100644 --- a/test/e2e/tests/https_onrequest_tests.dart +++ b/test/e2e/tests/https_onrequest_tests.dart @@ -79,26 +79,22 @@ void runHttpsOnRequestTests( expect(response.statusCode, equals(404)); }); - test( - 'handles multiple concurrent requests', - () async { - // Reduced from 10 to 5 requests to avoid CI timeout issues - // The emulator spawns separate workers which can be slow in CI - print('Making 5 concurrent requests...'); - final futures = >[]; - - for (var i = 0; i < 5; i++) { - futures.add(() async { - final response = await client.get('helloworld'); - expect(response.statusCode, equals(200)); - expect(response.body, contains('Hello from Dart Functions!')); - }()); - } - - await Future.wait(futures); - }, - timeout: const Timeout(Duration(seconds: 60)), - ); + test('handles multiple concurrent requests', () async { + // Reduced from 10 to 5 requests to avoid CI timeout issues + // The emulator spawns separate workers which can be slow in CI + print('Making 5 concurrent requests...'); + final futures = >[]; + + for (var i = 0; i < 5; i++) { + futures.add(() async { + final response = await client.get('helloworld'); + expect(response.statusCode, equals(200)); + expect(response.body, contains('Hello from Dart Functions!')); + }()); + } + + await Future.wait(futures); + }, timeout: const Timeout(Duration(seconds: 60))); test('function is discoverable via emulator', () async { print('GET ${client.baseUrl}/helloworld'); diff --git a/test/unit/server_test.dart b/test/unit/server_test.dart index a7cf530..6ea6edd 100644 --- a/test/unit/server_test.dart +++ b/test/unit/server_test.dart @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:firebase_functions/firebase_functions.dart' - show RunFunctionsOptions; import 'package:firebase_functions/src/server.dart'; import 'package:shelf/shelf.dart'; import 'package:test/test.dart'; From 738ae5caf40a9d057bab8e38f75d6363e10e020b Mon Sep 17 00:00:00 2001 From: demolaf Date: Thu, 4 Jun 2026 13:03:31 +0100 Subject: [PATCH 3/6] chore: lint errors --- test/e2e/tests/https_onrequest_tests.dart | 36 +++++++++++++---------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/test/e2e/tests/https_onrequest_tests.dart b/test/e2e/tests/https_onrequest_tests.dart index a31c6c1..599f054 100644 --- a/test/e2e/tests/https_onrequest_tests.dart +++ b/test/e2e/tests/https_onrequest_tests.dart @@ -79,22 +79,26 @@ void runHttpsOnRequestTests( expect(response.statusCode, equals(404)); }); - test('handles multiple concurrent requests', () async { - // Reduced from 10 to 5 requests to avoid CI timeout issues - // The emulator spawns separate workers which can be slow in CI - print('Making 5 concurrent requests...'); - final futures = >[]; - - for (var i = 0; i < 5; i++) { - futures.add(() async { - final response = await client.get('helloworld'); - expect(response.statusCode, equals(200)); - expect(response.body, contains('Hello from Dart Functions!')); - }()); - } - - await Future.wait(futures); - }, timeout: const Timeout(Duration(seconds: 60))); + test( + 'handles multiple concurrent requests', + () async { + // Reduced from 10 to 5 requests to avoid CI timeout issues + // The emulator spawns separate workers which can be slow in CI + print('Making 5 concurrent requests...'); + final futures = >[]; + + for (var i = 0; i < 5; i++) { + futures.add(() async { + final response = await client.get('helloworld'); + expect(response.statusCode, equals(200)); + expect(response.body, contains('Hello from Dart Functions!')); + }()); + } + + await Future.wait(futures); + }, + timeout: const Timeout(Duration(seconds: 60)), + ); test('function is discoverable via emulator', () async { print('GET ${client.baseUrl}/helloworld'); From daae3443323fb39ba90ae0da0c268b05935df74e Mon Sep 17 00:00:00 2001 From: demolaf Date: Thu, 4 Jun 2026 13:24:14 +0100 Subject: [PATCH 4/6] fix: remove poweredByHeader default value. --- lib/src/server.dart | 8 ++++---- test/unit/server_test.dart | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/src/server.dart b/lib/src/server.dart index ac944e4..6e41f62 100644 --- a/lib/src/server.dart +++ b/lib/src/server.dart @@ -32,13 +32,13 @@ typedef FunctionsRunner = FutureOr Function(Firebase firebase); /// Runtime configuration options for [runFunctions]. class RunFunctionsOptions { - const RunFunctionsOptions({this.poweredByHeader = 'Dart with package:shelf'}); + const RunFunctionsOptions({this.poweredByHeader}); /// Value for the `x-powered-by` response header. /// - /// Defaults to `'Dart with package:shelf'`. Pass `null` to omit the header - /// entirely. This applies to all responses, including internally-generated - /// shelf error responses. + /// Defaults to `null`, which omits the header entirely. Pass a string to set + /// a custom value. This applies to all responses, including + /// internally-generated shelf error responses. final String? poweredByHeader; } diff --git a/test/unit/server_test.dart b/test/unit/server_test.dart index 6ea6edd..d849428 100644 --- a/test/unit/server_test.dart +++ b/test/unit/server_test.dart @@ -52,9 +52,9 @@ void main() { }); group('RunFunctionsOptions', () { - test('defaults to shelf powered-by header', () { + test('defaults to null (no header)', () { const opts = RunFunctionsOptions(); - expect(opts.poweredByHeader, 'Dart with package:shelf'); + expect(opts.poweredByHeader, isNull); }); test('accepts null to remove the header', () { From 33d5e93d6b042ac745fe34ad76c44085d2ace785 Mon Sep 17 00:00:00 2001 From: demolaf Date: Thu, 4 Jun 2026 13:26:56 +0100 Subject: [PATCH 5/6] chore: lint errors --- test/unit/server_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/server_test.dart b/test/unit/server_test.dart index d849428..f784f52 100644 --- a/test/unit/server_test.dart +++ b/test/unit/server_test.dart @@ -58,7 +58,7 @@ void main() { }); test('accepts null to remove the header', () { - const opts = RunFunctionsOptions(poweredByHeader: null); + const opts = RunFunctionsOptions(); expect(opts.poweredByHeader, isNull); }); From c0fb73e9805b7e1b905aa817daf4259a7ec9bbda Mon Sep 17 00:00:00 2001 From: demolaf Date: Fri, 19 Jun 2026 12:03:10 +0100 Subject: [PATCH 6/6] remove duplicate test --- test/unit/server_test.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/unit/server_test.dart b/test/unit/server_test.dart index f784f52..fd9059b 100644 --- a/test/unit/server_test.dart +++ b/test/unit/server_test.dart @@ -57,11 +57,6 @@ void main() { expect(opts.poweredByHeader, isNull); }); - test('accepts null to remove the header', () { - const opts = RunFunctionsOptions(); - expect(opts.poweredByHeader, isNull); - }); - test('accepts a custom header value', () { const opts = RunFunctionsOptions(poweredByHeader: 'MyApp/1.0'); expect(opts.poweredByHeader, 'MyApp/1.0');