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

Browser Storage APIs for نقل الملفات Applications

استفد من IndexedDB, File System Access API, and Cache API for نقل الملفات apps. Storage limits, performance, and browser compatibility.

يتوزع تخزين المتصفح في تطبيق نقل الملفات بوضوح عبر أربع واجهات برمجية: IndexedDB للبيانات الوصفية المنظَّمة للجلسات والأجزاء (معاملاتية، غير متزامنة، مكتوبة)، وFile System Access API لقراءة وكتابة ملفات متعددة الجيجابايت مباشرةً على قرص المستخدم، وCache API لاستجابات HTTP وأصول shell التطبيق، وStorage Manager API لإدارة الحصص وتلميحات الاستدامة. يجلس Origin Private File System (OPFS) إلى جانب هذه للإدخال/إخراج عالي الأداء في بيئة معزولة. اختيار الواجهة المناسبة مهم لأن الحدود تتراوح من 1 جيجابايت على iOS Safari إلى "60% من القرص الحر" على Chrome سطح المكتب، والاختيار الخاطئ يؤدي في نهاية المطاف إلى QuotaExceededError في الإنتاج.

تعيين كل واجهة إلى الوظيفة المناسبة

استخدم IndexedDB لأي شيء يبدو كسجل: جلسات الرفع، ومؤشرات الأجزاء المكتملة، وبيانات المشاركة الوصفية، ورموز الإلغاء. إنها غير متزامنة ومعاملاتية وقابلة للفهرسة وتنجو عبر الجلسات.

استخدم File System Access API حين تحتاج إلى تسليم البايتات إلى القرص دون تحميل الملف كاملًا في ذاكرة الوصول العشوائي — مثالية لحفظ التنزيلات المفكَّكة تشفيرها التي تتجاوز 500 ميغابايت. تدعمها Chrome وEdge وOpera؛ بينما تُنفِّذ Firefox وSafari جزءًا قراءة فقط عبر showOpenFilePicker.

استخدم Cache API لكائنات HTTP Response — حزمة JS ومقاطع CSS والأيقونات وربما استجابات API مخزَّنة مؤقتًا. إنها مُحسَّنة لاعتراض fetch في Service Workers.

استخدم OPFS (فرع Origin-Private الخاص من File System Access API) حين تريد تخزينًا سريعًا ومعزولًا وغير مرئي للمستخدم، كمخزن مؤقت للكتابة خلال تمرير تشفير متعدد الجيجابايت. يصل إلى إنتاجية قرص أعلى بمرتبة من IndexedDB للمزق الثنائية.

IndexedDB بدون الحواف الخشنة

لواجهة IndexedDB الخام API قائمة على الأحداث يشتهر بصعوبتها. استخدم حزمة idb لجاك أرشيبالد (1.5 كيلوبايت مضغوطًا) أو Dexie.js (20 كيلوبايت، واجهة استعلام أغنى):

import { openDB } from 'idb';
const db = await openDB('transfers', 2, {
  upgrade(db, oldVersion) {
    if (oldVersion < 1) {
      const sessions = db.createObjectStore('sessions', { keyPath: 'id' });
      sessions.createIndex('by_expiry', 'expiresAt');
    }
    if (oldVersion < 2) {
      db.createObjectStore('chunks', { keyPath: ['sessionId', 'index'] });
    }
  }
});
await db.put('sessions', { id: 'abc', fileName: 'report.pdf', expiresAt: Date.now() + 86400000 });

تعمل ترقيات الإصدار في رد نداء upgrade. احرص دائمًا على حماية الترقيات بـoldVersion حتى يحصل المستخدمون القافزون من v1 إلى v3 على الخطوتين معًا.

تتعامل IndexedDB مع معظم أشكال البيانات بما فيها Blobs ومراجع File عبر النسخ المنظَّم. يعني ذلك إمكانية تخزين مقبض File في سجل جلسة وإعادة قراءة بايتات الملف الأصلي بعد إعادة تحميل التاب — مثالي لرفعات قابلة للاستئناف.

