@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
@@ -1,4 +1,4 @@
1
- import '@firebase/installations';
1
+ import { onIdChange } from '@firebase/installations';
2
2
  import { Component } from '@firebase/component';
3
3
  import { openDB, deleteDB } from 'idb';
4
4
  import { ErrorFactory, validateIndexedDBOpenable, isIndexedDBAvailable, areCookiesEnabled, getModularInstance } from '@firebase/util';
@@ -54,6 +54,7 @@ var MessageType;
54
54
  (function (MessageType) {
55
55
  MessageType["PUSH_RECEIVED"] = "push-received";
56
56
  MessageType["NOTIFICATION_CLICKED"] = "notification-clicked";
57
+ MessageType["FID_REGISTERED"] = "fid-registered";
57
58
  })(MessageType || (MessageType = {}));
58
59
 
59
60
  /**
@@ -219,6 +220,53 @@ function checkTokenDetails(tokenDetails) {
219
220
  subscriptionOptions.vapidKey.length > 0);
220
221
  }
221
222
 
223
+ /**
224
+ * @license
225
+ * Copyright 2017 Google LLC
226
+ *
227
+ * Licensed under the Apache License, Version 2.0 (the "License");
228
+ * you may not use this file except in compliance with the License.
229
+ * You may obtain a copy of the License at
230
+ *
231
+ * http://www.apache.org/licenses/LICENSE-2.0
232
+ *
233
+ * Unless required by applicable law or agreed to in writing, software
234
+ * distributed under the License is distributed on an "AS IS" BASIS,
235
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
236
+ * See the License for the specific language governing permissions and
237
+ * limitations under the License.
238
+ */
239
+ const ERROR_MAP = {
240
+ ["missing-app-config-values" /* ErrorCode.MISSING_APP_CONFIG_VALUES */]: 'Missing App configuration value: "{$valueName}"',
241
+ ["only-available-in-window" /* ErrorCode.AVAILABLE_IN_WINDOW */]: 'This method is available in a Window context.',
242
+ ["only-available-in-sw" /* ErrorCode.AVAILABLE_IN_SW */]: 'This method is available in a service worker context.',
243
+ ["permission-default" /* ErrorCode.PERMISSION_DEFAULT */]: 'The notification permission was not granted and dismissed instead.',
244
+ ["permission-blocked" /* ErrorCode.PERMISSION_BLOCKED */]: 'The notification permission was not granted and blocked instead.',
245
+ ["unsupported-browser" /* ErrorCode.UNSUPPORTED_BROWSER */]: "This browser doesn't support the API's required to use the Firebase SDK.",
246
+ ["indexed-db-unsupported" /* ErrorCode.INDEXED_DB_UNSUPPORTED */]: "This browser doesn't support indexedDb.open() (ex. Safari iFrame, Firefox Private Browsing, etc)",
247
+ ["failed-service-worker-registration" /* ErrorCode.FAILED_DEFAULT_REGISTRATION */]: 'We are unable to register the default service worker. {$browserErrorMessage}',
248
+ ["token-subscribe-failed" /* ErrorCode.TOKEN_SUBSCRIBE_FAILED */]: 'A problem occurred while subscribing the user to FCM: {$errorInfo}',
249
+ ["token-subscribe-no-token" /* ErrorCode.TOKEN_SUBSCRIBE_NO_TOKEN */]: 'FCM returned no token when subscribing the user to push.',
250
+ ["fid-registration-failed" /* ErrorCode.FID_REGISTRATION_FAILED */]: 'A problem occurred while creating an FCM registration via FID: {$errorInfo}',
251
+ ["fid-unregister-failed" /* ErrorCode.FID_UNREGISTER_FAILED */]: 'A problem occurred while unregistering the FCM registration via FID: {$errorInfo}',
252
+ ["fid-registration-idb-schema-unavailable" /* ErrorCode.FID_REGISTRATION_IDB_SCHEMA_UNAVAILABLE */]: 'Unable to read or persist FID registration metadata because the messaging ' +
253
+ 'IndexedDB schema is unavailable (for example, the database could not be ' +
254
+ 'upgraded to the latest version).',
255
+ ["token-unsubscribe-failed" /* ErrorCode.TOKEN_UNSUBSCRIBE_FAILED */]: 'A problem occurred while unsubscribing the ' +
256
+ 'user from FCM: {$errorInfo}',
257
+ ["token-update-failed" /* ErrorCode.TOKEN_UPDATE_FAILED */]: 'A problem occurred while updating the user from FCM: {$errorInfo}',
258
+ ["token-update-no-token" /* ErrorCode.TOKEN_UPDATE_NO_TOKEN */]: 'FCM returned no token when updating the user to push.',
259
+ ["use-sw-after-get-token" /* ErrorCode.USE_SW_AFTER_GET_TOKEN */]: 'The useServiceWorker() method may only be called once and must be ' +
260
+ 'called before calling getToken() to ensure your service worker is used.',
261
+ ["invalid-sw-registration" /* ErrorCode.INVALID_SW_REGISTRATION */]: 'The input to useServiceWorker() must be a ServiceWorkerRegistration.',
262
+ ["invalid-bg-handler" /* ErrorCode.INVALID_BG_HANDLER */]: 'The input to setBackgroundMessageHandler() must be a function.',
263
+ ["invalid-vapid-key" /* ErrorCode.INVALID_VAPID_KEY */]: 'The public VAPID key must be a string.',
264
+ ["use-vapid-key-after-get-token" /* ErrorCode.USE_VAPID_KEY_AFTER_GET_TOKEN */]: 'The usePublicVapidKey() method may only be called once and must be ' +
265
+ 'called before calling getToken() to ensure your VAPID key is used.',
266
+ ["invalid-on-registered-handler" /* ErrorCode.INVALID_ON_REGISTERED_HANDLER */]: 'No onRegistered callback handler was provided or registered. Implement onRegistered() before register().'
267
+ };
268
+ const ERROR_FACTORY = new ErrorFactory('messaging', 'Messaging', ERROR_MAP);
269
+
222
270
  /**
223
271
  * @license
224
272
  * Copyright 2019 Google LLC
@@ -235,41 +283,74 @@ function checkTokenDetails(tokenDetails) {
235
283
  * See the License for the specific language governing permissions and
236
284
  * limitations under the License.
237
285
  */
238
- // Exported for tests.
239
286
  const DATABASE_NAME = 'firebase-messaging-database';
240
- const DATABASE_VERSION = 1;
241
- const OBJECT_STORE_NAME = 'firebase-messaging-store';
287
+ const DATABASE_VERSION = 2;
288
+ const TOKEN_OBJECT_STORE_NAME = 'firebase-messaging-store';
289
+ const FID_REGISTRATION_OBJECT_STORE_NAME = 'firebase-messaging-fid-registration-store';
290
+ const defaultIdb = { openDB, deleteDB };
291
+ let idbImpl = defaultIdb;
292
+ // Open v2, but fall back to v1 if upgrade/open fails. Cache as `unknown` and guard store access.
242
293
  let dbPromise = null;
294
+ function migrateMessagingDb(upgradeDb, oldVersion, targetSchemaVersion) {
295
+ // Intentional fall-through for v2: run all intermediate migrations.
296
+ // eslint-disable-next-line default-case
297
+ switch (oldVersion) {
298
+ case 0:
299
+ upgradeDb.createObjectStore(TOKEN_OBJECT_STORE_NAME);
300
+ if (targetSchemaVersion === 1) {
301
+ break;
302
+ }
303
+ // fall through
304
+ case 1:
305
+ if (targetSchemaVersion === 2) {
306
+ upgradeDb.createObjectStore(FID_REGISTRATION_OBJECT_STORE_NAME);
307
+ }
308
+ }
309
+ }
310
+ function createOpenDbOptions(targetSchemaVersion) {
311
+ return {
312
+ upgrade: (upgradeDb, oldVersion) => {
313
+ migrateMessagingDb(upgradeDb, oldVersion, targetSchemaVersion);
314
+ },
315
+ blocked: () => {
316
+ /* no-op */
317
+ },
318
+ blocking: (_currentVersion, _blockedVersion, event) => {
319
+ dbPromise = null;
320
+ event.target?.close();
321
+ },
322
+ terminated: () => {
323
+ dbPromise = null;
324
+ }
325
+ };
326
+ }
243
327
  function getDbPromise() {
244
328
  if (!dbPromise) {
245
- dbPromise = openDB(DATABASE_NAME, DATABASE_VERSION, {
246
- upgrade: (upgradeDb, oldVersion) => {
247
- // We don't use 'break' in this switch statement, the fall-through behavior is what we want,
248
- // because if there are multiple versions between the old version and the current version, we
249
- // want ALL the migrations that correspond to those versions to run, not only the last one.
250
- // eslint-disable-next-line default-case
251
- switch (oldVersion) {
252
- case 0:
253
- upgradeDb.createObjectStore(OBJECT_STORE_NAME);
254
- }
255
- }
256
- });
329
+ const openLatest = idbImpl.openDB(DATABASE_NAME, DATABASE_VERSION, createOpenDbOptions(2));
330
+ // Assign synchronously to avoid concurrent openDB() calls.
331
+ dbPromise = openLatest.catch(() => idbImpl.openDB(DATABASE_NAME, DATABASE_VERSION - 1, createOpenDbOptions(1)));
257
332
  }
258
333
  return dbPromise;
259
334
  }
