如何从Firebase Service Worker向Flutter Web应用传递FCM后台消息数据
Hey there! Let's work through your FCM background messaging issues step by step—you're on the right track with using a Service Worker, but there are a few key fixes needed to get data passed to your Flutter app properly.
First: Resolve the importScripts Undefined Error
This usually happens if your Service Worker file isn't placed correctly or registered properly in Flutter Web:
- Make sure your
firebase-messaging-sw.jsis in the root of yourwebfolder (same directory asindex.html). Flutter serves files from this root in production, so placing it here ensures it's accessible. - In your
index.html, add this script to register the Service Worker (place it before the Flutter entry script):<script> if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/firebase-messaging-sw.js') .then(registration => { console.log('Service Worker registered:', registration); }) .catch(err => { console.error('Service Worker registration failed:', err); }); }); } </script> - Note: In Flutter's development mode, you might need to run with
flutter run -d chrome --web-port 5000(or another port) to avoid Service Worker caching issues, and ensure Chrome allows local Service Workers (checkchrome://flags/#allow-insecure-localhostif needed).
Second: Properly Transfer Data from Service Worker to Flutter
You can't modify client.state or use window in a Service Worker—postMessage() is the only safe way to send data to your Flutter app. Here's how to fix your Service Worker code and set up Flutter to listen for these messages:
Updated Service Worker Code
importScripts("https://www.gstatic.com/firebasejs/8.6.1/firebase-app.js"); importScripts("https://www.gstatic.com/firebasejs/8.6.1/firebase-messaging.js"); firebase.initializeApp({ apiKey: "YOUR_API_KEY", authDomain: "YOUR_AUTH_DOMAIN", projectId: "YOUR_PROJECT_ID", storageBucket: "YOUR_STORAGE_BUCKET", messagingSenderId: "YOUR_SENDER_ID", appId: "YOUR_APP_ID", measurementId: "YOUR_MEASUREMENT_ID", }); const messaging = firebase.messaging(); // Handle background messages messaging.onBackgroundMessage((m) => { const type = m.data.type; const id = m.data.id; console.log(`Received background message: ${type} | ID: ${id}`); // Send data to all active Flutter clients clients.matchAll({ type: "window", includeUncontrolled: true }) .then(clientList => { clientList.forEach(client => { // Send the actual data via postMessage client.postMessage({ type: type, id: id, messageType: 'backgroundFCM' // Add identifier to distinguish FCM messages }); }); }); // Show notification with attached data return self.registration.showNotification( m.data.title, { body: m.data.body || m.data.type, data: { type: type, id: id } // Attach data for click handling } ); }); // Handle notification clicks self.addEventListener("notificationclick", (event) => { event.notification.close(); // Activate Flutter app and send click data event.waitUntil( clients.matchAll({ type: "window", includeUncontrolled: true }) .then(clientList => { // Find existing Flutter client or open new one const client = clientList.find(c => c.url.includes(window.location.origin)); if (client) { client.focus(); client.postMessage({ type: event.notification.data.type, id: event.notification.data.id, messageType: 'notificationClick' }); } else { clients.openWindow('/').then(newClient => { // Wait for new client to load before sending data setTimeout(() => { newClient.postMessage({ type: event.notification.data.type, id: event.notification.data.id, messageType: 'notificationClick' }); }, 1000); }); } }) ); });
Flutter Side: Listen for Messages from Service Worker
Use dart:html to listen for the message event from the Service Worker, then pass the data to your Flutter business logic with a StreamController:
import 'dart:html' as html; import 'dart:async'; import 'package:flutter/material.dart'; // Stream to broadcast FCM messages across your app final StreamController<Map<String, dynamic>> _fcmBackgroundStream = StreamController.broadcast(); Stream<Map<String, dynamic>> get fcmBackgroundStream => _fcmBackgroundStream.stream; void initFCMBackgroundListener() { // Listen for messages from the Service Worker html.window.addEventListener('message', (html.Event event) { if (event is html.MessageEvent) { final data = event.data as Map<String, dynamic>; if (data['messageType'] != null) { _fcmBackgroundStream.add(data); } } }); } void main() { initFCMBackgroundListener(); runApp(const MyApp()); } // Example widget using the FCM stream class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('FCM Background Test')), body: StreamBuilder<Map<String, dynamic>>( stream: fcmBackgroundStream, builder: (context, snapshot) { if (snapshot.hasData) { final message = snapshot.data!; return Center( child: Text( '${message['messageType']}\nType: ${message['type']}\nID: ${message['id']}', textAlign: TextAlign.center, ), ); } return const Center(child: Text('Waiting for background messages...')); }, ), ), ); } }
Key Reminders
- Service Workers are isolated: They can't access Flutter app state directly—only use
postMessage()to communicate. - Notification data: Always attach
typeandidto the notification'sdatafield to retrieve it on click. - Client targeting: When sending messages, verify the
client.urlmatches your app's origin to ensure you're talking to the right Flutter instance.
内容的提问来源于stack exchange,提问作者DieabeS