File System Access API للتنزيلات الكبيرة

تتيح لك الواجهة تسليم دفق كتابة لحوار حفظ المتصفح:

const handle = await window.showSaveFilePicker({
  suggestedName: 'decrypted-archive.zip',
  types: [{ description: 'Zip', accept: { 'application/zip': ['.zip'] } }]
});
const writable = await handle.createWritable();
await decryptionStream.pipeTo(writable);

تتدفق البايتات مباشرةً إلى القرص دون حط رحلها في كومة JS. هذه هي الطريقة العملية الوحيدة لحفظ ملف مفكَّك تشفيره بحجم 10 جيجابايت في المتصفح.

بالنسبة لـFirefox وSafari، استخدم StreamSaver.js كبديل، الذي يستعمل Service Worker لتصنيع استجابة دفقية تُشغِّل واجهة التنزيل. نفس الاستخدام، بأجزاء متحركة أكثر قليلًا.

تتيح مقابض الملفات الدائمة لتطبيق إعادة فتح الملفات عبر الجلسات. بمجرد أن يمنح المستخدم الإذن عبر showOpenFilePicker، يمكنك تخزين FileSystemFileHandle في IndexedDB واستدعاء handle.requestPermission() لاحقًا لاستعادة الوصول دون مطالبة المستخدم مجددًا عند كل ملف.

OPFS للمساحة المؤقتة

Origin Private File System هو تخزين معزول لكل أصل يتصرف كنظام ملفات لكنه غير مرئي للمستخدم:

const root = await navigator.storage.getDirectory();
const fh = await root.getFileHandle('scratch.bin', { create: true });
const access = await fh.createSyncAccessHandle(); // workers only
access.write(buffer, { at: offset });
access.flush();
access.close();

createSyncAccessHandle متاح فقط داخل Web Workers (بما فيها Service Workers). إنه متزامن وسريع جدًا — تُظهر المعايير سرعات 3-10 أضعاف IndexedDB للكتابات المتسلسلة. استخدمه لتخزين مخرجات تشفير بضع مئات من الميغابايت مؤقتًا قبل الرفع، أو لتخزين نسخة عمل مفكَّكة التشفير مؤقتًا دون تلويث مجلد التنزيلات الخاص بالمستخدم.

أصدرت Safari 17 OPFS مع مقابض وصول متزامن؛ Firefox 111 أعقبها. جميع المتصفحات الثلاثة الرئيسية تدعمه الآن، مما يجعله مناسبًا لكود الإنتاج.

حصص التخزين والبقاء ضمنها

تشترك جميع الواجهات في مجموعة حصص الأصل نفسها. أسقف تقريبية:

  • Chrome سطح المكتب: 60% من القرص الحر
  • Firefox سطح المكتب: 50% من القرص الحر، بحد أقصى 2 جيجابايت لكل أصل افتراضيًا
  • Safari سطح المكتب: تحذير عند 1 جيجابايت، يرتفع إلى ~20% من القرص بموافقة المستخدم
  • iOS Safari: 1 جيجابايت لكل أصل، إخلاء مكثَّف بعد 7 أيام من عدم الاستخدام
  • Chrome أندرويد: 10% من القرص الحر، إخلاء تحت الضغط

تحقق من الحصة وقت التشغيل:

const { quota, usage } = await navigator.storage.estimate();
console.log(`Using ${(usage/1e9).toFixed(2)} GB of ${(quota/1e9).toFixed(2)} GB`);

اطلب الاستدامة للمخازن الحرجة:

const persisted = await navigator.storage.persist();

تُعيد true إذا منح المتصفح تخزينًا دائمًا، بمعنى أنه لن يُخلي تحت الضغط. تمنح Chrome هذا تلقائيًا للمواقع التي تفاعل معها المستخدم كثيرًا؛ Firefox يطلب التأكيد.

Cache API لـApp Shell والعمل دون اتصال

تخزِّن Cache API أزواج Request + Response وهي الخيار الصحيح داخل Service Workers:

