تخطي إلى المحتوى الرئيسي

التخزين السحابي

رفع الملفات مباشرة إلى مزودي التخزين السحابي دون المرور عبر خادمك.

Amazon S3

التثبيت

npm install @samithahansaka/dropup

الاستخدام الأساسي

import { useDropup } from '@samithahansaka/dropup';
import { createS3Uploader } from '@samithahansaka/dropup/cloud/s3';

function S3Uploader() {
const { files, actions, getDropProps, getInputProps } = useDropup({
upload: createS3Uploader({
getPresignedUrl: async (file) => {
// استدعاء الخادم للحصول على رابط موقع مسبقاً
const response = await fetch('/api/s3/presign', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
filename: file.name,
contentType: file.type,
}),
});
return response.json();
},
}),

onUploadComplete: (file) => {
console.log('تم الرفع إلى S3:', file.uploadedUrl);
},
});

return (
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>أفلت الملفات للرفع إلى S3</p>
</div>
);
}

الخادم: إنشاء رابط موقع مسبقاً

// مثال Node.js / Express
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3 = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
});

app.post('/api/s3/presign', async (req, res) => {
const { filename, contentType } = req.body;
const key = `uploads/${Date.now()}-${filename}`;

const command = new PutObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: key,
ContentType: contentType,
});

const url = await getSignedUrl(s3, command, { expiresIn: 3600 });

res.json({
url,
fields: {}, // لـ PUT البسيط، لا حاجة لحقول إضافية
});
});

S3 مع POST (نموذج متعدد الأجزاء)

لسياسات S3 POST:

createS3Uploader({
getPresignedUrl: async (file) => {
const response = await fetch('/api/s3/presign-post', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
filename: file.name,
contentType: file.type,
}),
});

const { url, fields } = await response.json();

return {
url, // رابط حاوية S3
fields, // حقول السياسة لتضمينها في النموذج
};
},
});

Google Cloud Storage

import { createGCSUploader } from '@samithahansaka/dropup/cloud/gcs';

function GCSUploader() {
const { files, getDropProps, getInputProps } = useDropup({
upload: createGCSUploader({
getSignedUrl: async (file) => {
const response = await fetch('/api/gcs/sign', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
filename: file.name,
contentType: file.type,
}),
});
return response.json();
},
}),
});

return (
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>الرفع إلى Google Cloud Storage</p>
</div>
);
}

الخادم: رابط GCS الموقع

// مثال Node.js
import { Storage } from '@google-cloud/storage';

const storage = new Storage();
const bucket = storage.bucket(process.env.GCS_BUCKET);

app.post('/api/gcs/sign', async (req, res) => {
const { filename, contentType } = req.body;
const blob = bucket.file(`uploads/${Date.now()}-${filename}`);

const [url] = await blob.getSignedUrl({
version: 'v4',
action: 'write',
expires: Date.now() + 15 * 60 * 1000, // 15 دقيقة
contentType,
});

res.json({ url });
});

Azure Blob Storage

import { createAzureUploader } from '@samithahansaka/dropup/cloud/azure';

function AzureUploader() {
const { files, getDropProps, getInputProps } = useDropup({
upload: createAzureUploader({
getSasUrl: async (file) => {
const response = await fetch('/api/azure/sas', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
filename: file.name,
contentType: file.type,
}),
});
return response.json();
},
}),
});

return (
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>الرفع إلى Azure Blob Storage</p>
</div>
);
}

الخادم: رابط Azure SAS

// مثال Node.js
import {
BlobServiceClient,
generateBlobSASQueryParameters,
BlobSASPermissions,
} from '@azure/storage-blob';

const blobServiceClient = BlobServiceClient.fromConnectionString(
process.env.AZURE_STORAGE_CONNECTION_STRING
);

