@firebase/messaging 0.12.26 → 0.13.0-20260526192810

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/esm/index.esm.js +919 -206
  2. package/dist/esm/index.esm.js.map +1 -1
  3. package/dist/esm/index.sw.esm.js +798 -85
  4. package/dist/esm/index.sw.esm.js.map +1 -1
  5. package/dist/esm/src/api/onRegistered.d.ts +27 -0
  6. package/dist/esm/src/api/onUnregistered.d.ts +27 -0
  7. package/dist/esm/src/api/register.d.ts +31 -0
  8. package/dist/esm/src/api/unregister.d.ts +25 -0
  9. package/dist/esm/src/api.d.ts +59 -2
  10. package/dist/esm/src/helpers/fid-change-registration.d.ts +30 -0
  11. package/dist/esm/src/helpers/logToFirelog.d.ts +2 -0
  12. package/dist/esm/src/helpers/migrate-old-database.d.ts +1 -1
  13. package/dist/esm/src/index.d.ts +1 -1
  14. package/dist/esm/src/index.sw.d.ts +1 -1
  15. package/dist/esm/src/interfaces/internal-message-payload.d.ts +7 -1
  16. package/dist/esm/src/interfaces/public-types.d.ts +11 -0
  17. package/dist/esm/src/internals/idb-manager.d.ts +17 -4
  18. package/dist/esm/src/internals/register-fid.d.ts +27 -0
  19. package/dist/esm/src/internals/requests.d.ts +35 -1
  20. package/dist/esm/src/internals/token-manager.d.ts +6 -3
  21. package/dist/esm/src/messaging-service.d.ts +27 -2
  22. package/dist/esm/src/testing/fakes/token-details.d.ts +1 -1
  23. package/dist/esm/src/util/errors.d.ts +11 -1
  24. package/dist/index-public.d.ts +68 -0
  25. package/dist/index.cjs.js +922 -205
  26. package/dist/index.cjs.js.map +1 -1
  27. package/dist/index.sw.cjs +799 -84
  28. package/dist/index.sw.cjs.map +1 -1
  29. package/dist/internal.d.ts +73 -0
  30. package/dist/private.d.ts +73 -0
  31. package/dist/src/api/onRegistered.d.ts +27 -0
  32. package/dist/src/api/onUnregistered.d.ts +27 -0
  33. package/dist/src/api/register.d.ts +31 -0
  34. package/dist/src/api/unregister.d.ts +25 -0
  35. package/dist/src/api.d.ts +59 -2
  36. package/dist/src/helpers/fid-change-registration.d.ts +30 -0
  37. package/dist/src/helpers/logToFirelog.d.ts +2 -0
  38. package/dist/src/helpers/migrate-old-database.d.ts +1 -1
  39. package/dist/src/index.d.ts +1 -1
  40. package/dist/src/index.sw.d.ts +1 -1
  41. package/dist/src/interfaces/internal-message-payload.d.ts +7 -1
  42. package/dist/src/interfaces/public-types.d.ts +11 -0
  43. package/dist/src/internals/idb-manager.d.ts +17 -4
  44. package/dist/src/internals/register-fid.d.ts +27 -0
  45. package/dist/src/internals/requests.d.ts +35 -1
  46. package/dist/src/internals/token-manager.d.ts +6 -3
  47. package/dist/src/messaging-service.d.ts +27 -2
  48. package/dist/src/testing/fakes/token-details.d.ts +1 -1
  49. package/dist/src/util/errors.d.ts +11 -1
  50. package/dist/sw/index-public.d.ts +35 -1
  51. package/dist/sw/internal.d.ts +38 -1
  52. package/dist/sw/private.d.ts +38 -1
  53. package/package.json +2 -2
  54. /package/dist/esm/src/interfaces/{token-details.d.ts → registration-details.d.ts} +0 -0
  55. /package/dist/src/interfaces/{token-details.d.ts → registration-details.d.ts} +0 -0
package/dist/index.sw.cjs CHANGED
@@ -29,6 +29,13 @@ const ENDPOINT = 'https://fcmregistrations.googleapis.com/v1';
29
29
  /** Key of FCM Payload in Notification's data field. */
30
30
  const FCM_MSG = 'FCM_MSG';
31
31
  const CONSOLE_CAMPAIGN_ID = 'google.c.a.c_id';
32
+ const MAX_NUMBER_OF_EVENTS_PER_LOG_REQUEST = 1000;
33
+ const MAX_RETRIES = 3;
34
+ const LOG_INTERVAL_IN_MS = 86400000; //24 hour
35
+ const DEFAULT_BACKOFF_TIME_MS = 5000;
36
+ // FCM log source name registered at Firelog: 'FCM_CLIENT_EVENT_LOGGING'. It uniquely identifies
37
+ // FCM's logging configuration.
38
+ const FCM_LOG_SOURCE = 1249;
32
39
  // Defined as in proto/messaging_event.proto. Neglecting fields that are supported.
33
40
  const SDK_PLATFORM_WEB = 3;
34
41
  const EVENT_MESSAGE_DELIVERED = 1;
@@ -56,6 +63,7 @@ var MessageType;
56
63
  (function (MessageType) {
57
64
  MessageType["PUSH_RECEIVED"] = "push-received";
58
65
  MessageType["NOTIFICATION_CLICKED"] = "notification-clicked";
66
+ MessageType["FID_REGISTERED"] = "fid-registered";
59
67
  })(MessageType || (MessageType = {}));
60
68
 