const cache = await caches.open('shell-v7');
await cache.addAll([
  '/', '/app.js', '/app.css', '/icons/192.png'
]);

استرجاع عند الاعتراض:

self.addEventListener('fetch', (e) => {
  e.respondWith(caches.match(e.request).then(r => r ?? fetch(e.request)));
});

لا تضع بايتات الملف المُشفَّرة في Cache API. كائن استجابة بحجم 2 جيجابايت يتجاوز حصة iOS Safari دفعةً واحدة ولا يمكن جلبه بـRange بعد ذلك. البايتات تنتمي إلى OPFS أو مباشرةً إلى القرص عبر File System Access.

التعامل مع الإخلاء وفقدان البيانات بأناقة

التخزين غير الدائم يُخلى — يجب التخطيط لذلك. iOS Safari يُخلي بعد 7 أيام من عدم الاستخدام بصرف النظر عن الحصة. Chrome يُخلي فقط حين يكون القرص تحت ضغط حقيقي. Firefox يُخلي الأصول الأقل استخدامًا حين تمتلئ مجموعة الحصص.

نمطان دفاعيان:

  • اكتب أي حالة لا يمكن إعادة إنشاؤها (معرِّفات جلسة الرفع، إزاحات الأجزاء الجزئية) بتنسيق ملائم للإعادة حتى يتمكن تحميل صفحة جديدة من إعادة الجلب من الخادم والاستمرار.
  • للحالة طويلة الأمد، اطلب navigator.storage.persist() وأظهر واجهة للمستخدمين للتأكيد حين يطلب المتصفح ذلك.

أبقِ الخادم كمصدر حقيقة لأي شيء لا يمكنك تحمُّل فقدانه. عامل تخزين المتصفح كذاكرة تخزين مؤقتة سريعة قد تختفي بين عشية وضحاها.

الألغام في دعم المتصفحات

ثلاثة مطبات تظهر مرارًا:

  1. تعداد indexedDB.databases() غير مدعوم في Firefox (المستخدمون الذين اختاروا "حذف الكوكيز عند الإغلاق" يفقدون كل محتوى IndexedDB دون إطلاق أحداث).
  2. FileSystemFileHandle.queryPermission() يتصرف بشكل مختلف بعد إعادة التحميل — أحيانًا يُعيد 'prompt' حتى بعد المنح. استدعِ requestPermission() دائمًا بشكل دفاعي.
  3. وضع التصفح الخاص/المجهول يمنح جميع الواجهات الثلاث حصةً منفصلة وأصغر تخص الجلسة فقط. الكود الذي يعمل في التصفح العادي يمكن أن يصطدم بـQuotaExceededError فورًا في نوافذ خاصة.

تستخدم HexaTransfer IndexedDB لحالة الجلسة، وOPFS لتخزين النص المشفَّر مؤقتًا خلال التشفير الدفقي، وFile System Access API للتنزيلات المفكَّكة بحجم 10 جيجابايت على المتصفحات المدعومة. جرِّبها على hexatransfer.com — مجانًا، بدون حساب، بحد أقصى 10 جيجابايت.

اختيار مجموعة لتطبيقك

لمعظم تطبيقات النقل، المجموعة الصحيحة هي: غلاف idb فوق IndexedDB للبيانات الوصفية، ومقابض الوصول المتزامن لـOPFS لمساحة التشفير/فك التشفير المؤقتة، وCache API لـapp shell داخل Service Worker، وFile System Access API للتنزيلات النهائية مع بديل StreamSaver، واستدعاء navigator.storage.persist() أثناء الإعداد الأولي. هذا يغطي كل متصفح يُشحن اليوم، ويبقى ضمن الحصص على الجوال، ويتعافى بأناقة حين يُخلى شيء. ابنِ محوِّلات صغيرة حول كل واجهة حتى يوم أضافت OPFS طريقةً جديدة أو رفعت Safari حصتها — تغيِّر ملفًا واحدًا وتُطلق.

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

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

إرسال ملف