Skip to content

Add async isSupported to Modular FM SDK #4665

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Apr 7, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions packages-exp/messaging-exp/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,21 @@
* limitations under the License.
*/

import { FirebaseApp, _getProvider } 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 } 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.
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -15,21 +15,21 @@
* 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';

export async function isWindowSupported(): Promise<boolean> {
// 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.
let isDBOpenable: boolean;
try {
isDBOpenable = await validateIndexedDBOpenable();
} catch (error) {
return false;
}
}

/**
* Checks to see if the required APIs exist.
*/
function isWindowControllerSupported(): boolean {
return (
isDBOpenable &&
'indexedDB' in window &&
indexedDB !== null &&
navigator.cookieEnabled &&
Expand All @@ -45,8 +45,19 @@ function isWindowControllerSupported(): boolean {
/**
* Checks to see if the required APIs exist within SW Context.
*/
function isSWControllerSupported(): boolean {
export async function isSwSupported(): Promise<boolean> {
// 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.
let isDBOpenable: boolean;
try {
isDBOpenable = await validateIndexedDBOpenable();
} catch (error) {
return false;
}

return (
isDBOpenable &&
'indexedDB' in self &&
indexedDB !== null &&
'PushManager' in self &&
Expand Down
54 changes: 47 additions & 7 deletions packages-exp/messaging-exp/src/helpers/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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 registerWindowMessaging(): void {
_registerComponent(
new Component('messaging-exp', WindowMessagingFactory, ComponentType.PUBLIC)
);
}

export function registerSwMessaging(): void {
_registerComponent(
new Component('messaging-exp', messagingFactory, ComponentType.PUBLIC)
new Component('messaging-exp', SwMessagingFactory, ComponentType.PUBLIC)
);
}
8 changes: 5 additions & 3 deletions packages-exp/messaging-exp/src/index.sw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,18 @@
* 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 {
'messaging-exp': FirebaseMessaging;
}
}

registerMessaging();
registerMessagingInSw();
8 changes: 5 additions & 3 deletions packages-exp/messaging-exp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,19 @@
* 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,
onMessage,
getMessaging,
onBackgroundMessage
} from './api';
export { isWindowSupported as isSupported } from './api/isSupported';
export * from './interfaces/public-types';

declare module '@firebase/component' {
Expand All @@ -34,4 +36,4 @@ declare module '@firebase/component' {
}
}

registerMessaging();
registerMessagingInWindow();
3 changes: 3 additions & 0 deletions packages-exp/messaging-exp/src/util/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -50,6 +51,8 @@ export const ERROR_MAP: ErrorMap<ErrorCode> = {
'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]:
Expand Down
9 changes: 6 additions & 3 deletions packages/util/src/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,21 +144,24 @@ 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<boolean> {
return new Promise((resolve, reject) => {
try {
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);
};
Expand Down