61
69
  /**
@@ -221,6 +229,53 @@ function checkTokenDetails(tokenDetails) {
221
229
  subscriptionOptions.vapidKey.length > 0);
222
230
  }
223
231
 
232
+ /**
233
+ * @license
234
+ * Copyright 2017 Google LLC
235
+ *
236
+ * Licensed under the Apache License, Version 2.0 (the "License");
237
+ * you may not use this file except in compliance with the License.
238
+ * You may obtain a copy of the License at
239
+ *
240
+ * http://www.apache.org/licenses/LICENSE-2.0
241
+ *
242
+ * Unless required by applicable law or agreed to in writing, software
243
+ * distributed under the License is distributed on an "AS IS" BASIS,
244
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
245
+ * See the License for the specific language governing permissions and
246
+ * limitations under the License.
247
+ */
248
+ const ERROR_MAP = {
249
+ ["missing-app-config-values" /* ErrorCode.MISSING_APP_CONFIG_VALUES */]: 'Missing App configuration value: "{$valueName}"',
250
+ ["only-available-in-window" /* ErrorCode.AVAILABLE_IN_WINDOW */]: 'This method is available in a Window context.',
251
+ ["only-available-in-sw" /* ErrorCode.AVAILABLE_IN_SW */]: 'This method is available in a service worker context.',
252
+ ["permission-default" /* ErrorCode.PERMISSION_DEFAULT */]: 'The notification permission was not granted and dismissed instead.',
253
+ ["permission-blocked" /* ErrorCode.PERMISSION_BLOCKED */]: 'The notification permission was not granted and blocked instead.',
254
+ ["unsupported-browser" /* ErrorCode.UNSUPPORTED_BROWSER */]: "This browser doesn't support the API's required to use the Firebase SDK.",
255
+ ["indexed-db-unsupported" /* ErrorCode.INDEXED_DB_UNSUPPORTED */]: "This browser doesn't support indexedDb.open() (ex. Safari iFrame, Firefox Private Browsing, etc)",
256
+ ["failed-service-worker-registration" /* ErrorCode.FAILED_DEFAULT_REGISTRATION */]: 'We are unable to register the default service worker. {$browserErrorMessage}',
257
+ ["token-subscribe-failed" /* ErrorCode.TOKEN_SUBSCRIBE_FAILED */]: 'A problem occurred while subscribing the user to FCM: {$errorInfo}',
258
+ ["token-subscribe-no-token" /* ErrorCode.TOKEN_SUBSCRIBE_NO_TOKEN */]: 'FCM returned no token when subscribing the user to push.',
259
+ ["fid-registration-failed" /* ErrorCode.FID_REGISTRATION_FAILED */]: 'A problem occurred while creating an FCM registration via FID: {$errorInfo}',
260
+ ["fid-unregister-failed" /* ErrorCode.FID_UNREGISTER_FAILED */]: 'A problem occurred while unregistering the FCM registration via FID: {$errorInfo}',
261
+ ["fid-registration-idb-schema-unavailable" /* ErrorCode.FID_REGISTRATION_IDB_SCHEMA_UNAVAILABLE */]: 'Unable to read or persist FID registration metadata because the messaging ' +
262
+ 'IndexedDB schema is unavailable (for example, the database could not be ' +
263
+ 'upgraded to the latest version).',
264
+ ["token-unsubscribe-failed" /* ErrorCode.TOKEN_UNSUBSCRIBE_FAILED */]: 'A problem occurred while unsubscribing the ' +
265
+ 'user from FCM: {$errorInfo}',
266
+ ["token-update-failed" /* ErrorCode.TOKEN_UPDATE_FAILED */]: 'A problem occurred while updating the user from FCM: {$errorInfo}',
267
+ ["token-update-no-token" /* ErrorCode.TOKEN_UPDATE_NO_TOKEN */]: 'FCM returned no token when updating the user to push.',
268
+ ["use-sw-after-get-token" /* ErrorCode.USE_SW_AFTER_GET_TOKEN */]: 'The useServiceWorker() method may only be called once and must be ' +
269
+ 'called before calling getToken() to ensure your service worker is used.',
270
+ ["invalid-sw-registration" /* ErrorCode.INVALID_SW_REGISTRATION */]: 'The input to useServiceWorker() must be a ServiceWorkerRegistration.',
271
+ ["invalid-bg-handler" /* ErrorCode.INVALID_BG_HANDLER */]: 'The input to setBackgroundMessageHandler() must be a function.',
272
+ ["invalid-vapid-key" /* ErrorCode.INVALID_VAPID_KEY */]: 'The public VAPID key must be a string.',
273
+ ["use-vapid-key-after-get-token" /* ErrorCode.USE_VAPID_KEY_AFTER_GET_TOKEN */]: 'The usePublicVapidKey() method may only be called once and must be ' +
274
+ 'called before calling getToken() to ensure your VAPID key is used.',
275
+ ["invalid-on-registered-handler" /* ErrorCode.INVALID_ON_REGISTERED_HANDLER */]: 'No onRegistered callback handler was provided or registered. Implement onRegistered() before register().'
276
+ };
277
+ const ERROR_FACTORY = new util.ErrorFactory('messaging', 'Messaging', ERROR_MAP);
278
+
224
279
  /**
225
280
  * @license
226
281
  * Copyright 2019 Google LLC
@@ -237,41 +292,74 @@ function checkTokenDetails(tokenDetails) {
237
292
  * See the License for the specific language governing permissions and
238
293
  * limitations under the License.
239
294
  */
240
- // Exported for tests.
241
295
  const DATABASE_NAME = 'firebase-messaging-database';
242
- const DATABASE_VERSION = 1;
243
- const OBJECT_STORE_NAME = 'firebase-messaging-store';
296
+ const DATABASE_VERSION = 2;
297
+ const TOKEN_OBJECT_STORE_NAME = 'firebase-messaging-store';
298
+ const FID_REGISTRATION_OBJECT_STORE_NAME = 'firebase-messaging-fid-registration-store';
299
+ const defaultIdb = { openDB: idb.openDB, deleteDB: idb.deleteDB };
300
+ let idbImpl = defaultIdb;
301
+ // Open v2, but fall back to v1 if upgrade/open fails. Cache as `unknown` and guard store access.
244
302
  let dbPromise = null;
303
+ function migrateMessagingDb(upgradeDb, oldVersion, targetSchemaVersion) {
304
+ // Intentional fall-through for v2: run all intermediate migrations.
305
+ // eslint-disable-next-line default-case
306
+ switch (oldVersion) {
307
+ case 0:
308
+ upgradeDb.createObjectStore(TOKEN_OBJECT_STORE_NAME);
309
+ if (targetSchemaVersion === 1) {
310
+ break;
311
+ }
312
+ // fall through
313
+ case 1:
314
+ if (targetSchemaVersion === 2) {
315
+ upgradeDb.createObjectStore(FID_REGISTRATION_OBJECT_STORE_NAME);
316
+ }
317
+ }
318
+ }
319
+ function createOpenDbOptions(targetSchemaVersion) {
320
+ return {
321
+ upgrade: (upgradeDb, oldVersion) => {
322
+ migrateMessagingDb(upgradeDb, oldVersion, targetSchemaVersion);
323
+ },
324
+ blocked: () => {
325
+ /* no-op */
326
+ },
327
+ blocking: (_currentVersion, _blockedVersion, event) => {
328
+ dbPromise = null;
329
+ event.target?.close();
330
+ },
331
+ terminated: () => {
332
+ dbPromise = null;
333
+ }
334
+ };
335
+ }
245
336
  function getDbPromise() {
246
337
  if (!dbPromise) {
247
- dbPromise = idb.openDB(DATABASE_NAME, DATABASE_VERSION, {
248
- upgrade: (upgradeDb, oldVersion) => {
249
- // We don't use 'break' in this switch statement, the fall-through behavior is what we want,
250
- // because if there are multiple versions between the old version and the current version, we
251
- // want ALL the migrations that correspond to those versions to run, not only the last one.
252
- // eslint-disable-next-line default-case
253
- switch (oldVersion) {
254
- case 0:
255
- upgradeDb.createObjectStore(OBJECT_STORE_NAME);
256
- }
257
- }
258
- });
338
+ const openLatest = idbImpl.openDB(DATABASE_NAME, DATABASE_VERSION, createOpenDbOptions(2));
339
+ // Assign synchronously to avoid concurrent openDB() calls.
340
+ dbPromise = openLatest.catch(() => idbImpl.openDB(DATABASE_NAME, DATABASE_VERSION - 1, createOpenDbOptions(1)));
259
341
  }
260
342
  return dbPromise;
261
343
  }