260
- /** Gets record(s) from the objectStore that match the given key. */
335
+ function hasObjectStore(db, storeName) {
336
+ return db.objectStoreNames.contains(storeName);
337
+ }
338
+ function assertFidRegistrationObjectStore(db) {
339
+ if (!hasObjectStore(db, FID_REGISTRATION_OBJECT_STORE_NAME)) {
340
+ throw ERROR_FACTORY.create("fid-registration-idb-schema-unavailable" /* ErrorCode.FID_REGISTRATION_IDB_SCHEMA_UNAVAILABLE */);
341
+ }
342
+ }
261
343
  async function dbGet(firebaseDependencies) {
262
344
  const key = getKey(firebaseDependencies);
263
345
  const db = await getDbPromise();
264
346
  const tokenDetails = (await db
265
- .transaction(OBJECT_STORE_NAME)
266
- .objectStore(OBJECT_STORE_NAME)
347
+ .transaction(TOKEN_OBJECT_STORE_NAME)
348
+ .objectStore(TOKEN_OBJECT_STORE_NAME)
267
349
  .get(key));
268
350
  if (tokenDetails) {
269
351
  return tokenDetails;
270
352
  }
271
353
  else {
272
- // Check if there is a tokenDetails object in the old DB.
273
354
  const oldTokenDetails = await migrateOldDatabase(firebaseDependencies.appConfig.senderId);
274
355
  if (oldTokenDetails) {
275
356
  await dbSet(firebaseDependencies, oldTokenDetails);
@@ -277,67 +358,62 @@ async function dbGet(firebaseDependencies) {
277
358
  }
278
359
  }
279
360
  }
280
- /** Assigns or overwrites the record for the given key with the given value. */
281
361
  async function dbSet(firebaseDependencies, tokenDetails) {
282
362
  const key = getKey(firebaseDependencies);
283
363
  const db = await getDbPromise();
284
- const tx = db.transaction(OBJECT_STORE_NAME, 'readwrite');
285
- await tx.objectStore(OBJECT_STORE_NAME).put(tokenDetails, key);
364
+ const stores = [TOKEN_OBJECT_STORE_NAME];
365
+ const hasFidStore = hasObjectStore(db, FID_REGISTRATION_OBJECT_STORE_NAME);
366
+ if (hasFidStore) {
367
+ stores.push(FID_REGISTRATION_OBJECT_STORE_NAME);
368
+ }
369
+ const tx = db.transaction(stores, 'readwrite');
370
+ await tx.objectStore(TOKEN_OBJECT_STORE_NAME).put(tokenDetails, key);
371
+ if (hasFidStore) {
372
+ await tx.objectStore(FID_REGISTRATION_OBJECT_STORE_NAME).delete(key);
373
+ }
286
374
  await tx.done;
287
375
  return tokenDetails;
288
376
  }
289
- /** Removes record(s) from the objectStore that match the given key. */
290
377
  async function dbRemove(firebaseDependencies) {
291
378
  const key = getKey(firebaseDependencies);
292
379
  const db = await getDbPromise();
293
- const tx = db.transaction(OBJECT_STORE_NAME, 'readwrite');
294
- await tx.objectStore(OBJECT_STORE_NAME).delete(key);
380
+ const tx = db.transaction(TOKEN_OBJECT_STORE_NAME, 'readwrite');
381
+ await tx.objectStore(TOKEN_OBJECT_STORE_NAME).delete(key);
382
+ await tx.done;
383
+ }
384
+ async function dbGetFidRegistration(firebaseDependencies) {
385
+ const key = getKey(firebaseDependencies);
386
+ const db = await getDbPromise();
387
+ assertFidRegistrationObjectStore(db);
388
+ return (await db
389
+ .transaction(FID_REGISTRATION_OBJECT_STORE_NAME)
390
+ .objectStore(FID_REGISTRATION_OBJECT_STORE_NAME)
391
+ .get(key));
392
+ }
393
+ async function dbSetFidRegistration(firebaseDependencies, details) {
394
+ const key = getKey(firebaseDependencies);
395
+ const db = await getDbPromise();
396
+ assertFidRegistrationObjectStore(db);
397
+ const tx = db.transaction([TOKEN_OBJECT_STORE_NAME, FID_REGISTRATION_OBJECT_STORE_NAME], 'readwrite');
398
+ await tx.objectStore(FID_REGISTRATION_OBJECT_STORE_NAME).put(details, key);
399
+ await tx.objectStore(TOKEN_OBJECT_STORE_NAME).delete(key);
400
+ await tx.done;
401
+ return details;
402
+ }
403
+ async function dbRemoveFidRegistration(firebaseDependencies) {
404
+ const key = getKey(firebaseDependencies);
405
+ const db = await getDbPromise();
406
+ assertFidRegistrationObjectStore(db);
407
+ const tx = db.transaction(FID_REGISTRATION_OBJECT_STORE_NAME, 'readwrite');
408
+ await tx.objectStore(FID_REGISTRATION_OBJECT_STORE_NAME).delete(key);
295
409
  await tx.done;
296
410
  }
297
411
  function getKey({ appConfig }) {
298
412
  return appConfig.appId;
299
413
  }
300
414
 
301
- /**
302
- * @license
303
- * Copyright 2017 Google LLC
304
- *
305
- * Licensed under the Apache License, Version 2.0 (the "License");
306
- * you may not use this file except in compliance with the License.
307
- * You may obtain a copy of the License at
308
- *
309
- * http://www.apache.org/licenses/LICENSE-2.0
310
- *
311
- * Unless required by applicable law or agreed to in writing, software
312
- * distributed under the License is distributed on an "AS IS" BASIS,
313
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
314
- * See the License for the specific language governing permissions and
315
- * limitations under the License.
316
- */
317
- const ERROR_MAP = {
318
- ["missing-app-config-values" /* ErrorCode.MISSING_APP_CONFIG_VALUES */]: 'Missing App configuration value: "{$valueName}"',
319
- ["only-available-in-window" /* ErrorCode.AVAILABLE_IN_WINDOW */]: 'This method is available in a Window context.',
320
- ["only-available-in-sw" /* ErrorCode.AVAILABLE_IN_SW */]: 'This method is available in a service worker context.',
321
- ["permission-default" /* ErrorCode.PERMISSION_DEFAULT */]: 'The notification permission was not granted and dismissed instead.',
322
- ["permission-blocked" /* ErrorCode.PERMISSION_BLOCKED */]: 'The notification permission was not granted and blocked instead.',
323
- ["unsupported-browser" /* ErrorCode.UNSUPPORTED_BROWSER */]: "This browser doesn't support the API's required to use the Firebase SDK.",
324
- ["indexed-db-unsupported" /* ErrorCode.INDEXED_DB_UNSUPPORTED */]: "This browser doesn't support indexedDb.open() (ex. Safari iFrame, Firefox Private Browsing, etc)",
325
- ["failed-service-worker-registration" /* ErrorCode.FAILED_DEFAULT_REGISTRATION */]: 'We are unable to register the default service worker. {$browserErrorMessage}',
326
- ["token-subscribe-failed" /* ErrorCode.TOKEN_SUBSCRIBE_FAILED */]: 'A problem occurred while subscribing the user to FCM: {$errorInfo}',
327
- ["token-subscribe-no-token" /* ErrorCode.TOKEN_SUBSCRIBE_NO_TOKEN */]: 'FCM returned no token when subscribing the user to push.',
328
- ["token-unsubscribe-failed" /* ErrorCode.TOKEN_UNSUBSCRIBE_FAILED */]: 'A problem occurred while unsubscribing the ' +
329
- 'user from FCM: {$errorInfo}',
330
- ["token-update-failed" /* ErrorCode.TOKEN_UPDATE_FAILED */]: 'A problem occurred while updating the user from FCM: {$errorInfo}',
331
- ["token-update-no-token" /* ErrorCode.TOKEN_UPDATE_NO_TOKEN */]: 'FCM returned no token when updating the user to push.',
332
- ["use-sw-after-get-token" /* ErrorCode.USE_SW_AFTER_GET_TOKEN */]: 'The useServiceWorker() method may only be called once and must be ' +
333
- 'called before calling getToken() to ensure your service worker is used.',
334
- ["invalid-sw-registration" /* ErrorCode.INVALID_SW_REGISTRATION */]: 'The input to useServiceWorker() must be a ServiceWorkerRegistration.',
335
- ["invalid-bg-handler" /* ErrorCode.INVALID_BG_HANDLER */]: 'The input to setBackgroundMessageHandler() must be a function.',
336
- ["invalid-vapid-key" /* ErrorCode.INVALID_VAPID_KEY */]: 'The public VAPID key must be a string.',
337
- ["use-vapid-key-after-get-token" /* ErrorCode.USE_VAPID_KEY_AFTER_GET_TOKEN */]: 'The usePublicVapidKey() method may only be called once and must be ' +
338
- 'called before calling getToken() to ensure your VAPID key is used.'
339
- };
340
- const ERROR_FACTORY = new ErrorFactory('messaging', 'Messaging', ERROR_MAP);
415
+ const name = "@firebase/messaging";
416
+ const version = "0.13.0-20260526192810";
341
417
 
342
418
  /**
343
419
  * @license
@@ -355,9 +431,14 @@ const ERROR_FACTORY = new ErrorFactory('messaging', 'Messaging', ERROR_MAP);
355
431
  * See the License for the specific language governing permissions and
356
432
  * limitations under the License.
357
433
  */
434
+ /** Max attempts (initial fetch + retries) when CreateRegistration `fetch()` throws. */
435
+ const FID_REGISTRATION_FETCH_MAX_ATTEMPTS = 3;
436
+ /** Base delay in ms; backoff is `BASE * 2^attempt` after each failed attempt. */
437
+ const FID_REGISTRATION_FETCH_BASE_BACKOFF_MS = 1000;
358
438
  async function requestGetToken(firebaseDependencies, subscriptionOptions) {
359
439
  const headers = await getHeaders(firebaseDependencies);
360
- const body = getBody(subscriptionOptions);
440
+ const body = getBody(subscriptionOptions, firebaseDependencies.appConfig.appName,
441
+ /* includeSdkVersion= */ false);
361
442
  const subscribeOptions = {
362
443
  method: 'POST',
363
444
  headers,
@@ -384,9 +465,128 @@ async function requestGetToken(firebaseDependencies, subscriptionOptions) {
384
465
  }
385
466
  return responseData.token;
386
467
  }
468
+ async function requestCreateRegistration(firebaseDependencies, subscriptionOptions) {
469
+ const headers = await getHeaders(firebaseDependencies);
470
+ const body = getBody(subscriptionOptions, firebaseDependencies.appConfig.appName,
471
+ /* includeSdkVersion= */ true);
472
+ const subscribeOptions = {
473
+ method: 'POST',
474
+ headers,
475
+ body: JSON.stringify(body)
476
+ };
477
+ let response;
478
+ try {
479
+ response = await fetchWithExponentialRetry(() => fetch(getEndpoint(firebaseDependencies.appConfig), subscribeOptions), FID_REGISTRATION_FETCH_MAX_ATTEMPTS, FID_REGISTRATION_FETCH_BASE_BACKOFF_MS);
480
+ }
481
+ catch (err) {
482
+ throw ERROR_FACTORY.create("fid-registration-failed" /* ErrorCode.FID_REGISTRATION_FAILED */, {
483
+ errorInfo: err?.toString()
484
+ });
485
+ }
486
+ if (response.ok) {
487
+ const responseFid = await parseCreateRegistrationSuccessFid(response);
488
+ return { responseFid };
489
+ }
490
+ // `fetch()` succeeded, but the backend returned a non-2xx response.
491
+ // Best-effort parse the body to extract `error.message`, but always fail with
492
+ // `FID_REGISTRATION_FAILED` to keep the error surface uniform.
493
+ // Best-effort extraction of error details; the main signal is response.ok / status.
494
+ let responseData;
495
+ try {
496
+ responseData = (await response.json());
497
+ }
498
+ catch (err) {
499
+ throw ERROR_FACTORY.create("fid-registration-failed" /* ErrorCode.FID_REGISTRATION_FAILED */, {
500
+ errorInfo: response.statusText
501
+ });
502
+ }
503
+ const message = responseData.error?.message ?? response.statusText;
504
+ throw ERROR_FACTORY.create("fid-registration-failed" /* ErrorCode.FID_REGISTRATION_FAILED */, {
505
+ errorInfo: message
506
+ });
507
+ }
508
+ /**
509
+ * Deletes an FCM Web registration via DeleteRegistration using the Firebase Installation ID (FID).
510
+ */
511
+ async function requestDeleteRegistration(firebaseDependencies, fid) {
512
+ const headers = await getHeaders(firebaseDependencies);
513
+ const options = {
514
+ method: 'DELETE',
515
+ headers
516
+ };
517
+ let response;
518
+ try {
519
+ response = await fetch(`${getEndpoint(firebaseDependencies.appConfig)}/${fid}`, options);
520
+ }
521
+ catch (err) {
522
+ throw ERROR_FACTORY.create("fid-unregister-failed" /* ErrorCode.FID_UNREGISTER_FAILED */, {
523
+ errorInfo: err?.toString()
524
+ });
525
+ }
526
+ if (response.ok) {
527
+ return;
528
+ }
529
+ // Best-effort parse error details; surface uniform error code.
530
+ try {
531
+ const responseData = (await response.json());
532
+ const message = responseData.error?.message ?? response.statusText;
533
+ throw message;
534
+ }
535
+ catch (err) {
536
+ // If parsing failed, fall back to status text.
537
+ throw ERROR_FACTORY.create("fid-unregister-failed" /* ErrorCode.FID_UNREGISTER_FAILED */, {
538
+ errorInfo: (typeof err === 'string' && err) ||
539
+ response.statusText ||
540
+ err?.toString()
541
+ });
542
+ }
543
+ }
544
+ /**
545
+ * Parses a successful CreateRegistration body. The backend must return JSON with a non-empty
546
+ * string `name`: a resource name `projects/{projectId}/registrations/{fid}`
547
+ */
548
+ async function parseCreateRegistrationSuccessFid(response) {
549
+ const text = await response.text();
550
+ if (!text.trim()) {
551
+ throw ERROR_FACTORY.create("fid-registration-failed" /* ErrorCode.FID_REGISTRATION_FAILED */, {
552
+ errorInfo: 'CreateRegistration succeeded but response body is empty'
553
+ });
554
+ }
555
+ let data;
556
+ try {
557
+ data = JSON.parse(text);
558
+ }
559
+ catch {
560
+ throw ERROR_FACTORY.create("fid-registration-failed" /* ErrorCode.FID_REGISTRATION_FAILED */, {
561
+ errorInfo: 'CreateRegistration succeeded but response body is not valid JSON'
562
+ });
563
+ }
564
+ const name = data.name;
565
+ if (typeof name !== 'string' || name.length === 0) {
566
+ throw ERROR_FACTORY.create("fid-registration-failed" /* ErrorCode.FID_REGISTRATION_FAILED */, {
567
+ errorInfo: 'CreateRegistration succeeded but response did not include a non-empty name'
568
+ });
569
+ }
570
+ return parseFidFromRegistrationResourceName(name);
571
+ }
572
+ const REGISTRATIONS_NAME_SEGMENT = '/registrations/';
573
+ /** Extracts the Firebase Installation ID from CreateRegistration `name` (resource path). */
574
+ function parseFidFromRegistrationResourceName(name) {
575
+ const segmentIndex = name.indexOf(REGISTRATIONS_NAME_SEGMENT);
576
+ if (segmentIndex !== -1) {
577
+ const fid = name.slice(segmentIndex + REGISTRATIONS_NAME_SEGMENT.length);
578
+ if (fid.length > 0) {
579
+ return fid;
580
+ }
581
+ }
582
+ throw ERROR_FACTORY.create("fid-registration-failed" /* ErrorCode.FID_REGISTRATION_FAILED */, {
583
+ errorInfo: 'CreateRegistration succeeded but response name is not a valid registration resource name'
584
+ });
585
+ }
387
586
  async function requestUpdateToken(firebaseDependencies, tokenDetails) {
388
587
  const headers = await getHeaders(firebaseDependencies);
389
- const body = getBody(tokenDetails.subscriptionOptions);
588
+ const body = getBody(tokenDetails.subscriptionOptions, firebaseDependencies.appConfig.appName,
589
+ /* includeSdkVersion= */ false);
390
590
  const updateOptions = {
391
591
  method: 'PATCH',
392
592
  headers,
@@ -435,6 +635,26 @@ async function requestDeleteToken(firebaseDependencies, token) {
435
635
  });
436
636
  }
437
637
  }
638
+ /**
639
+ * Re-runs `operation` when it throws, with exponential backoff between attempts.
640
+ * Rethrows the last error if all attempts fail.
641
+ */
642
+ async function fetchWithExponentialRetry(operation, maxAttempts, baseBackoffMs) {
643
+ let lastError;
644
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
645
+ try {
646
+ return await operation();
647
+ }
648
+ catch (err) {
649
+ lastError = err;
650
+ if (attempt < maxAttempts - 1) {
651
+ const delayMs = baseBackoffMs * Math.pow(2, attempt);
652
+ await new Promise(resolve => setTimeout(resolve, delayMs));
653
+ }
654
+ }
655
+ }
656
+ throw lastError;
657
+ }
438
658
  function getEndpoint({ projectId }) {
439
659
  return `${ENDPOINT}/projects/${projectId}/registrations`;
440
660
  }
