maher-tradingview-api
Version:
Tradingview instant stocks API, indicator alerts, trading bot, and more !
1,408 lines (1,207 loc) • 56.4 kB
JavaScript
/* eslint-disable linebreak-style */
/* eslint-disable no-plusplus */
/* eslint-disable no-unused-vars */
/* eslint-disable prefer-const */
/* eslint-disable no-undef */
/**
* loading-error-handler.js
* معالج الأخطاء أثناء تحميل البيانات من TradingView
* يقوم بمراقبة الأخطاء ومحاولة إعادة التحميل تلقائياً
*/
// عدد محاولات إعادة التحميل التلقائية
const MAX_AUTO_RETRIES = 3;
// وقت الانتظار بين المحاولات (بالمللي ثانية)
const RETRY_DELAY = 3000;
// متغير لتخزين عدد المحاولات الحالية
let currentRetryAttempt = 0;
// مراقب عالمي للأخطاء
window.addEventListener('error', function(event) {
console.warn('تم اكتشاف خطأ عام:', event.error);
// التحقق مما إذا كان الخطأ متعلقاً بـ WebSocket
const errorMsg = event.message || '';
if (
errorMsg.includes('WebSocket') ||
(event.error && event.error.message && (
event.error.message.includes('WebSocket') ||
event.error.message.includes('connect') ||
event.error.message.includes('ETIMEDOUT') ||
event.error.message.includes('timeout')
))
) {
console.error('خطأ في اتصال WebSocket:', event.error);
handleWebSocketError();
}
// تسجيل أخطاء الرسوم البيانية
if (
errorMsg.includes('chart') ||
errorMsg.includes('Canvas') ||
errorMsg.includes('رسم بياني') ||
(event.error && event.error.message && (
event.error.message.includes('chart') ||
event.error.message.includes('Canvas') ||
event.error.message.includes('رسم بياني')
))
) {
logError('windowError', 'خطأ متعلق بالرسوم البيانية', event.error);
}
});
// إنشاء مستمع الحدث المخصص لتحميل بيانات الرسم البياني
window.dispatchChartDataLoaded = function(symbol, data) {
if (symbol && data) {
// حفظ البيانات في التخزين المؤقت
cacheChartData(symbol, data);
// إرسال الحدث
const event = new CustomEvent('chartDataLoaded', {
detail: {
symbol: symbol,
data: data,
timestamp: new Date().getTime()
}
});
document.dispatchEvent(event);
console.log(`تم إرسال حدث تحميل بيانات الرسم البياني لـ ${symbol}`);
}
};
// عند تحميل الصفحة
document.addEventListener('DOMContentLoaded', function() {
// تحقق من وجود قسم الخطأ
const errorSection = document.querySelector('.error');
if (errorSection) {
console.log('تم اكتشاف رسالة خطأ في الصفحة، التحقق من نوع الخطأ...');
// التحقق من نوع الخطأ
const errorText = errorSection.textContent || '';
if (errorText.includes('انتهت مهلة الانتظار') || errorText.includes('Timeout')) {
handleTimeoutError();
} else if (errorText.includes('WebSocket') || errorText.includes('اتصال')) {
handleWebSocketError();
}
}
// إضافة مراقب للأخطاء في الرسوم البيانية
monitorChartsForErrors();
// التحقق من حالة الاتصال
checkNetworkConnection();
// إضافة الدوال إلى النافذة العالمية
window.reloadChart = reloadChart;
window.reloadIndicator = reloadIndicator;
});
/**
* معالجة أخطاء انتهاء المهلة
*/
function handleTimeoutError() {
console.log('تم اكتشاف خطأ انتهاء المهلة، التحضير لإعادة المحاولة...');
// تحديث واجهة المستخدم
const errorSection = document.querySelector('.error');
if (errorSection) {
// إنشاء عنصر عداد تنازلي
const countdownEl = document.createElement('div');
countdownEl.id = 'retry-countdown';
countdownEl.style.marginTop = '15px';
countdownEl.style.padding = '10px';
countdownEl.style.backgroundColor = '#f8f9fa';
countdownEl.style.borderRadius = '4px';
countdownEl.style.textAlign = 'center';
countdownEl.style.fontWeight = 'bold';
// إضافة العنصر إلى قسم الخطأ
errorSection.appendChild(countdownEl);
// بدء العد التنازلي
let countdownSeconds = 5;
updateCountdown(countdownEl, countdownSeconds);
const countdownInterval = setInterval(() => {
countdownSeconds--;
updateCountdown(countdownEl, countdownSeconds);
if (countdownSeconds <= 0) {
clearInterval(countdownInterval);
// إعادة تحميل الصفحة
window.location.reload();
}
}, 1000);
}
}
/**
* تحديث عرض العد التنازلي
*/
function updateCountdown(element, seconds) {
if (element) {
element.textContent = `ستتم محاولة إعادة التحميل تلقائياً خلال ${seconds} ثانية...`;
}
}
/**
* مراقبة الرسوم البيانية للأخطاء
*/
function monitorChartsForErrors() {
console.log('بدء مراقبة جميع الرسوم البيانية للأخطاء...');
// التحقق من وجود الرسم البياني الرئيسي
const priceChart = document.getElementById('priceChart');
if (priceChart) {
console.log('مراقبة الرسم البياني الرئيسي للأخطاء...');
// التحقق بعد مهلة زمنية كافية للسماح بتحميل البيانات
setTimeout(() => {
try {
const chart = Chart.getChart(priceChart);
// إذا لم يتم تهيئة الرسم البياني أو لم تكن هناك بيانات
if (!chart || !chart.data || !chart.data.datasets || chart.data.datasets.length === 0 ||
(chart.data.datasets[0].data && chart.data.datasets[0].data.length === 0)) {
console.warn('الرسم البياني الرئيسي غير مهيأ أو لا يحتوي على بيانات');
// التحقق من توفر البيانات الخام
if (window.candleData && Array.isArray(window.candleData) && window.candleData.length > 0) {
console.log(`البيانات الخام متوفرة (${window.candleData.length} نقطة)، محاولة إعادة تهيئة الرسم البياني...`);
// محاولة إصلاح الرسم البياني أولاً
if (typeof reloadChart === 'function') {
const success = reloadChart('priceChart');
if (!success) {
// إذا فشلت المحاولة، إظهار رسالة الخطأ
showChartLoadingError(priceChart);
}
} else {
// إذا لم تكن الوظيفة متاحة بعد، عرض رسالة الخطأ
showChartLoadingError(priceChart);
}
} else {
console.warn('لا توجد بيانات خام متاحة للرسم البياني');
showChartLoadingError(priceChart);
}
} else {
console.log('الرسم البياني الرئيسي محمل بشكل صحيح');
}
} catch (error) {
console.error('خطأ أثناء التحقق من الرسم البياني:', error);
showChartLoadingError(priceChart);
}
}, 5000);
}
// مراقبة جميع الرسوم البيانية الأخرى (المؤشرات الفنية)
const technicalCharts = [
'maChart', 'bbChart', 'rsiChart', 'macdChart', 'volumeChart',
'emaChart', 'stochasticChart', 'adxChart', 'obvChart', 'trendChart'
];
technicalCharts.forEach(chartId => {
const chartElement = document.getElementById(chartId);
if (chartElement) {
console.log(`إعداد مراقبة المؤشر الفني: ${chartId}`);
// استخدام مهلة أطول قليلاً للمؤشرات الفنية للسماح بتحميلها بالكامل
setTimeout(() => {
try {
const chart = Chart.getChart(chartElement);
if (!chart || !chart.data || !chart.data.datasets || chart.data.datasets.length === 0 ||
(chart.data.datasets[0] && chart.data.datasets[0].data && chart.data.datasets[0].data.length === 0)) {
console.warn(`المؤشر الفني ${chartId} غير مهيأ أو لا يحتوي على بيانات`);
// التحقق من توفر البيانات الخام
if (window.candleData && Array.isArray(window.candleData) && window.candleData.length > 0) {
console.log(`البيانات الخام متوفرة للمؤشر ${chartId}، محاولة إعادة تهيئة...`);
// محاولة إصلاح المؤشر أولاً
if (typeof reloadIndicator === 'function') {
const success = reloadIndicator(chartId);
if (!success) {
// إذا فشلت المحاولة، إظهار رسالة الخطأ
showIndicatorError(chartElement);
}
} else {
// إذا لم تكن الوظيفة متاحة بعد، عرض رسالة الخطأ
showIndicatorError(chartElement);
}
} else {
console.warn(`لا توجد بيانات خام متاحة للمؤشر ${chartId}`);
showIndicatorError(chartElement);
}
} else {
console.log(`المؤشر الفني ${chartId} محمل بشكل صحيح`);
}
} catch (error) {
console.error(`خطأ في مؤشر ${chartId}:`, error);
showIndicatorError(chartElement);
}
}, 6000);
}
});
}
/**
* إظهار خطأ تحميل الرسم البياني الرئيسي
*/
function showChartLoadingError(canvas) {
try {
// التحقق من وجود العنصر
if (!canvas) return;
// التحقق من أن Canvas لم يتم التعامل معه بالفعل
if (canvas.getAttribute('data-error-handled') === 'true') {
return;
}
const ctx = canvas.getContext('2d');
// تنظيف الـ canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// رسم خلفية بيضاء
ctx.fillStyle = '#f8f8f8';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// رسم رسالة الخطأ
ctx.fillStyle = '#e74c3c';
ctx.font = 'bold 14px Arial';
ctx.textAlign = 'center';
ctx.fillText('فشل تحميل بيانات الرسم البياني', canvas.width / 2, canvas.height / 2 - 10);
// رسم رسالة إضافية
ctx.fillStyle = '#666';
ctx.font = '12px Arial';
ctx.fillText('قد تكون هناك مشكلة في الاتصال بالخادم أو في تحميل البيانات', canvas.width / 2, canvas.height / 2 + 20);
// إضافة زر إعادة المحاولة
const container = canvas.parentElement;
if (container) {
container.style.position = 'relative';
// إزالة أزرار قديمة إن وجدت
const oldButtons = container.querySelectorAll('.retry-button, .reload-chart-button, .alt-source-button');
oldButtons.forEach(btn => btn.remove());
// إنشاء زر إعادة المحاولة
const retryButton = document.createElement('button');
retryButton.textContent = 'إعادة تحميل البيانات';
retryButton.className = 'retry-button';
retryButton.style.position = 'absolute';
retryButton.style.top = '50%';
retryButton.style.left = '50%';
retryButton.style.transform = 'translate(-50%, -50%)';
retryButton.style.padding = '8px 16px';
retryButton.style.backgroundColor = '#3498db';
retryButton.style.color = 'white';
retryButton.style.border = 'none';
retryButton.style.borderRadius = '4px';
retryButton.style.cursor = 'pointer';
// تطبيق زر أنيق
retryButton.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.1)';
retryButton.style.transition = 'background-color 0.3s';
// تغيير اللون عند المرور فوق الزر
retryButton.addEventListener('mouseover', function() {
this.style.backgroundColor = '#2980b9';
});
retryButton.addEventListener('mouseout', function() {
this.style.backgroundColor = '#3498db';
});
// إضافة حدث النقر
retryButton.addEventListener('click', function() {
this.disabled = true;
this.textContent = 'جاري إعادة التحميل...';
// محاولة إعادة تهيئة الرسم البياني أولاً
try {
if (typeof reloadChart === 'function') {
reloadChart(canvas.id);
// تعطيل الزر مؤقتًا
setTimeout(() => {
if (checkCanvasHasData(canvas)) {
// تم التحميل بنجاح، إزالة الخطأ
this.remove();
// إزالة الأزرار الأخرى
const otherButtons = container.querySelectorAll('.alt-source-button, .reload-page-button');
otherButtons.forEach(btn => btn.remove());
} else {
// فشل التحميل، إعادة تمكين الزر
this.disabled = false;
this.textContent = 'إعادة تحميل البيانات';
// إضافة زر إعادة تحميل الصفحة بالكامل
addReloadPageButton(container);
}
}, 2000);
} else {
// إذا لم تكن وظيفة إعادة تحميل الرسم البياني متوفرة، إعادة تحميل الصفحة
window.location.reload();
}
} catch (error) {
console.error('فشل في إعادة تحميل الرسم البياني:', error);
// إعادة تحميل الصفحة كحل أخير
window.location.reload();
}
});
// إنشاء زر استخدام مصدر بديل
const altSourceButton = document.createElement('button');
altSourceButton.textContent = 'استخدام مصدر بيانات بديل';
altSourceButton.className = 'alt-source-button';
altSourceButton.style.position = 'absolute';
altSourceButton.style.top = '50%';
altSourceButton.style.left = '50%';
altSourceButton.style.transform = 'translate(-50%, 30px)';
altSourceButton.style.padding = '8px 16px';
altSourceButton.style.backgroundColor = '#2ecc71';
altSourceButton.style.color = 'white';
altSourceButton.style.border = 'none';
altSourceButton.style.borderRadius = '4px';
altSourceButton.style.cursor = 'pointer';
altSourceButton.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.1)';
altSourceButton.style.transition = 'background-color 0.3s';
// تغيير اللون عند المرور فوق الزر
altSourceButton.addEventListener('mouseover', function() {
this.style.backgroundColor = '#27ae60';
});
altSourceButton.addEventListener('mouseout', function() {
this.style.backgroundColor = '#2ecc71';
});
// إضافة حدث النقر للزر البديل
altSourceButton.addEventListener('click', async function() {
this.disabled = true;
this.textContent = 'جارٍ تحميل مصدر بديل...';
// محاولة استخدام مصدر بديل
try {
// البحث عن رمز السهم من خلال URL أو متغيرات عالمية
let symbol = '';
// محاولة استخراج الرمز من URL
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('symbol')) {
symbol = urlParams.get('symbol');
} else if (window.currentSymbol) {
// استخدام متغير عالمي إذا كان موجودًا
symbol = window.currentSymbol;
} else {
// قيمة افتراضية
symbol = 'AAPL';
}
// البحث عن الإطار الزمني
let timeframe = 'D';
if (urlParams.has('timeframe')) {
timeframe = urlParams.get('timeframe');
} else if (window.currentTimeframe) {
timeframe = window.currentTimeframe;
}
// محاولة استخدام مصدر بديل
const success = await tryAlternativeDataSource(symbol, timeframe);
if (success) {
// تم تحميل البيانات بنجاح، إزالة رسالة الخطأ والزر
this.remove();
retryButton.remove();
// إزالة علامة الخطأ من الرسم البياني
canvas.removeAttribute('data-error-handled');
} else {
// فشل التحميل، إعادة تمكين الزر
this.disabled = false;
this.textContent = 'استخدام مصدر بيانات بديل آخر';
// إظهار زر إعادة تحميل الصفحة
addReloadPageButton(container);
}
} catch (error) {
console.error('فشل في تحميل المصدر البديل:', error);
this.disabled = false;
this.textContent = 'فشل تحميل المصدر البديل';
// إظهار زر إعادة تحميل الصفحة
addReloadPageButton(container);
}
});
// إضافة الأزرار إلى الحاوية
container.appendChild(retryButton);
container.appendChild(altSourceButton);
}
// وضع علامة على Canvas بأنه تم معالجة الخطأ
canvas.setAttribute('data-error-handled', 'true');
} catch (error) {
console.error('خطأ أثناء عرض رسالة خطأ التحميل:', error);
}
}
/**
* إظهار خطأ في المؤشر الفني
*/
function showIndicatorError(canvas) {
try {
if (!canvas) return;
// التحقق مما إذا كان الخطأ قد تم معالجته بالفعل
if (canvas.getAttribute('data-error-handled') === 'true') return;
const ctx = canvas.getContext('2d');
// تنظيف الـ canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// رسم خلفية فاتحة
ctx.fillStyle = '#f9f9f9';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// رسم إطار برتقالي خفيف
ctx.strokeStyle = '#e67e22';
ctx.lineWidth = 2;
ctx.strokeRect(2, 2, canvas.width - 4, canvas.height - 4);
// رسم رسالة الخطأ
ctx.fillStyle = '#e67e22';
ctx.font = '12px Arial';
ctx.textAlign = 'center';
ctx.fillText('فشل تحميل المؤشر الفني', canvas.width / 2, canvas.height / 2 - 5);
// إضافة زر إعادة المحاولة صغير
const container = canvas.parentElement;
if (container) {
// إزالة أزرار قديمة إن وجدت
const oldButtons = container.querySelectorAll('.indicator-retry-button');
oldButtons.forEach(btn => btn.remove());
// إنشاء زر إعادة المحاولة
const retryButton = document.createElement('button');
retryButton.textContent = 'إعادة تحميل المؤشر';
retryButton.className = 'indicator-retry-button';
retryButton.style.position = 'absolute';
retryButton.style.bottom = '10px';
retryButton.style.left = '50%';
retryButton.style.transform = 'translateX(-50%)';
retryButton.style.padding = '5px 10px';
retryButton.style.backgroundColor = '#e67e22';
retryButton.style.color = 'white';
retryButton.style.border = 'none';
retryButton.style.borderRadius = '3px';
retryButton.style.fontSize = '11px';
retryButton.style.cursor = 'pointer';
retryButton.addEventListener('click', function() {
this.disabled = true;
this.textContent = 'جارٍ التحميل...';
// محاولة إعادة تهيئة المؤشر
setTimeout(() => {
try {
reloadIndicator(canvas.id);
} catch (error) {
console.error('فشل إعادة تحميل المؤشر:', error);
} finally {
setTimeout(() => {
// تمكين الزر مرة أخرى إذا استمر الخطأ
if (!checkCanvasHasData(canvas)) {
this.disabled = false;
this.textContent = 'إعادة تحميل المؤشر';
} else {
this.remove();
}
}, 1000);
}
}, 300);
});
// إضافة الزر إلى الحاوية
container.appendChild(retryButton);
}
// وضع علامة على Canvas بأنه تم معالجة الخطأ
canvas.setAttribute('data-error-handled', 'true');
} catch (error) {
console.error('خطأ أثناء عرض خطأ المؤشر:', error);
}
}
/**
* إضافة زر إعادة تحميل الصفحة بالكامل
*/
function addReloadPageButton(container) {
// إزالة أزرار قديمة إن وجدت
const oldButtons = container.querySelectorAll('.reload-page-button');
oldButtons.forEach(btn => btn.remove());
const reloadButton = document.createElement('button');
reloadButton.textContent = 'إعادة تحميل الصفحة بالكامل';
reloadButton.className = 'reload-page-button';
reloadButton.style.position = 'absolute';
reloadButton.style.top = '60%';
reloadButton.style.left = '50%';
reloadButton.style.transform = 'translate(-50%, 30px)';
reloadButton.style.padding = '6px 12px';
reloadButton.style.backgroundColor = '#95a5a6';
reloadButton.style.color = 'white';
reloadButton.style.border = 'none';
reloadButton.style.borderRadius = '3px';
reloadButton.style.fontSize = '12px';
reloadButton.style.cursor = 'pointer';
reloadButton.addEventListener('click', function() {
window.location.reload();
});
container.appendChild(reloadButton);
}
/**
* محاولة تحميل البيانات من مصدر بديل
* @param {string} symbol - رمز السهم
* @param {string} timeframe - الإطار الزمني
* @return {Promise<boolean>} وعد بنجاح أو فشل العملية
*/
async function tryAlternativeDataSource(symbol, timeframe = 'D') {
// التحقق من وجود مدير مصادر البيانات
if (!window.DataSourcesManager) {
console.warn('مدير مصادر البيانات غير متوفر');
return false;
}
try {
console.log('محاولة استخدام مصدر بيانات بديل...');
// تبديل المصدر إلى المصدر التالي
const nextSource = window.DataSourcesManager.switchToNextSource();
// محاولة جلب البيانات من المصدر البديل
const data = await window.DataSourcesManager.fetchChartData(symbol, timeframe);
if (!data || data.length === 0) {
console.error('فشل في جلب البيانات من المصدر البديل');
return false;
}
console.log(`تم جلب البيانات بنجاح من المصدر البديل: ${nextSource} (${data.length} نقطة)`);
// تحديث البيانات العالمية
window.candleData = data;
// إعادة رسم جميع الرسوم البيانية
if (typeof reloadChart === 'function') {
reloadChart('priceChart');
}
// إعادة رسم جميع المؤشرات الفنية
const technicalCharts = [
'maChart', 'bbChart', 'rsiChart', 'macdChart', 'volumeChart',
'emaChart', 'stochasticChart', 'adxChart', 'obvChart', 'trendChart'
];
technicalCharts.forEach(chartId => {
if (document.getElementById(chartId) && typeof reloadIndicator === 'function') {
reloadIndicator(chartId);
}
});
// إظهار إشعار للمستخدم
showDataSourceChangedNotification(nextSource);
return true;
} catch (error) {
console.error('فشل في استخدام مصدر البيانات البديل:', error);
return false;
}
}
/**
* إضافة زر "استخدام مصدر بيانات بديل" إلى رسالة الخطأ
*/
function addAlternativeSourceButton() {
// التحقق من وجود مدير مصادر البيانات
if (typeof DataSourcesManager === 'undefined' || !DataSourcesManager.switchToNextSource) {
return;
}
// البحث عن حاوية رسالة الخطأ
const errorContainer = document.querySelector('.error-container');
if (!errorContainer) {
return;
}
// التحقق مما إذا كان الزر موجودًا بالفعل
if (errorContainer.querySelector('.alt-source-btn')) {
return;
}
// إنشاء زر جديد
const altSourceBtn = document.createElement('button');
altSourceBtn.className = 'alt-source-btn';
altSourceBtn.textContent = 'استخدام مصدر بيانات بديل';
// إضافة معالج النقر
altSourceBtn.addEventListener('click', function() {
// محاولة التبديل إلى المصدر التالي
const success = tryAlternativeDataSource();
if (success) {
// إخفاء رسالة الخطأ
hideLoadingError();
// إعادة تحديث الرسم البياني
const urlParams = new URLSearchParams(window.location.search);
const symbol = urlParams.get('symbol') || 'AAPL';
const timeframe = urlParams.get('timeframe') || 'D';
if (typeof fetchAndRenderChart === 'function') {
fetchAndRenderChart(symbol, timeframe);
}
} else {
// عرض رسالة خطأ إذا فشلت جميع المصادر
showLoadingError('جميع مصادر البيانات غير متاحة', 'للأسف، جميع مصادر البيانات البديلة غير متاحة حاليًا. يرجى المحاولة مرة أخرى لاحقًا.', false);
}
});
// إضافة الزر إلى حاوية الخطأ
errorContainer.appendChild(altSourceBtn);
}
/**
* عرض إشعار بتغيير مصدر البيانات
*/
function showDataSourceChangedNotification(sourceName) {
// التحقق من وجود حاوية الإشعارات
const notificationsContainer = document.getElementById('dataSourceNotifications');
if (!notificationsContainer) {
return;
}
// إنشاء إشعار جديد
const notification = document.createElement('div');
notification.className = 'data-source-notification';
notification.innerHTML = `
<strong>تم تغيير مصدر البيانات!</strong>
<p>يتم الآن استخدام: ${sourceName}</p>
<button class="dismiss-button">إغلاق</button>
`;
// إضافة معالج النقر لزر الإغلاق
const dismissButton = notification.querySelector('.dismiss-button');
dismissButton.addEventListener('click', function() {
notification.remove();
});
// إضافة الإشعار إلى الحاوية
notificationsContainer.appendChild(notification);
// إزالة الإشعار تلقائيًا بعد 10 ثوانٍ
setTimeout(() => {
if (notification.parentNode) {
notification.remove();
}
}, 10000);
}
/**
* التحقق مما إذا كان Canvas يحتوي على بيانات
*/
function checkCanvasHasData(canvas) {
try {
const chart = Chart.getChart(canvas);
return chart && chart.data && chart.data.datasets &&
chart.data.datasets.length > 0 &&
chart.data.datasets[0].data &&
chart.data.datasets[0].data.length > 0;
} catch (error) {
console.error('خطأ أثناء فحص بيانات Canvas:', error);
return false;
}
}
/**
* معالجة أخطاء WebSocket
*/
function handleWebSocketError() {
console.log('معالجة خطأ اتصال WebSocket...');
// قراءة معلمات URL للحصول على رمز السهم والإطار الزمني
const urlParams = new URLSearchParams(window.location.search);
const symbol = urlParams.get('symbol') || 'AAPL';
const timeframe = urlParams.get('timeframe') || 'D';
// عرض رسالة خطأ للمستخدم
showLoadingError('خطأ في الاتصال', 'لا يمكن الاتصال بخدمة البيانات. يرجى التحقق من اتصالك بالإنترنت ومحاولة تحديث الصفحة.', true);
// إضافة زر "استخدام مصدر بيانات بديل"
addAlternativeSourceButton();
// إذا كان عدد المحاولات أقل من الحد الأقصى، قم بإعادة المحاولة تلقائيًا
if (currentRetryAttempt < MAX_AUTO_RETRIES) {
currentRetryAttempt++;
console.log(`محاولة إعادة الاتصال (${currentRetryAttempt}/${MAX_AUTO_RETRIES})...`);
// تأخير قبل إعادة المحاولة
setTimeout(() => {
// محاولة استخدام مصدر بديل
const switched = tryAlternativeDataSource();
if (!switched) {
// إعادة تحميل البيانات
if (typeof fetchAndRenderChart === 'function') {
fetchAndRenderChart(symbol, timeframe);
}
}
}, RETRY_DELAY);
} else {
console.error('تم الوصول إلى الحد الأقصى من محاولات إعادة الاتصال التلقائية');
// محاولة استخدام مصدر بديل كملاذ أخير
tryAlternativeDataSource();
}
}
/**
* محاولة استخدام مصدر بيانات بديل عند فشل المصدر الحالي
* @param {Error} error الخطأ الذي تسبب في الفشل
* @returns {boolean} ما إذا كان قد تم التبديل بنجاح
*/
function tryAlternativeDataSource(error) {
console.log('محاولة استخدام مصدر بيانات بديل...');
if (typeof DataSourcesManager !== 'undefined' && DataSourcesManager.switchToNextSource) {
// تحديث حالة المصدر الحالي
const currentSource = DataSourcesManager.currentSource;
// زيادة عداد الأخطاء للمصدر الحالي
if (DataSourcesManager.sourcesStatus && DataSourcesManager.sourcesStatus[currentSource]) {
DataSourcesManager.sourcesStatus[currentSource].errorCount++;
DataSourcesManager.sourcesStatus[currentSource].available = false;
DataSourcesManager.sourcesStatus[currentSource].lastCheck = Date.now();
}
// محاولة التبديل إلى المصدر التالي
const success = DataSourcesManager.switchToNextSource();
if (success) {
console.log('تم التبديل بنجاح إلى مصدر بيانات بديل');
return true;
} else {
console.error('فشل التبديل إلى مصدر بيانات بديل');
return false;
}
}
console.error('مدير مصادر البيانات غير متاح');
return false;
}
// نظام تسجيل الأخطاء
/**
* نظام تسجيل الأخطاء
* @param {string} source - مصدر الخطأ
* @param {string} message - رسالة الخطأ
* @param {Error} [error] - كائن الخطأ (اختياري)
*/
function logError(source, message, error = null) {
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] [${source}] ${message}`;
console.error(logMessage);
if (error) {
console.error('تفاصيل الخطأ:', error);
}
// يمكن إضافة منطق هنا لإرسال السجلات إلى الخادم أو تخزينها محلياً
try {
const errorLogs = JSON.parse(localStorage.getItem('chartErrorLogs') || '[]');
errorLogs.push({
timestamp,
source,
message,
details: error ? error.toString() : null
});
// الاحتفاظ بآخر 50 خطأ فقط
if (errorLogs.length > 50) {
errorLogs.shift(); // إزالة أقدم خطأ
}
localStorage.setItem('chartErrorLogs', JSON.stringify(errorLogs));
} catch (e) {
console.error('فشل في تخزين سجل الأخطاء:', e);
}
}
/**
* فحص شامل لصحة الرسوم البيانية
* يتم استدعاء هذه الوظيفة دورياً للتحقق من صحة جميع الرسوم البيانية
*/
function performFullChartsHealthCheck() {
console.log('بدء فحص شامل لصحة جميع الرسوم البيانية...');
// فحص الرسم البياني الرئيسي
const priceChart = document.getElementById('priceChart');
if (priceChart) {
checkChartHealth(priceChart, 'priceChart', true);
}
// فحص المؤشرات الفنية
const technicalCharts = [
'maChart', 'bbChart', 'rsiChart', 'macdChart', 'volumeChart',
'emaChart', 'stochasticChart', 'adxChart', 'obvChart', 'trendChart'
];
technicalCharts.forEach(chartId => {
const chartElement = document.getElementById(chartId);
if (chartElement) {
checkChartHealth(chartElement, chartId, false);
}
});
}
/**
* فحص صحة رسم بياني محدد
* @param {HTMLElement} canvas - عنصر الـ canvas
* @param {string} chartId - معرف الرسم البياني
* @param {boolean} isPrimaryChart - هل هو الرسم البياني الرئيسي
*/
function checkChartHealth(canvas, chartId, isPrimaryChart) {
try {
const chart = Chart.getChart(canvas);
// التحقق من وجود الرسم البياني
if (!chart) {
logError('healthCheck', `الرسم البياني ${chartId} غير موجود`);
if (isPrimaryChart) {
showChartLoadingError(canvas);
} else {
showIndicatorError(canvas);
}
return false;
}
// التحقق من وجود البيانات
if (!chart.data || !chart.data.datasets || chart.data.datasets.length === 0) {
logError('healthCheck', `الرسم البياني ${chartId} لا يحتوي على مجموعات بيانات`);
if (isPrimaryChart) {
showChartLoadingError(canvas);
} else {
showIndicatorError(canvas);
}
return false;
}
// التحقق من أن البيانات ليست فارغة
const hasData = chart.data.datasets.some(dataset =>
dataset.data && dataset.data.length > 0);
if (!hasData) {
logError('healthCheck', `الرسم البياني ${chartId} يحتوي على بيانات فارغة`);
if (isPrimaryChart) {
showChartLoadingError(canvas);
} else {
showIndicatorError(canvas);
}
return false;
}
// التحقق من أن جميع البيانات ليست صفرية أو غير معرفة
const allDataUndefinedOrZero = chart.data.datasets.every(dataset =>
!dataset.data || dataset.data.every(point =>
point === undefined || point === null || point === 0));
if (allDataUndefinedOrZero) {
logError('healthCheck', `الرسم البياني ${chartId} يحتوي على بيانات غير صالحة (كلها صفر أو غير معرفة)`);
if (isPrimaryChart) {
showChartLoadingError(canvas);
} else {
showIndicatorError(canvas);
}
return false;
}
return true;
} catch (error) {
logError('healthCheck', `خطأ أثناء فحص صحة الرسم البياني ${chartId}`, error);
if (isPrimaryChart) {
showChartLoadingError(canvas);
} else {
showIndicatorError(canvas);
}
return false;
}
}
// بدء فحص صحة الرسوم البيانية بعد وقت كافٍ للتحميل
setTimeout(performFullChartsHealthCheck, 10000);
// تشغيل فحص صحة دوري كل 30 ثانية
setInterval(performFullChartsHealthCheck, 30000);
/**
* إعادة تحميل الرسم البياني الرئيسي
* @param {string} chartId - معرف عنصر الرسم البياني
* @return {boolean} - نجاح أو فشل عملية إعادة التحميل
*/
function reloadChart(chartId) {
try {
console.log(`محاولة إعادة تحميل الرسم البياني: ${chartId}...`);
// التأكد من وجود البيانات
if (!window.candleData || !Array.isArray(window.candleData) || window.candleData.length === 0) {
console.error('لا توجد بيانات متاحة لإعادة تحميل الرسم البياني');
return false;
}
const canvas = document.getElementById(chartId);
if (!canvas) {
console.error(`لا يمكن العثور على عنصر الرسم البياني: ${chartId}`);
return false;
}
// محاولة تدمير الرسم البياني الحالي إذا وُجد
try {
const existingChart = Chart.getChart(canvas);
if (existingChart) {
existingChart.destroy();
console.log('تم تدمير الرسم البياني القديم بنجاح');
}
} catch (destroyError) {
console.warn('خطأ أثناء تدمير الرسم البياني القديم:', destroyError);
// نستمر في المحاولة رغم ذلك
}
// إعادة رسم البيانات استنادا إلى نوع الرسم البياني
if (chartId === 'priceChart' && window.ChartRenderer) {
const data = window.candleData;
const labels = data.map(candle => typeof candle.time === 'string' ? candle.time.split(',')[0] : candle.time);
const values = data.map(candle => parseFloat(candle.close));
console.log(`إعادة رسم البياني الرئيسي مع ${values.length} نقطة بيانات...`);
window.ChartRenderer.renderPriceChart(chartId, labels, values, '');
// إزالة علامة الخطأ
canvas.removeAttribute('data-error-handled');
return true;
} else {
console.warn('لا يمكن إعادة تحميل رسم بياني غير معروف:', chartId);
return false;
}
} catch (error) {
console.error(`فشل في إعادة تحميل الرسم البياني ${chartId}:`, error);
return false;
}
}
/**
* إعادة تحميل المؤشر الفني
* @param {string} indicatorId - معرف عنصر المؤشر الفني
* @return {boolean} - نجاح أو فشل عملية إعادة التحميل
*/
function reloadIndicator(indicatorId) {
try {
console.log(`محاولة إعادة تحميل المؤشر الفني: ${indicatorId}...`);
// التأكد من وجود البيانات
if (!window.candleData || !Array.isArray(window.candleData) || window.candleData.length === 0) {
console.error('لا توجد بيانات متاحة لإعادة تحميل المؤشر');
return false;
}
const canvas = document.getElementById(indicatorId);
if (!canvas) {
console.error(`لا يمكن العثور على عنصر المؤشر: ${indicatorId}`);
return false;
}
// محاولة تدمير المؤشر الحالي إذا وُجد
try {
const existingChart = Chart.getChart(canvas);
if (existingChart) {
existingChart.destroy();
console.log(`تم تدمير المؤشر القديم ${indicatorId} بنجاح`);
}
} catch (destroyError) {
console.warn(`خطأ أثناء تدمير المؤشر القديم ${indicatorId}:`, destroyError);
// نستمر في المحاولة رغم ذلك
}
// تحديد نوع المؤشر من المعرف وإعادة رسمه
const indicatorType = indicatorId.replace('Chart', '').toLowerCase();
const data = window.candleData;
// إعادة رسم المؤشر حسب نوعه
if (!window.TechnicalIndicators) {
console.error('مكتبة المؤشرات الفنية غير متاحة');
return false;
}
console.log(`إعادة رسم المؤشر ${indicatorType}...`);
switch (indicatorType) {
case 'ma':
window.TechnicalIndicators.renderMAChart(indicatorId, data, [20, 50, 200]);
break;
case 'ema':
window.TechnicalIndicators.renderMAChart(indicatorId, data, [12, 26]);
break;
case 'bb':
window.TechnicalIndicators.renderBollingerBandsChart(indicatorId, data, 20, 2);
break;
case 'rsi':
window.TechnicalIndicators.renderRSIChart(indicatorId, data, 14);
break;
case 'macd':
window.TechnicalIndicators.renderMACDChart(indicatorId, data, 12, 26, 9);
break;
case 'volume':
// عادة ما تكون هناك وظيفة خاصة للرسم البياني للحجم
if (window.TechnicalIndicators.renderVolumeChart) {
window.TechnicalIndicators.renderVolumeChart(indicatorId, data);
}
break;
case 'stochastic':
if (window.AdditionalIndicators && window.AdditionalIndicators.renderStochasticChart) {
window.AdditionalIndicators.renderStochasticChart(indicatorId, data, 14, 3);
}
break;
case 'adx':
if (window.AdditionalIndicators && window.AdditionalIndicators.renderADXChart) {
window.AdditionalIndicators.renderADXChart(indicatorId, data, 14);
}
break;
case 'obv':
if (window.AdditionalIndicators && window.AdditionalIndicators.renderOBVChart) {
window.AdditionalIndicators.renderOBVChart(indicatorId, data);
}
break;
case 'trend':
// يعتمد على التنفيذ المحدد لمؤشر الاتجاه
break;
default:
console.warn(`نوع المؤشر غير معروف أو غير مدعوم: ${indicatorType}`);
return false;
}
// إزالة علامة الخطأ
canvas.removeAttribute('data-error-handled');
return true;
} catch (error) {
console.error(`فشل في إعادة تحميل المؤشر ${indicatorId}:`, error);
return false;
}
}
/**
* التحقق من حالة الاتصال بالشبكة
*/
function checkNetworkConnection() {
// التحقق من حالة الاتصال بالشبكة
if (navigator.onLine) {
console.log('متصل بالإنترنت');
// محاولة استخدام البيانات المخزنة مؤقتًا إذا كانت متاحة
const symbol = 'AAPL'; // استبدلها بالرمز المناسب
tryUseCache(symbol);
} else {
console.warn('غير متصل بالإنترنت');
// عرض رسالة خطأ مخصصة
let errorSection = document.querySelector('.network-error');
if (!errorSection) {
errorSection = document.createElement('div');
errorSection.className = 'error network-error';
errorSection.style.backgroundColor = '#fff3cd';
errorSection.style.color = '#856404';
errorSection.style.padding = '10px';
errorSection.style.borderRadius = '4px';
errorSection.style.margin = '10px 0';
errorSection.style.borderLeft = '4px solid #856404';
const message = document.createElement('p');
message.textContent = 'لا يوجد اتصال بالإنترنت. يرجى التحقق من اتصالك ومحاولة إعادة التحميل.';
errorSection.appendChild(message);
const details = document.createElement('p');
details.style.fontSize = '0.9em';
details.style.color = '#666';
details.textContent = 'تفاصيل تقنية: قد يكون هناك مشكلة في الاتصال بالشبكة أو تم حظر الاتصال بواسطة جدار الحماية.';
errorSection.appendChild(details);
// إضافة القسم إلى الصفحة
const container = document.querySelector('.container') || document.body;
const firstCard = container.querySelector('.card');
if (firstCard) {
container.insertBefore(errorSection, firstCard);
} else {
container.appendChild(errorSection);
}
}
}
}
/**
* نظام تخزين مؤقت للبيانات
* هذه الوظائف تساعد في تخزين بيانات الرسوم البيانية مؤقتًا واسترجاعها عند الحاجة
*/
/**
* حفظ بيانات الرسم البياني مؤقتًا
* @param {string} symbol - رمز السهم أو الأداة المالية
* @param {Array} data - بيانات الشموع اليابانية
*/
function cacheChartData(symbol, data) {
if (!symbol || !data || !Array.isArray(data) || data.length === 0) return;
try {
const timestamp = new Date().getTime();
const cacheData = {
timestamp: timestamp,
symbol: symbol,
data: data,
expiresAt: timestamp + (24 * 60 * 60 * 1000) // تنتهي الصلاحية بعد 24 ساعة
};
localStorage.setItem(`chart_data_${symbol}`, JSON.stringify(cacheData));
console.log(`تم تخزين بيانات الرسم البياني لـ ${symbol} مؤقتًا (${data.length} نقطة)`);
} catch (error) {
console.error('فشل في تخزين بيانات الرسم البياني مؤقتًا:', error);
}
}
/**
* استرجاع بيانات الرسم البياني المخزنة مؤقتًا
* @param {string} symbol - رمز السهم أو الأداة المالية
* @return {Array|null} - بيانات الشموع اليابانية أو null إذا لم تكن متاحة
*/
function getCachedChartData(symbol) {
if (!symbol) return null;
try {
const cachedDataString = localStorage.getItem(`chart_data_${symbol}`);
if (!cachedDataString) return null;
const cachedData = JSON.parse(cachedDataString);
// التحقق من انتهاء صلاحية البيانات
const now = new Date().getTime();
if (cachedData.expiresAt < now) {
console.log(`البيانات المخزنة مؤقتًا لـ ${symbol} منتهية الصلاحية، سيتم حذفها`);
localStorage.removeItem(`chart_data_${symbol}`);
return null;
}
// حساب عمر البيانات بالدقائق
const cacheAgeMinutes = Math.round((now - cachedData.timestamp) / (60 * 1000));
console.log(`استرجاع بيانات مخزنة مؤقتًا لـ ${symbol} (${cachedData.data.length} نقطة، عمر البيانات: ${cacheAgeMinutes} دقيقة)`);
return cachedData.data;
} catch (error) {
console.error('فشل في استرجاع بيانات الرسم البياني المخزنة مؤقتًا:', error);
return null;
}
}
/**
* استخدام البيانات المخزنة مؤقتًا لإعادة رسم الرسوم البيانية في حالة فشل التحميل
* @param {string} symbol - رمز السهم أو الأداة المالية
* @return {boolean} - ما إذا كان تم استرجاع وتطبيق البيانات بنجاح
*/
function tryUseCache(symbol) {
if (!symbol) return false;
const cachedData = getCachedChartData(symbol);
if (!cachedData) return false;
try {
console.log(`محاولة استخدام البيانات المخزنة مؤقتًا لـ ${symbol}...`);
// تعيين البيانات المخزنة مؤقتًا كبيانات حالية
window.candleData = cachedData;
// إعادة رسم الرسم البياني الرئيسي
const priceChart = document.getElementById('priceChart');
if (priceChart && window.ChartRenderer) {
const labels = cachedData.map(candle => typeof candle.time === 'string' ? candle.time.split(',')[0] : candle.time);
const values = cachedData.map(candle => parseFloat(candle.close));
window.ChartRenderer.renderPriceChart('priceChart', labels, values, symbol);
// إعادة رسم المؤشرات الفنية
const technicalCharts = [
'maChart', 'bbChart', 'rsiChart', 'macdChart', 'volumeChart',
'emaChart', 'stochasticChart', 'adxChart', 'obvChart', 'trendChart'
];
technicalCharts.forEach(chartId => {
const chartElement = document.getElementById(chartId);
if (chartElement && typeof reloadIndicator === 'function') {
reloadIndicator(chartId);
}
});
// إضافة إشعار لإخبار المستخدم أنه يتم عرض بيانات مخزنة مؤقتًا
showCachedDataNotification();
return true;
}
} catch (error) {
console.error('فشل في استخدام البيانات المخزنة مؤقتًا:', error);
}
return false;
}
/**
* عرض إشعار للمستخدم يوضح أنه يتم عرض بيانات مخزنة مؤقتًا
*/
function showCachedDataNotification() {
const notification = document.createElement('div');
notification.className = 'cached-data-notification';
notification.innerHTML = `
<strong>⚠️ تنبيه:</strong> يتم عرض بيانات محفوظة مؤقتًا. قد لا تكون هذه أحدث البيانات المتاحة.
<button class="refresh-button">تحديث البيانات</button>
`;
// تنسيق الإشعار
notification.style.backgroundColor = '#fff3cd';
notification.style.color = '#856404';
notification.style.padding = '10px 15px';
notification.style.borderRadius = '5px';
notification.style.margin = '10px 0';
notification.style.textAlign = 'center';
notification.style.position = 'relative';
// تنسيق زر التحديث
const refreshButton = notification.querySelector('.refresh-button');
refreshButton.style.backgroundColor = '#856404';
refreshButton.style.color = 'white';
refreshButton.style.border = 'none';
refreshButton.style.borderRadius = '3px';
refreshButton.style.padding = '5px 10px';
refreshButton.style.marginLeft = '10px';
refreshButton.style.cursor = 'pointer';
// إضافة حدث النقر لزر التحديث
refreshButton.addEventListener('click', function() {
window.location.reload();
});
// إضافة الإشعار في أعلى المحتوى
const container = document.querySelector('.container') || document.body;
container.insertBefore(notification, container.firstChild);
}
// مراقبة تغييرات البيانات لتحديث التخزين المؤقت
document.addEventListener('chartDataLoaded', function(event) {
if (event.detail && event.detail.symbol && event.detail.data) {
cacheChartData(event.detail.symbol, event.detail.data);
}
});
/**
* عرض رسالة خطأ التحميل للمستخدم
* @param {string} title - عنوان الخطأ
* @param {string} message - رسالة الخطأ
* @param {boolean} showAlternativeBtn - ما إذا كان يجب عرض زر المصدر البديل
*/
function showLoadingError(title, message, showAlternativeBtn = false) {
console.log(`عرض رسالة الخطأ: ${title} - ${message}`);
// البحث عن حاوية الخطأ الموجودة أو إنشاء واحدة جديدة
let errorContainer = document.querySelector('.error-container');
if (!errorContainer) {
errorContainer = document.createElement('div');
errorContainer.className = 'error-container';
errorContainer.style.backgroundColor = '#fff3f3';
errorContainer.style.border = '1px solid #ffcccc';
errorContainer.style.borderLeft = '4px solid #e74c3c';
errorContainer.style.padding = '15px 20px';
errorContainer.style.margin = '15px 0';
errorContainer.style.borderRadius = '4px';
// إضافة الحاوية إلى الصفحة
const container = document.querySelector('.container') || document.body;
const chartContainer = container.querySelector('.chart-container');
if (chartContainer) {
chartContainer.parentNode.insertBefore(errorContainer, chartContainer);
} else {
container.insertBefore(errorContainer, container.firstChild);
}
} else {
// مسح المحتوى الحالي
errorContainer.innerHTML = '';
}
// إضافة العنوان
const titleEl = document.createElement('h3');
titleEl.textContent = title;
titleEl.style.color = '#e74c3c';
titleEl.style.margin = '0 0 10px 0';
titleEl.style.fontSize = '16px';
errorContainer.appendChild(titleEl);
// إضافة الرسالة
const messageEl = document.createElement('p');
messageEl.textContent = message;
messageEl.style.margin = '0 0 15px 0';
messageEl.style.fontSize = '14px';
messageEl.style.color = '#333';
errorContainer.appendChild(messageEl);
// إضافة زر "استخدام مصدر بيانات بديل" إذا كانت مطلوبة
if (showAlternativeBtn) {
addAlternativeSourceButton(errorContainer);
} else {
// إضافة زر إغلاق
const dismissButton = document.createElement('button');
dismissButton.textContent = 'إغلاق';
dismissButton.style.padding = '5px 12px';
dismissButton.style.backgroundColor = '#e74c3c';
dismissButton.style.color = 'white';
dismissButton.style.border = 'none';
dismissButton.style.borderRadius = '3px';
dismissButton.style.cursor = 'pointer';
dismissButton.style.fontSize = '12px';
dismissButton.addEventListener('click', hideLoadingError);
errorContainer.appendChild(dismissButton);
}
}
/**
* إخفاء رسالة خطأ التحميل
*/
function hideLoadingError() {
const errorContainer = document.querySelector('.error-container');
if (errorContainer) {
errorContainer.style.display = 'none';
}
}
// عرض الوظائف للاستخدام العام
window.LoadingErrorHandler = {
handleTimeoutError: handleTimeoutError,
monitorChartsForErrors: monitorChartsForErrors,
showChartLoadingError: showChartLoadingError,
handleWebSocketError: handleWebSocketError,
checkNetworkConnection: checkNetworkConnection,
reloadChart: reloadChart,
reloadIndicator: reloadIndicator
};