diff --git a/packages-exp/messaging-exp/src/api.ts b/packages-exp/messaging-exp/src/api.ts index e65f31a2f2b..7c8c74c9277 100644 --- a/packages-exp/messaging-exp/src/api.ts +++ b/packages-exp/messaging-exp/src/api.ts @@ -15,22 +15,21 @@ * limitations under the License. */ +import { FirebaseApp, _getProvider, getApp } from '@firebase/app-exp'; +import { FirebaseMessaging, MessagePayload } from './interfaces/public-types'; import { - FirebaseMessaging, - MessagePayload, NextFn, Observer, - Unsubscribe -} from './interfaces/public-types'; + Unsubscribe, + getModularInstance +} from '@firebase/util'; import { MessagingService } from './messaging-service'; import { Provider } from '@firebase/component'; import { deleteToken as _deleteToken } from './api/deleteToken'; -import { _getProvider, FirebaseApp, getApp } from '@firebase/app-exp'; import { getToken as _getToken } from './api/getToken'; import { onBackgroundMessage as _onBackgroundMessage } from './api/onBackgroundMessage'; import { onMessage as _onMessage } from './api/onMessage'; -import { getModularInstance } from '@firebase/util'; /** * Retrieves a firebase messaging instance. @@ -131,7 +130,7 @@ export function onMessage( * * @returns To stop listening for messages execute this returned function * - * make it internal to hide it from the browser entrypoint + * make it internal to hide it from the browser entry point. * @internal */ export function onBackgroundMessage( diff --git a/packages-exp/messaging-exp/src/helpers/isSupported.ts b/packages-exp/messaging-exp/src/api/isSupported.ts similarity index 58% rename from packages-exp/messaging-exp/src/helpers/isSupported.ts rename to packages-exp/messaging-exp/src/api/isSupported.ts index c73122eaad1..634854c786a 100644 --- a/packages-exp/messaging-exp/src/helpers/isSupported.ts +++ b/packages-exp/messaging-exp/src/api/isSupported.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,21 +15,14 @@ * limitations under the License. */ -export function isSupported(): boolean { - if (self && 'ServiceWorkerGlobalScope' in self) { - // Running in ServiceWorker context - return isSWControllerSupported(); - } else { - // Assume we are in the window context. - return isWindowControllerSupported(); - } -} +import { validateIndexedDBOpenable } from '@firebase/util'; -/** - * Checks to see if the required APIs exist. - */ -function isWindowControllerSupported(): boolean { +export async function isWindowSupported(): Promise { + // firebase-js-sdk/issues/2393 reveals that idb#open in Safari iframe and Firefox private browsing + // might be prohibited to run. In these contexts, an error would be thrown during the messaging + // instantiating phase, informing the developers to import/call isSupported for special handling. return ( + (await validateIndexedDBOpenable()) && 'indexedDB' in window && indexedDB !== null && navigator.cookieEnabled && @@ -45,8 +38,12 @@ function isWindowControllerSupported(): boolean { /** * Checks to see if the required APIs exist within SW Context. */ -function isSWControllerSupported(): boolean { +export async function isSwSupported(): Promise { + // firebase-js-sdk/issues/2393 reveals that idb#open in Safari iframe and Firefox private browsing + // might be prohibited to run. In these contexts, an error would be thrown during the messaging + // instantiating phase, informing the developers to import/call isSupported for special handling. return ( + (await validateIndexedDBOpenable()) && 'indexedDB' in self && indexedDB !== null && 'PushManager' in self && diff --git a/packages-exp/messaging-exp/src/helpers/register.ts b/packages-exp/messaging-exp/src/helpers/register.ts index cb6b99259bb..ed79d6513c9 100644 --- a/packages-exp/messaging-exp/src/helpers/register.ts +++ b/packages-exp/messaging-exp/src/helpers/register.ts @@ -22,17 +22,27 @@ import { InstanceFactory } from '@firebase/component'; import { ERROR_FACTORY, ErrorCode } from '../util/errors'; +import { isSwSupported, isWindowSupported } from '../api/isSupported'; import { MessagingService } from '../messaging-service'; import { _registerComponent } from '@firebase/app-exp'; -import { isSupported } from './isSupported'; -const messagingFactory: InstanceFactory<'messaging-exp'> = ( +const WindowMessagingFactory: InstanceFactory<'messaging-exp'> = ( container: ComponentContainer ) => { - if (!isSupported()) { - throw ERROR_FACTORY.create(ErrorCode.UNSUPPORTED_BROWSER); - } + // Conscious decision to make this async check non-blocking during the messaging instance + // initialization phase for performance consideration. An error would be thrown latter for + // developer's information. Developers can then choose to import and call `isSupported` for + // special handling. + isWindowSupported() + .then(isSupported => { + if (!isSupported) { + throw ERROR_FACTORY.create(ErrorCode.UNSUPPORTED_BROWSER); + } + }) + .catch(_ => { + throw ERROR_FACTORY.create(ErrorCode.INDEXED_DB_UNSUPPORTED); + }); return new MessagingService( container.getProvider('app-exp').getImmediate(), @@ -41,8 +51,38 @@ const messagingFactory: InstanceFactory<'messaging-exp'> = ( ); }; -export function registerMessaging(): void { +const SwMessagingFactory: InstanceFactory<'messaging-exp'> = ( + container: ComponentContainer +) => { + // Conscious decision to make this async check non-blocking during the messaging instance + // initialization phase for performance consideration. An error would be thrown latter for + // developer's information. Developers can then choose to import and call `isSupported` for + // special handling. + isSwSupported() + .then(isSupported => { + if (!isSupported) { + throw ERROR_FACTORY.create(ErrorCode.UNSUPPORTED_BROWSER); + } + }) + .catch(_ => { + throw ERROR_FACTORY.create(ErrorCode.INDEXED_DB_UNSUPPORTED); + }); + + return new MessagingService( + container.getProvider('app-exp').getImmediate(), + container.getProvider('installations-exp-internal').getImmediate(), + container.getProvider('analytics-internal') + ); +}; + +export function registerMessagingInWindow(): void { + _registerComponent( + new Component('messaging-exp', WindowMessagingFactory, ComponentType.PUBLIC) + ); +} + +export function registerMessagingInSw(): void { _registerComponent( - new Component('messaging-exp', messagingFactory, ComponentType.PUBLIC) + new Component('messaging-exp', SwMessagingFactory, ComponentType.PUBLIC) ); } diff --git a/packages-exp/messaging-exp/src/index.sw.ts b/packages-exp/messaging-exp/src/index.sw.ts index 75e588fc62a..3ca89665377 100644 --- a/packages-exp/messaging-exp/src/index.sw.ts +++ b/packages-exp/messaging-exp/src/index.sw.ts @@ -15,11 +15,13 @@ * limitations under the License. */ -import { FirebaseMessaging } from './interfaces/public-types'; -import { registerMessaging } from './helpers/register'; import '@firebase/installations-exp'; +import { FirebaseMessaging } from './interfaces/public-types'; +import { registerMessagingInSw } from './helpers/register'; + export { onBackgroundMessage, getMessaging } from './api'; +export { isSwSupported as isSupported } from './api/isSupported'; declare module '@firebase/component' { interface NameServiceMapping { @@ -27,4 +29,4 @@ declare module '@firebase/component' { } } -registerMessaging(); +registerMessagingInSw(); diff --git a/packages-exp/messaging-exp/src/index.ts b/packages-exp/messaging-exp/src/index.ts index 1265d573eb8..ba6a8a348da 100644 --- a/packages-exp/messaging-exp/src/index.ts +++ b/packages-exp/messaging-exp/src/index.ts @@ -15,10 +15,11 @@ * limitations under the License. */ -import { FirebaseMessaging } from './interfaces/public-types'; -import { registerMessaging } from './helpers/register'; import '@firebase/installations-exp'; +import { FirebaseMessaging } from './interfaces/public-types'; +import { registerMessagingInWindow } from './helpers/register'; + export { getToken, deleteToken, @@ -26,6 +27,7 @@ export { getMessaging, onBackgroundMessage } from './api'; +export { isWindowSupported as isSupported } from './api/isSupported'; export * from './interfaces/public-types'; declare module '@firebase/component' { @@ -34,4 +36,4 @@ declare module '@firebase/component' { } } -registerMessaging(); +registerMessagingInWindow(); diff --git a/packages-exp/messaging-exp/src/util/errors.ts b/packages-exp/messaging-exp/src/util/errors.ts index 6a8fca2fdef..14b001b6dc2 100644 --- a/packages-exp/messaging-exp/src/util/errors.ts +++ b/packages-exp/messaging-exp/src/util/errors.ts @@ -24,6 +24,7 @@ export const enum ErrorCode { PERMISSION_DEFAULT = 'permission-default', PERMISSION_BLOCKED = 'permission-blocked', UNSUPPORTED_BROWSER = 'unsupported-browser', + INDEXED_DB_UNSUPPORTED = 'indexed-db-unsupported', FAILED_DEFAULT_REGISTRATION = 'failed-service-worker-registration', TOKEN_SUBSCRIBE_FAILED = 'token-subscribe-failed', TOKEN_SUBSCRIBE_NO_TOKEN = 'token-subscribe-no-token', @@ -50,6 +51,8 @@ export const ERROR_MAP: ErrorMap = { 'The notification permission was not granted and blocked instead.', [ErrorCode.UNSUPPORTED_BROWSER]: "This browser doesn't support the API's required to use the firebase SDK.", + [ErrorCode.INDEXED_DB_UNSUPPORTED]: + "This browser doesn't support indexedDb.open() (ex. Safari iFrame, Firefox Private Browsing, etc)", [ErrorCode.FAILED_DEFAULT_REGISTRATION]: 'We are unable to register the default service worker. {$browserErrorMessage}', [ErrorCode.TOKEN_SUBSCRIBE_FAILED]: diff --git a/packages/util/src/environment.ts b/packages/util/src/environment.ts index 8e903010564..92fdd4478c7 100644 --- a/packages/util/src/environment.ts +++ b/packages/util/src/environment.ts @@ -144,8 +144,11 @@ export function isIndexedDBAvailable(): boolean { } /** - * This method validates browser context for indexedDB by opening a dummy indexedDB database and reject + * This method validates browser/sw context for indexedDB by opening a dummy indexedDB database and reject * if errors occur during the database open operation. + * + * @throws exception if current browser/sw context can't run idb.open (ex: Safari iframe, Firefox + * private browsing) */ export function validateIndexedDBOpenable(): Promise { return new Promise((resolve, reject) => { @@ -153,12 +156,12 @@ export function validateIndexedDBOpenable(): Promise { let preExist: boolean = true; const DB_CHECK_NAME = 'validate-browser-context-for-indexeddb-analytics-module'; - const request = window.indexedDB.open(DB_CHECK_NAME); + const request = self.indexedDB.open(DB_CHECK_NAME); request.onsuccess = () => { request.result.close(); // delete database only when it doesn't pre-exist if (!preExist) { - window.indexedDB.deleteDatabase(DB_CHECK_NAME); + self.indexedDB.deleteDatabase(DB_CHECK_NAME); } resolve(true); };