From 1294e64c813b6dbfb64f8dc24d90ec69a66f71ae Mon Sep 17 00:00:00 2001 From: Christina Holland Date: Wed, 4 Dec 2024 09:26:18 -0800 Subject: [PATCH] Wait for service worker registration to become active before any operations (#8661) --- .changeset/neat-beans-rescue.md | 5 ++ .../src/helpers/registerDefaultSw.ts | 48 ++++++++++++++++++- packages/messaging/src/util/constants.ts | 1 + 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 .changeset/neat-beans-rescue.md 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.