@@ -447,14 +667,45 @@ async function getHeaders({ appConfig, installations }) {
447
667
  'x-goog-firebase-installations-auth': `FIS ${authToken}`
448
668
  });
449
669
  }
450
- function getBody({ p256dh, auth, endpoint, vapidKey }) {
670
+ /**
671
+ * Hostname for the registering web client (e.g. `www.example.com`), or the app name
672
+ * (`appNameFallback`) when the scope cannot be resolved (e.g. some test environments).
673
+ */
674
+ function getRegistrationOrigin(swScope, appNameFallback) {
675
+ try {
676
+ if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(swScope)) {
677
+ return new URL(swScope).host;
678
+ }
679
+ }
680
+ catch {
681
+ // Fall through to relative-scope handling.
682
+ }
683
+ try {
684
+ if (typeof self !== 'undefined' && self.location?.href) {
685
+ return new URL(swScope, self.location.origin).host;
686
+ }
687
+ }
688
+ catch {
689
+ // Fall through.
690
+ }
691
+ if (typeof self !== 'undefined' && self.location?.host) {
692
+ return self.location.host;
693
+ }
694
+ return appNameFallback;
695
+ }
696
+ function getBody({ p256dh, auth, endpoint, vapidKey, swScope }, appNameFallback, includeSdkVersion) {
451
697
  const body = {
452
698
  web: {
699
+ origin: getRegistrationOrigin(swScope, appNameFallback),
453
700
  endpoint,
454
701
  auth,
455
702
  p256dh
456
703
  }
457
704
  };
705
+ if (includeSdkVersion) {
706
+ // eslint-disable-next-line camelcase
707
+ body.fcm_sdk_version = version;
708
+ }
458
709
  if (vapidKey !== DEFAULT_VAPID_KEY) {
459
710
  body.web.applicationPubKey = vapidKey;
460
711
  }
@@ -480,7 +731,7 @@ function getBody({ p256dh, auth, endpoint, vapidKey }) {
480
731
  // UpdateRegistration will be called once every week.
481
732
  const TOKEN_EXPIRATION_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
482
733
  async function getTokenInternal(messaging) {
483
- const pushSubscription = await getPushSubscription(messaging.swRegistration, messaging.vapidKey);
734
+ const pushSubscription = await getPushSubscription$1(messaging.swRegistration, messaging.vapidKey);
484
735
  const subscriptionOptions = {
485
736
  vapidKey: messaging.vapidKey,
486
737
  swScope: messaging.swRegistration.scope,
@@ -518,14 +769,42 @@ async function getTokenInternal(messaging) {
518
769
  }
519
770
  }
520
771
  /**
521
- * This method deletes the token from the database, unsubscribes the token from FCM, and unregisters
522
- * the push subscription if it exists.
772
+ * Legacy getToken() path: there is a token row in IndexedDB. Revoke it with FCM, drop the row, and
773
+ * clear any leftover FID registration metadata (apps may mix APIs).
774
+ */
775
+ async function revokeLegacyFcmTokenAndClearCaches(messaging, tokenDetails) {
776
+ await requestDeleteToken(messaging.firebaseDependencies, tokenDetails.token);
777
+ await dbRemove(messaging.firebaseDependencies);
778
+ await removeFidRegistrationBestEffort(messaging.firebaseDependencies);
779
+ }
780
+ /**
781
+ * No legacy token row: the client may only have FID-based registration (register() flow). If so,
782
+ * delete that registration on the server, always scrub local FID metadata, then surface
783
+ * onUnregistered when we actually had an FID.
784
+ */
785
+ async function revokeFidRegistrationIfStored(messaging) {
786
+ const stored = await dbGetFidRegistration(messaging.firebaseDependencies).catch(() => undefined);
787
+ const fid = stored?.fid;
788
+ if (fid) {
789
+ await requestDeleteRegistration(messaging.firebaseDependencies, fid);
790
+ }
791
+ await removeFidRegistrationBestEffort(messaging.firebaseDependencies);
792
+ if (fid) {
793
+ notifyOnUnregistered(messaging, fid);
794
+ }
795
+ }
796
+ /**
797
+ * Revokes the app's FCM registration: legacy token (getToken/deleteToken) and/or FID-based
798
+ * registration (register/unregister), clears local caches, notifies onUnregistered when a stored
799
+ * FID existed, then unsubscribes the push subscription when present.
523
800
  */
524
- async function deleteTokenInternal(messaging) {
801
+ async function revokeRegistrationInternal(messaging) {
525
802
  const tokenDetails = await dbGet(messaging.firebaseDependencies);
526
803
  if (tokenDetails) {
527
- await requestDeleteToken(messaging.firebaseDependencies, tokenDetails.token);
528
- await dbRemove(messaging.firebaseDependencies);
804
+ await revokeLegacyFcmTokenAndClearCaches(messaging, tokenDetails);
805
+ }
806
+ else {
807
+ await revokeFidRegistrationIfStored(messaging);
529
808
  }
530
809
  // Unsubscribe from the push subscription.
531
810
  const pushSubscription = await messaging.swRegistration.pushManager.getSubscription();
@@ -563,7 +842,7 @@ async function getNewToken(firebaseDependencies, subscriptionOptions) {
563
842
  /**
564
843
  * Gets a PushSubscription for the current user.
565
844
  */
566
- async function getPushSubscription(swRegistration, vapidKey) {
845
+ async function getPushSubscription$1(swRegistration, vapidKey) {
567
846
  const subscription = await swRegistration.pushManager.getSubscription();
568
847
  if (subscription) {
569
848
  return subscription;
@@ -585,6 +864,338 @@ function isTokenValid(dbOptions, currentOptions) {
585
864
  const isP256dhEqual = currentOptions.p256dh === dbOptions.p256dh;
586
865
  return isVapidKeyEqual && isEndpointEqual && isAuthEqual && isP256dhEqual;
587
866
  }
867
+ /** Clears FID registration metadata; apps may mix legacy getToken() with FID register/unregister. */
868
+ async function removeFidRegistrationBestEffort(firebaseDependencies) {
869
+ try {
870
+ await dbRemoveFidRegistration(firebaseDependencies);
871
+ }
872
+ catch {
873
+ // Ignore.
874
+ }
875
+ }
876
+ function notifyOnRegistered(messaging, fid) {
877
+ const handler = messaging.onRegisteredHandler;
878
+ if (!handler) {
879
+ return;
880
+ }
881
+ if (typeof handler === 'function') {
882
+ handler(fid);
883
+ }
884
+ else {
885
+ handler.next(fid);
886
+ }
887
+ }
888
+ function notifyOnUnregistered(messaging, fid) {
889
+ const handler = messaging.onUnregisteredHandler;
890
+ if (!handler) {
891
+ return;
892
+ }
893
+ if (typeof handler === 'function') {
894
+ handler(fid);
895
+ }
896
+ else {
897
+ handler.next(fid);
898
+ }
899
+ }
900
+
901
+ /**
902
+ * @license
903
+ * Copyright 2020 Google LLC
904
+ *
905
+ * Licensed under the Apache License, Version 2.0 (the "License");
906
+ * you may not use this file except in compliance with the License.
907
+ * You may obtain a copy of the License at
908
+ *
909
+ * http://www.apache.org/licenses/LICENSE-2.0
910
+ *
911
+ * Unless required by applicable law or agreed to in writing, software
912
+ * distributed under the License is distributed on an "AS IS" BASIS,
913
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
914
+ * See the License for the specific language governing permissions and
915
+ * limitations under the License.
916
+ */
917
+ async function registerDefaultSw(messaging) {
918
+ try {
919
+ messaging.swRegistration = await navigator.serviceWorker.register(DEFAULT_SW_PATH, {
920
+ scope: DEFAULT_SW_SCOPE
921
+ });
922
+ // The timing when browser updates sw when sw has an update is unreliable from experiment. It
923
+ // leads to version conflict when the SDK upgrades to a newer version in the main page, but sw
924
+ // is stuck with the old version. For example,
925
+ // https://github.com/firebase/firebase-js-sdk/issues/2590 The following line reliably updates
926
+ // sw if there was an update.
927
+ messaging.swRegistration.update().catch(() => {
928
+ /* it is non blocking and we don't care if it failed */
929
+ });
930
+ await waitForRegistrationActive(messaging.swRegistration);
931
+ }
932
+ catch (e) {
933
+ throw ERROR_FACTORY.create("failed-service-worker-registration" /* ErrorCode.FAILED_DEFAULT_REGISTRATION */, {
934
+ browserErrorMessage: e?.message
935
+ });
936
+ }
937
+ }
938
+ /**
939
+ * Waits for registration to become active. MDN documentation claims that
940
+ * a service worker registration should be ready to use after awaiting
941
+ * navigator.serviceWorker.register() but that doesn't seem to be the case in
942
+ * practice, causing the SDK to throw errors when calling
943
+ * swRegistration.pushManager.subscribe() too soon after register(). The only
944
+ * solution seems to be waiting for the service worker registration `state`
945
+ * to become "active".
946
+ */
947
+ async function waitForRegistrationActive(registration) {
948
+ return new Promise((resolve, reject) => {
949
+ const rejectTimeout = setTimeout(() => reject(new Error(`Service worker not registered after ${DEFAULT_REGISTRATION_TIMEOUT} ms`)), DEFAULT_REGISTRATION_TIMEOUT);
950
+ const incomingSw = registration.installing || registration.waiting;
951
+ if (registration.active) {
952
+ clearTimeout(rejectTimeout);
953
+ resolve();
954
+ }
955
+ else if (incomingSw) {
956
+ incomingSw.onstatechange = ev => {
957
+ if (ev.target?.state === 'activated') {
958
+ incomingSw.onstatechange = null;
959
+ clearTimeout(rejectTimeout);
960
+ resolve();
961
+ }
962
+ };
963
+ }
964
+ else {
965
+ clearTimeout(rejectTimeout);
966
+ reject(new Error('No incoming service worker found.'));
967
+ }
968
+ });
969
+ }
970
+
971
+ /**
972
+ * @license
973
+ * Copyright 2020 Google LLC
974
+ *
975
+ * Licensed under the Apache License, Version 2.0 (the "License");
976
+ * you may not use this file except in compliance with the License.
977
+ * You may obtain a copy of the License at
978
+ *
979
+ * http://www.apache.org/licenses/LICENSE-2.0
980
+ *
981
+ * Unless required by applicable law or agreed to in writing, software
982
+ * distributed under the License is distributed on an "AS IS" BASIS,
983
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
984
+ * See the License for the specific language governing permissions and
985
+ * limitations under the License.
986
+ */
987
+ async function updateSwReg(messaging, swRegistration) {
988
+ if (!swRegistration && !messaging.swRegistration) {
989
+ await registerDefaultSw(messaging);
990
+ }
991
+ if (!swRegistration && !!messaging.swRegistration) {
992
+ return;
993
+ }
994
+ if (!(swRegistration instanceof ServiceWorkerRegistration)) {
995
+ throw ERROR_FACTORY.create("invalid-sw-registration" /* ErrorCode.INVALID_SW_REGISTRATION */);
996
+ }
997
+ messaging.swRegistration = swRegistration;
998
+ }
999
+
1000
+ /**
1001
+ * @license
1002
+ * Copyright 2020 Google LLC
1003
+ *
1004
+ * Licensed under the Apache License, Version 2.0 (the "License");
1005
+ * you may not use this file except in compliance with the License.
1006
+ * You may obtain a copy of the License at
1007
+ *
1008
+ * http://www.apache.org/licenses/LICENSE-2.0
1009
+ *
1010
+ * Unless required by applicable law or agreed to in writing, software
1011
+ * distributed under the License is distributed on an "AS IS" BASIS,
1012
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1013
+ * See the License for the specific language governing permissions and
1014
+ * limitations under the License.
1015
+ */
1016
+ async function updateVapidKey(messaging, vapidKey) {
1017
+ if (!!vapidKey) {
1018
+ messaging.vapidKey = vapidKey;
1019
+ }
1020
+ else if (!messaging.vapidKey) {
1021
+ messaging.vapidKey = DEFAULT_VAPID_KEY;
1022
+ }
1023
+ }
1024
+
1025
+ /**
1026
+ * @license
1027
+ * Copyright 2020 Google LLC
1028
+ *
1029
+ * Licensed under the Apache License, Version 2.0 (the "License");
1030
+ * you may not use this file except in compliance with the License.
1031
+ * You may obtain a copy of the License at
1032
+ *
1033
+ * http://www.apache.org/licenses/LICENSE-2.0
1034
+ *
1035
+ * Unless required by applicable law or agreed to in writing, software
1036
+ * distributed under the License is distributed on an "AS IS" BASIS,
1037
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1038
+ * See the License for the specific language governing permissions and
1039
+ * limitations under the License.
1040
+ */
1041
+ /** Retries when CreateRegistration echoes an FID that does not match Installations.getId(). */
1042
+ const FID_REGISTRATION_FID_MATCH_MAX_ATTEMPTS = 3;
1043
+ /**
1044
+ * For the new FID-based register path:
1045
+ * - Create (or refresh) an FCM Web registration in the backend via CreateRegistration.
1046
+ * - Use the FIS auth token produced by the installations instance (implicitly associated with FID).
1047
+ * - CreateRegistration must echo the installation in `name` (e.g.
1048
+ * `projects/{projectId}/registrations/{fid}`); it must match `expectedFid` from
1049
+ * Installations.getId(). On mismatch we refresh the auth token and retry, then fail with
1050
+ * `fid-registration-failed`.
1051
+ */
1052
+ async function registerFcmRegistrationWithFid(messaging, expectedFid) {
1053
+ const pushSubscription = await getPushSubscription(messaging.swRegistration, messaging.vapidKey);
1054
+ const subscriptionOptions = {
1055
+ vapidKey: messaging.vapidKey,
1056
+ swScope: messaging.swRegistration.scope,
1057
+ endpoint: pushSubscription.endpoint,
1058
+ auth: arrayToBase64(pushSubscription.getKey('auth')),
1059
+ p256dh: arrayToBase64(pushSubscription.getKey('p256dh'))
1060
+ };
1061
+ const installations = messaging.firebaseDependencies.installations;
1062
+ for (let attempt = 0; attempt < FID_REGISTRATION_FID_MATCH_MAX_ATTEMPTS; attempt++) {
1063
+ const { responseFid } = await requestCreateRegistration(messaging.firebaseDependencies, subscriptionOptions);
1064
+ if (responseFid === expectedFid) {
1065
+ return;
1066
+ }
1067
+ // If CreateRegistration echoes an unexpected FID, the FIS auth token used for the request may
1068
+ // be stale relative to the installation the backend associates with the call. Force-refresh
1069
+ // the token before retrying so the next attempt uses credentials aligned with Installations.
1070
+ if (attempt < FID_REGISTRATION_FID_MATCH_MAX_ATTEMPTS - 1) {
1071
+ await installations.getToken(true);
1072
+ }
1073
+ }
1074
+ throw ERROR_FACTORY.create("fid-registration-failed" /* ErrorCode.FID_REGISTRATION_FAILED */, {
1075
+ errorInfo: 'CreateRegistration response FID does not match Firebase Installation ID'
1076
+ });
1077
+ }
1078
+ async function getPushSubscription(swRegistration, vapidKey) {
1079
+ const subscription = await swRegistration.pushManager.getSubscription();
1080
+ if (subscription) {
1081
+ return subscription;
1082
+ }
1083
+ // Chrome/Firefox require applicationServerKey to be of type Uint8Array.
1084
+ return swRegistration.pushManager.subscribe({
1085
+ userVisibleOnly: true,
1086
+ // `PushManager.subscribe` expects a `BufferSource`; `base64ToArray` produces a typed array.
1087
+ // Cast to satisfy the lib typing differences across TS DOM versions.
1088
+ applicationServerKey: base64ToArray(vapidKey)
1089
+ });
1090
+ }
1091
+
1092
+ /**
1093
+ * @license
1094
+ * Copyright 2020 Google LLC
1095
+ *
1096
+ * Licensed under the Apache License, Version 2.0 (the "License");
1097
+ * you may not use this file except in compliance with the License.
1098
+ * You may obtain a copy of the License at
1099
+ *
1100
+ * http://www.apache.org/licenses/LICENSE-2.0
1101
+ *
1102
+ * Unless required by applicable law or agreed to in writing, software
1103
+ * distributed under the License is distributed on an "AS IS" BASIS,
1104
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1105
+ * See the License for the specific language governing permissions and
1106
+ * limitations under the License.
1107
+ */
1108
+ const FID_REGISTRATION_REFRESH_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
1109
+ /**
1110
+ * Registers the app instance with FCM using its Firebase Installation ID (FID). The FID is
1111
+ * delivered via the `onRegistered` callback. Call this to establish an FID-based identity.
1112
+ * Once `onRegistered` provides an FID, instruct your backend to remove any legacy token
1113
+ * previously associated with this instance. The backend send API supports FID as a target.
1114
+ *
1115
+ * When called multiple times, `onRegistered` is invoked on each call with the current FID.
1116
+ * Backend registration sync runs on first register, when the FID changes, or on weekly refresh.
1117
+ *
1118
+ * @param messaging - The MessagingService instance.
1119
+ * @param options - Optional. Same options as getToken (vapidKey, serviceWorkerRegistration).
1120
+ */
1121
+ async function register$1(messaging, options) {
1122
+ if (!navigator) {
1123
+ throw ERROR_FACTORY.create("only-available-in-window" /* ErrorCode.AVAILABLE_IN_WINDOW */);
1124
+ }
1125
+ if (Notification.permission === 'default') {
1126
+ await Notification.requestPermission();
1127
+ }
1128
+ if (Notification.permission !== 'granted') {
1129
+ throw ERROR_FACTORY.create("permission-blocked" /* ErrorCode.PERMISSION_BLOCKED */);
1130
+ }
1131
+ if (!messaging.onRegisteredHandler) {
1132
+ throw ERROR_FACTORY.create("invalid-on-registered-handler" /* ErrorCode.INVALID_ON_REGISTERED_HANDLER */);
1133
+ }
1134
+ await updateVapidKey(messaging, options?.vapidKey);
1135
+ await updateSwReg(messaging, options?.serviceWorkerRegistration);
1136
+ // Keep the queue alive after a failed register() so future calls can retry.
1137
+ const prev = messaging._registerNotifyChain.catch(() => { });
1138
+ messaging._registerNotifyChain = prev.then(async () => {
1139
+ const fid = await messaging.firebaseDependencies.installations.getId();
1140
+ const stored = await dbGetFidRegistration(messaging.firebaseDependencies);
1141
+ const now = Date.now();
1142
+ const shouldRefresh = !stored ||
1143
+ stored.fid !== fid ||
1144
+ now >= stored.lastRegisterTime + FID_REGISTRATION_REFRESH_MS;
1145
+ if (shouldRefresh) {
1146
+ await registerFcmRegistrationWithFid(messaging, fid);
1147
+ await dbSetFidRegistration(messaging.firebaseDependencies, {
1148
+ fid,
1149
+ lastRegisterTime: now,
1150
+ vapidKey: messaging.vapidKey
1151
+ });
1152
+ }
1153
+ const handler = messaging.onRegisteredHandler;
1154
+ if (!handler) {
1155
+ throw ERROR_FACTORY.create("invalid-on-registered-handler" /* ErrorCode.INVALID_ON_REGISTERED_HANDLER */);
1156
+ }
1157
+ notifyOnRegistered(messaging, fid);
1158
+ });
1159
+ return messaging._registerNotifyChain;
1160
+ }
1161
+
1162
+ /**
1163
+ * @license
1164
+ * Copyright 2026 Google LLC
1165
+ *
1166
+ * Licensed under the Apache License, Version 2.0 (the "License");
1167
+ * you may not use this file except in compliance with the License.
1168
+ * You may obtain a copy of the License at
1169
+ *
1170
+ * http://www.apache.org/licenses/LICENSE-2.0
1171
+ *
1172
+ * Unless required by applicable law or agreed to in writing, software
1173
+ * distributed under the License is distributed on an "AS IS" BASIS,
1174
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1175
+ * See the License for the specific language governing permissions and
1176
+ * limitations under the License.
1177
+ */
1178
+ /**
1179
+ * When the Firebase Installation ID changes, re-run `register()` so FCM registration and
1180
+ * onRegistered run for the new FID. No-op if no onRegistered handler is set or the app
1181
+ * instance was never registered with FCM.
1182
+ */
1183
+ function subscribeFidChangeRegistration(messaging, installations) {
1184
+ return onIdChange(installations, () => {
1185
+ void (async () => {
1186
+ if (!messaging.onRegisteredHandler) {
1187
+ return;
1188
+ }
1189
+ const stored = await dbGetFidRegistration(messaging.firebaseDependencies);
1190
+ if (!stored) {
1191
+ return;
1192
+ }
1193
+ await register$1(messaging).catch(() => {
1194
+ // Best-effort: permission may be revoked or SW unavailable after FID rotation.
1195
+ });
1196
+ })();
1197
+ });
1198
+ }
588
1199
 
589
1200
  /**
590
1201
  * @license
@@ -783,8 +1394,25 @@ class MessagingService {
783
1394
  this.deliveryMetricsExportedToBigQueryEnabled = false;
784
1395
  this.onBackgroundMessageHandler = null;
785
1396
  this.onMessageHandler = null;
1397
+ /** Observer for the event that the app instance is registered with FCM via Firebase Installation ID (FID). */
1398
+ this.onRegisteredHandler = null;
1399
+ /** Observer for the event that the app instance is unregistered from FCM (FID no longer active). */
1400
+ this.onUnregisteredHandler = null;
1401
+ /**
1402
+ * Serializes the FID get + compare + notify step so concurrent register() calls
1403
+ * do not race each other.
1404
+ */
1405
+ this._registerNotifyChain = Promise.resolve();
1406
+ /** Unsubscribe from Installations `onIdChange` when messaging is deleted. */
1407
+ this._fidChangeUnsubscribe = null;
786
1408
  this.logEvents = [];
787
- this.isLogServiceStarted = false;
1409
+ /**
1410
+ * Single source of truth for the logging loop lifecycle.
1411
+ *
1412
+ * `scheduled` holds the active timer id; `flushing` indicates an async dispatch
1413
+ * is in progress (prevents duplicate starts); `stopped` means idle.
1414
+ */
1415
+ this.logQueue = { state: 'stopped' };
788
1416
  const appConfig = extractAppConfig(app);
789
1417
  this.firebaseDependencies = {
790
1418
  app,
@@ -794,131 +1422,15 @@ class MessagingService {
794
1422
  };
795
1423
  }
796
1424
  _delete() {
797
- return Promise.resolve();
798
- }
799
- }
800
-
801
- /**
802
- * @license
803
- * Copyright 2020 Google LLC
804
- *
805
- * Licensed under the Apache License, Version 2.0 (the "License");
806
- * you may not use this file except in compliance with the License.
807
- * You may obtain a copy of the License at
808
- *
809
- * http://www.apache.org/licenses/LICENSE-2.0
810
- *
811
- * Unless required by applicable law or agreed to in writing, software
812
- * distributed under the License is distributed on an "AS IS" BASIS,
813
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
814
- * See the License for the specific language governing permissions and
815
- * limitations under the License.
816
- */
817
- async function registerDefaultSw(messaging) {
818
- try {
819
- messaging.swRegistration = await navigator.serviceWorker.register(DEFAULT_SW_PATH, {
820
- scope: DEFAULT_SW_SCOPE
821
- });
822
- // The timing when browser updates sw when sw has an update is unreliable from experiment. It
823
- // leads to version conflict when the SDK upgrades to a newer version in the main page, but sw
824
- // is stuck with the old version. For example,
825
- // https://github.com/firebase/firebase-js-sdk/issues/2590 The following line reliably updates
826
- // sw if there was an update.
827
- messaging.swRegistration.update().catch(() => {
828
- /* it is non blocking and we don't care if it failed */
829
- });
830
- await waitForRegistrationActive(messaging.swRegistration);
831
- }
832
- catch (e) {
833
- throw ERROR_FACTORY.create("failed-service-worker-registration" /* ErrorCode.FAILED_DEFAULT_REGISTRATION */, {
834
- browserErrorMessage: e?.message
835
- });
836
- }
837
- }
838
- /**
839
- * Waits for registration to become active. MDN documentation claims that
840
- * a service worker registration should be ready to use after awaiting
841
- * navigator.serviceWorker.register() but that doesn't seem to be the case in
842
- * practice, causing the SDK to throw errors when calling
843
- * swRegistration.pushManager.subscribe() too soon after register(). The only
844
- * solution seems to be waiting for the service worker registration `state`
845
- * to become "active".
846
- */
847
- async function waitForRegistrationActive(registration) {
848
- return new Promise((resolve, reject) => {
849
- const rejectTimeout = setTimeout(() => reject(new Error(`Service worker not registered after ${DEFAULT_REGISTRATION_TIMEOUT} ms`)), DEFAULT_REGISTRATION_TIMEOUT);
850
- const incomingSw = registration.installing || registration.waiting;
851
- if (registration.active) {
852
- clearTimeout(rejectTimeout);
853
- resolve();
854
- }
855
- else if (incomingSw) {
856
- incomingSw.onstatechange = ev => {
857
- if (ev.target?.state === 'activated') {
858
- incomingSw.onstatechange = null;
859
- clearTimeout(rejectTimeout);
860
- resolve();
861
- }
862
- };
1425
+ if (this._fidChangeUnsubscribe) {
1426
+ this._fidChangeUnsubscribe();
1427
+ this._fidChangeUnsubscribe = null;
863
1428
  }
864
- else {
865
- clearTimeout(rejectTimeout);
866
- reject(new Error('No incoming service worker found.'));
1429
+ if (this.logQueue.state === 'scheduled') {
1430
+ clearTimeout(this.logQueue.timerId);
867
1431
  }
868
- });
869
- }
870
-
871
- /**
872
- * @license
873
- * Copyright 2020 Google LLC
874
- *
875
- * Licensed under the Apache License, Version 2.0 (the "License");
876
- * you may not use this file except in compliance with the License.
877
- * You may obtain a copy of the License at
878
- *
879
- * http://www.apache.org/licenses/LICENSE-2.0
880
- *
881
- * Unless required by applicable law or agreed to in writing, software
882
- * distributed under the License is distributed on an "AS IS" BASIS,
883
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
884
- * See the License for the specific language governing permissions and
885
- * limitations under the License.
886
- */
887
- async function updateSwReg(messaging, swRegistration) {
888
- if (!swRegistration && !messaging.swRegistration) {
889
- await registerDefaultSw(messaging);
890
- }
891
- if (!swRegistration && !!messaging.swRegistration) {
892
- return;
893
- }
894
- if (!(swRegistration instanceof ServiceWorkerRegistration)) {
895
- throw ERROR_FACTORY.create("invalid-sw-registration" /* ErrorCode.INVALID_SW_REGISTRATION */);
896
- }
897
- messaging.swRegistration = swRegistration;
898
- }
899
-
900
- /**
901
- * @license
902
- * Copyright 2020 Google LLC
903
- *
904
- * Licensed under the Apache License, Version 2.0 (the "License");
905
- * you may not use this file except in compliance with the License.
906
- * You may obtain a copy of the License at
907
- *
908
- * http://www.apache.org/licenses/LICENSE-2.0
909
- *
910
- * Unless required by applicable law or agreed to in writing, software
911
- * distributed under the License is distributed on an "AS IS" BASIS,
912
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
913
- * See the License for the specific language governing permissions and
914
- * limitations under the License.
915
- */
916
- async function updateVapidKey(messaging, vapidKey) {
917
- if (!!vapidKey) {
918
- messaging.vapidKey = vapidKey;
919
- }
920
- else if (!messaging.vapidKey) {
921
- messaging.vapidKey = DEFAULT_VAPID_KEY;
1432
+ this.logQueue = { state: 'stopped' };
1433
+ return Promise.resolve();
922
1434
  }
923
1435
  }
924
1436
 
@@ -1022,6 +1534,16 @@ async function messageEventListener(messaging, event) {
1022
1534
  messaging.onMessageHandler.next(externalizePayload(internalPayload));
1023
1535
  }
1024
1536
  }
1537
+ if (messaging.onRegisteredHandler &&
1538
+ internalPayload.messageType === MessageType.FID_REGISTERED) {
1539
+ const fid = internalPayload.fid;
1540
+ if (typeof messaging.onRegisteredHandler === 'function') {
1541
+ messaging.onRegisteredHandler(fid);
1542
+ }
1543
+ else {
1544
+ messaging.onRegisteredHandler.next(fid);
1545
+ }
1546
+ }
1025
1547
  // Log to Scion if applicable
1026
1548
  const dataPayload = internalPayload.data;
1027
1549
  if (isConsoleMessage(dataPayload) &&
@@ -1030,9 +1552,6 @@ async function messageEventListener(messaging, event) {
1030
1552
  }
1031
1553
  }
1032
1554
 
1033
- const name = "@firebase/messaging";
1034
- const version = "0.12.26";
1035
-
1036
1555
  /**
1037
1556
  * @license
1038
1557
  * Copyright 2020 Google LLC
@@ -1052,6 +1571,7 @@ const version = "0.12.26";
1052
1571
  const WindowMessagingFactory = (container) => {
1053
1572
  const messaging = new MessagingService(container.getProvider('app').getImmediate(), container.getProvider('installations-internal').getImmediate(), container.getProvider('analytics-internal'));
1054
1573
  navigator.serviceWorker.addEventListener('message', e => messageEventListener(messaging, e));
1574
+ messaging._fidChangeUnsubscribe = subscribeFidChangeRegistration(messaging, container.getProvider('installations').getImmediate());
1055
1575
  return messaging;
1056
1576
  };
1057
1577
  const WindowMessagingInternalFactory = (container) => {
@@ -1059,7 +1579,8 @@ const WindowMessagingInternalFactory = (container) => {
1059
1579
  .getProvider('messaging')
1060
1580
  .getImmediate();
1061
1581
  const messagingInternal = {
1062
- getToken: (options) => getToken$1(messaging, options)
1582
+ getToken: (options) => getToken$1(messaging, options),
1583
+ register: (options) => register$1(messaging, options)
1063
1584
  };
1064
1585
  return messagingInternal;
1065
1586
  };
@@ -1139,7 +1660,7 @@ async function deleteToken$1(messaging) {
1139
1660
  if (!messaging.swRegistration) {
1140
1661
  await registerDefaultSw(messaging);
1141
1662
  }
1142
- return deleteTokenInternal(messaging);
1663
+ return revokeRegistrationInternal(messaging);
1143
1664
  }
1144
1665
 
1145
1666
  /**
@@ -1168,6 +1689,129 @@ function onMessage$1(messaging, nextOrObserver) {
1168
1689
  };
1169
1690
  }
1170
1691
 
1692
+ /**
1693
+ * @license
1694
+ * Copyright 2020 Google LLC
1695
+ *
1696
+ * Licensed under the Apache License, Version 2.0 (the "License");
1697
+ * you may not use this file except in compliance with the License.
1698
+ * You may obtain a copy of the License at
1699
+ *
1700
+ * http://www.apache.org/licenses/LICENSE-2.0
1701
+ *
1702
+ * Unless required by applicable law or agreed to in writing, software
1703
+ * distributed under the License is distributed on an "AS IS" BASIS,
1704
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1705
+ * See the License for the specific language governing permissions and
1706
+ * limitations under the License.
1707
+ */
1708
+ /**
1709
+ * Subscribes to an event that the app instance is registered with FCM via Firebase Installation ID (FID).
1710
+ * Use the FID passed to the callback to upload it to your application server.
1711
+ *
1712
+ * @param messaging - The {@link MessagingService} instance.
1713
+ * @param nextOrObserver - A function or observer object called when an FID is registered.
1714
+ * @returns Unsubscribe function to stop listening.
1715
+ */
1716
+ function onRegistered$1(messaging, nextOrObserver) {
1717
+ messaging.onRegisteredHandler = nextOrObserver;
1718
+ return () => {
1719
+ if (messaging.onRegisteredHandler === nextOrObserver) {
1720
+ messaging.onRegisteredHandler = null;
1721
+ }
1722
+ };
1723
+ }
1724
+
1725
+ /**
1726
+ * @license
1727
+ * Copyright 2026 Google LLC
1728
+ *
1729
+ * Licensed under the Apache License, Version 2.0 (the "License");
1730
+ * you may not use this file except in compliance with the License.
1731
+ * You may obtain a copy of the License at
1732
+ *
1733
+ * http://www.apache.org/licenses/LICENSE-2.0
1734
+ *
1735
+ * Unless required by applicable law or agreed to in writing, software
1736
+ * distributed under the License is distributed on an "AS IS" BASIS,
1737
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1738
+ * See the License for the specific language governing permissions and
1739
+ * limitations under the License.
1740
+ */
1741
+ /**
1742
+ * Subscribes to an event that the app instance is unregistered from FCM so the FID is no longer active.
1743
+ * Use this to notify your backend to remove this FID to prevent 404 errors on send.
1744
+ *
1745
+ * @param messaging - The {@link MessagingService} instance.
1746
+ * @param nextOrObserver - A function or observer object called with the unregistered FID.
1747
+ * @returns Unsubscribe function to stop listening.
1748
+ */
1749
+ function onUnregistered$1(messaging, nextOrObserver) {
1750
+ messaging.onUnregisteredHandler = nextOrObserver;
1751
+ return () => {
1752
+ if (messaging.onUnregisteredHandler === nextOrObserver) {
1753
+ messaging.onUnregisteredHandler = null;
1754
+ }
1755
+ };
1756
+ }
1757
+
1758
+ /**
1759
+ * @license
1760
+ * Copyright 2026 Google LLC
1761
+ *
1762
+ * Licensed under the Apache License, Version 2.0 (the "License");
1763
+ * you may not use this file except in compliance with the License.
1764
+ * You may obtain a copy of the License at
1765
+ *
1766
+ * http://www.apache.org/licenses/LICENSE-2.0
1767
+ *
1768
+ * Unless required by applicable law or agreed to in writing, software
1769
+ * distributed under the License is distributed on an "AS IS" BASIS,
1770
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1771
+ * See the License for the specific language governing permissions and
1772
+ * limitations under the License.
1773
+ */
1774
+ /**
1775
+ * Unregisters the app instance from FCM by deleting its FID-based registration.
1776
+ *
1777
+ * On success, triggers the `onUnregistered` callback (if set) with the unregistered FID.
1778
+ *
1779
+ * @param messaging - The MessagingService instance.
1780
+ */
1781
+ async function unregister$1(messaging) {
1782
+ if (!navigator) {
1783
+ throw ERROR_FACTORY.create("only-available-in-window" /* ErrorCode.AVAILABLE_IN_WINDOW */);
1784
+ }
1785
+ // Prefer the last successfully registered FID from local metadata when available.
1786
+ const stored = await dbGetFidRegistration(messaging.firebaseDependencies).catch(() => undefined);
1787
+ const fid = stored?.fid ?? (await messaging.firebaseDependencies.installations.getId());
1788
+ await requestDeleteRegistration(messaging.firebaseDependencies, fid);
1789
+ // Best-effort local cleanup; still resolve even if schema is unavailable.
1790
+ try {
1791
+ await dbRemoveFidRegistration(messaging.firebaseDependencies);
1792
+ }
1793
+ catch {
1794
+ // Ignore.
1795
+ }
1796
+ // Best-effort cleanup of legacy token details created via getToken().
1797
+ try {
1798
+ await dbRemove(messaging.firebaseDependencies);
1799
+ }
1800
+ catch {
1801
+ // Ignore.
1802
+ }
1803
+ const handler = messaging.onUnregisteredHandler;
1804
+ if (!handler) {
1805
+ return;
1806
+ }
1807
+ if (typeof handler === 'function') {
1808
+ handler(fid);
1809
+ }
1810
+ else {
1811
+ handler.next(fid);
1812
+ }
1813
+ }
1814
+
1171
1815
  /**
1172
1816
  * @license
1173
1817
  * Copyright 2017 Google LLC
@@ -1220,6 +1864,9 @@ function getMessagingInWindow(app = getApp()) {
1220
1864
  *
1221
1865
  * @returns The promise resolves with an FCM registration token.
1222
1866
  *
1867
+ * @deprecated Use {@link register} together with {@link onRegistered} for Firebase
1868
+ * Installation ID-based messaging instead of retrieving an FCM registration token with this API.
1869
+ *
1223
1870
  * @public
1224
1871
  */
1225
1872
  async function getToken(messaging, options) {
@@ -1230,10 +1877,18 @@ async function getToken(messaging, options) {
1230
1877
  * Deletes the registration token associated with this {@link Messaging} instance and unsubscribes
1231
1878
  * the {@link Messaging} instance from the push subscription.
1232
1879
  *
1880
+ * If there is no legacy registration token but the client has FID-based registration metadata
1881
+ * (from {@link register}), this deletes that registration on the server, clears local metadata, and
1882
+ * invokes {@link onUnregistered} with the removed FID when successful.
1883
+ *
1233
1884
  * @param messaging - The {@link Messaging} instance.
1234
1885
  *
1235
1886
  * @returns The promise resolves when the token has been successfully deleted.
1236
1887
  *
1888
+ * @deprecated Use {@link onUnregistered} to observe when the client is no longer
1889
+ * registered and update your backend accordingly, instead of explicitly deleting the
1890
+ * registration token with this API.
1891
+ *
1237
1892
  * @public
1238
1893
  */
1239
1894
  function deleteToken(messaging) {
@@ -1257,6 +1912,64 @@ function onMessage(messaging, nextOrObserver) {
1257
1912
  messaging = getModularInstance(messaging);
1258
1913
  return onMessage$1(messaging, nextOrObserver);
1259
1914
  }
1915
+ /**
1916
+ * Registers the app instance with FCM using its Firebase Installation ID (FID). The FID is
1917
+ * delivered via the {@link onRegistered} callback, not as a return value. Call this to establish
1918
+ * an FID-based identity; once {@link onRegistered} provides an FID, instruct your backend to
1919
+ * remove any legacy token previously associated with this instance. The backend send API
1920
+ * supports FID as a target.
1921
+ *
1922
+ * @param messaging - The {@link Messaging} instance.
1923
+ * @param options - Optional. VAPID key and/or service worker registration (same as getToken).
1924
+ * @returns Promise that resolves when registration has been initiated; FID is delivered via onRegistered.
1925
+ *
1926
+ * @public
1927
+ */
1928
+ async function register(messaging, options) {
1929
+ messaging = getModularInstance(messaging);
1930
+ return register$1(messaging, options);
1931
+ }
1932
+ /**
1933
+ * Unregisters the app instance from FCM by deleting its FID-based registration.
1934
+ * On success, triggers {@link onUnregistered} (if registered) with the unregistered FID.
1935
+ *
1936
+ * @param messaging - The {@link Messaging} instance.
1937
+ *
1938
+ * @public
1939
+ */
1940
+ async function unregister(messaging) {
1941
+ messaging = getModularInstance(messaging);
1942
+ return unregister$1(messaging);
1943
+ }
1944
+ /**
1945
+ * Subscribes to an event that the app instance is registered with FCM via Firebase Installation ID (FID).
1946
+ * Use the FID passed to the callback to upload it to your application server. When you receive an FID
1947
+ * after calling {@link register}, instruct your backend to remove any legacy token for this instance.
1948
+ *
1949
+ * @param messaging - The {@link Messaging} instance.
1950
+ * @param nextOrObserver - A function or observer object called when an FID is registered.
1951
+ * @returns Unsubscribe function to stop listening.
1952
+ *
1953
+ * @public
1954
+ */
1955
+ function onRegistered(messaging, nextOrObserver) {
1956
+ messaging = getModularInstance(messaging);
1957
+ return onRegistered$1(messaging, nextOrObserver);
1958
+ }
1959
+ /**
1960
+ * Subscribes to an event that the app instance is unregistered from FCM (FID no longer active).
1961
+ * Use this to notify your backend to remove this FID to prevent 404 errors on send.
1962
+ *
1963
+ * @param messaging - The {@link Messaging} instance.
1964
+ * @param nextOrObserver - A function or observer object called with the unregistered FID.
1965
+ * @returns Unsubscribe function to stop listening.
1966
+ *
1967
+ * @public
1968
+ */
1969
+ function onUnregistered(messaging, nextOrObserver) {
1970
+ messaging = getModularInstance(messaging);
1971
+ return onUnregistered$1(messaging, nextOrObserver);
1972
+ }
1260
1973
 
1261
1974
  /**
1262
1975
  * The Firebase Cloud Messaging Web SDK.
@@ -1266,5 +1979,5 @@ function onMessage(messaging, nextOrObserver) {
1266
1979
  */
1267
1980
  registerMessagingInWindow();
1268
1981
 
1269
- export { deleteToken, getMessagingInWindow as getMessaging, getToken, isWindowSupported as isSupported, onMessage };
1982
+ export { deleteToken, getMessagingInWindow as getMessaging, getToken, isWindowSupported as isSupported, onMessage, onRegistered, onUnregistered, register, unregister };
1270
1983
  //# sourceMappingURL=index.esm.js.map