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