From da473bd416d6cf10cf37729c7e60189e42ca3938 Mon Sep 17 00:00:00 2001 From: Jude Kwashie Date: Mon, 18 May 2026 11:19:54 +0000 Subject: [PATCH] feat(messaging, web): add support for custom service worker script path in `getToken` method --- .../firebase_messaging/lib/src/messaging.dart | 6 ++++ .../test/firebase_messaging_test.dart | 29 +++++++++++++++++-- .../firebase_messaging/test/mock.dart | 7 +++-- .../method_channel_messaging.dart | 1 + .../platform_interface_messaging.dart | 1 + .../lib/firebase_messaging_web.dart | 6 ++-- .../lib/src/interop/messaging.dart | 22 ++++++++++---- .../lib/src/interop/messaging_interop.dart | 3 ++ 8 files changed, 63 insertions(+), 12 deletions(-) diff --git a/packages/firebase_messaging/firebase_messaging/lib/src/messaging.dart b/packages/firebase_messaging/firebase_messaging/lib/src/messaging.dart index 4205bdd61bc1..de82a44a0dc1 100644 --- a/packages/firebase_messaging/firebase_messaging/lib/src/messaging.dart +++ b/packages/firebase_messaging/firebase_messaging/lib/src/messaging.dart @@ -114,11 +114,17 @@ class FirebaseMessaging extends FirebasePluginPlatform { /// Returns the default FCM token for this device. /// /// On web, a [vapidKey] is required. + /// + /// On web, a custom messaging service worker can be registered with + /// [serviceWorkerScriptPath]. This must point to a JavaScript file in the + /// root of the app's `web` directory. Future getToken({ String? vapidKey, + String? serviceWorkerScriptPath, }) { return _delegate.getToken( vapidKey: vapidKey, + serviceWorkerScriptPath: serviceWorkerScriptPath, ); } diff --git a/packages/firebase_messaging/firebase_messaging/test/firebase_messaging_test.dart b/packages/firebase_messaging/firebase_messaging/test/firebase_messaging_test.dart index af53b2720741..f3e1dd4204bb 100644 --- a/packages/firebase_messaging/firebase_messaging/test/firebase_messaging_test.dart +++ b/packages/firebase_messaging/firebase_messaging/test/firebase_messaging_test.dart @@ -99,12 +99,35 @@ void main() { group('getToken', () { test('verify delegate method is called with correct args', () async { const vapidKey = 'test-vapid-key'; - when(kMockMessagingPlatform.getToken(vapidKey: anyNamed('vapidKey'))) - .thenAnswer((_) => Future.value('')); + when(kMockMessagingPlatform.getToken( + vapidKey: anyNamed('vapidKey'), + serviceWorkerScriptPath: anyNamed('serviceWorkerScriptPath'), + )).thenAnswer((_) => Future.value('')); await messaging!.getToken(vapidKey: vapidKey); - verify(kMockMessagingPlatform.getToken(vapidKey: vapidKey)); + verify(kMockMessagingPlatform.getToken( + vapidKey: vapidKey, + serviceWorkerScriptPath: null, + )); + }); + + test('verify delegate method is called with service worker path', + () async { + const serviceWorkerScriptPath = 'custom-messaging-sw.js'; + when(kMockMessagingPlatform.getToken( + vapidKey: anyNamed('vapidKey'), + serviceWorkerScriptPath: anyNamed('serviceWorkerScriptPath'), + )).thenAnswer((_) => Future.value('')); + + await messaging!.getToken( + serviceWorkerScriptPath: serviceWorkerScriptPath, + ); + + verify(kMockMessagingPlatform.getToken( + vapidKey: null, + serviceWorkerScriptPath: serviceWorkerScriptPath, + )); }); }); diff --git a/packages/firebase_messaging/firebase_messaging/test/mock.dart b/packages/firebase_messaging/firebase_messaging/test/mock.dart index 2493e87ee85b..4555c8d77d30 100644 --- a/packages/firebase_messaging/firebase_messaging/test/mock.dart +++ b/packages/firebase_messaging/firebase_messaging/test/mock.dart @@ -96,9 +96,12 @@ class MockFirebaseMessaging extends Mock } @override - Future getToken({String? vapidKey}) { + Future getToken({String? vapidKey, String? serviceWorkerScriptPath}) { return super.noSuchMethod( - Invocation.method(#getToken, [], {#vapidKey: vapidKey}), + Invocation.method(#getToken, [], { + #vapidKey: vapidKey, + #serviceWorkerScriptPath: serviceWorkerScriptPath + }), returnValue: Future.value(''), returnValueForMissingStub: Future.value('')); } diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/method_channel/method_channel_messaging.dart b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/method_channel/method_channel_messaging.dart index ffaac715c5b2..73788d05c3de 100644 --- a/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/method_channel/method_channel_messaging.dart +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/method_channel/method_channel_messaging.dart @@ -240,6 +240,7 @@ class MethodChannelFirebaseMessaging extends FirebaseMessagingPlatform { @override Future getToken({ String? vapidKey, // not used yet; web only property + String? serviceWorkerScriptPath, // web only property }) async { await _APNSTokenCheck(); diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/platform_interface/platform_interface_messaging.dart b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/platform_interface/platform_interface_messaging.dart index 3e0ffa7d90d8..44d20d32ed5b 100644 --- a/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/platform_interface/platform_interface_messaging.dart +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/platform_interface/platform_interface_messaging.dart @@ -173,6 +173,7 @@ abstract class FirebaseMessagingPlatform extends PlatformInterface { /// Returns the default FCM token for this device and optionally [senderId]. Future getToken({ String? vapidKey, + String? serviceWorkerScriptPath, }) { throw UnimplementedError('getToken() is not implemented'); } diff --git a/packages/firebase_messaging/firebase_messaging_web/lib/firebase_messaging_web.dart b/packages/firebase_messaging/firebase_messaging_web/lib/firebase_messaging_web.dart index 1443a65d2fbd..7825b227fa1c 100644 --- a/packages/firebase_messaging/firebase_messaging_web/lib/firebase_messaging_web.dart +++ b/packages/firebase_messaging/firebase_messaging_web/lib/firebase_messaging_web.dart @@ -112,7 +112,8 @@ class FirebaseMessagingWeb extends FirebaseMessagingPlatform { } @override - Future getToken({String? vapidKey}) async { + Future getToken( + {String? vapidKey, String? serviceWorkerScriptPath}) async { _delegate; if (!_initialized) { @@ -121,7 +122,8 @@ class FirebaseMessagingWeb extends FirebaseMessagingPlatform { } return convertWebExceptions( - () => _delegate.getToken(vapidKey: vapidKey), + () => _delegate.getToken( + vapidKey: vapidKey, serviceWorkerScriptPath: serviceWorkerScriptPath), ); } diff --git a/packages/firebase_messaging/firebase_messaging_web/lib/src/interop/messaging.dart b/packages/firebase_messaging/firebase_messaging_web/lib/src/interop/messaging.dart index d3a0e2673b4a..265724c2c288 100644 --- a/packages/firebase_messaging/firebase_messaging_web/lib/src/interop/messaging.dart +++ b/packages/firebase_messaging/firebase_messaging_web/lib/src/interop/messaging.dart @@ -9,7 +9,7 @@ import 'dart:async'; import 'dart:js_interop'; import 'package:firebase_core_web/firebase_core_web_interop.dart'; - +import 'package:web/web.dart' as web; import 'messaging_interop.dart' as messaging_interop; export 'messaging_interop.dart'; @@ -43,15 +43,24 @@ class Messaging extends JsObjectWrapper { /// After calling [requestPermission] you can call this method to get an FCM registration token /// that can be used to send push messages to this user. - Future getToken({String? vapidKey}) async { + Future getToken( + {String? vapidKey, String? serviceWorkerScriptPath}) async { try { + web.ServiceWorkerRegistration? serviceWorkerRegistration; + if (serviceWorkerScriptPath != null) { + serviceWorkerRegistration = await web.window.navigator.serviceWorker + .register(serviceWorkerScriptPath.toJS) + .toDart; + } final token = (await messaging_interop .getToken( jsObject, - vapidKey == null + vapidKey == null && serviceWorkerRegistration == null ? null : messaging_interop.GetTokenOptions( - vapidKey: vapidKey.toJS)) + vapidKey: vapidKey?.toJS, + serviceWorkerRegistration: serviceWorkerRegistration, + )) .toDart) .toDart; return token; @@ -62,7 +71,10 @@ class Messaging extends JsObjectWrapper { if (err.toString().toLowerCase().contains('no active service worker') && firstGetTokenCall) { firstGetTokenCall = false; - return getToken(vapidKey: vapidKey); + return getToken( + vapidKey: vapidKey, + serviceWorkerScriptPath: serviceWorkerScriptPath, + ); } rethrow; } diff --git a/packages/firebase_messaging/firebase_messaging_web/lib/src/interop/messaging_interop.dart b/packages/firebase_messaging/firebase_messaging_web/lib/src/interop/messaging_interop.dart index 66e1fe87e49e..9603e421c961 100644 --- a/packages/firebase_messaging/firebase_messaging_web/lib/src/interop/messaging_interop.dart +++ b/packages/firebase_messaging/firebase_messaging_web/lib/src/interop/messaging_interop.dart @@ -9,6 +9,7 @@ library; import 'dart:js_interop'; +import 'package:web/web.dart' as web; import 'package:firebase_core_web/firebase_core_web_interop.dart'; @@ -50,8 +51,10 @@ extension type GetTokenOptions._(JSObject _) implements JSObject { external factory GetTokenOptions({ JSString? vapidKey, /*dynamic serviceWorkerRegistration */ + web.ServiceWorkerRegistration? serviceWorkerRegistration, }); external JSString get vapidKey; + external web.ServiceWorkerRegistration get serviceWorkerRegistration; } extension type NotificationPayloadJsImpl._(JSObject _) implements JSObject {