262
- /** Gets record(s) from the objectStore that match the given key. */
344
+ function hasObjectStore(db, storeName) {
345
+ return db.objectStoreNames.contains(storeName);
346
+ }
347
+ function assertFidRegistrationObjectStore(db) {
348
+ if (!hasObjectStore(db, FID_REGISTRATION_OBJECT_STORE_NAME)) {
349
+ throw ERROR_FACTORY.create("fid-registration-idb-schema-unavailable" /* ErrorCode.FID_REGISTRATION_IDB_SCHEMA_UNAVAILABLE */);
350
+ }
351
+ }
263
352
  async function dbGet(firebaseDependencies) {
264
353
  const key = getKey(firebaseDependencies);
265
354
  const db = await getDbPromise();
266
355
  const tokenDetails = (await db
267
- .transaction(OBJECT_STORE_NAME)
268
- .objectStore(OBJECT_STORE_NAME)
356
+ .transaction(TOKEN_OBJECT_STORE_NAME)
357
+ .objectStore(TOKEN_OBJECT_STORE_NAME)
269
358
  .get(key));
270
359
  if (tokenDetails) {
271
360
  return tokenDetails;
272
361
  }
273
362
  else {
274
- // Check if there is a tokenDetails object in the old DB.
275
363
  const oldTokenDetails = await migrateOldDatabase(firebaseDependencies.appConfig.senderId);
276
364
  if (oldTokenDetails) {
277
365
  await dbSet(firebaseDependencies, oldTokenDetails);
@@ -279,67 +367,61 @@ async function dbGet(firebaseDependencies) {
279
367
  }
280
368
  }
281
369
  }
282
- /** Assigns or overwrites the record for the given key with the given value. */
283
370
  async function dbSet(firebaseDependencies, tokenDetails) {
284
371
  const key = getKey(firebaseDependencies);
285
372
  const db = await getDbPromise();
286
- const tx = db.transaction(OBJECT_STORE_NAME, 'readwrite');
287
- await tx.objectStore(OBJECT_STORE_NAME).put(tokenDetails, key);
373
+ const stores = [TOKEN_OBJECT_STORE_NAME];
374
+ const hasFidStore = hasObjectStore(db, FID_REGISTRATION_OBJECT_STORE_NAME);
375
+ if (hasFidStore) {
376
+ stores.push(FID_REGISTRATION_OBJECT_STORE_NAME);
377
+ }
378
+ const tx = db.transaction(stores, 'readwrite');
379
+ await tx.objectStore(TOKEN_OBJECT_STORE_NAME).put(tokenDetails, key);
380
+ if (hasFidStore) {
381
+ await tx.objectStore(FID_REGISTRATION_OBJECT_STORE_NAME).delete(key);
382
+ }
288
383
  await tx.done;
289
384
  return tokenDetails;
290
385
  }
291
- /** Removes record(s) from the objectStore that match the given key. */
292
386
  async function dbRemove(firebaseDependencies) {
293
387
  const key = getKey(firebaseDependencies);
294
388
  const db = await getDbPromise();
295
- const tx = db.transaction(OBJECT_STORE_NAME, 'readwrite');
296
- await tx.objectStore(OBJECT_STORE_NAME).delete(key);
389
+ const tx = db.transaction(TOKEN_OBJECT_STORE_NAME, 'readwrite');
390
+ await tx.objectStore(TOKEN_OBJECT_STORE_NAME).delete(key);
391
+ await tx.done;
392
+ }
393
+ async function dbGetFidRegistration(firebaseDependencies) {
394
+ const key = getKey(firebaseDependencies);
395
+ const db = await getDbPromise();
396
+ assertFidRegistrationObjectStore(db);
397
+ return (await db
398
+ .transaction(FID_REGISTRATION_OBJECT_STORE_NAME)
399
+ .objectStore(FID_REGISTRATION_OBJECT_STORE_NAME)
400
+ .get(key));
401
+ }
402
+ async function dbSetFidRegistration(firebaseDependencies, details) {
403
+ const key = getKey(firebaseDependencies);
404
+ const db = await getDbPromise();
405
+ assertFidRegistrationObjectStore(db);
406
+ const tx = db.transaction([TOKEN_OBJECT_STORE_NAME, FID_REGISTRATION_OBJECT_STORE_NAME], 'readwrite');
407
+ await tx.objectStore(FID_REGISTRATION_OBJECT_STORE_NAME).put(details, key);
408
+ await tx.objectStore(TOKEN_OBJECT_STORE_NAME).delete(key);
409
+ await tx.done;
410
+ return details;
411
+ }
412
+ async function dbRemoveFidRegistration(firebaseDependencies) {
413
+ const key = getKey(firebaseDependencies);
414
+ const db = await getDbPromise();
415
+ assertFidRegistrationObjectStore(db);
416
+ const tx = db.transaction(FID_REGISTRATION_OBJECT_STORE_NAME, 'readwrite');
417
+ await tx.objectStore(FID_REGISTRATION_OBJECT_STORE_NAME).delete(key);
297
418
  await tx.done;
298
419
  }
299
420
  function getKey({ appConfig }) {
300
421
  return appConfig.appId;
301
422
  }
302
423
 
303
- /**
304
- * @license
305
- * Copyright 2017 Google LLC
306
- *
307
- * Licensed under the Apache License, Version 2.0 (the "License");
308
- * you may not use this file except in compliance with the License.
309
- * You may obtain a copy of the License at
310
- *
311
- * http://www.apache.org/licenses/LICENSE-2.0
312
- *
313
- * Unless required by applicable law or agreed to in writing, software
314
- * distributed under the License is distributed on an "AS IS" BASIS,
315
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
316
- * See the License for the specific language governing permissions and
317
- * limitations under the License.
318
- */
319
- const ERROR_MAP = {
320
- ["missing-app-config-values" /* ErrorCode.MISSING_APP_CONFIG_VALUES */]: 'Missing App configuration value: "{$valueName}"',
321
- ["only-available-in-window" /* ErrorCode.AVAILABLE_IN_WINDOW */]: 'This method is available in a Window context.',
322
- ["only-available-in-sw" /* ErrorCode.AVAILABLE_IN_SW */]: 'This method is available in a service worker context.',
323
- ["permission-default" /* ErrorCode.PERMISSION_DEFAULT */]: 'The notification permission was not granted and dismissed instead.',
324
- ["permission-blocked" /* ErrorCode.PERMISSION_BLOCKED */]: 'The notification permission was not granted and blocked instead.',
325
- ["unsupported-browser" /* ErrorCode.UNSUPPORTED_BROWSER */]: "This browser doesn't support the API's required to use the Firebase SDK.",
326
- ["indexed-db-unsupported" /* ErrorCode.INDEXED_DB_UNSUPPORTED */]: "This browser doesn't support indexedDb.open() (ex. Safari iFrame, Firefox Private Browsing, etc)",
327
- ["failed-service-worker-registration" /* ErrorCode.FAILED_DEFAULT_REGISTRATION */]: 'We are unable to register the default service worker. {$browserErrorMessage}',
328
- ["token-subscribe-failed" /* ErrorCode.TOKEN_SUBSCRIBE_FAILED */]: 'A problem occurred while subscribing the user to FCM: {$errorInfo}',
329
- ["token-subscribe-no-token" /* ErrorCode.TOKEN_SUBSCRIBE_NO_TOKEN */]: 'FCM returned no token when subscribing the user to push.',
330
- ["token-unsubscribe-failed" /* ErrorCode.TOKEN_UNSUBSCRIBE_FAILED */]: 'A problem occurred while unsubscribing the ' +
331
- 'user from FCM: {$errorInfo}',
332
- ["token-update-failed" /* ErrorCode.TOKEN_UPDATE_FAILED */]: 'A problem occurred while updating the user from FCM: {$errorInfo}',
333
- ["token-update-no-token" /* ErrorCode.TOKEN_UPDATE_NO_TOKEN */]: 'FCM returned no token when updating the user to push.',
334
- ["use-sw-after-get-token" /* ErrorCode.USE_SW_AFTER_GET_TOKEN */]: 'The useServiceWorker() method may only be called once and must be ' +
335
- 'called before calling getToken() to ensure your service worker is used.',
336
- ["invalid-sw-registration" /* ErrorCode.INVALID_SW_REGISTRATION */]: 'The input to useServiceWorker() must be a ServiceWorkerRegistration.',
337
- ["invalid-bg-handler" /* ErrorCode.INVALID_BG_HANDLER */]: 'The input to setBackgroundMessageHandler() must be a function.',
338
- ["invalid-vapid-key" /* ErrorCode.INVALID_VAPID_KEY */]: 'The public VAPID key must be a string.',
339
- ["use-vapid-key-after-get-token" /* ErrorCode.USE_VAPID_KEY_AFTER_GET_TOKEN */]: 'The usePublicVapidKey() method may only be called once and must be ' +
340
- 'called before calling getToken() to ensure your VAPID key is used.'
341
- };
342
- const ERROR_FACTORY = new util.ErrorFactory('messaging', 'Messaging', ERROR_MAP);
424
+ const version = "0.13.0-20260526192810";
343
425
 
344
426
  /**
345
427
  * @license
@@ -357,9 +439,14 @@ const ERROR_FACTORY = new util.ErrorFactory('messaging', 'Messaging', ERROR_MAP)
357
439
  * See the License for the specific language governing permissions and
358
440
  * limitations under the License.
359
441
  */
442
+ /** Max attempts (initial fetch + retries) when CreateRegistration `fetch()` throws. */
443
+ const FID_REGISTRATION_FETCH_MAX_ATTEMPTS = 3;
444
+ /** Base delay in ms; backoff is `BASE * 2^attempt` after each failed attempt. */
445
+ const FID_REGISTRATION_FETCH_BASE_BACKOFF_MS = 1000;
360
446
  async function requestGetToken(firebaseDependencies, subscriptionOptions) {
361
447
  const headers = await getHeaders(firebaseDependencies);
362
- const body = getBody(subscriptionOptions);
448
+ const body = getBody(subscriptionOptions, firebaseDependencies.appConfig.appName,
449
+ /* includeSdkVersion= */ false);
363
450
  const subscribeOptions = {
364
451
  method: 'POST',
365
452
  headers,
@@ -386,9 +473,128 @@ async function requestGetToken(firebaseDependencies, subscriptionOptions) {
386
473
  }
387
474
  return responseData.token;
388
475
  }
476
+ async function requestCreateRegistration(firebaseDependencies, subscriptionOptions) {
477
+ const headers = await getHeaders(firebaseDependencies);
478
+ const body = getBody(subscriptionOptions, firebaseDependencies.appConfig.appName,
479
+ /* includeSdkVersion= */ true);
480
+ const subscribeOptions = {
481
+ method: 'POST',
482
+ headers,
483
+ body: JSON.stringify(body)
484
+ };
485
+ let response;
486
+ try {
487
+ response = await fetchWithExponentialRetry(() => fetch(getEndpoint(firebaseDependencies.appConfig), subscribeOptions), FID_REGISTRATION_FETCH_MAX_ATTEMPTS, FID_REGISTRATION_FETCH_BASE_BACKOFF_MS);
488
+ }
489
+ catch (err) {
490
+ throw ERROR_FACTORY.create("fid-registration-failed" /* ErrorCode.FID_REGISTRATION_FAILED */, {
491
+ errorInfo: err?.toString()
492
+ });
493
+ }
494
+ if (response.ok) {
495
+ const responseFid = await parseCreateRegistrationSuccessFid(response);
496
+ return { responseFid };
497
+ }
498
+ // `fetch()` succeeded, but the backend returned a non-2xx response.
499
+ // Best-effort parse the body to extract `error.message`, but always fail with
500
+ // `FID_REGISTRATION_FAILED` to keep the error surface uniform.
501
+ // Best-effort extraction of error details; the main signal is response.ok / status.
502
+ let responseData;
503
+ try {
504
+ responseData = (await response.json());
505
+ }
506
+ catch (err) {
507
+ throw ERROR_FACTORY.create("fid-registration-failed" /* ErrorCode.FID_REGISTRATION_FAILED */, {
508
+ errorInfo: response.statusText
509
+ });
510
+ }
511
+ const message = responseData.error?.message ?? response.statusText;
512
+ throw ERROR_FACTORY.create("fid-registration-failed" /* ErrorCode.FID_REGISTRATION_FAILED */, {
513
+ errorInfo: message
514
+ });
515
+ }
516
+ /**
517
+ * Deletes an FCM Web registration via DeleteRegistration using the Firebase Installation ID (FID).
518
+ */
519
+ async function requestDeleteRegistration(firebaseDependencies, fid) {
520
+ const headers = await getHeaders(firebaseDependencies);
521
+ const options = {
522
+ method: 'DELETE',
523
+ headers
524
+ };
525
+ let response;
526
+ try {
527
+ response = await fetch(`${getEndpoint(firebaseDependencies.appConfig)}/${fid}`, options);
528
+ }
529
+ catch (err) {
530
+ throw ERROR_FACTORY.create("fid-unregister-failed" /* ErrorCode.FID_UNREGISTER_FAILED */, {
531
+ errorInfo: err?.toString()
532
+ });
533
+ }
534
+ if (response.ok) {
535
+ return;
536
+ }
537
+ // Best-effort parse error details; surface uniform error code.
538
+ try {
539
+ const responseData = (await response.json());
540
+ const message = responseData.error?.message ?? response.statusText;
541
+ throw message;
542
+ }
543
+ catch (err) {
544
+ // If parsing failed, fall back to status text.
545
+ throw ERROR_FACTORY.create("fid-unregister-failed" /* ErrorCode.FID_UNREGISTER_FAILED */, {
546
+ errorInfo: (typeof err === 'string' && err) ||
547
+ response.statusText ||
548
+ err?.toString()
549
+ });
550
+ }
551
+ }
552
+ /**
553
+ * Parses a successful CreateRegistration body. The backend must return JSON with a non-empty
554
+ * string `name`: a resource name `projects/{projectId}/registrations/{fid}`
555
+ */
556
+ async function parseCreateRegistrationSuccessFid(response) {
557
+ const text = await response.text();
558
+ if (!text.trim()) {
559
+ throw ERROR_FACTORY.create("fid-registration-failed" /* ErrorCode.FID_REGISTRATION_FAILED */, {
560
+ errorInfo: 'CreateRegistration succeeded but response body is empty'
561
+ });
562
+ }
563
+ let data;
564
+ try {
565
+ data = JSON.parse(text);
566
+ }
567
+ catch {
568
+ throw ERROR_FACTORY.create("fid-registration-failed" /* ErrorCode.FID_REGISTRATION_FAILED */, {
569
+ errorInfo: 'CreateRegistration succeeded but response body is not valid JSON'
570
+ });
571
+ }
572
+ const name = data.name;
573
+ if (typeof name !== 'string' || name.length === 0) {
574
+ throw ERROR_FACTORY.create("fid-registration-failed" /* ErrorCode.FID_REGISTRATION_FAILED */, {
575
+ errorInfo: 'CreateRegistration succeeded but response did not include a non-empty name'
576
+ });
577
+ }
578
+ return parseFidFromRegistrationResourceName(name);
579
+ }
580
+ const REGISTRATIONS_NAME_SEGMENT = '/registrations/';
581
+ /** Extracts the Firebase Installation ID from CreateRegistration `name` (resource path). */
582
+ function parseFidFromRegistrationResourceName(name) {
583
+ const segmentIndex = name.indexOf(REGISTRATIONS_NAME_SEGMENT);
584
+ if (segmentIndex !== -1) {
585
+ const fid = name.slice(segmentIndex + REGISTRATIONS_NAME_SEGMENT.length);
586
+ if (fid.length > 0) {
587
+ return fid;
588
+ }
589
+ }
590
+ throw ERROR_FACTORY.create("fid-registration-failed" /* ErrorCode.FID_REGISTRATION_FAILED */, {
591
+ errorInfo: 'CreateRegistration succeeded but response name is not a valid registration resource name'
592
+ });
593
+ }
389
594
  async function requestUpdateToken(firebaseDependencies, tokenDetails) {
390
595
  const headers = await getHeaders(firebaseDependencies);
391
- const body = getBody(tokenDetails.subscriptionOptions);
596
+ const body = getBody(tokenDetails.subscriptionOptions, firebaseDependencies.appConfig.appName,
597
+ /* includeSdkVersion= */ false);
392
598
  const updateOptions = {
393
599
  method: 'PATCH',
394
600
  headers,
@@ -437,6 +643,26 @@ async function requestDeleteToken(firebaseDependencies, token) {
437
643
  });
438
644
  }
439
645
  }
646
+ /**
647
+ * Re-runs `operation` when it throws, with exponential backoff between attempts.
648
+ * Rethrows the last error if all attempts fail.
649
+ */
650
+ async function fetchWithExponentialRetry(operation, maxAttempts, baseBackoffMs) {
651
+ let lastError;
652
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
653
+ try {
654
+ return await operation();
655
+ }
656
+ catch (err) {
657
+ lastError = err;
658
+ if (attempt < maxAttempts - 1) {
659
+ const delayMs = baseBackoffMs * Math.pow(2, attempt);
660
+ await new Promise(resolve => setTimeout(resolve, delayMs));
661
+ }
662
+ }
663
+ }
664
+ throw lastError;
665
+ }
440
666
  function getEndpoint({ projectId }) {
441
667
  return `${ENDPOINT}/projects/${projectId}/registrations`;
442
668
  }
