انتقل إلى المحتوى
HexaTransfer
العودة إلى المدونة
تعمق تقني

التخزين المؤقت للملفات بعامل الخدمة للنقل دون اتصال

استخدم Service Workers لتمكين قدرات نقل الملفات دون اتصال. استراتيجيات التخزين المؤقت والمزامنة الخلفية وأنماط تطبيقات الويب التقدمية.

عمال الخدمة يتيحون لتطبيق نقل الملفات الاستمرار في العمل حين تنقطع الشبكة: خزِّن HTML shell وJS باستخدام Cache API، وضع رفع فاشل في قائمة انتظار بـ Background Sync API، واحفظ الأجزاء الجزئية في IndexedDB، وأعِد تشغيل كل شيء حين يعود الاتصال. العامل يعمل على خيط منفصل بحلقة أحداث خاصة به، ويعترض أحداث fetch لنطاقه، ويستمر عبر إغلاق التبويبات. لأدوات الرفع، الوصفة الصحيحة هي: استراتيجية stale-while-revalidate للـ app shell، وتجزئة مدعومة بـ IndexedDB للنقل الجاري، وتسجيل Background Sync دوري يُعيد المحاولة كل 15 دقيقة حتى النجاح.

ما يفعله عامل الخدمة فعلياً لتطبيقات النقل

الفائدة الكبرى أن navigator.serviceWorker يصمد عبر إعادة تحميل التبويبة وفترات عدم الاتصال وحتى نوم الهاتف. حين يبدأ مستخدم رفع 2 جيجابايت على Wi-Fi قطار متقطع، تريد أن تبقى الأجزاء التي نجحت ناجحة، وأن تُعيد الأجزاء الجارية المحاولة عند إعادة الاتصال، وأن تكون الحالة كلها قابلة للاسترداد إذا أنهى المتصفح التبويبة لاستعادة الذاكرة. عامل الخدمة — الذي يعمل مستقلاً عن أي تبويبة محددة — هو القطعة التي تُتيح كل ذلك.

الـ API يمنحك ثلاثة لبنات بناء: Cache لحفظ الاستجابات بالـ URL، وIndexedDB للبيانات المنظَّمة (قوائم انتظار الأجزاء، حالة الجلسة)، وSyncManager لجدولة المحاولات التي تُطلَق حين يكون الجهاز متصلاً.

تسجيل العامل وإدارة إصداراته

سجِّل مرة واحدة عند تحميل التطبيق وتعامل مع التحديثات بشكل صريح:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js', { scope: '/' })
    .then((reg) => reg.addEventListener('updatefound', () => {
      const sw = reg.installing;
      sw.addEventListener('statechange', () => {
        if (sw.state === 'installed' && navigator.serviceWorker.controller) {
          // إصدار جديد جاهز، اطلب من المستخدم التحديث
        }
      });
    }));
}

رقِّم مفاتيح الذاكرة المؤقتة (transfer-v7) حتى يُبطِل نشر جديد الأصول القديمة دون ترك JavaScript متقادم. الخطأ الكلاسيكي: index.html محفوظ إلى الأبد، المستخدمون لا يحصلون على التحديث، وأنت عالق في تصحيح الأخطاء عبر Slack أسابيع. اربط الذاكرات المؤقتة ببصمات البناء ونظِّفها في حدث activate.

استراتيجيات التخزين المؤقت للـ App Shell مقابل بيانات المستخدم

الموارد المختلفة تستحق استراتيجيات مختلفة:

  • App shell (HTML وCSS وJS والأيقونات): cache-first مع احتياطي شبكة. تحميل فوري، يعمل دون اتصال.
  • بيانات API الوصفية (/shares/:id): network-first مع احتياطي ذاكرة مؤقتة، TTL 60 ثانية. طازج عند الاتصال، قابل للاستخدام بدونه.
  • بايتات الملف: لا تخزِّن مؤقتاً إطلاقاً. الملفات كثيراً ما تكون جيجابايتات متعددة وCache API لها حصص المنشأ (نموذجياً 60% من القرص الحر).
  • الخطوط من CDN: stale-while-revalidate. سريع ويُحدَّث تلقائياً.

