שליחת הודעות למספר מכשירים בפלטפורמות של Apple

כדי לטרגט הודעה למספר מכשירים, משתמשים בהודעות בנושאים. התכונה הזו מאפשרת לשלוח הודעה לכמה מכשירים שהביעו הסכמה לקבלת עדכונים בנושא מסוים.

המדריך הזה מתמקד בשליחת הודעות בנושאים משרת האפליקציה באמצעות Admin SDK או API ל-REST עבור FCM, ובקבלה שלהן וטיפול בהן באפליקציה של Apple. בדף הזה מפורטים כל השלבים לביצוע הפעולות האלה, מההגדרה ועד האימות. לכן יכול להיות שהוא יכיל שלבים שכבר ביצעתם אם הגדרתם אפליקציית לקוח של Apple עבור FCM או אם ביצעתם את השלבים לשליחת ההודעה הראשונה.

הוספת Firebase לפרויקט ב-Apple

בקטע הזה נסביר על משימות שאולי כבר ביצעתם אם כבר הפעלתם באפליקציה תכונות אחרות של Firebase. ספציפית ל-FCM, תצטרכו להעלות את מפתח האימות של APNs ולהירשם לקבלת התראות מרחוק.

דרישות מוקדמות

  • מתקינים את הרכיבים הבאים:

    • Xcode 15.2 ואילך
  • עליכם לוודא שהפרויקט עומד בדרישות הבאות:

    • הפרויקט צריך לטרגט את גרסאות הפלטפורמה הבאות או גרסאות מתקדמות יותר:
      • iOS 13
      • macOS 10.15
      • tvOS 13
      • watchOS 7
  • מגדירים מכשיר פיזי של Apple כדי להפעיל את האפליקציה, ומבצעים את הפעולות הבאות:

אם עדיין אין לכם פרויקט Xcode ואתם רק רוצים לנסות מוצר של Firebase, תוכלו להוריד אחד מדוגמאות למתחילים.

יצירת פרויקט Firebase

לפני שמוסיפים את Firebase לאפליקציה ל-Apple, צריך ליצור פרויקט Firebase כדי לקשר אותו לאפליקציה. מידע נוסף על פרויקטים ב-Firebase זמין במאמר הסבר על פרויקטים ב-Firebase.

רישום האפליקציה ב-Firebase

כדי להשתמש ב-Firebase באפליקציה ל-Apple, צריך לרשום את האפליקציה לפרויקט ב-Firebase. לעיתים קרובות, רישום האפליקציה נקרא 'הוספת' האפליקציה לפרויקט.

  1. נכנסים למסוף Firebase.

  2. במרכז הדף 'סקירה כללית של הפרויקט', לוחצים על הסמל iOS+ כדי להפעיל את תהליך העבודה להגדרה.

    אם כבר הוספתם אפליקציה לפרויקט Firebase, לוחצים על הוספת אפליקציה כדי להציג את אפשרויות הפלטפורמה.

  3. מזינים את מזהה החבילה של האפליקציה בשדה bundle ID.

  4. (אופציונלי) מזינים פרטים נוספים על האפליקציה: הכינוי של האפליקציה ומזהה App Store.

  5. לוחצים על רישום האפליקציה.

הוספת קובץ תצורה של Firebase

  1. לוחצים על Download GoogleService-Info.plist כדי לקבל את קובץ התצורה של Firebase לפלטפורמות Apple‏ (GoogleService-Info.plist).

  2. מעבירים את קובץ התצורה לתיקיית השורש של פרויקט Xcode. אם מופיעה בקשה, בוחרים להוסיף את קובץ התצורה לכל היעדים.

אם יש לכם כמה מזהי חבילות בפרויקט, עליכם לשייך כל מזהה חבילה לאפליקציה רשומה במסוף Firebase כדי שלכל אפליקציה יהיה קובץ GoogleService-Info.plist משלה.

הוספת ערכות SDK של Firebase לאפליקציה

שימוש ב-Swift Package Manager כדי להתקין ולנהל יחסי תלות ב-Firebase.

  1. ב-Xcode, כשפרויקט האפליקציה פתוח, עוברים אל קובץ > הוספת חבילות.
  2. כשמופיעה בקשה, מוסיפים את המאגר של Firebase SDK לפלטפורמות Apple:
  3.   https://github.com/firebase/firebase-ios-sdk.git
  4. בוחרים את הספרייה Firebase Cloud Messaging.
  5. מוסיפים את הדגל -ObjC לקטע Other Linker Flags (דגלים אחרים של קישור) בהגדרות ה-build של היעד.
  6. כדי ליהנות מחוויית שימוש אופטימלית ב-Firebase Cloud Messaging, מומלץ להפעיל את Google Analytics בפרויקט Firebase ולהוסיף את Firebase SDK for Google Analytics לאפליקציה. אפשר לבחור בספרייה ללא איסוף של מזהי IDFA או עם איסוף של מזהי IDFA.
  7. בסיום, Xcode יתחיל לפתור את יחסי התלות ולהוריד אותם באופן אוטומטי ברקע.

