diff --git a/.changeset/neat-beans-rescue.md b/.changeset/neat-beans-rescue.md new file mode 100644 index 00000000000..278e5511da0 --- /dev/null +++ b/.changeset/neat-beans-rescue.md @@ -0,0 +1,5 @@ +--- +'@firebase/messaging': patch +--- + +Fix an issue where PushManager.subscribe() is called too soon after registering the default service worker. diff --git a/packages/messaging/src/helpers/registerDefaultSw.ts b/packages/messaging/src/helpers/registerDefaultSw.ts index 239e6ed8244..28890c93bd2 100644 --- a/packages/messaging/src/helpers/registerDefaultSw.ts +++ b/packages/messaging/src/helpers/registerDefaultSw.ts @@ -15,7 +15,11 @@ * limitations under the License. */ -import { DEFAULT_SW_PATH, DEFAULT_SW_SCOPE } from '../util/constants'; +import { + DEFAULT_REGISTRATION_TIMEOUT, + DEFAULT_SW_PATH, + DEFAULT_SW_SCOPE +} from '../util/constants'; import { ERROR_FACTORY, ErrorCode } from '../util/errors'; import { MessagingService } from '../messaging-service'; @@ -39,9 +43,51 @@ export async function registerDefaultSw( messaging.swRegistration.update().catch(() => { /* it is non blocking and we don't care if it failed */ }); + await waitForRegistrationActive(messaging.swRegistration); } catch (e) { throw ERROR_FACTORY.create(ErrorCode.FAILED_DEFAULT_REGISTRATION, { browserErrorMessage: (e as Error)?.message }); } } + +/** + * Waits for registration to become active. MDN documentation claims that + * a service worker registration should be ready to use after awaiting + * navigator.serviceWorker.register() but that doesn't seem to be the case in + * practice, causing the SDK to throw errors when calling + * swRegistration.pushManager.subscribe() too soon after register(). The only + * solution seems to be waiting for the service worker registration `state` + * to become "active". + */ +async function waitForRegistrationActive( + registration: ServiceWorkerRegistration +): Promise { + return new Promise((resolve, reject) => { + const rejectTimeout = setTimeout( + () => + reject( + new Error( + `Service worker not registered after ${DEFAULT_REGISTRATION_TIMEOUT} ms` + ) + ), + DEFAULT_REGISTRATION_TIMEOUT + ); + const incomingSw = registration.installing || registration.waiting; + if (registration.active) { + clearTimeout(rejectTimeout); + resolve(); + } else if (incomingSw) { + incomingSw.onstatechange = ev => { + if ((ev.target as ServiceWorker)?.state === 'activated') { + incomingSw.onstatechange = null; + clearTimeout(rejectTimeout); + resolve(); + } + }; + } else { + clearTimeout(rejectTimeout); + reject(new Error('No incoming service worker found.')); + } + }); +} diff --git a/packages/messaging/src/util/constants.ts b/packages/messaging/src/util/constants.ts index 8491380a5a0..e06c8cfaa34 100644 --- a/packages/messaging/src/util/constants.ts +++ b/packages/messaging/src/util/constants.ts @@ -36,6 +36,7 @@ export const MAX_NUMBER_OF_EVENTS_PER_LOG_REQUEST = 1000; export const MAX_RETRIES = 3; export const LOG_INTERVAL_IN_MS = 86400000; //24 hour export const DEFAULT_BACKOFF_TIME_MS = 5000; +export const DEFAULT_REGISTRATION_TIMEOUT = 10000; // FCM log source name registered at Firelog: 'FCM_CLIENT_EVENT_LOGGING'. It uniquely identifies // FCM's logging configuration.