/** * @fileoverview Encrypted Migration & Workspace Backup Service (AES-256-GCM / PBKDF2 / ZIP) * @module services/backup-service * @description خدمة تصدير واستيراد النسخ الاحتياطية الشاملة والمستقلة لبيئة العمل وتشفير الوسائط وحزم البيانات. */ (global => { 'use strict'; /** * تعريفات وحدات وأقسام بيئة العمل القابلة للتصدير والاستيراد */ const MODULE_DEFINITIONS = Object.freeze([ Object.freeze({ key: 'rotation_library', title: 'مكتبة المنشورات وتدوير المحتوى', icon: 'fa-arrows-spin', color: '#0d9488', desc: 'تصدير واستيراد كل منشوراتك المؤرشفة، الكلمات المفتاحية، الصور المرفقة، وحالة التدوير.' }), Object.freeze({ key: 'rotation_groups', title: 'حزم ومجموعات المنشورات المخصصة', icon: 'fa-layer-group', color: '#8b5cf6', desc: 'تصدير واستيراد الحزم والمجموعات المخصصة بالمنشورات المحددة للتدوير.' }), Object.freeze({ key: 'rotation_followups', title: 'رسائل المتابعة التلقائية للواتساب', icon: 'fa-brands fa-whatsapp', color: '#00a884', desc: 'تصدير واستيراد رسائل ومواعيد وتكرار المتابعة المرتبطة بالعملاء والمنشورات.' }), Object.freeze({ key: 'campaign_history', title: 'أرشيف سجلات الحملات والنتائج', icon: 'fa-box-archive', color: '#3b82f6', desc: 'تصدير واستيراد السجل الكامل لنتائج النشر والبث وتفاصيل الوجهات.' }), Object.freeze({ key: 'scheduled_campaigns', title: 'طابور الحملات المجدولة المستقبلية', icon: 'fa-calendar-days', color: '#f59e0b', desc: 'تصدير واستيراد طابور الحملات المجدولة ومواعيدها ومرفقاتها.' }), Object.freeze({ key: 'facebook_registry', title: 'مجموعات فيسبوك، رادار السلامة، والتصنيفات', icon: 'fa-users-rectangle', color: '#1877f2', desc: 'تصدير واستيراد قائمة جروبات الفيسبوك، الشرائح، الحجر الصحي، والتصنيفات.' }), Object.freeze({ key: 'whatsapp_queue', title: 'طابور مستلمي وأرقام واتساب', icon: 'fa-address-book', color: '#10b981', desc: 'تصدير واستيراد قائمة أرقام ومستلمي الواتساب في طابور الانتظار.' }), Object.freeze({ key: 'protection_pacing', title: 'إعدادات التخفي وفترات تبريد الاتصال', icon: 'fa-shield-halved', color: '#6366f1', desc: 'تصدير واستيراد إعدادات دروع التخفي ومحاكاة السلوك وفترات الانتظار.' }), Object.freeze({ key: 'contacts_manager', title: 'سجل جهات الاتصال والعملاء والوسوم', icon: 'fa-address-book', color: '#0284c7', desc: 'تصدير واستيراد كل بيانات العميل، مراحل المبيعات، الوسوم، والحقول المخصصة.' }), Object.freeze({ key: 'calendar_agenda', title: 'تقويم المهام والأجندة الذكية والتذكيرات', icon: 'fa-calendar-check', color: '#1d4ed8', desc: 'تصدير واستيراد كل أحداث ومهام الأجندة والمواعيد المجدولة.' }), Object.freeze({ key: 'fb_recent_published', title: 'سجل التعبئة السريعة لمنشورات فيسبوك', icon: 'fa-clock-rotate-left', color: '#059669', desc: 'تصدير واستيراد سجل التعبئة السريعة والمنشورات الحديثة.' }), Object.freeze({ key: 'store_rates_cache', title: 'إعدادات المتجر وأسعار الصرف المؤرشفة', icon: 'fa-store', color: '#7c3aed', desc: 'تصدير واستيراد أسعار الصرف المؤرشفة محلياً وبيانات المتجر.' }) ]); /** * تحويل Blob إلى نص Base64 */ async function blobToBase64(blob) { if (global.FritreeStorage && typeof global.FritreeStorage.blobToB64 === 'function') { return await global.FritreeStorage.blobToB64(blob); } if (blob instanceof Uint8Array) { blob = new Blob([blob]); } const buffer = await blob.arrayBuffer(); const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } return `data:${blob.type || 'image/jpeg'};base64,${btoa(binary)}`; } /** * تحويل نص Base64 إلى Blob */ function base64ToBlob(base64Data) { if (global.FritreeStorage && typeof global.FritreeStorage.b64ToBlob === 'function') { return global.FritreeStorage.b64ToBlob(base64Data); } const parts = base64Data.split(","); const mimeType = parts[0].split(":")[1].split(";")[0]; const byteCharacters = atob(parts[1] || parts[0]); const byteArrays = []; for (let offset = 0; offset < byteCharacters.length; offset += 512) { const slice = byteCharacters.slice(offset, offset + 512); const byteNumbers = new Array(slice.length); for (let i = 0; i < slice.length; i++) { byteNumbers[i] = slice.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); byteArrays.push(byteArray); } return new Blob(byteArrays, { type: mimeType }); } /** * بناء واجهة قسم النسخ الاحتياطي والتصدير والاستيراد (#oboufoudrv) */ function buildBackupPaneLayout() { const contentArea = document.getElementById('s7ihkirolg'); if (!contentArea) return false; const host = document.getElementById('fritree-dynamic-panes-host') || contentArea; let pane = document.getElementById('oboufoudrv'); if (pane) { if (pane.parentElement !== host) { host.appendChild(pane); } return true; } pane = document.createElement('section'); pane.className = 'rjiai07g77'; pane.id = 'oboufoudrv'; pane.innerHTML = `

مركز النسخ الاحتياطي وتصدير واستيراد بيئة العمل

تشفير متقدم AES-256-GCM مع دعم تقييد النسخ بكود الجهاز الموثق أو تصديرها كنسخ عامة.

تصدير حزمة بيئة العمل الشاملة (.zip)

يتم تشفير وحزم كافة الأقسام (المنشورات، جهات الاتصال، التقويم، الحملات، الإعدادات، والوسائط) داخل ملف ZIP مشفر.

استيراد واستعادة ملف النسخة الاحتياطية

اسحب وأفلت ملف الـ ZIP المشفر هنا أو اضغط لاختيار الملف من جهازك

تصدير واستيراد الأقسام والوحدات المستقلة

`; host.appendChild(pane); renderModulesGrid(); bindBackupEvents(); return true; } /** * رسم شبكة بطاقات الأقسام المستقلة */ function renderModulesGrid() { const container = document.getElementById('backup-modules-grid'); if (!container) return; container.innerHTML = ''; MODULE_DEFINITIONS.forEach(mod => { const card = document.createElement('div'); card.style.cssText = ` border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 14px; background: var(--bg-card); display: flex; flex-direction: column; justify-content: space-between; gap: 8px; box-shadow: var(--shadow-xs); box-sizing: border-box; `; card.innerHTML = `
${mod.title}

${mod.desc}

جاري الحساب...
`; container.appendChild(card); }); refreshModuleBadges(); } /** * تجميع بيانات قسم محدد من التخزين المشفر * @param {string} moduleKey * @returns {Promise<{ payload: Object, mediaBlobsToExport: Array }>} */ async function collectModuleData(moduleKey) { const payload = { moduleKey: moduleKey, timestamp: new Date().toISOString(), version: "1.3.0-ModularFull", algo: "PBKDF2-SHA256-AES-GCM" }; const mediaBlobsToExport = []; switch (moduleKey) { case 'rotation_library': payload.contentLibraryData = await global.FritreeStorage.get('local_content_library', []); payload.contentCategoriesData = await global.FritreeStorage.get('local_content_categories', []); payload.previousPostsData = await global.FritreeStorage.get('local_previous_posts', []); payload.selectedPrevPostIds = await global.FritreeStorage.get('local_selected_prev_posts', []); payload.isRotationActive = await global.FritreeStorage.get('local_is_rotation_active', 'false'); payload.rotationMode = await global.FritreeStorage.get('local_rotation_mode', 'balanced'); if (payload.previousPostsData && Array.isArray(payload.previousPostsData)) { for (const post of payload.previousPostsData) { if (post.mediaReferences && Array.isArray(post.mediaReferences)) { for (const ref of post.mediaReferences) { const fullBlob = await global.FritreeStorage.get(`media_blob_${ref.id}`); const thumbBlob = await global.FritreeStorage.get(`media_thumb_${ref.id}`); if (fullBlob) { mediaBlobsToExport.push({ id: ref.id, suffix: 'blob', blob: fullBlob, filename: ref.filename || `${ref.id}_blob.jpg` }); } if (thumbBlob) { mediaBlobsToExport.push({ id: ref.id, suffix: 'thumb', blob: thumbBlob, filename: ref.filename || `${ref.id}_thumb.jpg` }); } } } } } break; case 'rotation_groups': payload.postGroupsData = await global.FritreeStorage.get('local_post_groups', []); break; case 'rotation_followups': payload.followupPosts = (await global.FritreeStorage.get('local_previous_posts', [])).filter(p => p.enableWaFollowUp); break; case 'campaign_history': payload.campaignHistoryData = await global.FritreeStorage.get('campaignHistoryData', []); break; case 'scheduled_campaigns': payload.scheduledCampaignsData = await global.FritreeStorage.get('scheduledCampaignsData', []); break; case 'facebook_registry': payload.localGroups = await global.FritreeStorage.get('local_groups', []); payload.selectedGroupIds = await global.FritreeStorage.get('local_selected_groups', []); payload.savedTags = await global.FritreeStorage.get('local_saved_tags', {}); payload.groupClassifications = await global.FritreeStorage.get('local_fb_group_classifications', {}); payload.quarantinedGroups = await global.FritreeStorage.get('local_fb_quarantined_groups', []); payload.quarantineTimestamps = await global.FritreeStorage.get('local_fb_quarantine_timestamps', {}); payload.customCooldowns = await global.FritreeStorage.get('local_fb_custom_group_cooldowns', {}); payload.whitelistedGroups = await global.FritreeStorage.get('local_fb_whitelisted_groups', []); payload.blacklistedGroups = await global.FritreeStorage.get('local_fb_blacklisted_groups', []); break; case 'whatsapp_queue': payload.waRecipients = await global.FritreeStorage.get('local_wa_recipients_queue', []); break; case 'protection_pacing': payload.sleepIntervals = await global.FritreeStorage.get('local_sleep_intervals', {}); payload.protectionSettings = await global.FritreeStorage.get('local_protection_settings', {}); payload.shieldConfigMatrix = await global.FritreeStorage.get('local_shield_config_matrix', {}); break; case 'contacts_manager': payload.contactsDatabase = await global.FritreeStorage.get('local_contacts_database', []); break; case 'calendar_agenda': payload.calendarEvents = await global.FritreeStorage.get('local_calendar_events_db', []); break; case 'fb_recent_published': payload.fbRecentPublishedPosts = await global.FritreeStorage.get('fbRecentPublishedPosts', []); if (payload.fbRecentPublishedPosts && Array.isArray(payload.fbRecentPublishedPosts)) { for (const item of payload.fbRecentPublishedPosts) { if (item.images && Array.isArray(item.images)) { for (const img of item.images) { const rawBlob = await global.FritreeStorage.get(`media_blob_recent_${item.id}_${img.id}`); if (rawBlob) { mediaBlobsToExport.push({ id: `recent_${item.id}_${img.id}`, suffix: 'blob', blob: rawBlob, filename: img.name || `${img.id}.jpg` }); } } } } } break; case 'store_rates_cache': payload.cachedRates = await global.FritreeStorage.get('cached_internet_rates', {}); payload.ratesLastUpdated = await global.FritreeStorage.get('rates_last_updated', ''); break; } return { payload, mediaBlobsToExport }; } /** * استعادة وفك تشفير وحفظ بيانات قسم محدد في التخزين المشفر */ async function restoreModuleData(payload, zipFiles = [], passphrase = '', currentId = '') { if (payload.contentCategoriesData) await global.FritreeStorage.set('local_content_categories', payload.contentCategoriesData); if (payload.contentLibraryData) await global.FritreeStorage.set('local_content_library', payload.contentLibraryData); if (payload.previousPostsData) { const existingPosts = await global.FritreeStorage.get('local_previous_posts', []); const mergedPosts = [...existingPosts]; for (const importedPost of payload.previousPostsData) { if (!importedPost.id) { importedPost.id = 'rot_' + Date.now() + '_' + Math.random().toString(36).substr(2, 4); } const duplicateIndex = mergedPosts.findIndex(p => p.id === importedPost.id); if (duplicateIndex !== -1) { mergedPosts[duplicateIndex] = { ...mergedPosts[duplicateIndex], ...importedPost }; } else { mergedPosts.push(importedPost); } } await global.FritreeStorage.set('local_previous_posts', mergedPosts); } if (payload.postGroupsData) await global.FritreeStorage.set('local_post_groups', payload.postGroupsData); if (payload.campaignHistoryData) await global.FritreeStorage.set('campaignHistoryData', payload.campaignHistoryData); if (payload.scheduledCampaignsData) await global.FritreeStorage.set('scheduledCampaignsData', payload.scheduledCampaignsData); if (payload.localGroups) await global.FritreeStorage.set('local_groups', payload.localGroups); if (payload.selectedGroupIds) await global.FritreeStorage.set('local_selected_groups', payload.selectedGroupIds); if (payload.savedTags) await global.FritreeStorage.set('local_saved_tags', payload.savedTags); if (payload.groupClassifications) await global.FritreeStorage.set('local_fb_group_classifications', payload.groupClassifications); if (payload.quarantinedGroups) await global.FritreeStorage.set('local_fb_quarantined_groups', payload.quarantinedGroups); if (payload.quarantineTimestamps) await global.FritreeStorage.set('local_fb_quarantine_timestamps', payload.quarantineTimestamps); if (payload.customCooldowns) await global.FritreeStorage.set('local_fb_custom_group_cooldowns', payload.customCooldowns); if (payload.whitelistedGroups) await global.FritreeStorage.set('local_fb_whitelisted_groups', payload.whitelistedGroups); if (payload.blacklistedGroups) await global.FritreeStorage.set('local_fb_blacklisted_groups', payload.blacklistedGroups); if (payload.waRecipients) await global.FritreeStorage.set('local_wa_recipients_queue', payload.waRecipients); if (payload.sleepIntervals) await global.FritreeStorage.set('local_sleep_intervals', payload.sleepIntervals); if (payload.protectionSettings) await global.FritreeStorage.set('local_protection_settings', payload.protectionSettings); if (payload.shieldConfigMatrix) await global.FritreeStorage.set('local_shield_config_matrix', payload.shieldConfigMatrix); if (payload.contactsDatabase && Array.isArray(payload.contactsDatabase)) { const existingContacts = await global.FritreeStorage.get('local_contacts_database', []); const mergedContacts = [...existingContacts]; for (const impCnt of payload.contactsDatabase) { if (!impCnt.id) impCnt.id = 'cnt_' + Date.now() + '_' + Math.random().toString(36).substr(2, 4); const dupIdx = mergedContacts.findIndex(c => c.id === impCnt.id || (c.phone && impCnt.phone && c.phone === impCnt.phone)); if (dupIdx !== -1) { mergedContacts[dupIdx] = { ...mergedContacts[dupIdx], ...impCnt }; } else { mergedContacts.push(impCnt); } } await global.FritreeStorage.set('local_contacts_database', mergedContacts); } if (payload.calendarEvents && Array.isArray(payload.calendarEvents)) { const existingEvents = await global.FritreeStorage.get('local_calendar_events_db', []); const mergedEvents = [...existingEvents]; for (const impEvt of payload.calendarEvents) { if (!impEvt.id) impEvt.id = 'evt_' + Date.now() + '_' + Math.random().toString(36).substr(2, 4); const dupIdx = mergedEvents.findIndex(e => e.id === impEvt.id); if (dupIdx !== -1) { mergedEvents[dupIdx] = { ...mergedEvents[dupIdx], ...impEvt }; } else { mergedEvents.push(impEvt); } } await global.FritreeStorage.set('local_calendar_events_db', mergedEvents); } if (payload.fbRecentPublishedPosts) await global.FritreeStorage.set('fbRecentPublishedPosts', payload.fbRecentPublishedPosts); if (payload.cachedRates) await global.FritreeStorage.set('cached_internet_rates', payload.cachedRates); if (payload.ratesLastUpdated) await global.FritreeStorage.set('rates_last_updated', payload.ratesLastUpdated); // فك تشفير واستعادة الصور والوسائط المرفقة const imageFiles = zipFiles.filter(file => file.name.startsWith("images/") && file.name.endsWith(".enc")); for (const file of imageFiles) { const fileParts = file.name.slice("images/".length).replace(".enc", "").split("_"); const suffix = fileParts.pop(); const mediaId = fileParts.join("_"); const encryptedImgText = new TextDecoder().decode(file.data); const decryptedImgB64 = await global.FritreeCrypto.decryptBackupString(encryptedImgText, passphrase, currentId); if (decryptedImgB64) { const imgBlob = base64ToBlob(decryptedImgB64); await global.FritreeStorage.set(`media_${suffix}_${mediaId}`, imgBlob); } } } /** * حساب إحصائيات وعدد العناصر بكل قسم */ async function refreshModuleBadges() { const stats = {}; for (const m of MODULE_DEFINITIONS) { let countText = "0 عناصر"; switch (m.key) { case 'rotation_library': const posts = await global.FritreeStorage.get('local_previous_posts', []); countText = `${posts.length.toLocaleString('en-US')} منشورات بالمكتبة`; break; case 'rotation_groups': const groups = await global.FritreeStorage.get('local_post_groups', []); countText = `${groups.length.toLocaleString('en-US')} حزمة مخصصة`; break; case 'rotation_followups': const followups = (await global.FritreeStorage.get('local_previous_posts', [])).filter(p => p.enableWaFollowUp); countText = `${followups.length.toLocaleString('en-US')} رسالة متابعة`; break; case 'campaign_history': const history = await global.FritreeStorage.get('campaignHistoryData', []); countText = `${history.length.toLocaleString('en-US')} سجل حملات`; break; case 'scheduled_campaigns': const sched = await global.FritreeStorage.get('scheduledCampaignsData', []); countText = `${sched.length.toLocaleString('en-US')} حملات مجدولة`; break; case 'facebook_registry': const fbGroups = await global.FritreeStorage.get('local_groups', []); countText = `${fbGroups.length.toLocaleString('en-US')} مجموعة متصلة`; break; case 'whatsapp_queue': const waQ = await global.FritreeStorage.get('local_wa_recipients_queue', []); countText = `${waQ.length.toLocaleString('en-US')} مستلم بالطابور`; break; case 'protection_pacing': countText = "مصفوفة فترات الأمان والتبريد"; break; case 'contacts_manager': const contacts = await global.FritreeStorage.get('local_contacts_database', []); countText = `${contacts.length.toLocaleString('en-US')} جهة اتصال بالسجل`; break; case 'calendar_agenda': const calEvents = await global.FritreeStorage.get('local_calendar_events_db', []); countText = `${calEvents.length.toLocaleString('en-US')} أحداث ومهام مجدولة`; break; case 'fb_recent_published': const recentFb = await global.FritreeStorage.get('fbRecentPublishedPosts', []); countText = `${recentFb.length.toLocaleString('en-US')} منشورات سريعة`; break; case 'store_rates_cache': countText = "أسعار الصرف وسجل الكاش المعتمد"; break; } stats[m.key] = countText; const badge = document.getElementById(`module-count-${m.key}`); if (badge) { badge.textContent = countText; } } return stats; } /** * تصدير قسم منفرد وتشفيره وحزمه في ملف ZIP */ async function exportSingleModule(moduleKey, passphrase = '', targetAccountId = '') { const { payload, mediaBlobsToExport } = await collectModuleData(moduleKey); const plainText = JSON.stringify(payload); const targetId = targetAccountId ? targetAccountId.trim() : null; const encryptedMetaBase64 = await global.FritreeCrypto.encryptBackupString(plainText, passphrase, targetId); if (!encryptedMetaBase64) { throw new Error("فشل تشفير بيانات القسم المحدد."); } const zipFiles = []; const textEncoder = new TextEncoder(); zipFiles.push({ name: "module_data.json.enc", data: textEncoder.encode(encryptedMetaBase64) }); if (mediaBlobsToExport.length > 0) { for (const img of mediaBlobsToExport) { const imgB64 = await blobToBase64(img.blob); const encryptedImgB64 = await global.FritreeCrypto.encryptBackupString(imgB64, passphrase, targetId); if (encryptedImgB64) { zipFiles.push({ name: `images/${img.id}_${img.suffix}.enc`, data: textEncoder.encode(encryptedImgB64) }); } } } const zipBytes = global.FritreeZipArchive.createZip(zipFiles); const blob = new Blob([zipBytes], { type: 'application/zip' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; const now = new Date(); const pad = num => String(num).padStart(2, '0'); const timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}`; a.download = `قسم_مشفر_${moduleKey}_${timestamp}.zip`; a.click(); URL.revokeObjectURL(url); } /** * تصدير النسخة الاحتياطية الشاملة لبيئة العمل */ async function exportMasterFullBackup(selectedModuleKeys, passphrase = '', targetAccountId = '') { const masterPayload = { timestamp: new Date().toISOString(), version: "1.3.0-MasterFull", algo: "PBKDF2-SHA256-AES-GCM", modules: {} }; const allMediaBlobs = []; for (const key of selectedModuleKeys) { const { payload, mediaBlobsToExport } = await collectModuleData(key); masterPayload.modules[key] = payload; if (mediaBlobsToExport.length > 0) { allMediaBlobs.push(...mediaBlobsToExport); } } const plainText = JSON.stringify(masterPayload); const targetId = targetAccountId ? targetAccountId.trim() : null; const encryptedMetadataBase64 = await global.FritreeCrypto.encryptBackupString(plainText, passphrase, targetId); if (!encryptedMetadataBase64) { throw new Error("فشل تشفير بيئة العمل الشاملة."); } const zipFiles = []; const textEncoder = new TextEncoder(); zipFiles.push({ name: "workspace_data.json.enc", data: textEncoder.encode(encryptedMetadataBase64) }); for (const img of allMediaBlobs) { const imgB64 = await blobToBase64(img.blob); const encryptedImgB64 = await global.FritreeCrypto.encryptBackupString(imgB64, passphrase, targetId); if (encryptedImgB64) { zipFiles.push({ name: `images/${img.id}_${img.suffix}.enc`, data: textEncoder.encode(encryptedImgB64) }); } } const zipBytes = global.FritreeZipArchive.createZip(zipFiles); const blob = new Blob([zipBytes], { type: 'application/zip' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; const now = new Date(); const pad = num => String(num).padStart(2, '0'); const timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}`; const fileSuffix = targetId ? "مقيد_بمعرف" : "عام_مفتوح"; a.download = `حزمة_بيئة_عمل_فريتري_شاملة_${fileSuffix}_${timestamp}.zip`; a.click(); URL.revokeObjectURL(url); } /** * استيراد ملف ZIP وفك تشفيره */ async function importBackupZipFile(arrayBuffer, passphrase = '') { const zipFiles = global.FritreeZipArchive.readZip(arrayBuffer); const metaFile = zipFiles.find(f => f.name === "workspace_data.json.enc" || f.name === "module_data.json.enc"); if (!metaFile) { throw new Error("فشل الاستيراد: صيغة الملف غير صالحة أو غير متوافقة."); } const encryptedText = new TextDecoder().decode(metaFile.data); const currentId = await global.FritreeCrypto.getOrGenerateAccountId(); const decryptedText = await global.FritreeCrypto.decryptBackupString(encryptedText, passphrase, currentId); if (!decryptedText) { throw new Error("فشل فك التشفير: كلمة المرور غير صحيحة أو الملف مقفول لجهاز آخر."); } const data = JSON.parse(decryptedText); if (data.modules && typeof data.modules === 'object') { for (const [mKey, mPayload] of Object.entries(data.modules)) { await restoreModuleData(mPayload, zipFiles, passphrase, currentId); } } else { await restoreModuleData(data, zipFiles, passphrase, currentId); } await refreshModuleBadges(); return true; } /** * ربط أحداث واجهة النسخ الاحتياطي */ function bindBackupEvents() { const btnExportMaster = document.getElementById('btn-export-master-backup'); if (btnExportMaster) { btnExportMaster.onclick = async () => { const pass = (document.getElementById('backup-master-password')?.value || '').trim(); const targetId = (document.getElementById('backup-target-device-id')?.value || '').trim(); const keys = MODULE_DEFINITIONS.map(m => m.key); btnExportMaster.disabled = true; btnExportMaster.innerHTML = ' جاري التشفير والحزم...'; try { await exportMasterFullBackup(keys, pass, targetId); alert('تم بنجاح تصدير وتنزيل حزمة بيئة العمل الشاملة المشفرة!'); } catch (e) { alert('خطأ أثناء التصدير: ' + e.message); } finally { btnExportMaster.disabled = false; btnExportMaster.innerHTML = ' تصدير وتنزيل الحزمة الشاملة الآن'; } }; } const dropzone = document.getElementById('backup-import-dropzone'); const fileInput = document.getElementById('backup-import-file-input'); if (dropzone && fileInput) { dropzone.onclick = () => fileInput.click(); fileInput.onchange = async (e) => { if (e.target.files.length > 0) { await handleIncomingZipFile(e.target.files[0]); } }; dropzone.ondragover = (e) => { e.preventDefault(); dropzone.style.borderColor = 'var(--primary)'; dropzone.style.background = 'var(--primary-light)'; }; dropzone.ondragleave = () => { dropzone.style.borderColor = 'var(--border-strong)'; dropzone.style.background = 'var(--bg-subtle)'; }; dropzone.ondrop = async (e) => { e.preventDefault(); dropzone.style.borderColor = 'var(--border-strong)'; dropzone.style.background = 'var(--bg-subtle)'; if (e.dataTransfer && e.dataTransfer.files.length > 0) { await handleIncomingZipFile(e.dataTransfer.files[0]); } }; } document.querySelectorAll('.btn-export-single-mod').forEach(btn => { btn.onclick = async () => { const modKey = btn.getAttribute('data-mod-key'); if (!modKey) return; const pass = prompt("اكتب كلمة مرور لتشفير هذا القسم (اختياري، اضغط موافق بدون كتابة للتصدير العام):") || ''; try { await exportSingleModule(modKey, pass); alert('تم بنجاح تصدير ملف القسم المشفر!'); } catch (e) { alert('خطأ أثناء تصدير القسم: ' + e.message); } }; }); } async function handleIncomingZipFile(file) { if (!file || !file.name.endsWith('.zip')) { alert('يرجى اختيار ملف بصيغة ZIP صالحة!'); return; } const pass = (document.getElementById('backup-import-password')?.value || '').trim(); try { const buffer = await file.arrayBuffer(); await importBackupZipFile(buffer, pass); alert('تم استيراد واستعادة بيئة العمل بنجاح تام!'); if (global.syncWorkspaceUI) global.syncWorkspaceUI(); } catch (e) { alert('فشل استيراد الملف: ' + e.message); } } /** * تصدير خدمة النسخ الاحتياطي والتصدير/الاستيراد */ global.FritreeBackupService = { MODULE_DEFINITIONS, buildPane: buildBackupPaneLayout, refreshBadges: refreshModuleBadges, exportSingleModule, exportMasterBackup: exportMasterFullBackup, importZipFile: importBackupZipFile, init: function() { buildBackupPaneLayout(); return true; } }; global.FritreeMigration = global.FritreeBackupService; if (typeof document !== 'undefined') { if (document.readyState === 'complete' || document.readyState === 'interactive') { global.FritreeBackupService.init(); } else { document.addEventListener('DOMContentLoaded', () => global.FritreeBackupService.init()); } } })(typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : this);