في معالج fetch:

self.addEventListener('fetch', (e) => {
  const url = new URL(e.request.url);
  if (url.pathname.startsWith('/assets/')) {
    e.respondWith(cacheFirst(e.request, 'shell-v7'));
  } else if (url.pathname.startsWith('/api/shares/')) {
    e.respondWith(networkFirst(e.request, 'api-v1', 60));
  }
});

لا تعترض طلبات رفع الملفات الثنائية — مرِّر حولها بفحص e.request.method === 'PUT' والعودة مبكراً. توجيه رفع جيجابايت عبر العامل كارثة على الذاكرة.

وضع رفع فاشل في قائمة انتظار بـ Background Sync

SyncManager هو مفتاح الرفع المرن. حين يفشل PUT لجزء ما، خزِّنه في IndexedDB وسجِّل مزامنة:

// في كود الصفحة
const reg = await navigator.serviceWorker.ready;
await reg.sync.register('flush-uploads');
// في sw.js
self.addEventListener('sync', (event) => {
  if (event.tag === 'flush-uploads') {
    event.waitUntil(flushPendingUploads());
  }
});

المتصفح يُطلِق حدث sync بمجرد عودة الشبكة، مع تراجع أسي حتى نحو 24 ساعة. Chrome وEdge يدعمانه؛ Safari شحن مجموعة فرعية في 17.5 تحت علامة "Background Fetch" لكنها تتطلب إذن المستخدم. لـ Safari، ارجع إلى إعادة المحاولة عند فتح الصفحة التالية المرئية عبر visibilitychange.

Background Fetch API أداة منفصلة مخصصة لعمليات الملفات الكبيرة — تعرض إشعاراً دائماً في واجهة المتصفح حتى يتتبع المستخدمون التقدم بعد إغلاق التبويبة. تستحق الاستخدام للرفع فوق 500 ميجابايت.

تخزين رفع جزئي في IndexedDB

IndexedDB هو مساحة التخرُّش الدائمة. افتح قاعدة بيانات صغيرة مرة واحدة ثم خزِّن بيانات الجلسة الوصفية وإزاحات الأجزاء:

const db = await openDB('transfers', 1, {
  upgrade(db) {
    db.createObjectStore('sessions', { keyPath: 'id' });
    db.createObjectStore('chunks', { keyPath: ['sessionId', 'index'] });
  }
});
await db.put('sessions', {
  id, fileName, fileSize, fileFingerprint, createdAt: Date.now(),
  completedIndexes: [], partUrls
});

لا تخزِّن بايتات الأجزاء الخام — تأتي من مقبض File الذي يستطيع IndexedDB الاحتفاظ به كمرجع نسخ منظَّمة يظل صالحاً عبر إعادة التحميل. تخزين المقبض يتجنب تكرار 2 جيجابايت من البايتات في قاعدة البيانات.

للتخزين الأصلي حدود: نحو 60% من القرص الحر على Chrome لسطح المكتب، و1 جيجابايت لكل منشأ على iOS Safari قبل بدء ضغط الإزالة. اطلب navigator.storage.persist() للحصول على الحزمة "الدائمة" التي تتجنب المتصفحات إزالتها تلقائياً.

التعامل مع الانتقالات بين عدم الاتصال والاتصال

استمع لأحداث online وoffline، في كل من الصفحة وعامل الخدمة:

// الصفحة
window.addEventListener('online', () => {
  ui.showBanner('عاد الاتصال — استئناف الرفع');
  navigator.serviceWorker.controller?.postMessage({ type: 'resume' });
});
window.addEventListener('offline', () => {
  ui.showBanner('غير متصل — الرفع موقوف');
});

