נתונים שזמינים במצב אופליין

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

אחסון

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

  • IndexedDB: אפשרות לאחסון אובייקטים ב-NoSQL לנתונים מובְנים ול-blobs (נתונים בינאריים).
  • WebStorage: דרך לאחסון צמדי מחרוזות של מפתח/ערך, באמצעות אחסון מקומי או אחסון סשן. הוא לא זמין בהקשר של קובץ שירות (service worker). ממשק ה-API הזה הוא סינכרוני, ולכן לא מומלץ להשתמש בו לאחסון נתונים מורכבים.
  • אחסון במטמון: כפי שמתואר במודול שמירת נתונים במטמון.

אפשר לנהל את כל נפח האחסון במכשיר באמצעות Storage Manager API בפלטפורמות נתמכות. Cache Storage API ו-IndexedDB מספקים גישה אסינכררונית לאחסון מתמיד של אפליקציות PWA, וניתן לגשת אליהם מהשרשור הראשי, מ-web workers ומ-service workers. לשניהם יש תפקיד חיוני בכך ש-PWA פועלים בצורה אמינה כשהחיבור לרשת לא יציב או לא קיים. אבל מתי כדאי להשתמש בכל אחת מהן?

משתמשים ב-Cache Storage API למשאבי רשת, דברים שאפשר לגשת אליהם על ידי שליחת בקשה באמצעות כתובת URL, כמו HTML,‏ CSS,‏ JavaScript, תמונות, סרטונים ואודיו.

משתמשים ב-IndexedDB כדי לאחסן נתונים מובְנים. הנתונים האלה כוללים נתונים שצריך להיות אפשר לחפש או לשלב אותם באופן דומה ל-NoSQL, או נתונים אחרים כמו נתונים ספציפיים למשתמש שלא בהכרח תואמים לבקשת כתובת URL. חשוב לזכור ש-IndexedDB לא מיועד לחיפוש טקסט מלא.

IndexedDB

כדי להשתמש ב-IndexedDB, קודם צריך לפתוח מסד נתונים. הפקודה הזו יוצרת מסד נתונים חדש אם הוא לא קיים. IndexedDB הוא API אסינכררוני, אבל הוא מקבל פונקציית קריאה חוזרת במקום להחזיר Promise. בדוגמה הבאה נעשה שימוש בספריית idb של Jake Archibald, שהיא מעטפת Promise זעירה ל-IndexedDB. ספריות עזר לא חייבות להשתמש ב-IndexedDB, אבל אם רוצים להשתמש בתחביר Promise, אפשר להשתמש בספרייה idb.

בדוגמה הבאה נוצרת מסד נתונים לאחסון מתכונים.

יצירת מסד נתונים ופתיחתו

כדי לפתוח מסד נתונים:

  1. משתמשים בפונקציה openDB כדי ליצור מסד נתונים חדש של IndexedDB שנקרא cookbook. מאחר שמסדי נתונים של IndexedDB הם בגרסאות, צריך להגדיל את מספר הגרסה בכל פעם שמבצעים שינויים במבנה מסד הנתונים. הפרמטר השני הוא גרסת מסד הנתונים. בדוגמה, הערך מוגדר ל-1.
  2. אובייקט אתחול שמכיל קריאה חוזרת מסוג upgrade() מועבר אל openDB(). פונקציית הקריאה החוזרת נקראת כשמסד הנתונים מותקן בפעם הראשונה או כשמתבצע שדרוג לגרסה חדשה. הפונקציה הזו היא המקום היחיד שבו יכולות להתרחש פעולות. הפעולות יכולות לכלול יצירת מאגרי אובייקטים חדשים (המבנים שבהם נעשה שימוש ב-IndexedDB כדי לארגן נתונים) או אינדקסים (שבהם רוצים לבצע חיפוש). זה גם המקום שבו צריכה להתבצע העברת הנתונים. בדרך כלל, הפונקציה upgrade() מכילה משפט switch ללא משפטי break כדי לאפשר לכל שלב להתרחש לפי הסדר, על סמך הגרסה הישנה של מסד הנתונים.
import { openDB } from 'idb';

async function createDB() {
  // Using https://github.com/jakearchibald/idb
  const db = await openDB('cookbook', 1, {
    upgrade(db, oldVersion, newVersion, transaction) {
      // Switch over the oldVersion, *without breaks*, to allow the database to be incrementally upgraded.
    switch(oldVersion) {
     case 0:
       // Placeholder to execute when database is created (oldVersion is 0)
     case 1:
       // Create a store of objects
       const store = db.createObjectStore('recipes', {
         // The `id` property of the object will be the key, and be incremented automatically
           autoIncrement: true,
           keyPath: 'id'
       });
       // Create an index called `name` based on the `type` property of objects in the store
       store.createIndex('type', 'type');
     }
   }
  });
}

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

נבחן את מאגר האובייקטים שנוצר עכשיו. אחרי שמוסיפים מתכונים למאגר האובייקטים ופותחים את DevTools בדפדפנים מבוססי Chromium או את Web Inspector ב-Safari, זה מה שצריך להופיע:

Safari ו-Chrome שמוצגים בהם תוכן של IndexedDB.

