שליחת הודעות למספר מכשירים בפלטפורמות של 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 כדי להריץ את האפליקציה, ומבצעים את הפעולות הבאות:

    • מקבלים מפתח אימות של התראות Apple לחשבון הפיתוח שלכם ב-Apple.
    • מפעילים את האפשרות 'התראות בדחיפה' ב-XCode בקטע אפליקציה > יכולות.

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

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

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

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

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

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

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

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

  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 יתחיל לפתור את יחסי התלות ולהוריד אותם באופן אוטומטי ברקע.

העלאה של מפתח האימות של נקודות ה-APN

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

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

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

  3. עוברים למיקום שבו שמרתם את המפתח, בוחרים אותו ולוחצים על פתיחה. מוסיפים את מזהה המפתח של המפתח (זמין ב- Apple Developer Member Center) ולוחצים על 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 וכל לקוח יכול להירשם אליו בהמשך.

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

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

השלבים הבאים