package client import ( "context" "fmt" firebase "firebase.google.com/go/v4" "firebase.google.com/go/v4/messaging" "google.golang.org/api/option" ) type FCMConfig interface { GetCredentialsFile() string GetProjectID() string } type FCMClient interface { SendNotification(ctx context.Context, token string, title string, body string, data map[string]string) error SendMulticastNotification(ctx context.Context, tokens []string, title string, body string, data map[string]string) error SendToTopic(ctx context.Context, topic string, title string, body string, data map[string]string) error } type fcmClient struct { messaging *messaging.Client } func NewFCMClient(cfg FCMConfig) (FCMClient, error) { ctx := context.Background() opt := option.WithCredentialsFile(cfg.GetCredentialsFile()) app, err := firebase.NewApp(ctx, &firebase.Config{ ProjectID: cfg.GetProjectID(), }, opt) if err != nil { return nil, fmt.Errorf("failed to initialize firebase app: %w", err) } msgClient, err := app.Messaging(ctx) if err != nil { return nil, fmt.Errorf("failed to initialize firebase messaging client: %w", err) } return &fcmClient{ messaging: msgClient, }, nil } // SendNotification sends a push notification to a single device token. func (f *fcmClient) SendNotification(ctx context.Context, token string, title string, body string, data map[string]string) error { message := &messaging.Message{ Token: token, Notification: &messaging.Notification{ Title: title, Body: body, }, Data: data, Android: &messaging.AndroidConfig{ Priority: "high", }, APNS: &messaging.APNSConfig{ Payload: &messaging.APNSPayload{ Aps: &messaging.Aps{ Sound: "default", }, }, }, } _, err := f.messaging.Send(ctx, message) if err != nil { return fmt.Errorf("failed to send FCM notification: %w", err) } return nil } // SendMulticastNotification sends a push notification to multiple device tokens. func (f *fcmClient) SendMulticastNotification(ctx context.Context, tokens []string, title string, body string, data map[string]string) error { if len(tokens) == 0 { return nil } message := &messaging.MulticastMessage{ Tokens: tokens, Notification: &messaging.Notification{ Title: title, Body: body, }, Data: data, Android: &messaging.AndroidConfig{ Priority: "high", }, APNS: &messaging.APNSConfig{ Payload: &messaging.APNSPayload{ Aps: &messaging.Aps{ Sound: "default", }, }, }, } batchResp, err := f.messaging.SendEachForMulticast(ctx, message) if err != nil { return fmt.Errorf("failed to send FCM multicast notification: %w", err) } if batchResp.FailureCount > 0 { return fmt.Errorf("FCM multicast: %d/%d messages failed to send", batchResp.FailureCount, len(tokens)) } return nil } // SendToTopic sends a push notification to all devices subscribed to a topic. func (f *fcmClient) SendToTopic(ctx context.Context, topic string, title string, body string, data map[string]string) error { message := &messaging.Message{ Topic: topic, Notification: &messaging.Notification{ Title: title, Body: body, }, Data: data, Android: &messaging.AndroidConfig{ Priority: "high", }, APNS: &messaging.APNSConfig{ Payload: &messaging.APNSPayload{ Aps: &messaging.Aps{ Sound: "default", }, }, }, } _, err := f.messaging.Send(ctx, message) if err != nil { return fmt.Errorf("failed to send FCM topic notification: %w", err) } return nil }