Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

getToken throw error : DOMException: Failed to execute 'subscribe' on 'PushManager': Subscription failed - no active Service Worker #7693

Open
EricaLi123 opened this issue Oct 11, 2023 · 20 comments

Comments

@EricaLi123
Copy link

Operating System

win 10

Browser Version

chrome 118.0.5993.71 (Official Build) (64-bit)

Firebase SDK Version

10.4.0

Firebase SDK Product:

Messaging

Describe your project's tooling

https://www.gstatic.com/firebasejs/10.4.0/firebase-app-compat.js
https://www.gstatic.com/firebasejs/10.4.0/firebase-messaging-compat.js

Describe the problem

  1. clear cookie
    image

  2. when open my web site for the first time, getToken throw the following error【NG】
    image

  3. when open my web site for the second time or refresh the tab, getToken works fine.
    image

Steps and code to reproduce issue

return this.messaging.getToken({
            vapidKey : this.vapidKey,
        }).then((currentToken) => {
            if (currentToken) {
                console.log(currentToken);
                return currentToken;
            } else {
                console.log('No Instance ID token available. Request permission to generate one.');
                return null;
            }
        }).catch((err) => {
            console.error(err);
        });
@EricaLi123 EricaLi123 added new A new issue that hasn't be categoirzed as question, bug or feature request question labels Oct 11, 2023
@EricaLi123 EricaLi123 changed the title An error occurred while retrieving token. DOMException: Failed to execute 'subscribe' on 'PushManager': Subscription failed - no active Service Worker getToken throw error : DOMException: Failed to execute 'subscribe' on 'PushManager': Subscription failed - no active Service Worker Oct 11, 2023
@jbalidiong jbalidiong added needs-attention and removed new A new issue that hasn't be categoirzed as question, bug or feature request labels Oct 11, 2023
@duc-gp
Copy link

duc-gp commented Oct 14, 2023

having the same issue. it seems the service worker file firebase-messaging-sw.js is installed when called getToken() for the first time, after refreshing and calling getToken again it then works..

@baryosef-loops
Copy link

baryosef-loops commented Oct 19, 2023

I have similar issue as well.
Reproducible in chrome 118 and safari.
Seems to be critical, I haven't use the API before chrome 118, so can't say if it is a regression. Is there any insight here from the team @jbalidiong ?

@EricaLi123
Copy link
Author

So I took this temporary approach to the problem.

this.messaging
      .getToken({
          vapidKey : this.vapidKey,
      }).catch((err) => {
          // https://github.com/firebase/firebase-js-sdk/issues/7693
          // 如果 getToken 时是还没注册 firebase-messaging-sw.js,就会报错如下,再次执行 getToken 就好了
          const error = "AbortError: Failed to execute 'subscribe' on 'PushManager': Subscription failed - no active Service Worker";
          if (err.toString() === error) {
              return this.messaging.getToken({
                  vapidKey : this.vapidKey,
              });
          } else {
              throw err;
          }
      })
      .then((currentToken) => {
          if (currentToken) {
              console.log(currentToken);
              return currentToken;
          } else {
              console.log(
                  'No Instance ID token available. Request permission to generate one.'
              );
              return null;
          }
      })
      .catch((err) => {
          console.error(err);
      });

I look forward to letting me know when this issue is completely fixed

@dpeese
Copy link

dpeese commented Oct 26, 2023

Facing same issue with latest firebase 10.5.0. Please help with the fix.

@ejirocodes
Copy link

@dpeese this solution worked for me

So I took this temporary approach to the problem.

this.messaging
      .getToken({
          vapidKey : this.vapidKey,
      }).catch((err) => {
          // https://github.com/firebase/firebase-js-sdk/issues/7693
          // 如果 getToken 时是还没注册 firebase-messaging-sw.js,就会报错如下,再次执行 getToken 就好了
          const error = "AbortError: Failed to execute 'subscribe' on 'PushManager': Subscription failed - no active Service Worker";
          if (err.toString() === error) {
              return this.messaging.getToken({
                  vapidKey : this.vapidKey,
              });
          } else {
              throw err;
          }
      })
      .then((currentToken) => {
          if (currentToken) {
              console.log(currentToken);
              return currentToken;
          } else {
              console.log(
                  'No Instance ID token available. Request permission to generate one.'
              );
              return null;
          }
      })
      .catch((err) => {
          console.error(err);
      });

I look forward to letting me know when this issue is completely fixed

@pcriadoperez
Copy link

Running into the same issue. So is the recommended solution to just retry if it fails?

@benixal
Copy link

benixal commented Dec 11, 2023

register Firebase service worker before getToken:
navigator.serviceWorker.register("/firebase-messaging-sw.js", { scope: "/firebase-cloud-messaging-push-scope" })

@KhanhKitin
Copy link

KhanhKitin commented Dec 20, 2023

Hi bro. I am also facing a similar issue as you. And I fix it as follows.