navigator.onLine مشهور بعدم الموثوقية على بوابات الالتقاط المؤسسية — يُبلِّغ بـ true حين يكون للجهاز اتصال شبكة محلية لكن بدون إنترنت. للكشف الموثوق، نفِّذ fetch('/ping', { cache: 'no-store' }) صغيراً بمهلة 3 ثوانٍ.

تحويله إلى PWA حقيقي

شحن manifest.json مع display: standalone ومجموعة أيقونات وstart_url: /. أضِف روابط apple-touch-icon لـ iOS. أعلِن معالجة الملفات حتى يتمكن نظام التشغيل من ربط تطبيقك بامتدادات محددة:

{
  "name": "Hex Transfer",
  "file_handlers": [{
    "action": "/share-target",
    "accept": { "application/*": [".pdf", ".zip", ".docx"] }
  }]
}

مع Web Share Target، يتيح هذا للمستخدمين مشاركة الملفات من قائمة مشاركة نظام التشغيل مباشرةً إلى تطبيقك. على Chrome Android وChrome لسطح المكتب، يستطيع PWA التسجيل كمعالج افتراضي لأنواع الملفات التي تُعلن عنها. هذا يحوِّل صفحة المتصفح إلى تطبيق يتصرف كأداة رفع أصيلة.

اختبار سيناريو عدم الاتصال

ثلاثة سيناريوهات للاختبار اليدوي، لأن الاختبارات الآلية لعدم الاتصال غير موثوقة:

  1. ابدأ رفع 500 ميجابايت على Wi-Fi سريع، انتقل إلى وضع الطائرة عند 30%، انتظر 30 ثانية، أعِد تشغيل Wi-Fi. يجب أن يستأنف الرفع من حيث توقف دون تدخل المستخدم.
  2. ابدأ رفعاً، أغلق التبويبة عند 60%، انتظر دقيقتين، أعِد الفتح. اعرض استئناف الجلسة.
  3. ابدأ رفعاً على الجوال، اقفل الشاشة 5 دقائق. Background sync يجب أن يُطلَق حين تفتح وينهي النقل.

مربع "Offline" في Chrome DevTools وApplication > Service Workers > Update on reload لا غنى عنهما. ملفات "Throttling" في لوحة الشبكة تتيح محاكاة Fast 3G وSlow 3G لمشاهدة سلوك واجهة الأخطاء.

تطبيق الويب الخاص بـ HexaTransfer يستخدم عامل خدمة لتخزين app shell مؤقتاً وIndexedDB لحالة الجلسة الجارية، حتى لا تُفقَد تقدم الرفع عند إعادة التحميل أو انقطاع الاتصال القصير.

أخطاء تستحق المعرفة

عمال الخدمة لديهم قائمة صغيرة من المزالق تعضُّ القادمين الجدد: تعمل فقط على HTTPS (ما عدا localhost)، وحصص الذاكرة المؤقتة تتفاوت تفاوتاً كبيراً بين المتصفحات، وiOS Safari لا يُوقِظ العمال بشكل موثوق لـ Background Sync، وDevTools قد تخزِّن عمالاً متقادمين بقوة (انقر دائماً "Bypass for network" أثناء التطوير)، وimportScripts يعمل بشكل متزامن أثناء التثبيت لذا لا تجلب سكريبتات خارجية بطيئة هناك. اكتب اختباراً تكاملياً صغيراً يتحقق من تفعيل العامل والمطالبة بالعملاء وخدمة الصفحة دون اتصال — هذا الاختبار الواحد يلتقط 80% من الانحدارات التي ستواجهها في الإنتاج.

جرّبها على hexatransfer.com — مجاناً، بدون حساب، حتى 10 جيجابايت.

أرسل ملفات كبيرة بأمان مع تشفير من طرف إلى طرف

انقل ملفات حتى 10 جيجابايت مجاناً مع تشفير من طرف إلى طرف. لا حاجة لحساب. يتم تشفير ملفاتك في متصفحك قبل الرفع — لا أحد آخر يستطيع قراءتها.

إرسال ملف