העלאת מפתח האימות של APNs

מעלים את מפתח האימות של APNs ל-Firebase. אם עדיין אין לכם מפתח אימות של APNs, עליכם ליצור מפתח כזה במרכז החברים של מפתחי Apple.

  1. בתוך הפרויקט במסוף Firebase, לוחצים על סמל גלגל השיניים, בוחרים באפשרות Project Settings (הגדרות הפרויקט) ואז בוחרים בכרטיסייה Cloud Messaging (Cloud Messaging).

  2. בקטע APNs authentication key בקטע iOS app configuration, לוחצים על הלחצן Upload.

  3. עוברים למיקום שבו שמרתם את המפתח, בוחרים אותו ולוחצים על פתיחה. מוסיפים את מזהה המפתח (זמין ב מרכז החברים של Apple Developer) ולוחצים על Upload.

איך מפעילים את Firebase באפליקציה

תצטרכו להוסיף לאפליקציה את קוד האתחול של Firebase. מייבאים את המודול של Firebase ומגדירים מכונה משותפת כפי שמתואר:

  1. מייבאים את המודול FirebaseCore ב-UIApplicationDelegate, וגם את כל המודולים האחרים של Firebase שבהם משתמש הנציג של האפליקציה. לדוגמה, כדי להשתמש ב-Cloud Firestore וב-Authentication:

    SwiftUI

    import SwiftUI
    import FirebaseCore
    import FirebaseFirestore
    import FirebaseAuth
    // ...
          

    Swift

    import FirebaseCore
    import FirebaseFirestore
    import FirebaseAuth
    // ...
          

    Objective-C

    @import FirebaseCore;
    @import FirebaseFirestore;
    @import FirebaseAuth;
    // ...
          
  2. מגדירים מופע משותף של FirebaseApp בשיטה application(_:didFinishLaunchingWithOptions:) של הנציג של האפליקציה:

    SwiftUI

    // Use Firebase library to configure APIs
    FirebaseApp.configure()

    Swift

    // Use Firebase library to configure APIs
    FirebaseApp.configure()

    Objective-C

    // Use Firebase library to configure APIs
    [FIRApp configure];
  3. אם אתם משתמשים ב-SwiftUI, עליכם ליצור נציג אפליקציה ולצרף אותו למבנה App דרך UIApplicationDelegateAdaptor או NSApplicationDelegateAdaptor. צריך גם להשבית את החלפת הקוד של נציג האפליקציה. מידע נוסף זמין בהוראות ל-SwiftUI.

    SwiftUI

    @main
    struct YourApp: App {
      // register app delegate for Firebase setup
      @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
    
      var body: some Scene {
        WindowGroup {
          NavigationView {
            ContentView()
          }
        }
      }
    }
          

הרשמה לקבלת התראות מרחוק

בזמן ההפעלה או בנקודה הרצויה בתהליך האפליקציה, רושמים את האפליקציה לקבלת התראות מרחוק. קוראים ל-registerForRemoteNotifications כפי שמוצג:

Swift


UNUserNotificationCenter.current().delegate = self

let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
  options: authOptions,
  completionHandler: { _, _ in }
)

application.registerForRemoteNotifications()

Objective-C


[UNUserNotificationCenter currentNotificationCenter].delegate = self;
UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert |
    UNAuthorizationOptionSound | UNAuthorizationOptionBadge;
[[UNUserNotificationCenter currentNotificationCenter]
    requestAuthorizationWithOptions:authOptions
    completionHandler:^(BOOL granted, NSError * _Nullable error) {
      // ...
    }];

[application registerForRemoteNotifications];

הרשמה של אפליקציית הלקוח לנושא

אפליקציות לקוח יכולות להירשם לכל נושא קיים, או ליצור נושא חדש. כשאפליקציית לקוח נרשמת למינוי לנושא חדש (שעדיין לא קיים בפרויקט Firebase שלכם), נוצר נושא חדש בשם הזה ב-FCM וכל לקוח יכול להירשם אליו לאחר מכן.

כדי להירשם לנושא, צריך להפעיל את שיטת המינוי מהשרשור הראשי של האפליקציה (השיטה FCM לא בטוחה לשרשור). אם הבקשה למינוי נכשלת בשלב הראשון, FCM ינסה שוב באופן אוטומטי. במקרים שבהם לא ניתן להשלים את המינוי, הוא יגרום להודעת שגיאה שאפשר לתפוס במטפל בהשלמה, כפי שמוצג בהמשך:

Swift

Messaging.messaging().subscribe(toTopic: "weather") { error in
  print("Subscribed to weather topic")
}

Objective-C

[[FIRMessaging messaging] subscribeToTopic:@"weather"
                                completion:^(NSError * _Nullable error) {
  NSLog(@"Subscribed to weather topic");
}];

הקריאה הזו שולחת בקשה אסינכררונית לקצה העורפי של FCM ומרשימה את הלקוח לנושא הנתון. לפני שמפעילים את subscribeToTopic:topic, צריך לוודא שמכונה של אפליקציית הלקוח כבר קיבלה אסימון רישום דרך קריאה חוזרת (callback)‏ didReceiveRegistrationToken.

בכל פעם שהאפליקציה מופעלת, FCM מוודא שכל הנושאים המבוקשים נכללים במינוי. כדי לבטל את ההרשמה, קוראים לפונקציה unsubscribeFromTopic:topic, ו-FCM מבטלת את ההרשמה לנושא ברקע.

קבלה וטיפול בהודעות בנושאים

FCM מעביר הודעות בנושאים באותו אופן שבו הוא מעביר הודעות אחרות במורד הזרם.

מטמיעים את application(_:didReceiveRemoteNotification:fetchCompletionHandler:) כפי שמוצג:

Swift

func application(_ application: UIApplication,
                 didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async
  -> UIBackgroundFetchResult {
  // If you are receiving a notification message while your app is in the background,
  // this callback will not be fired till the user taps on the notification launching the application.
  // TODO: Handle data of notification

  // With swizzling disabled you must let Messaging know about the message, for Analytics
  // Messaging.messaging().appDidReceiveMessage(userInfo)

  // Print message ID.
  if let messageID = userInfo[gcmMessageIDKey] {
    print("Message ID: \(messageID)")
  }

  // Print full message.
  print(userInfo)

  return UIBackgroundFetchResult.newData
}

Objective-C

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
    fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
  // If you are receiving a notification message while your app is in the background,
  // this callback will not be fired till the user taps on the notification launching the application.
  // TODO: Handle data of notification

  // With swizzling disabled you must let Messaging know about the message, for Analytics
  // [[FIRMessaging messaging] appDidReceiveMessage:userInfo];

  // ...

  // Print full message.
  NSLog(@"%@", userInfo);

  completionHandler(UIBackgroundFetchResultNewData);
}

יצירת בקשות שליחה

אחרי שיוצרים נושא, אפשר לשלוח אליו הודעות – באמצעות הרשמה של מכונות של אפליקציות לקוח לנושא בצד הלקוח או דרך ממשק ה-API של השרת. אם זו הפעם הראשונה שאתם יוצרים בקשות שליחה ל-FCM, כדאי לעיין במדריך בנושא סביבת השרת ו-FCM כדי לקבל מידע חשוב על ההגדרה והרקע.

בלוגיקה של השליחה בקצה העורפי, מציינים את שם הנושא הרצוי כפי שמוצג:

Node.js

// The topic name can be optionally prefixed with "/topics/".
const topic = 'highScores';

const message = {
  data: {
    score: '850',
    time: '2:45'
  },
  topic: topic
};

// Send a message to devices subscribed to the provided topic.
getMessaging().send(message)
  .then((response) => {
    // Response is a message ID string.
    console.log('Successfully sent message:', response);
  })
  .catch((error) => {
    console.log('Error sending message:', error);
  });

Java

// The topic name can be optionally prefixed with "/topics/".
String topic = "highScores";

// See documentation on defining a message payload.
Message message = Message.builder()
    .putData("score", "850")
    .putData("time", "2:45")
    .setTopic(topic)
    .build();

// Send a message to the devices subscribed to the provided topic.
String response = FirebaseMessaging.getInstance().send(message);
// Response is a message ID string.
System.out.println("Successfully sent message: " + response);

Python

# The topic name can be optionally prefixed with "/topics/".
topic = 'highScores'

# See documentation on defining a message payload.
message = messaging.Message(
    data={
        'score': '850',
        'time': '2:45',
    },
    topic=topic,
)

# Send a message to the devices subscribed to the provided topic.
response = messaging.send(message)
# Response is a message ID string.
print('Successfully sent message:', response)

Go

// The topic name can be optionally prefixed with "/topics/".
topic := "highScores"

// See documentation on defining a message payload.
message := &messaging.Message{
	Data: map[string]string{
		"score": "850",
		"time":  "2:45",
	},
	Topic: topic,
}

// Send a message to the devices subscribed to the provided topic.
response, err := client.Send(ctx, message)
if err != nil {
	log.Fatalln(err)
}
// Response is a message ID string.
fmt.Println("Successfully sent message:", response)

C#‎

// The topic name can be optionally prefixed with "/topics/".
var topic = "highScores";