const getOrRegisterServiceWorker = () => {
  if ('serviceWorker' in navigator) {
   return window.navigator.serviceWorker
     .getRegistration('/firebase-cloud-messaging-push-scope')
     .then((serviceWorker: TODO) => {
       if (serviceWorker){
         return serviceWorker;
       } 
       return window.navigator.serviceWorker.register('/firebase-messaging-sw.js')
       .then((serviceWorker: TODO)=>{
             console.log("success registering SW");
       }).catch((err)=>{
         console.log("registering failed",err);
       });
     }
 )}
 throw new Error('The browser doesn`t support service worker.');
};


export const requestToken = async () => {
  let currentToken = "";
  try {
    const serviceWorkerRegistration = await getOrRegisterServiceWorker();
    currentToken = await getToken(messaging, {
      vapidKey: key,
      serviceWorkerRegistration
    });
  } catch (error) {
    console.log("An error occurred while retrieving token. ", error);
  }
  return currentToken;
};

@kedniko
Copy link

kedniko commented Dec 23, 2023

Solved by calling getToken() twice.
The docs are confusing and outdated.

What should be the name of the service worker?
firebase-messaging-sw.js (as shown here) or service-worker.js (as shown here)

What version should I use for the Web namespaced API?
8.10.1 or the latest one?

@benixal
Copy link

benixal commented Jan 11, 2024

  1. Create a service worker
  2. Register it
  3. Then use serviceWorkerRegistration option to pass it to the getToken

https://github.com/benixal/FCM-Web-Notify-Example/blob/main/index.html#L35

https://www.youtube.com/watch?v=iz5arafmatc&t=6m12s

https://firebase.google.com/docs/reference/js/messaging_.gettokenoptions.md#gettokenoptionsserviceworkerregistration

@dyabol
Copy link

dyabol commented Apr 25, 2024

I resolve it by waiting for activated state of ServiceWorker.

const getFBToken = (
    m: Messaging,
    serviceWorkerRegistration: ServiceWorkerRegistration
): Promise<string> | undefined => {
    let sw: ServiceWorker | undefined;
    if (serviceWorkerRegistration.installing) {
        sw = serviceWorkerRegistration.installing;
    } else if (serviceWorkerRegistration.waiting) {
        sw = serviceWorkerRegistration.waiting;
    } else if (serviceWorkerRegistration.active) {
        sw = serviceWorkerRegistration.active;
    }
    if (sw) {
        if (sw.state === 'activated') {
            return getToken(m, {
                vapidKey: import.meta.env.VITE_PUSH_PUBLIC_KEY,
                serviceWorkerRegistration
            });
        }
        const deferred = new Deferred<string>();
        sw.addEventListener('statechange', async (e) => {
            if ((e.target as ServiceWorker)?.state === 'activated') {
                const token = await getToken(m, {
                    vapidKey: import.meta.env.VITE_PUSH_PUBLIC_KEY,
                    serviceWorkerRegistration
                });
                deferred.resolve(token);
            }
        });
        return deferred.promise;
    }
    return undefined;
};

@Danhhan
Copy link

Danhhan commented May 10, 2024

I resolved it like this

export async function generateToken({ subscribeTopic }: { subscribeTopic: (token: string) => void }) {
try {
const messaging = await getMessaging()
// requesting permission using Notification API
const permission = await Notification.requestPermission()
if (!messaging) {
return
}
if (permission !== 'granted') {
return
}
if (!navigator.serviceWorker) {
return
}
const serviceWorkerRegistration = await navigator.serviceWorker.ready
if (serviceWorkerRegistration.active?.state === 'activated') {
const token = await getToken(messaging, {
vapidKey: VITE_FIREBASE_VAPID_KEY,
serviceWorkerRegistration
})
/* eslint-disable no-console */
console.log('[generateToken] token: ', token)
subscribeTopic(token)
}
} catch (error) {
console.log('[generateToken]:: error occur generateToken', error)
}
}

@Arkovski
Copy link

I tried registration, marking as ready, doing 3 ways, but nothing helped. Just try to get FCM Token multiple times and everything would be fine 💯

navigator.serviceWorker
              .register("firebase-messaging-sw.js")
              .then(() => navigator.serviceWorker.ready)
              
navigator.serviceWorker
              .register("firebase-messaging-sw.js", { scope: '/' })
              .then(() => navigator.serviceWorker.ready)
              
navigator.serviceWorker
              .register("firebase-messaging-sw.js")
              .then((registration) => registration.update())
              .then(() => navigator.serviceWorker.ready)
  registerDesktopDeviceForFirebaseMessaging() {
    const messaging = getMessaging(this.app);

    const webPushCertificateKeyPair = "ABC";

    return this.tryGetFcmToken(messaging, webPushCertificateKeyPair, 3).catch((error) => {
      console.error("Failed to get FCM token after retries:", error);
      return Promise.reject();
    });
  }
  
  private tryGetFcmToken(messaging: Messaging, vapidKey: string, retries: number) {
    return new Promise((resolve, reject) => {
      const attempt = () => {
        getToken(messaging, { vapidKey })
          .then((fcmToken) => {
            return firstValueFrom(this.registerDesktopDeviceCommand(fcmToken))
              .then(resolve)
              .catch((error) => {
                console.error("FcmTokenRegistrationFailed");
                reject(error);
              });
          })
          .catch((error) => {
            if (retries > 0) {
              retries--;
              attempt();
            } else {
              reject(error);
            }
          });
      };
      attempt();
    });
  }