הוספת נתונים

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

  1. מתחילים עסקה עם הערך mode מוגדר ל-readwrite.
  2. מקבלים את מאגר האובייקטים שאליו מוסיפים נתונים.
  3. קוראים לפונקציה add() עם הנתונים שרוצים לשמור. השיטה מקבלת נתונים בצורת מילון (כצמדי מפתח/ערך) ומוסיפה אותם למאגר האובייקטים. צריך להיות אפשרי ליצור עותקים משכפלים של המילון באמצעות יצירת עותקים משכפלים של מבנים. אם רוצים לעדכן אובייקט קיים, צריך לבצע במקום זאת קריאה ל-method‏ put().

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

כפי שמוסבר במסמכי התיעוד של ספריית IDB, אם כותבים למסד הנתונים, הערך tx.done הוא האות לכך שהכול אושר והועבר למסד הנתונים. עם זאת, מומלץ להמתין לפעולות נפרדות כדי שתוכלו לראות שגיאות שגורמות לעסקה להיכשל.

// Using https://github.com/jakearchibald/idb
async function addData() {
  const cookies = {
      name: "Chocolate chips cookies",
      type: "dessert",
        cook_time_minutes: 25
  };
  const tx = await db.transaction('recipes', 'readwrite');
  const store = tx.objectStore('recipes');
  store.add(cookies);
  await tx.done;
}

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

אחזור נתונים

כך מקבלים נתונים מ-IndexedDB:

  1. מתחילים עסקה ומציינים את מאגר או מאגרי האובייקטים ואת סוג העסקה (אופציונלי).
  2. קוראים ל-objectStore() מהעסקה הזו. חשוב לציין את שם מאגר האובייקטים.
  3. קוראים לפונקציה get() עם המפתח שרוצים לקבל. כברירת מחדל, המאגר משתמש במפתח שלו כאינדקס.
// Using https://github.com/jakearchibald/idb
async function getData() {
  const tx = await db.transaction('recipes', 'readonly')
  const store = tx.objectStore('recipes');
// Because in our case the `id` is the key, we would
// have to know in advance the value of the id to
// retrieve the record
  const value = await store.get([id]);
}

מנהל האחסון

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

קיבולת האחסון משותפת בין כל אפשרויות האחסון, כולל אחסון במטמון, IndexedDB, אחסון ב-Web, ואפילו קובץ ה-service worker והיחסים התלויים בו. עם זאת, נפח האחסון הזמין משתנה בהתאם לדפדפן. סביר להניח שלא תגיעו למכסה. אתרים יכולים לאחסן מגה-בייטים ואפילו ג'יגה-בייטים של נתונים בדפדפנים מסוימים. לדוגמה, ב-Chrome הדפדפן יכול להשתמש ב-80% מכלל נפח הדיסק, ומקור מסוים יכול להשתמש ב-60% מכלל נפח הדיסק. בדפדפנים שתומכים ב-Storage API, אפשר לדעת כמה נפח אחסון עדיין זמין לאפליקציה, מה המכסה שלה ומה השימוש שלה. בדוגמה הבאה נעשה שימוש ב-Storage API כדי לקבל אומדן של המכסה והשימוש, ולאחר מכן מחושב האחוז של השימוש והבייטים שנותרו. הערה: הפונקציה navigator.storage מחזירה מופע של StorageManager. יש ממשק נפרד של Storage, וקל להתבלבל ביניהם.

if (navigator.storage && navigator.storage.estimate) {
  const quota = await navigator.storage.estimate();
  // quota.usage -> Number of bytes used.
  // quota.quota -> Maximum number of bytes available.
  const percentageUsed = (quota.usage / quota.quota) * 100;
  console.log(`You've used ${percentageUsed}% of the available storage.`);
  const remaining = quota.quota - quota.usage;
  console.log(`You can write up to ${remaining} more bytes.`);
}

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

כלי הפיתוח ל-Chrome בקטע 'אפליקציה', 'ניקוי האחסון'

בדפדפני Firefox ו-Safari אין מסך סיכום שבו אפשר לראות את כל המכסות והשימוש בנפח האחסון של המקור הנוכחי.

שמירת נתונים

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

כדי לבקש אחסון מתמיד, צריך להפעיל את StorageManager.persist(). כמו בעבר, אפשר לגשת לממשק StorageManager דרך נכס navigator.storage.

async function persistData() {
  if (navigator.storage && navigator.storage.persist) {
    const result = await navigator.storage.persist();
    console.log(`Data persisted: ${result}`);
}

אפשר גם לבדוק אם כבר הוקצה אחסון מתמיד במקור הנוכחי באמצעות קריאה ל-StorageManager.persisted(). Firefox מבקש מהמשתמש הרשאה להשתמש באחסון מתמיד. דפדפנים מבוססי Chromium מעניקים או דוחים עמידות על סמך שיטה חוקרת כדי לקבוע את מידת החשיבות של התוכן למשתמש. לדוגמה, אחד מהקריטריונים ל-Google Chrome הוא התקנה של אפליקציה מסוג PWA. אם המשתמש התקין סמל של אפליקציית ה-PWA במערכת ההפעלה, יכול להיות שהדפדפן יעניק אחסון מתמיד.

בקשה מהמשתמש ב-Mozilla Firefox להרשאת אחסון מתמיד.

תמיכה בדפדפן של API

Web Storage

Browser Support

  • Chrome: 4.
  • Edge: 12.
  • Firefox: 3.5.
  • Safari: 4.

Source

גישה למערכת קבצים

Browser Support

  • Chrome: 86.
  • Edge: 86.
  • Firefox: 111.
  • Safari: 15.2.

Source

מנהל אחסון

Browser Support

  • Chrome: 55.
  • Edge: 79.
  • Firefox: 57.
  • Safari: 15.2.

Source

משאבים