البيانات بلا اتصال بالإنترنت

للحصول على تجربة قوية بلا إنترنت، يحتاج تطبيق الويب التقدّمي (PWA) إلى إدارة مساحة التخزين. في فصل التخزين المؤقت، تعلمت أن تخزين ذاكرة التخزين المؤقت هو أحد الخيارات لحفظ البيانات على الجهاز. سنوضِّح لك في هذا الفصل كيفية إدارة البيانات بلا اتصال بالإنترنت، بما في ذلك الاحتفاظ بالبيانات والقيود المفروضة عليها والأدوات المتاحة.

مساحة التخزين

لا يقتصر التخزين على الملفات ومواد العرض فحسب، بل يمكن أن يتضمّن أنواعًا أخرى من البيانات. في جميع المتصفّحات التي تتوافق مع تطبيقات PWA، تتوفّر واجهات برمجة التطبيقات التالية للتخزين على الجهاز:

  • IndexedDB: خيار تخزين كائن NoSQL للبيانات المنظَّمة والجداول الثنائية (blob) (البيانات الثنائية).
  • WebStorage: طريقة لتخزين أزواج سلاسل المفتاح/القيمة باستخدام التخزين المحلي أو تخزين الجلسة. هذه الميزة غير متاحة ضمن سياق مشغّل الخدمات. واجهة برمجة التطبيقات هذه متزامنة، لذا لا يُنصح باستخدامها لتخزين البيانات المعقدة.
  • مساحة تخزين ذاكرة التخزين المؤقت: وفقًا لما ورد في وحدة التخزين المؤقت.

يمكنك إدارة كل مساحة التخزين على الجهاز باستخدام Storage Manager API على الأنظمة الأساسية المتوافقة. توفِّر واجهة برمجة التطبيقات Cache Storage API وIndexedDB إمكانية الوصول غير المتزامن إلى مساحة تخزين دائمة لتطبيقات PWA، ويمكن الوصول إليها من سلسلة التعليمات الرئيسية والعاملين على الويب وعاملي الخدمة. يؤدي كلا الأمرين أدوارًا أساسية في جعل تطبيقات الويب التقدّمية (PWA) تعمل بشكل موثوق عندما تكون الشبكة غير مستقرة أو غير موجودة. ولكن متى يجب استخدام كل منها؟

استخدِم Cache Storage API لموارد الشبكة والأشياء التي يمكنك الوصول إليها من خلال طلبها عبر عنوان URL، مثل HTML وCSS وJavaScript والصور والفيديوهات والصوت.

استخدام IndexedDB لتخزين البيانات المنظَّمة ويشمل ذلك البيانات التي يجب أن تكون قابلة للبحث أو الدمج بطريقة تشبه NoSQL، أو البيانات الأخرى مثل البيانات الخاصة بالمستخدم التي لا تتطابق بالضرورة مع طلب عنوان URL. تجدر الإشارة إلى أن IndexedDB غير مصمم للبحث في النص الكامل.

IndexedDB

لاستخدام IndexedDB، افتح قاعدة بيانات أولاً. يؤدي هذا إلى إنشاء قاعدة بيانات جديدة إذا لم تكن موجودة. IndexedDB هي واجهة برمجة تطبيقات غير متزامنة، ولكنها تحتاج إلى معاودة اتصال بدلاً من عرض وعد. يستخدم المثال التالي مكتبة idb لـ "جيك أرشيبالد"، وهي عبارة عن برنامج تضمين صغير جدًا في IndexedDB. لا يُشترط استخدام المكتبات المساعدة لقاعدة البيانات المفهرَسة، ولكن إذا كنت تريد استخدام بنية Promise، يمكنك استخدام مكتبة idb.

يقوم المثال التالي بإنشاء قاعدة بيانات للاحتفاظ بوصفات الطهي.

إنشاء قاعدة بيانات وفتحها

لفتح قاعدة بيانات:

  1. استخدم الدالة openDB لإنشاء قاعدة بيانات جديدة لقاعدة بيانات مفهرسة تسمى 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()" مع البيانات التي تحفظها. تتلقى الطريقة بيانات في شكل قاموس (كأزواج مفتاح/قيمة) وتضيفها إلى مخزن الكائنات. يجب أن يكون القاموس قابلاً للاستنساخ باستخدام الاستنساخ المنظم. إذا أردت تعديل عنصر حالي، يمكنك طلب الإجراء 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;
}

بمجرد إضافة الكعك، ستكون الوصفة في قاعدة البيانات مع وصفات أخرى. يتم ضبط رقم التعريف تلقائيًا وزيادته باستخدام قاعدة البيانات المفهرسة. إذا قمت بتشغيل هذا الرمز مرتين، فسيكون لديك إدخالان متطابقان لملفات تعريف الارتباط.

جارٍ استرداد البيانات

إليك كيفية الحصول على البيانات من 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) بشكل خاص لتخزين استجابات الشبكة وبثّها بشكل صحيح.

تتم مشاركة سعة التخزين بين جميع خيارات التخزين، بما في ذلك "مساحة التخزين في ذاكرة التخزين المؤقت" و"قاعدة البيانات المفهرسة" و"مساحة تخزين الويب" وحتى ملف مشغّل الخدمات وتبعياته. ومع ذلك، تختلف مساحة التخزين المتاحة من متصفّح إلى آخر. قد لا تنفد منك على الأرجح، فالمواقع الإلكترونية قد تخزّن ميغابايت وحتى غيغابايت من البيانات على بعض المتصفحات. يسمح 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 شاشة ملخص للاطّلاع على كل حصة مساحة التخزين والاستخدام الأصلي للأصل الحالي.

الاحتفاظ بالبيانات

يمكنك أن تطلب من المتصفِّح مساحة تخزين دائمة على الأنظمة الأساسية المتوافقة لتجنُّب الإخلاء التلقائي للبيانات بعد عدم النشاط أو عند ضغط مساحة التخزين. في حال منح الإذن، لن يخرج المتصفّح البيانات من مساحة التخزين مطلقًا. وتتضمن هذه الحماية تسجيل مشغّل الخدمات وقواعد بيانات 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 يطلب من المستخدم الحصول على إذن الاستمرار في التخزين.

دعم متصفح واجهة برمجة التطبيقات

التخزين على الويب

التوافق مع المتصفح

  • 4
  • 12
  • 3.5
  • 4

المصدر

الوصول إلى نظام الملفات

التوافق مع المتصفح

  • 86
  • 86
  • 111
  • 15.2

المصدر

مدير مساحة التخزين

التوافق مع المتصفح

  • 55
  • 79
  • 57
  • 15.2

المصدر

المراجِع