שימוש ב-Firebase באפליקציות אינטרנט דינמיות עם SSR (עיבוד בצד השרת)

אם עבדתם עם Firebase JS SDK או עם ערכות SDK אחרות של Firebase ללקוח, סביר להניח שאתם מכירים את הממשק FirebaseApp ואת אופן השימוש בו להגדרת מופעים של אפליקציות. כדי להקל על פעולות דומות בצד השרת, Firebase מספקת את FirebaseServerApp.

FirebaseServerApp הוא וריאנט של FirebaseApp לשימוש בסביבות של עיבוד בצד השרת (SSR). הוא כולל כלים להמשך סשנים ב-Firebase שמתפרסים על פני רינדור בצד הלקוח (CSR) / רינדור בצד השרת. הכלים והאסטרטגיות האלה יכולים לעזור לשפר אפליקציות אינטרנט דינמיות שנבנו באמצעות Firebase ונפרסו בסביבות של Google כמו Firebase App Hosting.

השתמש בFirebaseServerApp כדי:

  • להריץ קוד בצד השרת בהקשר של המשתמש, בניגוד ל-SDK של Firebase לאדמינים שיש לו הרשאות ניהול מלאות.
  • הפעלת השימוש ב-App Check בסביבות SSR.
  • המשך של סשן Firebase Auth שנוצר בלקוח.

מחזור החיים של FirebaseServerApp

מסגרות של עיבוד בצד השרת (SSR) וסביבות זמן ריצה אחרות שאינן דפדפן, כמו cloud workers, מבצעות אופטימיזציה של זמן האתחול על ידי שימוש חוזר במשאבים בכמה הפעלות. ‫FirebaseServerApp מיועד להתאים לסביבות האלה באמצעות מנגנון של ספירת הפניות. אם אפליקציה מפעילה את initializeServerApp עם אותם פרמטרים כמו initializeServerApp קודם, היא מקבלת את אותו מופע FirebaseServerApp שכבר אותחל. כך מצמצמים את התקורה המיותרת של אתחול והקצאות זיכרון. כשמפעילים את deleteApp במופע FirebaseServerApp, מספר ההפניות יורד והמופע משוחרר אחרי שמספר ההפניות מגיע לאפס.

ניקוי מופעים של FirebaseServerApp

יכול להיות שיהיה לכם קשה לדעת מתי להפעיל את deleteApp במופע FirebaseServerApp, במיוחד אם אתם מריצים הרבה פעולות אסינכרוניות במקביל. השדה releaseOnDeref של FirebaseServerAppSettings עוזר לפשט את התהליך. אם מקצים releaseOnDeref הפניה לאובייקט עם משך החיים של היקף הבקשה (לדוגמה, אובייקט הכותרות של בקשת ה-SSR), המערכת תפחית את מספר ההפניות של FirebaseServerApp כשהמסגרת תדרוש בחזרה את אובייקט הכותרות. הפעולה הזו תנקה באופן אוטומטי את מופע FirebaseServerApp.

דוגמה לשימוש בתג releaseOnDeref:

/// Next.js
import { headers } from 'next/headers'
import { FirebaseServerAppSettings, initializeServerApp} from "firebase/app";

export default async function Page() {
  const headersObj = await headers();
  let appSettings: FirebaseServerAppSettings = {};
  appSettings.releaseOnDeref = headersObj;
  const serverApp = initializeServerApp(firebaseConfig, appSettings);
  ...
}

המשך סשנים מאומתים שנוצרו במחשב הלקוח

כשמאתחלים מופע של FirebaseServerApp עם טוקן מזהה לאימות, הוא מאפשר לגשר בין סשנים של משתמשים מאומתים בין סביבות של עיבוד בצד הלקוח (CSR) ועיבוד בצד השרת (SSR). מופעים של Firebase Auth SDK שאותחלו עם אובייקט FirebaseServerApp שמכיל טוקן מזהה של אימות ינסו לאמת את המשתמש בזמן האתחול, בלי שהאפליקציה תצטרך להפעיל שיטות אימות כלשהן.

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

/// Next.js
import { initializeServerApp } from "firebase/app";
import { getAuth } from "firebase/auth";

// Replace the following with your app's
// Firebase project configuration
const firebaseConfig = {
  // ...
};

const firebaseServerAppSettings = {
  authIdToken: token  // See "Pass client tokens to the server side
                      // rendering phase" for an example on how transmit
                      // the token from the client and the server.
}

const serverApp =
  initializeServerApp(firebaseConfig,
                      firebaseServerAppSettings);
const serverAuth = getAuth(serverApp);

// FirebaseServerApp and Auth will now attempt
// to sign in the current user based on provided
// authIdToken.

שימוש ב-App Check בסביבות SSR

האכיפה של App Check מסתמכת על מופע של App Check SDK שערכות Firebase SDK משתמשות בו כדי לבצע קריאות פנימיות ל-getToken. לאחר מכן, הטוקן שמתקבל נכלל בבקשות לכל שירותי Firebase, וכך הקצה העורפי יכול לאמת את האפליקציה.

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

FirebaseServerApp מספקת חלופה. אם מסופק טוקן App Check שנוצר על ידי הלקוח במהלך האתחול של FirebaseServerApp, הוא ישמש את ערכות ה-SDK של מוצרי Firebase כשמפעילים שירותי Firebase, כך שלא יהיה צורך במופע של App Check SDK.

