12

In my app, for the web version, I use package firebase 7.3.0. I first instantiate Firebase app with a singleton and then instantiate Messaging() as I have done with all other Firebase services I use in my app :

App firebase = FirebaseWeb.instance.app;
  var firebaseMessaging = messaging();

I have subscribeToTopic() method which first calls getMessagingToken() method as it needs the returned token, but getMessagingToken() throws the error:

PlatformPushNotificationWeb.getMessagingToken() getToken error: FirebaseError: Messaging: We are unable to register the default service worker. Failed to register a ServiceWorker for scope ('http://localhost:5000/firebase-cloud-messaging-push-scope') with script ('http://localhost:5000/firebase-messaging-sw.js'): A bad HTTP response code (404) was received when fetching the script. (messaging/failed-service-worker-registration). (messaging/failed-service-worker-registration)
Future<String> getMessagingToken() async {
    String token;

    await firebaseMessaging.requestPermission().timeout(Duration(seconds: 5)).then((value) {
      print('PlatformPushNotificationWeb.getMessagingToken() requestPermission result is $value');
    }).catchError((e) => print('PlatformPushNotificationWeb.getMessagingToken() requestPermission error: $e'));

    await firebaseMessaging.getToken().then((value) {
      print(' PlatformPushNotificationWeb.getMessagingToken() token is $value');
      token = value;
    }).catchError((e) => print('PlatformPushNotificationWeb.getMessagingToken() getToken error: $e'));

    return token;
  }

I checked and in my index.html firebase-messaging is present:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>fixit cloud biking</title>
  <!--  <meta name="google-signin-client_id" content="YOUR_GOOGLE_SIGN_IN_OAUTH_CLIENT_ID.apps.googleusercontent.com">-->
  <meta name="google-signin-client_id" content="xxxxxxxxxx.apps.googleusercontent.com">
<!--  <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">-->
</head>
<!--<body>-->
<body id="app-container">
<script src="main.dart.js?version=45" type="application/javascript"></script>
<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/7.15.5/firebase-app.js"></script>

<!-- TODO: Add SDKs for Firebase products that you want to use
     https://firebase.google.com/docs/web/setup#available-libraries -->
<script src="https://www.gstatic.com/firebasejs/7.15.5/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.15.5/firebase-analytics.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.15.5/firebase-messaging.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.15.5/firebase-storage.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.15.5/firebase-database.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.15.5/firebase-remote-config.js"></script>
</body>
</html>