@@ -449,14 +675,45 @@ async function getHeaders({ appConfig, installations }) {
449
675
  'x-goog-firebase-installations-auth': `FIS ${authToken}`
450
676
  });
451
677
  }
452
- function getBody({ p256dh, auth, endpoint, vapidKey }) {
678
+ /**
679
+ * Hostname for the registering web client (e.g. `www.example.com`), or the app name
680
+ * (`appNameFallback`) when the scope cannot be resolved (e.g. some test environments).
681
+ */
682
+ function getRegistrationOrigin(swScope, appNameFallback) {
683
+ try {
684
+ if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(swScope)) {
685
+ return new URL(swScope).host;
686
+ }
687
+ }
688
+ catch {
689
+ // Fall through to relative-scope handling.
690
+ }
691
+ try {
692
+ if (typeof self !== 'undefined' && self.location?.href) {
693
+ return new URL(swScope, self.location.origin).host;
694
+ }
695
+ }
696
+ catch {
697
+ // Fall through.
698
+ }
699
+ if (typeof self !== 'undefined' && self.location?.host) {
700
+ return self.location.host;
701
+ }
702
+ return appNameFallback;
703
+ }
704
+ function getBody({ p256dh, auth, endpoint, vapidKey, swScope }, appNameFallback, includeSdkVersion) {
453
705
  const body = {
454
706
  web: {
707
+ origin: getRegistrationOrigin(swScope, appNameFallback),
455
708
  endpoint,
456
709
  auth,
457
710
  p256dh
458
711
  }
459
712
  };
713
+ if (includeSdkVersion) {
714
+ // eslint-disable-next-line camelcase
715
+ body.fcm_sdk_version = version;
716
+ }
460
717
  if (vapidKey !== DEFAULT_VAPID_KEY) {
461
718
  body.web.applicationPubKey = vapidKey;
462
719
  }
@@ -482,7 +739,7 @@ function getBody({ p256dh, auth, endpoint, vapidKey }) {
482
739
  // UpdateRegistration will be called once every week.
483
740
  const TOKEN_EXPIRATION_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
484
741
  async function getTokenInternal(messaging) {
485
- const pushSubscription = await getPushSubscription(messaging.swRegistration, messaging.vapidKey);
742
+ const pushSubscription = await getPushSubscription$1(messaging.swRegistration, messaging.vapidKey);
486
743
  const subscriptionOptions = {
487
744
  vapidKey: messaging.vapidKey,
488
745
  swScope: messaging.swRegistration.scope,
@@ -520,14 +777,42 @@ async function getTokenInternal(messaging) {
520
777
  }
521
778
  }
522
779
  /**
523
- * This method deletes the token from the database, unsubscribes the token from FCM, and unregisters
524
- * the push subscription if it exists.
780
+ * Legacy getToken() path: there is a token row in IndexedDB. Revoke it with FCM, drop the row, and
781
+ * clear any leftover FID registration metadata (apps may mix APIs).
782
+ */
783
+ async function revokeLegacyFcmTokenAndClearCaches(messaging, tokenDetails) {
784
+ await requestDeleteToken(messaging.firebaseDependencies, tokenDetails.token);
785
+ await dbRemove(messaging.firebaseDependencies);
786
+ await removeFidRegistrationBestEffort(messaging.firebaseDependencies);
787
+ }
788
+ /**
789
+ * No legacy token row: the client may only have FID-based registration (register() flow). If so,
790
+ * delete that registration on the server, always scrub local FID metadata, then surface
791
+ * onUnregistered when we actually had an FID.
525
792
  */
526
- async function deleteTokenInternal(messaging) {
793
+ async function revokeFidRegistrationIfStored(messaging) {
794
+ const stored = await dbGetFidRegistration(messaging.firebaseDependencies).catch(() => undefined);
795
+ const fid = stored?.fid;
796
+ if (fid) {
797
+ await requestDeleteRegistration(messaging.firebaseDependencies, fid);
798
+ }
799
+ await removeFidRegistrationBestEffort(messaging.firebaseDependencies);
800
+ if (fid) {
801
+ notifyOnUnregistered(messaging, fid);
802
+ }
803
+ }
804
+ /**
805
+ * Revokes the app's FCM registration: legacy token (getToken/deleteToken) and/or FID-based
806
+ * registration (register/unregister), clears local caches, notifies onUnregistered when a stored
807
+ * FID existed, then unsubscribes the push subscription when present.
808
+ */
809
+ async function revokeRegistrationInternal(messaging) {
527
810
  const tokenDetails = await dbGet(messaging.firebaseDependencies);
528
811
  if (tokenDetails) {
529
- await requestDeleteToken(messaging.firebaseDependencies, tokenDetails.token);
530
- await dbRemove(messaging.firebaseDependencies);
812
+ await revokeLegacyFcmTokenAndClearCaches(messaging, tokenDetails);
813
+ }
814
+ else {
815
+ await revokeFidRegistrationIfStored(messaging);
531
816
  }
532
817
  // Unsubscribe from the push subscription.
533
818
  const pushSubscription = await messaging.swRegistration.pushManager.getSubscription();
@@ -565,7 +850,7 @@ async function getNewToken(firebaseDependencies, subscriptionOptions) {
565
850
  /**
566
851
  * Gets a PushSubscription for the current user.
567
852
  */
568
- async function getPushSubscription(swRegistration, vapidKey) {
853
+ async function getPushSubscription$1(swRegistration, vapidKey) {
569
854
  const subscription = await swRegistration.pushManager.getSubscription();
570
855
  if (subscription) {
571
856
  return subscription;
@@ -587,6 +872,169 @@ function isTokenValid(dbOptions, currentOptions) {
587
872
  const isP256dhEqual = currentOptions.p256dh === dbOptions.p256dh;
588
873
  return isVapidKeyEqual && isEndpointEqual && isAuthEqual && isP256dhEqual;
589
874
  }
875
+ /** Clears FID registration metadata; apps may mix legacy getToken() with FID register/unregister. */
876
+ async function removeFidRegistrationBestEffort(firebaseDependencies) {
877
+ try {
878
+ await dbRemoveFidRegistration(firebaseDependencies);
879
+ }
880
+ catch {
881
+ // Ignore.
882
+ }
883
+ }
884
+ function notifyOnRegistered(messaging, fid) {
885
+ const handler = messaging.onRegisteredHandler;
886
+ if (!handler) {
887
+ return;
888
+ }
889
+ if (typeof handler === 'function') {
890
+ handler(fid);
891
+ }
892
+ else {
893
+ handler.next(fid);
894
+ }
895
+ }
896
+ function notifyOnUnregistered(messaging, fid) {
897
+ const handler = messaging.onUnregisteredHandler;
898
+ if (!handler) {
899
+ return;
900
+ }
901
+ if (typeof handler === 'function') {
902
+ handler(fid);
903
+ }
904
+ else {
905
+ handler.next(fid);
906
+ }
907
+ }
908
+
909
+ /**
910
+ * @license
911
+ * Copyright 2020 Google LLC
912
+ *
913
+ * Licensed under the Apache License, Version 2.0 (the "License");
914
+ * you may not use this file except in compliance with the License.
915
+ * You may obtain a copy of the License at
916
+ *
917
+ * http://www.apache.org/licenses/LICENSE-2.0
918
+ *
919
+ * Unless required by applicable law or agreed to in writing, software
920
+ * distributed under the License is distributed on an "AS IS" BASIS,
921
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
922
+ * See the License for the specific language governing permissions and
923
+ * limitations under the License.
924
+ */
925
+ async function updateVapidKey(messaging, vapidKey) {
926
+ if (!!vapidKey) {
927
+ messaging.vapidKey = vapidKey;
928
+ }
929
+ else if (!messaging.vapidKey) {
930
+ messaging.vapidKey = DEFAULT_VAPID_KEY;
931
+ }
932
+ }
933
+
934
+ /**
935
+ * @license
936
+ * Copyright 2020 Google LLC
937
+ *
938
+ * Licensed under the Apache License, Version 2.0 (the "License");
939
+ * you may not use this file except in compliance with the License.
940
+ * You may obtain a copy of the License at
941
+ *
942
+ * http://www.apache.org/licenses/LICENSE-2.0
943
+ *
944
+ * Unless required by applicable law or agreed to in writing, software
945
+ * distributed under the License is distributed on an "AS IS" BASIS,
946
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
947
+ * See the License for the specific language governing permissions and
948
+ * limitations under the License.
949
+ */
950
+ /** Retries when CreateRegistration echoes an FID that does not match Installations.getId(). */
951
+ const FID_REGISTRATION_FID_MATCH_MAX_ATTEMPTS = 3;
952
+ /**
953
+ * For the new FID-based register path:
954
+ * - Create (or refresh) an FCM Web registration in the backend via CreateRegistration.
955
+ * - Use the FIS auth token produced by the installations instance (implicitly associated with FID).
956
+ * - CreateRegistration must echo the installation in `name` (e.g.
957
+ * `projects/{projectId}/registrations/{fid}`); it must match `expectedFid` from
958
+ * Installations.getId(). On mismatch we refresh the auth token and retry, then fail with
959
+ * `fid-registration-failed`.
960
+ */
961
+ async function registerFcmRegistrationWithFid(messaging, expectedFid) {
962
+ const pushSubscription = await getPushSubscription(messaging.swRegistration, messaging.vapidKey);
963
+ const subscriptionOptions = {
964
+ vapidKey: messaging.vapidKey,
965
+ swScope: messaging.swRegistration.scope,
966
+ endpoint: pushSubscription.endpoint,
967
+ auth: arrayToBase64(pushSubscription.getKey('auth')),
968
+ p256dh: arrayToBase64(pushSubscription.getKey('p256dh'))
969
+ };
970
+ const installations = messaging.firebaseDependencies.installations;
971
+ for (let attempt = 0; attempt < FID_REGISTRATION_FID_MATCH_MAX_ATTEMPTS; attempt++) {
972
+ const { responseFid } = await requestCreateRegistration(messaging.firebaseDependencies, subscriptionOptions);
973
+ if (responseFid === expectedFid) {
974
+ return;
975
+ }
976
+ // If CreateRegistration echoes an unexpected FID, the FIS auth token used for the request may
977
+ // be stale relative to the installation the backend associates with the call. Force-refresh
978
+ // the token before retrying so the next attempt uses credentials aligned with Installations.
979
+ if (attempt < FID_REGISTRATION_FID_MATCH_MAX_ATTEMPTS - 1) {
980
+ await installations.getToken(true);
981
+ }
982
+ }
983
+ throw ERROR_FACTORY.create("fid-registration-failed" /* ErrorCode.FID_REGISTRATION_FAILED */, {
984
+ errorInfo: 'CreateRegistration response FID does not match Firebase Installation ID'
985
+ });
986
+ }
987
+ async function getPushSubscription(swRegistration, vapidKey) {
988
+ const subscription = await swRegistration.pushManager.getSubscription();
989
+ if (subscription) {
990
+ return subscription;
991
+ }
992
+ // Chrome/Firefox require applicationServerKey to be of type Uint8Array.
993
+ return swRegistration.pushManager.subscribe({
994
+ userVisibleOnly: true,
995
+ // `PushManager.subscribe` expects a `BufferSource`; `base64ToArray` produces a typed array.
996
+ // Cast to satisfy the lib typing differences across TS DOM versions.
997
+ applicationServerKey: base64ToArray(vapidKey)
998
+ });
999
+ }
1000
+
1001
+ /**
1002
+ * @license
1003
+ * Copyright 2026 Google LLC
1004
+ *
1005
+ * Licensed under the Apache License, Version 2.0 (the "License");
1006
+ * you may not use this file except in compliance with the License.
1007
+ * You may obtain a copy of the License at
1008
+ *
1009
+ * http://www.apache.org/licenses/LICENSE-2.0
1010
+ *
1011
+ * Unless required by applicable law or agreed to in writing, software
1012
+ * distributed under the License is distributed on an "AS IS" BASIS,
1013
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1014
+ * See the License for the specific language governing permissions and
1015
+ * limitations under the License.
1016
+ */
1017
+ /**
1018
+ * Re-runs FCM FID registration when push subscription keys change (e.g. `pushsubscriptionchange`
1019
+ * in the service worker). No-op if the app instance was never registered via `register()`.
1020
+ * Best-effort: callers should catch failures when permission or push may be unavailable.
1021
+ */
1022
+ async function refreshFidRegistrationIfStored(messaging) {
1023
+ const stored = await dbGetFidRegistration(messaging.firebaseDependencies).catch(() => undefined);
1024
+ if (!stored) {
1025
+ return undefined;
1026
+ }
1027
+ await updateVapidKey(messaging, stored.vapidKey);
1028
+ const fid = await messaging.firebaseDependencies.installations.getId();
1029
+ await registerFcmRegistrationWithFid(messaging, fid);
1030
+ await dbSetFidRegistration(messaging.firebaseDependencies, {
1031
+ fid,
1032
+ lastRegisterTime: Date.now(),
1033
+ vapidKey: messaging.vapidKey
1034
+ });
1035
+ notifyOnRegistered(messaging, fid);
1036
+ return fid;
1037
+ }
590
1038
 
591
1039
  /**
592
1040
  * @license
@@ -724,10 +1172,109 @@ function sleep(ms) {
724
1172
  * See the License for the specific language governing permissions and
725
1173
  * limitations under the License.
726
1174
  */
727
- _mergeStrings('AzSCbw63g1R0nCw85jG8', 'Iaya3yLKwmgvh7cF0q4');
1175
+ const LOG_ENDPOINT = 'https://play.google.com/log?format=json_proto3';
1176
+ /** First flush ASAP (next timer turn); `_dispatchLogEvents` reschedules with `LOG_INTERVAL_IN_MS`. */
1177
+ const INITIAL_LOG_FLUSH_DELAY_MS = 0;
1178
+ const FCM_TRANSPORT_KEY = _mergeStrings('AzSCbw63g1R0nCw85jG8', 'Iaya3yLKwmgvh7cF0q4');
1179
+ function startLoggingService(messaging) {
1180
+ // Start only if not already scheduled/in-flight and there is work to do.
1181
+ if (messaging.logQueue.state === 'stopped' &&
1182
+ messaging.logEvents.length > 0) {
1183
+ _processQueue(messaging, INITIAL_LOG_FLUSH_DELAY_MS);
1184
+ }
1185
+ }
1186
+ /** Clears queued Firelog events, cancels any pending flush timer, and stops the logging loop. */
1187
+ function stopLoggingServiceAndClearQueue(messaging) {
1188
+ if (messaging.logQueue.state === 'scheduled') {
1189
+ clearTimeout(messaging.logQueue.timerId);
1190
+ }
1191
+ messaging.logQueue = { state: 'stopped' };
1192
+ messaging.logEvents = [];
1193
+ }
1194
+ /**
1195
+ *
1196
+ * @param messaging the messaging instance.
1197
+ * @param offsetInMs this method execute after `offsetInMs` elapsed .
1198
+ */
1199
+ function _processQueue(messaging, offsetInMs) {
1200
+ if (messaging.logQueue.state === 'scheduled') {
1201
+ clearTimeout(messaging.logQueue.timerId);
1202
+ }
1203
+ messaging.logQueue = { state: 'stopped' };
1204
+ if (!messaging.deliveryMetricsExportedToBigQueryEnabled) {
1205
+ messaging.logEvents = [];
1206
+ return;
1207
+ }
1208
+ messaging.logQueue = {
1209
+ state: 'scheduled',
1210
+ timerId: setTimeout(async () => {
1211
+ // Mark in-flight so stageLog/startLoggingService won't schedule duplicates mid-dispatch.
1212
+ messaging.logQueue = { state: 'flushing' };
1213
+ if (!messaging.logEvents.length) {
1214
+ return _processQueue(messaging, LOG_INTERVAL_IN_MS);
1215
+ }
1216
+ await _dispatchLogEvents(messaging);
1217
+ }, offsetInMs)
1218
+ };
1219
+ }
1220
+ async function _dispatchLogEvents(messaging) {
1221
+ // Swap the queue to avoid losing events added during an in-flight dispatch.
1222
+ const eventsToSend = messaging.logEvents;
1223
+ messaging.logEvents = [];
1224
+ for (let i = 0, n = eventsToSend.length; i < n; i += MAX_NUMBER_OF_EVENTS_PER_LOG_REQUEST) {
1225
+ const batch = eventsToSend.slice(i, i + MAX_NUMBER_OF_EVENTS_PER_LOG_REQUEST);
1226
+ if (!batch.length) {
1227
+ break;
1228
+ }
1229
+ const logRequest = _createLogRequest(batch);
1230
+ let retryCount = 0, response = {};
1231
+ do {
1232
+ try {
1233
+ response = await fetch(LOG_ENDPOINT.concat('&key=', FCM_TRANSPORT_KEY), {
1234
+ method: 'POST',
1235
+ body: JSON.stringify(logRequest)
1236
+ });
1237
+ // don't retry on 200s or non retriable errors
1238
+ if (response.ok || (!response.ok && !isRetriableError(response))) {
1239
+ break;
1240
+ }
1241
+ if (!response.ok && isRetriableError(response)) {
1242
+ // rethrow to retry with quota
1243
+ throw new Error('a retriable Non-200 code is returned in fetch to Firelog endpoint. Retry');
1244
+ }
1245
+ }
1246
+ catch (error) {
1247
+ const isLastAttempt = retryCount === MAX_RETRIES;
1248
+ if (isLastAttempt) {
1249
+ // existing the do-while interactive retry logic because retry quota has reached.
1250
+ break;
1251
+ }
1252
+ }
1253
+ let delayInMs;
1254
+ try {
1255
+ delayInMs = Number((await response.json()).nextRequestWaitMillis);
1256
+ }
1257
+ catch (e) {
1258
+ delayInMs = DEFAULT_BACKOFF_TIME_MS;
1259
+ }
1260
+ await new Promise(resolve => setTimeout(resolve, delayInMs));
1261
+ retryCount++;
1262
+ } while (retryCount < MAX_RETRIES);
1263
+ }
1264
+ // Schedule next flush. If new events arrived during this dispatch, flush ASAP.
1265
+ _processQueue(messaging, messaging.logEvents.length ? INITIAL_LOG_FLUSH_DELAY_MS : LOG_INTERVAL_IN_MS);
1266
+ }
1267
+ function isRetriableError(response) {
1268
+ const httpStatus = response.status;
1269
+ return (httpStatus === 429 ||
1270
+ httpStatus === 500 ||
1271
+ httpStatus === 503 ||
1272
+ httpStatus === 504);
1273
+ }
728
1274
  async function stageLog(messaging, internalPayload) {
729
1275
  const fcmEvent = createFcmEvent(internalPayload, await messaging.firebaseDependencies.installations.getId());
730
1276
  createAndEnqueueLogEvent(messaging, fcmEvent, internalPayload.productId);
1277
+ startLoggingService(messaging);
731
1278
  }
732
1279
  function createFcmEvent(internalPayload, fid) {
733
1280
  const fcmEvent = {};
@@ -781,6 +1328,14 @@ function buildComplianceData(productId) {
781
1328
  };
782
1329
  return complianceData;
783
1330
  }
1331
+ function _createLogRequest(logEventQueue) {
1332
+ const logRequest = {};
1333
+ /* eslint-disable camelcase */
1334
+ logRequest.log_source = FCM_LOG_SOURCE.toString();
1335
+ logRequest.log_event = logEventQueue;
1336
+ /* eslint-enable camelcase */
1337
+ return logRequest;
1338
+ }
784
1339
  function _mergeStrings(s1, s2) {
785
1340
  const resultArray = [];
786
1341
  for (let i = 0; i < s1.length; i++) {
@@ -809,14 +1364,32 @@ function _mergeStrings(s1, s2) {
809
1364
  * limitations under the License.
810
1365
  */
811
1366
  async function onSubChange(event, messaging) {
1367
+ if (!messaging.swRegistration) {
1368
+ messaging.swRegistration = self.registration;
1369
+ }
812
1370
  const { newSubscription } = event;
813
1371
  if (!newSubscription) {
814
- // Subscription revoked, delete token
815
- await deleteTokenInternal(messaging);
1372
+ // Subscription revoked: legacy token and FID register/unregister paths both flow through
1373
+ // revokeRegistrationInternal (server revoke + onUnregistered when applicable).
1374
+ await revokeRegistrationInternal(messaging);
1375
+ return;
1376
+ }
1377
+ const storedFid = await dbGetFidRegistration(messaging.firebaseDependencies).catch(() => undefined);
1378
+ if (storedFid) {
1379
+ const fid = await refreshFidRegistrationIfStored(messaging).catch(() => {
1380
+ // Best-effort: push subscription may be unavailable after rotation.
1381
+ return undefined;
1382
+ });
1383
+ if (fid) {
1384
+ const clientList = await getClientList();
1385
+ if (hasVisibleClients(clientList)) {
1386
+ sendFidRegisteredToWindows(clientList, fid);
1387
+ }
1388
+ }
816
1389
  return;
817
1390
  }
818
1391
  const tokenDetails = await dbGet(messaging.firebaseDependencies);
819
- await deleteTokenInternal(messaging);
1392
+ await revokeRegistrationInternal(messaging);
820
1393
  messaging.vapidKey =
821
1394
  tokenDetails?.subscriptionOptions?.vapidKey ?? DEFAULT_VAPID_KEY;
822
1395
  await getTokenInternal(messaging);
@@ -827,7 +1400,11 @@ async function onPush(event, messaging) {
827
1400
  // Failed to get parsed MessagePayload from the PushEvent. Skip handling the push.
828
1401
  return;
829
1402
  }
830
- // log to Firelog with user consent
1403
+ /*
1404
+ * Log to Firelog based on user consent. Rather than calling startLoggingService once when
1405
+ * deliveryMetricsExportedToBigQueryEnabled is toggled, we now call stageLog for every received push.
1406
+ * This ensures the first telemetry event is uploaded immediately upon enabling the flag, simplifying debugging.
1407
+ */
831
1408
  if (messaging.deliveryMetricsExportedToBigQueryEnabled) {
832
1409
  await stageLog(messaging, internalPayload);
833
1410
  }
@@ -950,6 +1527,16 @@ function sendMessagePayloadInternalToWindows(clientList, internalPayload) {
950
1527
  client.postMessage(internalPayload);
951
1528
  }
952
1529
  }
1530
+ function sendFidRegisteredToWindows(clientList, fid) {
1531
+ const payload = {
1532
+ isFirebaseMessaging: true,
1533
+ messageType: MessageType.FID_REGISTERED,
1534
+ fid
1535
+ };
1536
+ for (const client of clientList) {
1537
+ client.postMessage(payload);
1538
+ }
1539
+ }
953
1540
  function getClientList() {
954
1541
  return self.clients.matchAll({
955
1542
  type: 'window',
@@ -1055,8 +1642,25 @@ class MessagingService {
1055
1642
  this.deliveryMetricsExportedToBigQueryEnabled = false;
1056
1643
  this.onBackgroundMessageHandler = null;
1057
1644
  this.onMessageHandler = null;
1645
+ /** Observer for the event that the app instance is registered with FCM via Firebase Installation ID (FID). */
1646
+ this.onRegisteredHandler = null;
1647
+ /** Observer for the event that the app instance is unregistered from FCM (FID no longer active). */
1648
+ this.onUnregisteredHandler = null;
1649
+ /**
1650
+ * Serializes the FID get + compare + notify step so concurrent register() calls
1651
+ * do not race each other.
1652
+ */
1653
+ this._registerNotifyChain = Promise.resolve();
1654
+ /** Unsubscribe from Installations `onIdChange` when messaging is deleted. */
1655
+ this._fidChangeUnsubscribe = null;
1058
1656
  this.logEvents = [];
1059
- this.isLogServiceStarted = false;
1657
+ /**
1658
+ * Single source of truth for the logging loop lifecycle.
1659
+ *
1660
+ * `scheduled` holds the active timer id; `flushing` indicates an async dispatch
1661
+ * is in progress (prevents duplicate starts); `stopped` means idle.
1662
+ */
1663
+ this.logQueue = { state: 'stopped' };
1060
1664
  const appConfig = extractAppConfig(app);
1061
1665
  this.firebaseDependencies = {
1062
1666
  app,
@@ -1066,6 +1670,14 @@ class MessagingService {
1066
1670
  };
1067
1671
  }
1068
1672
  _delete() {
1673
+ if (this._fidChangeUnsubscribe) {
1674
+ this._fidChangeUnsubscribe();
1675
+ this._fidChangeUnsubscribe = null;
1676
+ }
1677
+ if (this.logQueue.state === 'scheduled') {
1678
+ clearTimeout(this.logQueue.timerId);
1679
+ }
1680
+ this.logQueue = { state: 'stopped' };
1069
1681
  return Promise.resolve();
1070
1682
  }
1071
1683
  }
@@ -1168,6 +1780,72 @@ function onBackgroundMessage$1(messaging, nextOrObserver) {
1168
1780
  };
1169
1781
  }
1170
1782
 
1783
+ /**
1784
+ * @license
1785
+ * Copyright 2020 Google LLC
1786
+ *
1787
+ * Licensed under the Apache License, Version 2.0 (the "License");
1788
+ * you may not use this file except in compliance with the License.
1789
+ * You may obtain a copy of the License at
1790
+ *
1791
+ * http://www.apache.org/licenses/LICENSE-2.0
1792
+ *
1793
+ * Unless required by applicable law or agreed to in writing, software
1794
+ * distributed under the License is distributed on an "AS IS" BASIS,
1795
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1796
+ * See the License for the specific language governing permissions and
1797
+ * limitations under the License.
1798
+ */
1799
+ /**
1800
+ * Subscribes to an event that the app instance is registered with FCM via Firebase Installation ID (FID).
1801
+ * Use the FID passed to the callback to upload it to your application server.
1802
+ *
1803
+ * @param messaging - The {@link MessagingService} instance.
1804
+ * @param nextOrObserver - A function or observer object called when an FID is registered.
1805
+ * @returns Unsubscribe function to stop listening.
1806
+ */
1807
+ function onRegistered$1(messaging, nextOrObserver) {
1808
+ messaging.onRegisteredHandler = nextOrObserver;
1809
+ return () => {
1810
+ if (messaging.onRegisteredHandler === nextOrObserver) {
1811
+ messaging.onRegisteredHandler = null;
1812
+ }
1813
+ };
1814
+ }
1815
+
1816
+ /**
1817
+ * @license
1818
+ * Copyright 2026 Google LLC
1819
+ *
1820
+ * Licensed under the Apache License, Version 2.0 (the "License");
1821
+ * you may not use this file except in compliance with the License.
1822
+ * You may obtain a copy of the License at
1823
+ *
1824
+ * http://www.apache.org/licenses/LICENSE-2.0
1825
+ *
1826
+ * Unless required by applicable law or agreed to in writing, software
1827
+ * distributed under the License is distributed on an "AS IS" BASIS,
1828
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1829
+ * See the License for the specific language governing permissions and
1830
+ * limitations under the License.
1831
+ */
1832
+ /**
1833
+ * Subscribes to an event that the app instance is unregistered from FCM so the FID is no longer active.
1834
+ * Use this to notify your backend to remove this FID to prevent 404 errors on send.
1835
+ *
1836
+ * @param messaging - The {@link MessagingService} instance.
1837
+ * @param nextOrObserver - A function or observer object called with the unregistered FID.
1838
+ * @returns Unsubscribe function to stop listening.
1839
+ */
1840
+ function onUnregistered$1(messaging, nextOrObserver) {
1841
+ messaging.onUnregisteredHandler = nextOrObserver;
1842
+ return () => {
1843
+ if (messaging.onUnregisteredHandler === nextOrObserver) {
1844
+ messaging.onUnregisteredHandler = null;
1845
+ }
1846
+ };
1847
+ }
1848
+
1171
1849
  /**
1172
1850
  * @license
1173
1851
  * Copyright 2020 Google LLC
@@ -1185,8 +1863,14 @@ function onBackgroundMessage$1(messaging, nextOrObserver) {
1185
1863
  * limitations under the License.
1186
1864
  */
1187
1865
  function _setDeliveryMetricsExportedToBigQueryEnabled(messaging, enable) {
1188
- messaging.deliveryMetricsExportedToBigQueryEnabled =
1189
- enable;
1866
+ const messagingService = messaging;
1867
+ messagingService.deliveryMetricsExportedToBigQueryEnabled = enable;
1868
+ if (enable) {
1869
+ startLoggingService(messagingService);
1870
+ }
1871
+ else {
1872
+ stopLoggingServiceAndClearQueue(messagingService);
1873
+ }
1190
1874
  }
1191
1875
 
1192
1876
  /**
@@ -1236,7 +1920,7 @@ function getMessagingInSw(app$1 = app.getApp()) {
1236
1920
  * @param nextOrObserver - This function, or observer object with `next` defined, is called when a
1237
1921
  * message is received and the app is currently in the background.
1238
1922
  *
1239
- * @returns To stop listening for messages execute this returned function
1923
+ * @returns To stop listening for messages execute this returned function.
1240
1924
  *
1241
1925
  * @public
1242
1926
  */
@@ -1244,6 +1928,35 @@ function onBackgroundMessage(messaging, nextOrObserver) {
1244
1928
  messaging = util.getModularInstance(messaging);
1245
1929
  return onBackgroundMessage$1(messaging, nextOrObserver);
1246
1930
  }
1931
+ /**
1932
+ * Subscribes to an event that the app instance is registered with FCM via Firebase Installation ID (FID).
1933
+ * Use the FID passed to the callback to upload it to your application server. When you receive an FID
1934
+ * after calling {@link register}, instruct your backend to remove any legacy token for this instance.
1935
+ *
1936
+ * @param messaging - The {@link Messaging} instance.
1937
+ * @param nextOrObserver - A function or observer object called when an FID is registered.
1938
+ * @returns Unsubscribe function to stop listening.
1939
+ *
1940
+ * @public
1941
+ */
1942
+ function onRegistered(messaging, nextOrObserver) {
1943
+ messaging = util.getModularInstance(messaging);
1944
+ return onRegistered$1(messaging, nextOrObserver);
1945
+ }
1946
+ /**
1947
+ * Subscribes to an event that the app instance is unregistered from FCM (FID no longer active).
1948
+ * Use this to notify your backend to remove this FID to prevent 404 errors on send.
1949
+ *
1950
+ * @param messaging - The {@link Messaging} instance.
1951
+ * @param nextOrObserver - A function or observer object called with the unregistered FID.
1952
+ * @returns Unsubscribe function to stop listening.
1953
+ *
1954
+ * @public
1955
+ */
1956
+ function onUnregistered(messaging, nextOrObserver) {
1957
+ messaging = util.getModularInstance(messaging);
1958
+ return onUnregistered$1(messaging, nextOrObserver);
1959
+ }
1247
1960
  /**
1248
1961
  * Enables or disables Firebase Cloud Messaging message delivery metrics export to BigQuery. By
1249
1962
  * default, message delivery metrics are not exported to BigQuery. Use this method to enable or
@@ -1282,4 +1995,6 @@ exports.experimentalSetDeliveryMetricsExportedToBigQueryEnabled = experimentalSe
1282
1995
  exports.getMessaging = getMessagingInSw;
1283
1996
  exports.isSupported = isSwSupported;
1284
1997
  exports.onBackgroundMessage = onBackgroundMessage;
1998
+ exports.onRegistered = onRegistered;
1999
+ exports.onUnregistered = onUnregistered;
1285
2000
  //# sourceMappingURL=index.sw.cjs.map