בעזרת Cloud Functions, אתם יכולים לטפל באירועים ב-Firebase Realtime Database בלי לעדכן את קוד הלקוח.
Cloud Functions מאפשר להריץ פעולות של Realtime Database עם הרשאות אדמין מלאות, ומוודא שכל שינוי ב-Realtime Database מעובד בנפרד. אפשר לבצע שינויים ב-Firebase Realtime Database דרך DataSnapshot או דרך SDK לאדמינים.
במחזור חיים אופייני, פונקציית Firebase Realtime Database מבצעת את הפעולות הבאות:
- ממתין לשינויים במיקום מסוים ב-Realtime Database.
- מופעל כשאירוע מתרחש ומבצע את המשימות שלו (ראו מה אפשר לעשות עם Cloud Functions? לדוגמאות של תרחישי שימוש).
- מקבל אובייקט נתונים שמכיל תמונת מצב של הנתונים שמאוחסנים במסמך שצוין.
הפעלה של פונקציית Realtime Database
ליצור פונקציות חדשות לאירועים של Realtime Database באמצעות functions.database. כדי לקבוע מתי הפונקציה תופעל, צריך לציין את אחד מהגורמים המטפלים באירועים ואת הנתיב ב-Realtime Database שבו הוא יאזין לאירועים.
הגדרת הגורם המטפל באירועים
פונקציות מאפשרות לכם לטפל באירועים של Realtime Database בשתי רמות של ספציפיות: אתם יכולים להאזין באופן ספציפי רק לאירועי יצירה, עדכון או מחיקה, או להאזין לכל שינוי מכל סוג בנתיב. Cloud Functions תומך בגורמים המטפלים באירועים האלה של Realtime Database:
-
onWrite(), שמופעל כשנוצרים, מתעדכנים או נמחקים נתונים ב-Realtime Database. -
onCreate(), שמופעל כשנוצרים נתונים חדשים ב-Realtime Database. -
onUpdate(), שמופעל כשנתונים מתעדכנים ב-Realtime Database . -
onDelete(), שמופעל כשנתונים נמחקים מ-Realtime Database .
מציינים את המכונה והנתיב
כדי לקבוע מתי ואיפה הפונקציה תופעל, קוראים ל-ref(path) כדי לציין נתיב, ואם רוצים, מציינים מכונה של Realtime Database באמצעות instance('INSTANCE_NAME'). אם לא מציינים מכונה, הפונקציה נפרסת במכונה שמוגדרת כברירת מחדל ב-Realtime Database בפרויקט ב-Firebase. לדוגמה:
- מכונת Realtime Database שמוגדרת כברירת מחדל:
functions.database.ref('/foo/bar') - מכונה בשם "my-app-db-2":
functions.database.instance('my-app-db-2').ref('/foo/bar')
השיטות האלה מנחות את הפונקציה לטפל בכתיבה בנתיב מסוים בתוך המכונה של Realtime Database. מפרטי הנתיבים תואמים לכל פעולות הכתיבה שנוגעות בנתיב, כולל פעולות כתיבה שמתרחשות בכל מקום מתחתיו. אם תגדירו את הנתיב של הפונקציה כ-/foo/bar, היא תתאים לאירועים בשני המיקומים הבאים:
/foo/bar
/foo/bar/baz/really/deep/path
בשני המקרים, מערכת Firebase מפרשת שהאירוע מתרחש ב-/foo/bar, ונתוני האירוע כוללים את הנתונים הישנים והחדשים ב-/foo/bar. אם נתוני האירוע עשויים להיות גדולים, כדאי להשתמש בכמה פונקציות בנתיבים עמוקים יותר במקום בפונקציה אחת ליד הבסיס של מסד הנתונים. כדי ליהנות מהביצועים הטובים ביותר, כדאי לבקש נתונים רק ברמה העמוקה ביותר שאפשר.
אפשר לציין רכיב נתיב כתו כללי לחיפוש על ידי הקפת הרכיב בסוגריים מסולסלים. ref('foo/{bar}') תואם לכל צאצא של /foo. הערכים של רכיבי הנתיב של התווים הכלליים לחיפוש זמינים באובייקט EventContext.params של הפונקציה. בדוגמה הזו, הערך זמין בתור context.params.bar.
נתיבים עם תווים כלליים לחיפוש יכולים להתאים לכמה אירועים מכתיבה אחת. הוספה של
{
"foo": {
"hello": "world",
"firebase": "functions"
}
}
תואם לנתיב "/foo/{bar}" פעמיים: פעם אחת עם "hello": "world"
ופעם נוספת עם "firebase": "functions".
טיפול בנתוני אירועים
כשמטפלים באירוע Realtime Database, אובייקט הנתונים שמוחזר הוא DataSnapshot.
באירועים מסוג onWrite או onUpdate, הפרמטר הראשון הוא אובייקט Change שמכיל שתי תמונות מצב שמייצגות את מצב הנתונים לפני האירוע שהפעיל את הפונקציה ואחריו. באירועים onCreate ו-onDelete, אובייקט הנתונים שמוחזר הוא תמונת מצב של הנתונים שנוצרו או נמחקו.
בדוגמה הזו, הפונקציה מאחזרת את קובץ ה-snapshot של הנתיב שצוין, ממירה את המחרוזת במיקום הזה לאותיות רישיות וכותבת את המחרוזת ששונתה במסד הנתונים:
// Listens for new messages added to /messages/:pushId/original and creates an // uppercase version of the message to /messages/:pushId/uppercase exports.makeUppercase = functions.database.ref('/messages/{pushId}/original') .onCreate((snapshot, context) => { // Grab the current value of what was written to the Realtime Database. const original = snapshot.val(); functions.logger.log('Uppercasing', context.params.pushId, original); const uppercase = original.toUpperCase(); // You must return a Promise when performing asynchronous tasks inside a Functions such as // writing to the Firebase Realtime Database. // Setting an "uppercase" sibling in the Realtime Database returns a Promise. return snapshot.ref.parent.child('uppercase').set(uppercase); });
גישה לפרטי אימות המשתמשים
מ-EventContext.auth ומ-EventContext.authType, אתם יכולים לגשת לפרטי המשתמש, כולל ההרשאות, של המשתמש שהפעיל פונקציה. המידע הזה יכול לעזור לכם לאכוף כללי אבטחה, ולאפשר לפונקציה לבצע פעולות שונות בהתאם לרמת ההרשאות של המשתמש:
const functions = require('firebase-functions/v1');
const admin = require('firebase-admin');
exports.simpleDbFunction = functions.database.ref('/path')
.onCreate((snap, context) => {
if (context.authType === 'ADMIN') {
// do something
} else if (context.authType === 'USER') {
console.log(snap.val(), 'written by', context.auth.uid);
}
});
בנוסף, אתם יכולים להשתמש בפרטי האימות של המשתמש כדי "להתחזות" למשתמש ולבצע פעולות כתיבה בשמו. כדי למנוע בעיות שקשורות בו-זמניות, חשוב למחוק את המופע של האפליקציה כמו שמוצג בהמשך:
exports.impersonateMakeUpperCase = functions.database.ref('/messages/{pushId}/original')
.onCreate((snap, context) => {
const appOptions = JSON.parse(process.env.FIREBASE_CONFIG);
appOptions.databaseAuthVariableOverride = context.auth;
const app = admin.initializeApp(appOptions, 'app');
const uppercase = snap.val().toUpperCase();
const ref = snap.ref.parent.child('uppercase');
const deleteApp = () => app.delete().catch(() => null);
return app.database().ref(ref).set(uppercase).then(res => {
// Deleting the app is necessary for preventing concurrency leaks
return deleteApp().then(() => res);
}).catch(err => {
return deleteApp().then(() => Promise.reject(err));
});
});
קריאת הערך הקודם
לאובייקט Change יש מאפיין before שמאפשר לבדוק מה נשמר ב-Realtime Database לפני האירוע. המאפיין before מחזיר DataSnapshot שבו כל השיטות (לדוגמה, val() ו-exists()) מתייחסות לערך הקודם. כדי לקרוא שוב את הערך החדש, אפשר להשתמש ב-DataSnapshot המקורי או לקרוא את המאפיין after. המאפיין הזה בכל Change הוא DataSnapshot נוסף שמייצג את מצב הנתונים אחרי שהאירוע התרחש.
לדוגמה, אפשר להשתמש במאפיין before כדי לוודא שהפונקציה משנה את הטקסט לאותיות רישיות רק כשהוא נוצר בפעם הראשונה:
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
.onWrite((change, context) => {
// Only edit data when it is first created.
if (change.before.exists()) {
return null;
}
// Exit when the data is deleted.
if (!change.after.exists()) {
return null;
}
// Grab the current value of what was written to the Realtime Database.
const original = change.after.val();
console.log('Uppercasing', context.params.pushId, original);
const uppercase = original.toUpperCase();
// You must return a Promise when performing asynchronous tasks inside a Functions such as
// writing to the Firebase Realtime Database.
// Setting an "uppercase" sibling in the Realtime Database returns a Promise.
return change.after.ref.parent.child('uppercase').set(uppercase);
});