package notify import ( "context" "os" "sync" "time" firebase "firebase.google.com/go/v4" "firebase.google.com/go/v4/messaging" "doormile/utils" "google.golang.org/api/option" ) var ( fcmClient *messaging.Client fcmOnce sync.Once ) // InitFCM initializes the Firebase Admin SDK using the service account JSON file // at FIREBASE_SERVICE_ACCOUNT_PATH. Safe to call from main() at startup. // If the env var is unset or the file is invalid, FCM is disabled for the process // lifetime — all SendToDevice calls become no-ops. func InitFCM() { fcmOnce.Do(func() { path := os.Getenv("FIREBASE_SERVICE_ACCOUNT_PATH") if path == "" { utils.Warn("FCM: FIREBASE_SERVICE_ACCOUNT_PATH not set — push notifications disabled") return } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() app, err := firebase.NewApp(ctx, nil, option.WithCredentialsFile(path)) if err != nil { utils.Error("FCM: failed to initialize Firebase app", "error", err) return } client, err := app.Messaging(ctx) if err != nil { utils.Error("FCM: failed to get Messaging client", "error", err) return } fcmClient = client utils.Info("FCM: initialized successfully") }) } // SendToDevice sends an FCM notification to a single device token. // Returns nil (no-op) if FCM was not initialized or the token is empty. // The caller should log errors but must not block the booking flow on failure. func SendToDevice(token, title, body string, data map[string]string) error { if fcmClient == nil || token == "" { return nil } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() msg := &messaging.Message{ Notification: &messaging.Notification{ Title: title, Body: body, }, Data: data, Token: token, } _, err := fcmClient.Send(ctx, msg) return err }