app.post('/api/azure/sas', async (req, res) => {
const { filename, contentType } = req.body;
const containerClient = blobServiceClient.getContainerClient('uploads');
const blobName = `${Date.now()}-${filename}`;
const blobClient = containerClient.getBlockBlobClient(blobName);

const sasToken = generateBlobSASQueryParameters(
{
containerName: 'uploads',
blobName,
permissions: BlobSASPermissions.parse('cw'), // إنشاء، كتابة
expiresOn: new Date(Date.now() + 15 * 60 * 1000),
},
blobServiceClient.credential
).toString();

res.json({
url: `${blobClient.url}?${sasToken}`,
headers: {
'x-ms-blob-type': 'BlockBlob',
'Content-Type': contentType,
},
});
});

Cloudflare R2

R2 متوافق مع S3، لذا استخدم رافع S3:

import { createS3Uploader } from '@samithahansaka/dropup/cloud/s3';

function R2Uploader() {
const { files, getDropProps, getInputProps } = useDropup({
upload: createS3Uploader({
getPresignedUrl: async (file) => {
const response = await fetch('/api/r2/presign', {
method: 'POST',
body: JSON.stringify({ filename: file.name }),
});
return response.json();
},
}),
});

return (
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>الرفع إلى Cloudflare R2</p>
</div>
);
}

الخادم: رابط R2 الموقع مسبقاً

import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3 = new S3Client({
region: 'auto',
endpoint: `https://${process.env.CF_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
},
});

app.post('/api/r2/presign', async (req, res) => {
const { filename } = req.body;

const command = new PutObjectCommand({
Bucket: process.env.R2_BUCKET,
Key: `uploads/${Date.now()}-${filename}`,
});

const url = await getSignedUrl(s3, command, { expiresIn: 3600 });
res.json({ url });
});

DigitalOcean Spaces

متوافق مع S3 أيضاً:

// نفس S3، فقط حدث تكوين نقطة النهاية في الخادم
const s3 = new S3Client({
region: 'nyc3',
endpoint: 'https://nyc3.digitaloceanspaces.com',
credentials: {
accessKeyId: process.env.DO_SPACES_KEY,
secretAccessKey: process.env.DO_SPACES_SECRET,
},
});

مزود سحابي مخصص

أنشئ رافعك الخاص لأي خدمة سحابية:

import { useDropup, type CustomUploader } from '@samithahansaka/dropup';

const customCloudUploader: CustomUploader = async (file, options) => {
// 1. الحصول على رابط الرفع من الخادم
const { uploadUrl, fileUrl } = await fetch('/api/custom-cloud/init', {
method: 'POST',
body: JSON.stringify({ filename: file.name, size: file.size }),
}).then(r => r.json());

// 2. رفع الملف
const xhr = new XMLHttpRequest();

return new Promise((resolve, reject) => {
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
options.onProgress((e.loaded / e.total) * 100);
}
};

xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve({ url: fileUrl });
} else {
reject(new Error('فشل الرفع'));
}
};

xhr.onerror = () => reject(new Error('خطأ في الشبكة'));

// معالجة الإلغاء
options.signal.addEventListener('abort', () => xhr.abort());

xhr.open('PUT', uploadUrl);
xhr.send(file.file);
});
};

function CustomCloudUploader() {
const { files, getDropProps, getInputProps } = useDropup({
upload: customCloudUploader,
});

return (
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>الرفع إلى سحابة مخصصة</p>
</div>
);
}

أفضل ممارسات الأمان

  1. لا تكشف بيانات الاعتماد على العميل - أنشئ دائماً الروابط الموقعة على الخادم
  2. استخدم أوقات انتهاء قصيرة - 5-15 دقيقة عادة كافية
  3. تحقق من أنواع الملفات على الخادم - لا تعتمد فقط على التحقق على جانب العميل
  4. عيّن سياسات CORS مناسبة على تخزينك السحابي
  5. حدد أحجام الملفات في سياسات الروابط الموقعة مسبقاً
  6. استخدم حاويات منفصلة لرفع المستخدمين مقابل أصول التطبيق