Kodax

Firebase Storage — Firebase

تخزين Firebase (Storage)

Cloud Storage for Firebase يحفظ الصور والملفات، ويُحمى بقواعد أمان خاصة به (storage.rules).

المسارات

users/
├── {uid}/
│   ├── avatar.jpg
│   └── documents/
│       └── invoice-2026-03.pdf
public/
└── banners/

تنظيم المسار بمعرّف المستخدم يجعل قواعد الأمان بسيطة: "اكتب فقط تحت users/{uid} إذا كان uid هو أنت".

الرفع

import { getDownloadURL, getStorage, ref, uploadBytes } from "firebase/storage";
import { app } from "./firebase.js";

const storage = getStorage(app);

export async function uploadAvatar(uid, file) {
  const avatarRef = ref(storage, `users/${uid}/avatar.jpg`);
  const snap = await uploadBytes(avatarRef, file, { contentType: file.type });
  return getDownloadURL(snap.ref);
}

لملفات كبيرة مع شريط تقدم: uploadBytesResumable والاستماع لحدث state_changed (bytesTransferred / totalBytes) — في المثال الكامل.

روابط التنزيل

getDownloadURL يعيد رابطاً فيه رمز وصول دائم حتى تلغيه يدوياً من لوحة التحكم. من يملك الرابط يرى الملف حتى لو منعته القواعد. للملفات الحساسة: لا توزع روابط التنزيل؛ اقرأ الملف عبر SDK بقواعد تتحقق من المستخدم، أو أنشئ روابط موقّعة قصيرة المدة من الخادم (Admin SDK).

الحذف والبيانات الوصفية

deleteObject(ref) للحذف، و getMetadata/updateMetadata للنوع والحجم وبيانات مخصصة. احذف الملفات عند حذف السجل المرتبط بها لتجنب التكلفة والملفات اليتيمة.

القواعد: الحجم والنوع دائماً

match /users/{uid}/{allPaths=**} {
  allow read: if request.auth != null && request.auth.uid == uid;
  allow write: if request.auth != null && request.auth.uid == uid
               && request.resource.size < 5 * 1024 * 1024
               && request.resource.contentType.matches('image/.*');
}

بدون حد للحجم، يستطيع أي مستخدم ملء التخزين على حسابك المدفوع. ولاحظ: contentType يرسله العميل — القواعد تحد من الإساءة، أما التحقق الحقيقي من المحتوى فيحتاج دالة خادم تعالج الملف (مثل تصغير الصور وإعادة ترميزها).

تمرين

أضف للمثال الكامل دالة تحذف الصورة القديمة قبل رفع الجديدة، وشريط تقدم نصي، ورسالة واضحة عندما ترفض القواعد ملفاً أكبر من 5MB (رمز الخطأ storage/unauthorized).

import { 
  getStorage, 
  ref, 
  uploadBytes,
  uploadBytesResumable,
  getDownloadURL,
  deleteObject,
  listAll
} from 'firebase/storage';

const storage = getStorage();

// ===== رفع ملف بسيط =====
async function uploadFile(file, path) {
  const storageRef = ref(storage, path);
  const snapshot = await uploadBytes(storageRef, file);
  const url = await getDownloadURL(snapshot.ref);
  return url;
}

// ===== رفع مع تتبع التقدم =====
function uploadWithProgress(file, path, onProgress) {
  const storageRef = ref(storage, path);
  const uploadTask = uploadBytesResumable(storageRef, file);
  
  return new Promise((resolve, reject) => {
    uploadTask.on('state_changed',
      (snapshot) => {
        const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        onProgress(progress);
      },
      (error) => reject(error),
      async () => {
        const url = await getDownloadURL(uploadTask.snapshot.ref);
        resolve(url);
      }
    );
  });
}

// ===== استخدام مع input file =====
const fileInput = document.getElementById('file-input');
fileInput.addEventListener('change', async (e) => {
  const file = e.target.files[0];
  const userId = 'user123';
  const path = `users/${userId}/avatar.jpg`;
  
  const url = await uploadWithProgress(file, path, (progress) => {
    console.log(`تم رفع: ${progress.toFixed(0)}%`);
  });
  
  console.log('رابط الملف:', url);
});

// ===== حذف ملف =====
async function deleteFile(path) {
  const fileRef = ref(storage, path);
  await deleteObject(fileRef);
}

// ===== قائمة الملفات =====
async function listFiles(path) {
  const listRef = ref(storage, path);
  const result = await listAll(listRef);
  return result.items.map(item => item.fullPath);
}
اختبر فهمك