/// Next.js
import { initializeServerApp } from "firebase/app";

// Replace the following with your app's
// Firebase project configuration
const firebaseConfig = {
  // ...
};

const firebaseServerAppSettings = {
  appCheckToken: token // See "Pass client tokens to the server side
                       // rendering phase" for an example on how transmit
                       // the token from the client and the server.
}

const serverApp =
  initializeServerApp(firebaseConfig,
                      firebaseServerAppSettings);

// The App Check token will now be appended to all Firebase service requests.

העברת טוקנים של לקוחות לשלב העיבוד בצד השרת

כדי להעביר אסימונים מאומתים של מזהה אימות (ואסימונים של App Check) מהלקוח לשלב העיבוד בצד השרת (SSR), צריך להשתמש ב-service worker. הגישה הזו כוללת יירוט של בקשות אחזור שמפעילות SSR והוספה של הטוקנים לכותרות הבקשה.

במאמר ניהול סשנים באמצעות Service Workers מופיעה הטמעה לדוגמה של Service Worker של Firebase Auth. בנוסף, אפשר לעיין בקטע שינויים בצד השרת כדי לראות קוד שמדגים איך לנתח את האסימונים האלה מהכותרות לשימוש באתחול של FirebaseServerApp.

שימוש ב-Firestore בסביבות SSR

כשמפתחים אפליקציות אינטרנט עם עיבוד בצד השרת (SSR), לעיתים קרובות צריך לשתף נתונים בין השרת לבין הלקוח כדי לשפר את הביצועים ואת חוויית המשתמש. ערכת ה-SDK של Firestore מספקת כלי סריאליזציה שמאפשרים לכם לצלם תמונות מצב וסוגי נתונים ספציפיים בשרת ולהעביר אותם ישירות לרכיבים בצד הלקוח. התהליך הזה מבטל אחזור מיותר של נתונים, כי הוא מאפשר ללקוח להוסיף נתונים למצב באמצעות הנתונים שאוחזרו מראש במהלך שלב ה-SSR. בנוסף, אפשר לעבור מהמצבים הסדרתיים האלה למאזינים בזמן אמת, כדי לוודא שהאפליקציה תישאר מסונכרנת עם מסד הנתונים.

בקטע הזה מוסבר איך לעשות שימוש חוזר בנתונים שאוחזרו במהלך שלב הרינדור בצד השרת (SSR) ברכיבים בצד הלקוח.

סריאליזציה של סוגי נתונים

סוגים מסוימים של נתונים ב-Firestore מספקים toJSON שיטה להמרת הנתונים שלהם לפורמט שניתן לסדר אותו. הם כוללים מקרים של אובייקטים כמו Bytes,‏ GeoPoint, ‏ Timestamp ו-VectorValue.

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

import {
  Bytes
} from 'firebase/firestore';

const BYTES_DATA = new Uint8Array([0, 1, 2, 3, 4, 5]);
const bytes = Bytes.fromUint8Array(BYTES_DATA);
const bytesJSON = bytes.toJSON();

ביטול הסריאליזציה של סוגי נתונים

סוגי הנתונים ב-Firestore כוללים את השיטה הסטטית fromJSON להמרה של הנתונים הסדרתיים לסוג נתונים שניתן להפעלה ב-Firestore.

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

import {
  Bytes
} from 'firebase/firestore';

// Assuming the same `bytesJSON` variable from the previous example.
const deserializedBytes = Bytes.fromJSON(bytesJSON);

סדרות וביטול סדרות של תמונות מצב ב-Firestore

בדומה לסוגי הנתונים ב-Firestore, אפשר לבצע סריאליזציה של מופעים של DocumentSnapshot ושל QuerySnapshot באמצעות toJSON. עם זאת, כדי לבצע דה-סריאליזציה שלהם, צריך להשתמש בפונקציות העצמאיות documentSnapshotFromJSON ו-querySnapshotFromJSON במקום בשיטה סטטית fromJSON.

לדוגמה, אפשר לבצע סריאליזציה של התוצאות querySnapshot של פעולת query באמצעות השיטה toJSON:

import {
  collection,
  getDocs,
  query,
  querySnapshotFromJSON
} from 'firebase/firestore';
// Assuming a configured instance of Firestore in the variable `firestore`.
const queryRef = query(collection(firestore, QUERY_PATH));
const querySnapshot = await getDocs(queryRef);
const querySnapshotJson = querySnapshot.toJSON();

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

import {
  querySnapshotFromJSON
} from 'firebase/firestore';

// deserializedSnapshot is an object of type QuerySnapshot:

const deserializedSnapshot =
  querySnapshotFromJSON(firestore, querySnapshotJson);

מאזינים עם תמונות מצב סדרתיות

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

אם האפליקציה שלכם דורשת את העדכונים בזמן אמת האלה, אתם יכולים להשתמש בפונקציה onSnapshotResume כדי לאתחל את Firestore SnapshotListenerעם נתונים Snapshot מסודרים. לדוגמה:

const observer = {
  next: (qs) => {
    console.log("onSnapshot invoked: ", qs.data());
  },
  error: (e) => {
    console.log("error callback invoked: ", e.toString());
  }
};
const unsubscribe = onSnapshotResume(firestore, querySnapshotJson, observer);