notification-kit
Version:
A unified notification library for React + Capacitor apps. One API for push notifications, in-app notifications, and local notifications across Web, iOS, and Android.
599 lines (598 loc) • 17 kB
JavaScript
import { L as Logger, D as DynamicLoader } from "./inApp-ClGwbWFU.js";
import { i as isFirebaseAppConfig, C as ConfigValidator } from "./config-validator-CiAZwcZR.js";
class FirebaseNativeBridge {
static isInitialized = false;
/**
* Initialize Firebase on native platforms with runtime configuration
*/
static async initializeNative(config) {
if (this.isInitialized) {
Logger.warn("Firebase native bridge already initialized");
return;
}
const isNative = await DynamicLoader.isNativePlatform();
if (!isNative) {
return;
}
const platform = await DynamicLoader.getPlatform();
try {
this.validateConfig(config);
await this.configureNativePlatform(platform, config);
this.isInitialized = true;
Logger.info(`Firebase native bridge initialized for ${platform}`);
} catch (error) {
Logger.error("Failed to initialize Firebase native bridge:", error);
throw error;
}
}
/**
* Validate Firebase configuration
*/
static validateConfig(config) {
if (isFirebaseAppConfig(config)) {
Logger.debug("Firebase configuration uses existing app instance");
return;
}
const requiredFields = [
"apiKey",
"authDomain",
"projectId",
"storageBucket",
"messagingSenderId",
"appId"
];
const missingFields = requiredFields.filter((field) => !(field in config));
if (missingFields.length > 0) {
throw new Error(`Missing required Firebase configuration fields: ${missingFields.join(", ")}`);
}
Logger.debug("Firebase configuration validated", {
hasApiKey: "apiKey" in config,
hasAuthDomain: "authDomain" in config,
hasProjectId: "projectId" in config,
hasMessagingSenderId: "messagingSenderId" in config,
hasAppId: "appId" in config,
hasVapidKey: "vapidKey" in config
});
}
/**
* Configure Firebase for specific native platform
*/
static async configureNativePlatform(platform, config) {
if (platform === "ios") {
await this.configureIOS(config);
} else if (platform === "android") {
await this.configureAndroid(config);
}
}
/**
* Configure Firebase for iOS
*
* IMPORTANT: This approach ensures that:
* 1. GoogleService-Info.plist is NOT required in the repository
* 2. Firebase configuration is provided at runtime
* 3. Sensitive data never appears in version control
*/
static async configureIOS(_config) {
Logger.debug("iOS Firebase configuration prepared (credentials hidden)");
}
/**
* Configure Firebase for Android
*
* IMPORTANT: This approach ensures that:
* 1. google-services.json is NOT required in the repository
* 2. Firebase configuration is provided at runtime
* 3. Sensitive data never appears in version control
*/
static async configureAndroid(_config) {
Logger.debug("Android Firebase configuration prepared (credentials hidden)");
}
/**
* Get initialization status
*/
static isNativeInitialized() {
return this.isInitialized;
}
/**
* Reset initialization (useful for testing)
*/
static reset() {
this.isInitialized = false;
}
/**
* Validate environment variables
*/
static validateEnvironmentVariables() {
const requiredEnvVars = [
"FIREBASE_API_KEY",
"FIREBASE_AUTH_DOMAIN",
"FIREBASE_PROJECT_ID",
"FIREBASE_STORAGE_BUCKET",
"FIREBASE_MESSAGING_SENDER_ID",
"FIREBASE_APP_ID"
];
const missingEnvVars = requiredEnvVars.filter((envVar) => !process.env[envVar]);
if (missingEnvVars.length > 0) {
Logger.warn(
`Missing Firebase environment variables: ${missingEnvVars.join(", ")}. Make sure to set these in your .env file or deployment environment.`
);
}
}
}
class FirebaseProvider {
name = "firebase";
type = "firebase";
app = null;
messaging = null;
config = null;
currentToken = null;
messageListeners = [];
tokenListeners = [];
errorListeners = [];
unsubscribeMessage = null;
/**
* Initialize Firebase provider
*/
async init(config) {
try {
const { isFirebaseAppConfig: isFirebaseAppConfig2 } = await import("./config-validator-CiAZwcZR.js").then((n) => n.t);
this.config = config;
if (isFirebaseAppConfig2(config)) {
this.app = config.app;
} else {
ConfigValidator.validateFirebaseConfig(config);
ConfigValidator.validateEnvironmentVariables("firebase");
const isNative = await DynamicLoader.isNativePlatform();
if (isNative) {
await FirebaseNativeBridge.initializeNative(config);
}
const firebaseConfig = {
apiKey: config.apiKey,
authDomain: config.authDomain,
projectId: config.projectId,
storageBucket: config.storageBucket,
messagingSenderId: config.messagingSenderId,
appId: config.appId
};
if ("measurementId" in config && config.measurementId) {
firebaseConfig.measurementId = config.measurementId;
}
const firebaseApp = await DynamicLoader.loadFirebase();
if (!firebaseApp) {
throw new Error("Firebase is required but not installed");
}
this.app = firebaseApp.initializeApp(firebaseConfig);
}
if (await this.isSupported()) {
await this.initializeMessaging();
}
} catch (error) {
this.handleError(new Error(`Firebase initialization failed: ${error}`));
throw error;
}
}
/**
* Destroy Firebase provider
*/
async destroy() {
try {
if (this.unsubscribeMessage) {
this.unsubscribeMessage();
this.unsubscribeMessage = null;
}
if (this.currentToken && this.messaging) {
const firebaseMessaging = await DynamicLoader.loadFirebaseMessaging();
if (firebaseMessaging) {
await firebaseMessaging.deleteToken(this.messaging);
}
}
this.app = null;
this.messaging = null;
this.config = null;
this.currentToken = null;
this.messageListeners = [];
this.tokenListeners = [];
this.errorListeners = [];
} catch (error) {
this.handleError(new Error(`Firebase destroy failed: ${error}`));
throw error;
}
}
/**
* Request notification permission
*/
async requestPermission() {
try {
const isNative = await DynamicLoader.isNativePlatform();
if (isNative) {
return await this.requestNativePermission();
} else {
return await this.requestWebPermission();
}
} catch (error) {
this.handleError(new Error(`Permission request failed: ${error}`));
return false;
}
}
/**
* Check notification permission status
*/
async checkPermission() {
try {
const isNative = await DynamicLoader.isNativePlatform();
if (isNative) {
return await this.checkNativePermission();
} else {
return await this.checkWebPermission();
}
} catch (error) {
this.handleError(new Error(`Permission check failed: ${error}`));
return "denied";
}
}
/**
* Get FCM token
*/
async getToken() {
if (!this.messaging) {
throw new Error("Firebase messaging not initialized");
}
try {
const firebaseMessaging = await DynamicLoader.loadFirebaseMessaging();
if (!firebaseMessaging) {
throw new Error("Firebase messaging is required but not installed");
}
const token = await firebaseMessaging.getToken(
this.messaging,
this.config?.vapidKey ? {
vapidKey: this.config.vapidKey
} : void 0
);
if (token) {
this.currentToken = token;
return token;
} else {
throw new Error("No registration token available");
}
} catch (error) {
this.handleError(new Error(`Token retrieval failed: ${error}`));
throw error;
}
}
/**
* Refresh FCM token
*/
async refreshToken() {
if (!this.messaging) {
throw new Error("Firebase messaging not initialized");
}
try {
if (this.currentToken) {
const firebaseMessaging = await DynamicLoader.loadFirebaseMessaging();
if (firebaseMessaging) {
await firebaseMessaging.deleteToken(this.messaging);
}
}
const newToken = await this.getToken();
this.notifyTokenListeners(newToken);
return newToken;
} catch (error) {
this.handleError(new Error(`Token refresh failed: ${error}`));
throw error;
}
}
/**
* Delete FCM token
*/
async deleteToken() {
if (!this.messaging) {
throw new Error("Firebase messaging not initialized");
}
try {
const firebaseMessaging = await DynamicLoader.loadFirebaseMessaging();
if (!firebaseMessaging) {
throw new Error("Firebase messaging is required but not installed");
}
await firebaseMessaging.deleteToken(this.messaging);
this.currentToken = null;
} catch (error) {
this.handleError(new Error(`Token deletion failed: ${error}`));
throw error;
}
}
/**
* Subscribe to topic
*/
async subscribe(topic) {
if (!this.currentToken) {
throw new Error("No FCM token available");
}
await this.callTopicAPI("subscribe", topic, this.currentToken);
}
/**
* Unsubscribe from topic
*/
async unsubscribe(topic) {
if (!this.currentToken) {
throw new Error("No FCM token available");
}
await this.callTopicAPI("unsubscribe", topic, this.currentToken);
}
/**
* Get subscribed topics
*/
async getSubscriptions() {
if (!this.currentToken) {
throw new Error("No FCM token available");
}
return [];
}
/**
* Send notification (server-side only)
*/
async sendNotification(_payload) {
try {
throw new Error("Client-side notification sending not supported");
} catch (error) {
this.handleError(new Error(`Send notification failed: ${error}`));
throw error;
}
}
/**
* Listen for messages
*/
onMessage(callback) {
this.messageListeners.push(callback);
return () => {
const index = this.messageListeners.indexOf(callback);
if (index > -1) {
this.messageListeners.splice(index, 1);
}
};
}
/**
* Listen for token refresh
*/
onTokenRefresh(callback) {
this.tokenListeners.push(callback);
return () => {
const index = this.tokenListeners.indexOf(callback);
if (index > -1) {
this.tokenListeners.splice(index, 1);
}
};
}
/**
* Listen for errors
*/
onError(callback) {
this.errorListeners.push(callback);
return () => {
const index = this.errorListeners.indexOf(callback);
if (index > -1) {
this.errorListeners.splice(index, 1);
}
};
}
/**
* Check if Firebase messaging is supported
*/
async isSupported() {
try {
const isNative = await DynamicLoader.isNativePlatform();
if (isNative) {
return true;
} else {
const firebaseMessaging = await DynamicLoader.loadFirebaseMessaging();
return firebaseMessaging ? await firebaseMessaging.isSupported() : false;
}
} catch (_error) {
return false;
}
}
/**
* Get provider capabilities
*/
async getCapabilities() {
const isWeb = !await DynamicLoader.isNativePlatform();
return {
topics: true,
scheduling: false,
// Server-side only
analytics: true,
segmentation: true,
templates: false,
webhooks: false,
batch: false,
priority: true,
ttl: true,
collapse: true,
pushNotifications: true,
richMedia: true,
actions: true,
backgroundSync: true,
geofencing: false,
inAppMessages: false,
webPush: isWeb,
badges: !isWeb,
sounds: true,
vibration: !isWeb,
lights: !isWeb,
bigText: !isWeb,
bigPicture: !isWeb,
inbox: false,
progress: false,
channels: !isWeb,
groups: !isWeb,
categories: false,
quietHours: false,
deliveryReceipts: true,
clickTracking: true,
impressionTracking: true,
customData: true,
multipleDevices: true,
userTags: false,
triggers: false,
abTesting: false,
automation: false,
journeys: false,
realTimeUpdates: true
};
}
/**
* Initialize Firebase messaging
*/
async initializeMessaging() {
if (!this.app) {
throw new Error("Firebase app not initialized");
}
try {
const firebaseMessaging = await DynamicLoader.loadFirebaseMessaging();
if (!firebaseMessaging) {
throw new Error("Firebase messaging is required but not installed");
}
this.messaging = firebaseMessaging.getMessaging(this.app);
this.unsubscribeMessage = firebaseMessaging.onMessage(
this.messaging,
(payload) => {
const notificationPayload = {
title: "",
body: "",
data: payload.data || {},
...payload.from && { to: payload.from },
...payload.collapseKey && { collapseKey: payload.collapseKey }
};
if (payload.notification) {
const notification = {};
if (payload.notification.title)
notification.title = payload.notification.title;
if (payload.notification.body)
notification.body = payload.notification.body;
if (payload.notification.icon)
notification.icon = payload.notification.icon;
if (payload.notification.image)
notification.image = payload.notification.image;
if (payload.data) notification.data = payload.data;
notificationPayload.notification = notification;
}
this.notifyMessageListeners(notificationPayload);
}
);
} catch (error) {
throw new Error(`Firebase messaging initialization failed: ${error}`);
}
}
/**
* Request native permission
*/
async requestNativePermission() {
try {
const pushNotificationsModule = await DynamicLoader.loadPushNotifications();
if (!pushNotificationsModule) {
throw new Error("Push notifications require @capacitor/push-notifications");
}
const { PushNotifications } = pushNotificationsModule;
const result = await PushNotifications.requestPermissions();
return result.receive === "granted";
} catch (_error) {
return false;
}
}
/**
* Check native permission
*/
async checkNativePermission() {
try {
const pushNotificationsModule = await DynamicLoader.loadPushNotifications();
if (!pushNotificationsModule) {
throw new Error("Push notifications require @capacitor/push-notifications");
}
const { PushNotifications } = pushNotificationsModule;
const result = await PushNotifications.checkPermissions();
if (result.receive === "granted") {
return "granted";
} else if (result.receive === "denied") {
return "denied";
} else if (result.receive === "prompt") {
return "prompt";
} else {
return "unknown";
}
} catch (_error) {
return "denied";
}
}
/**
* Request web permission
*/
async requestWebPermission() {
if (!("Notification" in window)) {
return false;
}
if (Notification.permission === "granted") {
return true;
}
if (Notification.permission === "denied") {
return false;
}
const permission = await Notification.requestPermission();
return permission === "granted";
}
/**
* Check web permission
*/
async checkWebPermission() {
if (!("Notification" in window)) {
return "denied";
}
const permission = Notification.permission;
if (permission === "default") {
return "prompt";
}
return permission;
}
/**
* Call topic API (placeholder)
*/
async callTopicAPI(action, _topic, _token) {
throw new Error(`Topic ${action} must be implemented server-side`);
}
/**
* Notify message listeners
*/
notifyMessageListeners(payload) {
this.messageListeners.forEach((listener) => {
try {
listener(payload);
} catch (error) {
this.handleError(new Error(`Message listener error: ${error}`));
}
});
}
/**
* Notify token listeners
*/
notifyTokenListeners(token) {
this.tokenListeners.forEach((listener) => {
try {
listener(token);
} catch (error) {
this.handleError(new Error(`Token listener error: ${error}`));
}
});
}
/**
* Handle errors
*/
handleError(error) {
this.errorListeners.forEach((listener) => {
try {
listener(error);
} catch (listenerError) {
}
});
}
}
export {
FirebaseProvider
};
//# sourceMappingURL=FirebaseProvider-CbcLVLcm.js.map