Now, the error says 'http://localhost:5000/firebase-messaging-sw.js' not firebase-messaging.js as the library in the index.htmlfile. I noticed that Messaging()is not directly available through firebase app instance as it would be for other services, for Storage would befirebase.storage()`. Am I missing to setup something else for messaging?

Vincenzo
  • 5,304
  • 5
  • 38
  • 96

3 Answers3

30

Found this article https://medium.com/@rody.davis.jr/how-to-send-push-notifications-on-flutter-web-fcm-b3e64f1e2b76 and discovered that indeed there is a bit more setup for Firebase Cloud Messaging on web.

In index.html there is a script to add:

<script>
if ("serviceWorker" in navigator) {
  window.addEventListener("load", function () {
    // navigator.serviceWorker.register("/flutter_service_worker.js");
    navigator.serviceWorker.register("/firebase-messaging-sw.js");
  });
}
</script>

In project web folder create a new file firebase-messaging-sw.js where you import the firebase packages (match index.html versions), initialize Firebase app , and set the BackgroundMessageHandler. If I initialize Firebase app with the singleton then instantiating messaging() throws a syntax error, so it needs to be initialized with all parameters, otherwise on background messages won't work.

importScripts("https://www.gstatic.com/firebasejs/7.15.5/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/7.15.5/firebase-messaging.js");

//Using singleton breaks instantiating messaging()
// App firebase = FirebaseWeb.instance.app;


firebase.initializeApp({
  apiKey: 'api-key',
  authDomain: 'project-id.firebaseapp.com',
  databaseURL: 'https://project-id.firebaseio.com',
  projectId: 'project-id',
  storageBucket: 'project-id.appspot.com',
  messagingSenderId: 'sender-id',
  appId: 'app-id',
  measurementId: 'G-measurement-id',
});

const messaging = firebase.messaging();
messaging.setBackgroundMessageHandler(function (payload) {
    const promiseChain = clients
        .matchAll({
            type: "window",
            includeUncontrolled: true
        })
        .then(windowClients => {
            for (let i = 0; i < windowClients.length; i++) {
                const windowClient = windowClients[i];
                windowClient.postMessage(payload);
            }
        })
        .then(() => {
            return registration.showNotification("New Message");
        });
    return promiseChain;
});
self.addEventListener('notificationclick', function (event) {
    console.log('notification received: ', event)
});

So now, getToken() and subscribeToTopic() and onMessage() work as expected.

In my bloc I have a listener on onMessage() which (on web) Stream I convert to a Stream<Map<String,Dynamic>> as the firebase_messaging(on device) returns from :

Stream<Map<String, dynamic>> onMessage()  async* {

    print('PlatformPushNotificationWeb.onMessage() started');
    handleData(Payload payload, EventSink<Map<String, dynamic>> sink) {
        Map<String,dynamic> message = {
          'notification': {
            'title': payload.notification.title,
            'body': payload.notification.body,
            'sound': true
          },
          'data': payload.data
        };
      sink.add(message);
    }

    final transformer = StreamTransformer<Payload, Map<String, dynamic>>.fromHandlers(
        handleData: handleData);

    yield* firebaseMessaging.onMessage.transform(transformer);
  }

Hope it helps others. Cheers.

CAUTION: In an official release, note that your service worker is visible in the browser, which means this solution will expose your API keys and, any other sensitive data, to the world. For example, you can press F12 to open developer options and view the service worker file.

  • For moderate protection, you may store the firebase configuration in environment variables. Though, I would recommend encrypting them if you do this.
  • For a higher level of protection, one solution is to use cloud secret manager to store keys, where google provides encryption. I would recommend requiring the Admin sdk to obtain the 'secret' with a cloud function.
  • For best security, rotate any sensitive keys at some interval, and store them somewhere that they can be encrypted. Again, encryption is automatically done for you when using the google cloud secret manager.
  • When the code is completed, a minifier can be used to reduce your code as well, which is very helpful for security.
FoxDonut
  • 252
  • 2
  • 14
Vincenzo
  • 5,304
  • 5
  • 38
  • 96
  • Thank you for this response, this solved my problem as well https://stackoverflow.com/questions/66867680/flutter-failed-to-register-a-serviceworker-for-scope-with-script-the-script-has – ForestG Mar 30 '21 at 09:28
  • 1
    This answer should be wayyy upvoted. This solved it for me too. Cheers – lenz Mar 30 '21 at 16:07
  • Glad it helped. I struglled quite a bit with it. Now with thre latest official packages versions supporting web I switched to only those packs. Thanks for the comment. Cheers. – Vincenzo Mar 30 '21 at 17:05
  • this answer should have 1000 upvotes. Is the ONLY solution I could find online. – Mariano Zorrilla Apr 09 '21 at 15:16
  • @Mariano Zorrilla . So glad that it could help you. Indeed when I had the problem I couldn't find almost anything on the subject. – Vincenzo Apr 11 '21 at 19:56
  • this solution works very well for me !! ty!! – Lucas Buchalla Sesti Apr 19 '21 at 16:34
  • 1
    Where does that Stream bloc should reside? What is the Payload type? – MappaM May 05 '21 at 11:42
  • if commenting out `navigator.serviceWorker.register("/flutter_service_worker.js");` site doesn't load, it's there for a reason – temirbek May 06 '22 at 10:25
3

It turns out you can just create a file called firebase-messaging-sw.js in your web project folder and have some commented out JavaScript in it and then Flutter stops complaining.

Also modify index.html with this:

<script>
if ('serviceWorker' in navigator) {
  window.addEventListener("load", function () {
    navigator.serviceWorker.register("firebase-messaging-sw.js");
  });

  window.addEventListener('flutter-first-frame', function () {
    navigator.serviceWorker.register('flutter_service_worker.js');
  });
}

I really don't know why this works, but it's better than having some useless JavaScript code laying around.

Rexios
  • 532
  • 6
  • 11
2

As of December 2022, just creating an empty firebase-messaging-sw.js file at the root of the web directory in your flutter app will fix the issue.


Before that you of course follow the steps to add Firebase to your app: https://firebase.google.com/docs/flutter/setup

dart pub global activate flutterfire_cli
flutterfire configure

Then

In your lib/main.dart file, import the Firebase core plugin and the configuration file you generated earlier:

import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';

Finally

Also in your lib/main.dart file, initialize Firebase using the DefaultFirebaseOptions object exported by the configuration file:

await Firebase.initializeApp(
  options: DefaultFirebaseOptions.currentPlatform,
);
Antonin GAVREL
  • 9,682
  • 8
  • 54
  • 81
  • Hi and thanks for answering, what a coincidence!! I'm going through the problem again, after upgrading to latest Flutter, does it have to do with it? – Vincenzo Dec 17 '22 at 06:57
  • It's possible, in any case I am using the latest version of Flutter and it worked like this. Note that you need to use flutterfire configure to make it work correctly, see my edit. – Antonin GAVREL Dec 18 '22 at 02:44
  • I'm just about getting rid of firebase altogether. Since I started using it I only had to put up with be like this. They keep updating their code either with new naming which change from including to not including "firebase" or "Firebase" ..I first git rid of their db for the stupidity. Not being able to query for range on more than one parameter. Forcing to do two different queries an filter out returned data... Of course .. so they chan charge you more document reads.. so I put up a node server with MongoDb and only use their auth and messaging nowadays. – Vincenzo Dec 18 '22 at 07:09
  • So ai already put the subscribe/unsubscribe to topics part in it and I'll put all the rest of the messaging functions there an leave just bare https calls from my apps. Al least this way I don't have to put up with so much refactoring every time their developers wake up with some brilliant idea about renaming thing.. I guess they have so much useless people that have to justify their salary somehow. When you see continuous bs like this usually this is the reason. – Vincenzo Dec 18 '22 at 07:14
  • As a matter of fact after believing so much in Flutter I'm about to switch to React. So many problems with the plugins.. jeez I could understand them at v.1.0 but not anymore..production ready my...I really do dislike markup languages and that's why I never been into web developing..but I really guess it's time to make pace with it and make the switch to an actual production ready system. – Vincenzo Dec 18 '22 at 07:20
  • I also keep the bare minimum. Problem is that Apple and Google have monopoly when it comes to push notification since they are the only one that can push app notification while an app is terminated. Flutter Web is still very new but I have seen the incredible development in the past 2 years. For client I use Flutter and for server Nodejs express. – Antonin GAVREL Dec 19 '22 at 02:19
  • Same as me. Do you send pushes from device with the sdk or do you send them from the server? I'm thinking to switch to handle push notifications completely from the server as I just had to add the subscribe/unsubscribe methods. – Vincenzo Dec 19 '22 at 17:22
  • I send them initially from the client side as Admin to the server which then pushes it back to the client devices. I don't use topic as it is not supported for Flutter Web. (I also use flutter web as a client for those who don't get the app). – Antonin GAVREL Dec 19 '22 at 21:29
  • @AntoninGAVREL how to register firebase-messaging-sw.js in Index.html – Robert Robinson Mar 16 '23 at 08:22