@sayhicoelho
Copy link

This worked for me:

const registration = await navigator.serviceWorker.register("sw.js")

// Wait for serviceWorker.ready
await navigator.serviceWorker.ready

const currentToken = await getToken(messaging, {
  serviceWorkerRegistration: registration,
  vapidKey: '...'
})

@Poucaslontras
Copy link

Poucaslontras commented Sep 5, 2024

// Wait for serviceWorker.ready
await navigator.serviceWorker.ready

This line is problematic if you have another service worker installed (as it should be the case for PWA apps), because it will not wait for the service you just registered, it will wait for the 1st one serving that page.
I spent a few hours the other night trying to make sense of why my service worker was not responding and that was the culprit.

To fix you're going to have to wait on that SPECIFIC service worker. I know that there was a proposal to add that method to the spec, but I couldn't find it. w3c/ServiceWorker#1278 (comment)
So, this auxiliar function does the work:

function registerReady(swScript, options) {
        return navigator.serviceWorker.register(swScript, options).then(reg => {
                // If there is an active worker and nothing incoming, we are done.
                var incomingSw = reg.installing || reg.waiting;
                if (reg.active && !incomingSw) {
                    return Promise.resolve();
                }
                // If not, wait for the newest service worker to become activated.
                return new Promise(fulfill => {
                    incomingSw.onstatechange = evt => {
                        if (evt.target.state === 'activated') {
                            incomingSw.onstatechange = null;
                            return fulfill();
                        }
                    };
                });
            })
    }

And then use it:

registerReady("/firebase-messaging-sw.js", { scope: "/firebase-cloud-messaging-push-scope" }).then(function (registration) {
        //get token
        messaging.getToken({ vapidKey: '@Constants.FirebaseVapId', serviceWorkerRegistration: registration }).then(function (currentToken) {
            if (currentToken) {
                console.log('Generated new Firebase token: ' + currentToken);
            }
        })
        .catch(function (err) {
            console.log('Failed subscribing push notifications.', err);
        });
    });

@faical23
Copy link

    currentToken = await getToken(messaging, {
      vapidKey: key,
      serviceWorkerRegistration
    });

its work for me like this i add service worker registration as params to getToken function

@schellmax
Copy link

@Poucaslontras as is see it your solution in #7693 (comment) does not work as-is, because the Promise returned by registerReady() doesn't always resolve with a registration object, maybe you want to fix this

@Poucaslontras
Copy link

@Poucaslontras as is see it your solution in #7693 (comment) does not work as-is, because the Promise returned by registerReady() doesn't always resolve with a registration object, maybe you want to fix this

Not sure what do you mean, as this is solution works for us as is, and is currently in PROD.
In any case, the function registerReady() was taken out of here: https://stackoverflow.com/questions/35704511/serviceworkercontainer-ready-for-a-specific-scripturl
If you find an issue with it, please feel free to improve on it and share the results. That's what I did.

@schellmax
Copy link

schellmax commented Oct 5, 2024

@Poucaslontras the registerReady() function itself is fine, the problem is the returned Promise, which always resolves with undefined (instead of a ServiceWorkerRegistration object).

so when using the function like this (as in your example):
registerReady(...).then(function (registration) { ...
registration will always be undefined.

to make the returned Promise always resolve with a ServiceWorkerRegistration object, it should look something like this:

function registerReady(swScript, options) {
  let registration;
  return navigator.serviceWorker.register(swScript, options).then(reg => {
    registration = reg;
     // ... (existing implementation)
   }).then(() => registration);
}

@danielnitu
Copy link

I agree with @schellmax - the promise should return the registration - otherwise the logic is correct and fixes the issue so thank you @Poucaslontras!

Typescript example:

async function registerReady(scriptURL: string, options?: RegistrationOptions) {
  return navigator.serviceWorker
    .register(scriptURL, options)
    .then((registration) => {
      // If there is an active worker and nothing incoming, we are done.
      const incomingSw = registration.installing || registration.waiting;
      if (registration.active && !incomingSw) return Promise.resolve(registration);

      // If not, wait for the newest service worker to become activated.
      return new Promise<ServiceWorkerRegistration>((fulfill, reject) => {
        if (incomingSw) {
          incomingSw.onstatechange = (evt) => {
            if ((evt.target as ServiceWorker)?.state === 'activated') {
              incomingSw.onstatechange = null;
              return fulfill(registration);
            }
          };
        } else {
          reject(new Error('No incoming service worker found.'));
        }
      });
    })
    .catch((err) => {
      console.error('Error registering service worker:', err);
      return Promise.reject(err);
    });
}

And it can be called with async/await if you wish:

const registration = await registerReady('url', {});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests