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


ערכות ה-SDK של הלקוח עם Cloud Functions for Firebase מאפשרות לבצע קריאה לפונקציות ישירות מ- אפליקציית Firebase. כדי להפעיל פונקציה מהאפליקציה בצורה הזו, צריך לכתוב ולפרוס פונקציה שניתן להתקשר אליה ב-HTTP ב-Cloud Functions, ואז להוסיף לוגיקת לקוח כדי להפעיל את הפונקציה מהאפליקציה.

חשוב לזכור שפונקציות קריאה ל-HTTP דומות לפונקציות HTTP, אבל לא זהות להן. כדי להשתמש בפונקציות שניתן להפעיל באמצעות HTTP, צריך להשתמש ב-SDK של הלקוח בפלטפורמה שלכם יחד עם ה-API לקצה העורפי (או להטמיע את הפרוטוקול). ל-callables יש את ההבדלים העיקריים הבאים מפונקציות HTTP:

  • עם פונקציות קריאה, אסימונים מסוג Firebase Authentication, אסימוני FCM ואסימונים של App Check (אם הם זמינים) נכללים בבקשות באופן אוטומטי.
  • הטריגר מבצע באופן אוטומטי דה-סריאליזציה של גוף הבקשה ומאמת את אסימוני האימות.

Firebase SDK של Cloud Functions דור שני ואילך פועל באופן הדדי עם לקוח Firebase הזה גרסאות מינימליות של ערכות SDK שתומכות בפונקציות HTTPS שניתנות לקריאה:

  • Firebase SDK ל-Apple פלטפורמות 11.2.0
  • Firebase SDK עבור Android 21.0.0
  • Firebase Modular Web SDK גרסה 9.7.0

אם רוצים להוסיף פונקציונליות דומה לאפליקציה שנוצרה בפלטפורמה שאינה נתמכת, אפשר לעיין במפרט הפרוטוקול של https.onCall. בהמשך המדריך מפורטות הוראות לכתיבת פונקציה ניתנת לקריאה ב-HTTP, לפריסה שלה ולקריאה אליה בפלטפורמות של Apple, ב-Android, באינטרנט, ב-C++‎ וב-Unity.

כתיבת הפונקציה שניתן לבצע קריאה אליה ופריסה שלה

משתמשים ב-functions.https.onCall כדי ליצור פונקציה שניתן להפעיל אותה באמצעות HTTPS. השיטה הזו הפונקציה לוקחת שני פרמטרים: data, ופרמטר context אופציונלי:

  // Saves a message to the Firebase Realtime Database but sanitizes the
  // text by removing swearwords.
  exports.addMessage = functions.https.onCall((data, context) => {
    // ...
  });
  

לפונקציה שניתן להתקשר אליה ששומרת הודעת טקסט ב-Realtime Database, לדוגמה, data יכול להכיל את טקסט ההודעה, ואילו context פרמטרים שמייצגים פרטים על אימות המשתמש:

// Message text passed from the client.
const text = request.data.text;
// Authentication / user information is automatically added to the request.
const uid = request.auth.uid;
const name = request.auth.token.name || null;
const picture = request.auth.token.picture || null;
const email = request.auth.token.email || null;

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

אפשר גם לצרף אימות App Check כדי להגן על המשאבים בקצה העורפי מפני ניצול לרעה, כמו הונאות חיוב או פישינג. למידע נוסף, ראו הפעלת אכיפת App Check ב-Cloud Functions.

התוצאה נשלחת בחזרה

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

// returning result.
return {
  firstNumber: firstNumber,
  secondNumber: secondNumber,
  operator: "+",
  operationResult: firstNumber + secondNumber,
};

כדי להחזיר נתונים אחרי פעולה אסינכררונית, מחזירים הבטחה (promise). הנתונים שמוחזרים על ידי ה-promise נשלחים בחזרה ללקוח. לדוגמה, יכול להחזיר טקסט מזויף שהפונקציה שניתן להתקשר אליו כתבה אל Realtime Database:

// Saving the new message to the Realtime Database.
const sanitizedMessage = sanitizer.sanitizeText(text); // Sanitize message.

return getDatabase().ref("/messages").push({
  text: sanitizedMessage,
  author: {uid, name, picture, email},
}).then(() => {
  logger.info("New Message written");
  // Returning the sanitized message to the client.
  return {text: sanitizedMessage};
})

טיפול בשגיאות

כדי להבטיח שהלקוח יקבל פרטי שגיאה שימושיים, צריך להחזיר שגיאות מקובץ קריאה על ידי השלכה (או החזרת הבטחה שנדחתה) באמצעות מופע של functions.https.HttpsError לשגיאה יש מאפיין code שיכול להיות אחד מהערכים שמפורטים ב-functions.https.HttpsError. השגיאות כוללות גם את המחרוזת message, המוגדרת כברירת מחדל למחרוזת ריקה. הם יכולים גם לכלול שדה details אופציונלי עם בערך שרירותי. אם פונקציה מסוימת תיצור שגיאה שאינה HttpsError, במקום זאת הלקוח יקבל הודעת שגיאה עם ההודעה INTERNAL והקוד internal.

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

// Checking attribute.
if (!(typeof text === "string") || text.length === 0) {
  // Throwing an HttpsError so that the client gets the error details.
  throw new HttpsError("invalid-argument", "The function must be called " +
          "with one arguments \"text\" containing the message text to add.");
}
// Checking that the user is authenticated.
if (!request.auth) {
  // Throwing an HttpsError so that the client gets the error details.
  throw new HttpsError("failed-precondition", "The function must be " +
          "called while authenticated.");
}

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

אחרי ששומרים פונקציה ניתנת לקריאה ב-index.js, היא נפרסת יחד עם כל שאר הפונקציות כשמריצים את firebase deploy. כדי לפרוס רק את ה-callable, משתמשים בארגומנט --only כפי שמוצג כדי לבצע פריסות חלקיות:

firebase deploy --only functions:addMessage

אם תיתקלו בשגיאות בהרשאות במהלך פריסת פונקציות, צריך לוודא תפקידי ה-IAM המתאימים שהוקצו למשתמש שמריץ את פקודות הפריסה.

הגדרת סביבת הפיתוח של הלקוח

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

iOS+

פועלים לפי ההוראות להוספת Firebase לאפליקציה ל-Apple.

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

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

Web

  1. פועלים לפי ההוראות כדי מוסיפים את Firebase לאפליקציית האינטרנט. חשוב להפעיל את הפקודה הבאה מהטרמינל:
    npm install firebase@10.13.2 --save
  2. לדרוש באופן ידני גם את הליבה של Firebase וגם את Cloud Functions:

     import { initializeApp } from 'firebase/app';
     import { getFunctions } from 'firebase/functions';
    
     const app = initializeApp({
         projectId: '### CLOUD FUNCTIONS PROJECT ID ###',
         apiKey: '### FIREBASE API KEY ###',
         authDomain: '### FIREBASE AUTH DOMAIN ###',
       });
     const functions = getFunctions(app);

Web

  1. פועלים לפי ההוראות כדי להוסיף את Firebase לאפליקציית האינטרנט.
  2. הוספה של ספריות הלקוח המרכזיות של Firebase ו-Cloud Functions אל app:
    <script src="https://www.gstatic.com/firebasejs/8.10.1/firebase.js"></script>
    <script src="https://www.gstatic.com/firebasejs/8.10.1/firebase-functions.js"></script>

ערכת ה-SDK Cloud Functions זמינה גם כחבילת NPM.

  1. מריצים את הפקודה הבאה מהמסוף:
    npm install firebase@8.10.1 --save
  2. לדרוש באופן ידני גם את הליבה של Firebase וגם את Cloud Functions:
    const firebase = require("firebase");
    // Required for side-effects
    require("firebase/functions");

Kotlin+KTX

  1. פועלים לפי ההוראות כדי מוסיפים את Firebase לאפליקציה ל-Android.

  2. בקובץ Gradle של המודול (ברמת האפליקציה) (בדרך כלל <project>/<app-module>/build.gradle.kts או <project>/<app-module>/build.gradle), מוסיפים את התלות בספרייה Cloud Functions ל-Android. מומלץ להשתמש Firebase Android BoM כדי לשלוט בניהול גרסאות של ספריות.

    dependencies {
        // Import the BoM for the Firebase platform
        implementation(platform("com.google.firebase:firebase-bom:33.3.0"))
    
        // Add the dependency for the Cloud Functions library
        // When using the BoM, you don't specify versions in Firebase library dependencies
        implementation("com.google.firebase:firebase-functions")
    }

    כשמשתמשים ב-Firebase Android BoM, האפליקציה תמיד תשתמש בגרסאות תואמות של ספריות Firebase ל-Android.

    (חלופה)  מוסיפים יחסי תלות לספריות של Firebase בלי להשתמש ב-BoM

    אם בוחרים לא להשתמש ב-Firebase BoM, צריך לציין את כל הגרסאות של ספריות Firebase בשורת התלות שלהן.

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

    dependencies {
        // Add the dependency for the Cloud Functions library
        // When NOT using the BoM, you must specify versions in Firebase library dependencies
        implementation("com.google.firebase:firebase-functions:21.0.0")
    }
    מחפשים מודול ספרייה ספציפי ל-Kotlin? החל מ-אוקטובר 2023 (Firebase BoM 32.5.0), מפתחי Kotlin ומפתחי Java יוכלו להסתמך על מודול הספרייה הראשי (פרטים נוספים זמינים בשאלות הנפוצות לגבי היוזמה הזו).

Java

  1. פועלים לפי ההוראות כדי מוסיפים את Firebase לאפליקציה ל-Android.

  2. בקובץ Gradle של המודול (ברמת האפליקציה) (בדרך כלל <project>/<app-module>/build.gradle.kts או <project>/<app-module>/build.gradle), מוסיפים את התלות בספרייה Cloud Functions ל-Android. מומלץ להשתמש ב-Firebase Android BoM כדי לשלוט בגרסאות הספרייה.

    dependencies {
        // Import the BoM for the Firebase platform
        implementation(platform("com.google.firebase:firebase-bom:33.3.0"))
    
        // Add the dependency for the Cloud Functions library
        // When using the BoM, you don't specify versions in Firebase library dependencies
        implementation("com.google.firebase:firebase-functions")
    }

    כשמשתמשים ב-Firebase Android BoM, האפליקציה תמיד תשתמש בגרסאות תואמות של ספריות Firebase ל-Android.

    (חלופה)  מוסיפים יחסי תלות לספריות של Firebase בלי להשתמש ב-BoM

    אם בוחרים לא להשתמש ב-Firebase BoM, צריך לציין את כל הגרסאות של ספריות Firebase בשורת התלות שלהן.

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

    dependencies {
        // Add the dependency for the Cloud Functions library
        // When NOT using the BoM, you must specify versions in Firebase library dependencies
        implementation("com.google.firebase:firebase-functions:21.0.0")
    }
    מחפשים מודול ספרייה ספציפי ל-Kotlin? החל מ-אוקטובר 2023 (Firebase BoM 32.5.0), מפתחי Kotlin ומפתחי Java יוכלו להסתמך על מודול הספרייה הראשי (פרטים נוספים זמינים בשאלות הנפוצות לגבי היוזמה הזו).

Dart

  1. פועלים לפי ההוראות להוספת Firebase לאפליקציית Flutter.

  2. כדי להתקין את הפלאגין, מריצים את הפקודה הבאה מהרמה הבסיסית (root) של פרויקט Flutter:

    flutter pub add cloud_functions
    
  3. בסיום, יוצרים מחדש את אפליקציית Flutter:

    flutter run
    
  4. אחרי ההתקנה, אפשר לגשת לפלאגין cloud_functions על ידי ייבוא שלו לקוד Dart:

    import 'package:cloud_functions/cloud_functions.dart';
    

C++‎

ל-C++ עם Android:

  1. פועלים לפי ההוראות כדי מוסיפים את Firebase לפרויקט C++.
  2. מוסיפים את הספרייה firebase_functions לקובץ CMakeLists.txt.

ב-C++ בפלטפורמות של Apple:

  1. פועלים לפי ההוראות כדי להוסיף את Firebase לפרויקט C++‎.
  2. מוסיפים את הרצף Cloud Functions ל-Podfile:
    pod 'Firebase/Functions'
  3. שומרים את הקובץ ואז מריצים את הפקודה:
    pod install
  4. מוסיפים את הליבה של Firebase ואת המסגרות של Cloud Functions מ-Firebase C++ SDK לפרויקט Xcode.
    • firebase.framework
    • firebase_functions.framework

Unity

  1. פועלים לפי ההוראות להוספת Firebase לפרויקט ב-Unity.
  2. מוסיפים את FirebaseFunctions.unitypackage מ-Firebase Unity SDK לפרויקט ב-Unity.

איך מפעילים את ה-SDK של הלקוח

מאתחלים מופע של Cloud Functions:

Swift

lazy var functions = Functions.functions()

Objective-C

@property(strong, nonatomic) FIRFunctions *functions;
// ...
self.functions = [FIRFunctions functions];

Web

firebase.initializeApp({
  apiKey: '### FIREBASE API KEY ###',
  authDomain: '### FIREBASE AUTH DOMAIN ###',
  projectId: '### CLOUD FUNCTIONS PROJECT ID ###'
  databaseURL: 'https://### YOUR DATABASE NAME ###.firebaseio.com',
});

// Initialize Cloud Functions through Firebase
var functions = firebase.functions();

Web

const app = initializeApp({
  projectId: '### CLOUD FUNCTIONS PROJECT ID ###',
  apiKey: '### FIREBASE API KEY ###',
  authDomain: '### FIREBASE AUTH DOMAIN ###',
});
const functions = getFunctions(app);

Kotlin+KTX

private lateinit var functions: FirebaseFunctions
// ...
functions = Firebase.functions

Java

private FirebaseFunctions mFunctions;
// ...
mFunctions = FirebaseFunctions.getInstance();

Dart

final functions = FirebaseFunctions.instance;

C++‎

firebase::functions::Functions* functions;
// ...
functions = firebase::functions::Functions::GetInstance(app);

Unity

functions = Firebase.Functions.DefaultInstance;

קריאה לפונקציה

Swift

functions.httpsCallable("addMessage").call(["text": inputField.text]) { result, error in
  if let error = error as NSError? {
    if error.domain == FunctionsErrorDomain {
      let code = FunctionsErrorCode(rawValue: error.code)
      let message = error.localizedDescription
      let details = error.userInfo[FunctionsErrorDetailsKey]
    }
    // ...
  }
  if let data = result?.data as? [String: Any], let text = data["text"] as? String {
    self.resultField.text = text
  }
}

Objective-C

[[_functions HTTPSCallableWithName:@"addMessage"] callWithObject:@{@"text": _inputField.text}
                                                      completion:^(FIRHTTPSCallableResult * _Nullable result, NSError * _Nullable error) {
  if (error) {
    if ([error.domain isEqual:@"com.firebase.functions"]) {
      FIRFunctionsErrorCode code = error.code;
      NSString *message = error.localizedDescription;
      NSObject *details = error.userInfo[@"details"];
    }
    // ...
  }
  self->_resultField.text = result.data[@"text"];
}];

Web

var addMessage = firebase.functions().httpsCallable('addMessage');
addMessage({ text: messageText })
  .then((result) => {
    // Read result of the Cloud Function.
    var sanitizedMessage = result.data.text;
  });

Web

import { getFunctions, httpsCallable } from "firebase/functions";

const functions = getFunctions();
const addMessage = httpsCallable(functions, 'addMessage');
addMessage({ text: messageText })
  .then((result) => {
    // Read result of the Cloud Function.
    /** @type {any} */
    const data = result.data;
    const sanitizedMessage = data.text;
  });

Kotlin+KTX

private fun addMessage(text: String): Task<String> {
    // Create the arguments to the callable function.
    val data = hashMapOf(
        "text" to text,
        "push" to true,
    )

    return functions
        .getHttpsCallable("addMessage")
        .call(data)
        .continueWith { task ->
            // This continuation runs on either success or failure, but if the task
            // has failed then result will throw an Exception which will be
            // propagated down.
            val result = task.result?.data as String
            result
        }
}

Java

private Task<String> addMessage(String text) {
    // Create the arguments to the callable function.
    Map<String, Object> data = new HashMap<>();
    data.put("text", text);
    data.put("push", true);

    return mFunctions
            .getHttpsCallable("addMessage")
            .call(data)
            .continueWith(new Continuation<HttpsCallableResult, String>() {
                @Override
                public String then(@NonNull Task<HttpsCallableResult> task) throws Exception {
                    // This continuation runs on either success or failure, but if the task
                    // has failed then getResult() will throw an Exception which will be
                    // propagated down.
                    String result = (String) task.getResult().getData();
                    return result;
                }
            });
}

Dart

    final result = await FirebaseFunctions.instance.httpsCallable('addMessage').call(
      {
        "text": text,
        "push": true,
      },
    );
    _response = result.data as String;

C++‎

firebase::Future<firebase::functions::HttpsCallableResult> AddMessage(
    const std::string& text) {
  // Create the arguments to the callable function.
  firebase::Variant data = firebase::Variant::EmptyMap();
  data.map()["text"] = firebase::Variant(text);
  data.map()["push"] = true;

  // Call the function and add a callback for the result.
  firebase::functions::HttpsCallableReference doSomething =
      functions->GetHttpsCallable("addMessage");
  return doSomething.Call(data);
}

Unity

private Task<string> addMessage(string text) {
  // Create the arguments to the callable function.
  var data = new Dictionary<string, object>();
  data["text"] = text;
  data["push"] = true;

  // Call the function and extract the operation from the result.
  var function = functions.GetHttpsCallable("addMessage");
  return function.CallAsync(data).ContinueWith((task) => {
    return (string) task.Result.Data;
  });
}

טיפול בשגיאות אצל הלקוח

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

אם השגיאה שמוחזרת על ידי הפונקציה היא מסוג function.https.HttpsError, הלקוח מקבל את השגיאות code,‏ message ו-details מהשגיאה בשרת. אחרת, השגיאה מכילה את ההודעה INTERNAL ואת הקוד INTERNAL. בהנחיות טיפול בשגיאות בפונקציה להתקשרות.

Swift

if let error = error as NSError? {
  if error.domain == FunctionsErrorDomain {
    let code = FunctionsErrorCode(rawValue: error.code)
    let message = error.localizedDescription
    let details = error.userInfo[FunctionsErrorDetailsKey]
  }
  // ...
}

Objective-C

if (error) {
  if ([error.domain isEqual:@"com.firebase.functions"]) {
    FIRFunctionsErrorCode code = error.code;
    NSString *message = error.localizedDescription;
    NSObject *details = error.userInfo[@"details"];
  }
  // ...
}

Web

var addMessage = firebase.functions().httpsCallable('addMessage');
addMessage({ text: messageText })
  .then((result) => {
    // Read result of the Cloud Function.
    var sanitizedMessage = result.data.text;
  })
  .catch((error) => {
    // Getting the Error details.
    var code = error.code;
    var message = error.message;
    var details = error.details;
    // ...
  });

Web

import { getFunctions, httpsCallable } from "firebase/functions";

const functions = getFunctions();
const addMessage = httpsCallable(functions, 'addMessage');
addMessage({ text: messageText })
  .then((result) => {
    // Read result of the Cloud Function.
    /** @type {any} */
    const data = result.data;
    const sanitizedMessage = data.text;
  })
  .catch((error) => {
    // Getting the Error details.
    const code = error.code;
    const message = error.message;
    const details = error.details;
    // ...
  });

Kotlin+KTX

addMessage(inputMessage)
    .addOnCompleteListener { task ->
        if (!task.isSuccessful) {
            val e = task.exception
            if (e is FirebaseFunctionsException) {
                val code = e.code
                val details = e.details
            }
        }
    }

Java

addMessage(inputMessage)
        .addOnCompleteListener(new OnCompleteListener<String>() {
            @Override
            public void onComplete(@NonNull Task<String> task) {
                if (!task.isSuccessful()) {
                    Exception e = task.getException();
                    if (e instanceof FirebaseFunctionsException) {
                        FirebaseFunctionsException ffe = (FirebaseFunctionsException) e;
                        FirebaseFunctionsException.Code code = ffe.getCode();
                        Object details = ffe.getDetails();
                    }
                }
            }
        });

Dart

try {
  final result =
      await FirebaseFunctions.instance.httpsCallable('addMessage').call();
} on FirebaseFunctionsException catch (error) {
  print(error.code);
  print(error.details);
  print(error.message);
}

C++‎

void OnAddMessageCallback(
    const firebase::Future<firebase::functions::HttpsCallableResult>& future) {
  if (future.error() != firebase::functions::kErrorNone) {
    // Function error code, will be kErrorInternal if the failure was not
    // handled properly in the function call.
    auto code = static_cast<firebase::functions::Error>(future.error());

    // Display the error in the UI.
    DisplayError(code, future.error_message());
    return;
  }

  const firebase::functions::HttpsCallableResult* result = future.result();
  firebase::Variant data = result->data();
  // This will assert if the result returned from the function wasn't a string.
  std::string message = data.string_value();
  // Display the result in the UI.
  DisplayResult(message);
}

// ...

// ...
  auto future = AddMessage(message);
  future.OnCompletion(OnAddMessageCallback);
  // ...

Unity

 addMessage(text).ContinueWith((task) => {
  if (task.IsFaulted) {
    foreach (var inner in task.Exception.InnerExceptions) {
      if (inner is FunctionsException) {
        var e = (FunctionsException) inner;
        // Function error code, will be INTERNAL if the failure
        // was not handled properly in the function call.
        var code = e.ErrorCode;
        var message = e.ErrorMessage;
      }
    }
  } else {
    string result = task.Result;
  }
});

לפני השקת האפליקציה, כדאי להפעיל את App Check כדי לוודא שרק האפליקציות שלכם יכולות לגשת לנקודות הקצה של הפונקציות שניתן לבצע קריאה אליהן.