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

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

אחסון

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

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

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

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

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

IndexedDB

כדי להשתמש ב-IndexedDB, צריך קודם לפתוח מסד נתונים. אם לא קיימת מסד נתונים, הפעולה הזו יוצרת מסד נתונים חדש. ‫IndexedDB הוא API אסינכרוני, אבל הוא מקבל קריאה חוזרת במקום להחזיר Promise. בדוגמה הבאה נעשה שימוש בספריית idb של ג'ייק ארצ'יבלד, שהיא עטיפת 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.

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

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

הוספת נתונים

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

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

לעסקאות יש הבטחה (promise) שמושלמת כשהעסקה מסתיימת בהצלחה, או נדחית עם שגיאת עסקה.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;
}

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

נפח האחסון משותף לכל אפשרויות האחסון, כולל Cache Storage,‏ IndexedDB,‏ Web Storage ואפילו קובץ ה-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 בקטע Application, Clear Storage

בדפדפני 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

אחסון באינטרנט

Browser Support

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

Source

File System Access

Browser Support

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

Source

מנהל אחסון

Browser Support

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

Source

משאבים