// See documentation on defining a message payload.
var message = new Message()
{
    Data = new Dictionary<string, string>()
    {
        { "score", "850" },
        { "time", "2:45" },
    },
    Topic = topic,
};

// Send a message to the devices subscribed to the provided topic.
string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);
// Response is a message ID string.
Console.WriteLine("Successfully sent message: " + response);

REST

POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1

Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA
{
  "message":{
    "topic" : "foo-bar",
    "notification" : {
      "body" : "This is a Firebase Cloud Messaging Topic Message!",
      "title" : "FCM Message"
      }
   }
}

פקודת cURL:

curl -X POST -H "Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA" -H "Content-Type: application/json" -d '{
  "message": {
    "topic" : "foo-bar",
    "notification": {
      "body": "This is a Firebase Cloud Messaging Topic Message!",
      "title": "FCM Message"
    }
  }
}' https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1

כדי לשלוח הודעה לשילוב של נושאים, צריך לציין תנאי, שהוא ביטוי בוליאני שמציין את נושאי היעד. לדוגמה, התנאי הבא ישלח הודעות למכשירים שנרשמו ל-TopicA ול-TopicB או ל-TopicC:

"'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics)"

הפונקציה FCM מחשבת קודם את כל התנאים בסוגריים, ואז מחשבת את הביטוי מימין לשמאל. בביטוי שלמעלה, משתמש שנרשם לכל נושא בנפרד לא יקבל את ההודעה. באופן דומה, משתמש שלא נרשם ל-TopicA לא יקבל את ההודעה. השילובים הבאים מקבלים אותו:

  • TopicA וגם TopicB
  • TopicA וגם TopicC

אפשר לכלול עד חמישה נושאים בביטוי המותנה.

כדי לשלוח לתנאי:

Node.js

// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
const condition = '\'stock-GOOG\' in topics || \'industry-tech\' in topics';

// See documentation on defining a message payload.
const message = {
  notification: {
    title: '$FooCorp up 1.43% on the day',
    body: '$FooCorp gained 11.80 points to close at 835.67, up 1.43% on the day.'
  },
  condition: condition
};

// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
getMessaging().send(message)
  .then((response) => {
    // Response is a message ID string.
    console.log('Successfully sent message:', response);
  })
  .catch((error) => {
    console.log('Error sending message:', error);
  });

Java

// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
String condition = "'stock-GOOG' in topics || 'industry-tech' in topics";

// See documentation on defining a message payload.
Message message = Message.builder()
    .setNotification(Notification.builder()
        .setTitle("$GOOG up 1.43% on the day")
        .setBody("$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.")
        .build())
    .setCondition(condition)
    .build();

// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
String response = FirebaseMessaging.getInstance().send(message);
// Response is a message ID string.
System.out.println("Successfully sent message: " + response);

Python

# Define a condition which will send to devices which are subscribed
# to either the Google stock or the tech industry topics.
condition = "'stock-GOOG' in topics || 'industry-tech' in topics"

# See documentation on defining a message payload.
message = messaging.Message(
    notification=messaging.Notification(
        title='$GOOG up 1.43% on the day',
        body='$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.',
    ),
    condition=condition,
)

# Send a message to devices subscribed to the combination of topics
# specified by the provided condition.
response = messaging.send(message)
# Response is a message ID string.
print('Successfully sent message:', response)

Go

// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
condition := "'stock-GOOG' in topics || 'industry-tech' in topics"

// See documentation on defining a message payload.
message := &messaging.Message{
	Data: map[string]string{
		"score": "850",
		"time":  "2:45",
	},
	Condition: condition,
}

// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
response, err := client.Send(ctx, message)
if err != nil {
	log.Fatalln(err)
}
// Response is a message ID string.
fmt.Println("Successfully sent message:", response)

C#‎

// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
var condition = "'stock-GOOG' in topics || 'industry-tech' in topics";

// See documentation on defining a message payload.
var message = new Message()
{
    Notification = new Notification()
    {
        Title = "$GOOG up 1.43% on the day",
        Body = "$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.",
    },
    Condition = condition,
};

// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);
// Response is a message ID string.
Console.WriteLine("Successfully sent message: " + response);

REST

POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1

Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA
{
   "message":{
    "condition": "'dogs' in topics || 'cats' in topics",
    "notification" : {
      "body" : "This is a Firebase Cloud Messaging Topic Message!",
      "title" : "FCM Message",
    }
  }
}

פקודת cURL:

curl -X POST -H "Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA" -H "Content-Type: application/json" -d '{
  "notification": {
    "title": "FCM Message",
    "body": "This is a Firebase Cloud Messaging Topic Message!",
  },
  "condition": "'dogs' in topics || 'cats' in topics"
}' https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1

השלבים הבאים