UNPKG

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
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