maher-tradingview-api
Version:
Tradingview instant stocks API, indicator alerts, trading bot, and more !
556 lines (470 loc) • 19.7 kB
JavaScript
/* eslint-disable linebreak-style */
/* eslint-disable no-plusplus */
/* eslint-disable no-unused-vars */
/* eslint-disable prefer-const */
/* eslint-disable no-undef */
/**
* connection-manager.js
* مدير الاتصال بالإنترنت وتحسين تجربة المستخدم عند انقطاع الاتصال
*/
// عند تحميل الصفحة
document.addEventListener('DOMContentLoaded', function() {
// التحقق من حالة الاتصال
checkConnectionStatus();
// إضافة مراقب لتغييرات حالة الاتصال
window.addEventListener('online', updateConnectionStatus);
window.addEventListener('offline', updateConnectionStatus);
// تنفيذ اختبار دوري للاتصال
setInterval(pingServer, 30000); // كل 30 ثانية
// تهيئة مدير مصادر البيانات إذا كان متوفراً
initializeDataSourcesManager();
// التحقق من حالة مخزن التخزين المؤقت
checkCacheStorage();
});
/**
* التحقق من توفر مدير مصادر البيانات وتهيئته
*/
function initializeDataSourcesManager() {
if (!window.DataSourcesManager) {
console.warn('مدير مصادر البيانات غير متوفر، سيتم استخدام المصدر الأساسي فقط');
return false;
}
console.log('مدير مصادر البيانات متوفر، جارٍ التهيئة...');
// الاستماع لتغييرات مصدر البيانات
document.addEventListener('dataSourceChanged', handleDataSourceChange);
// محاولة استعادة المصدر المحفوظ في التخزين المحلي
try {
const savedSource = localStorage.getItem('preferredDataSource');
if (savedSource && window.DataSourcesManager.DATA_SOURCES) {
const validSources = Object.values(window.DataSourcesManager.DATA_SOURCES);
if (validSources.includes(savedSource)) {
console.log(`استخدام مصدر البيانات المحفوظ: ${savedSource}`);
window.DataSourcesManager.setDataSource(savedSource);
}
}
} catch (e) {
console.warn('خطأ في استعادة مصدر البيانات المفضل:', e);
}
// التحقق من صحة المصادر المتوفرة
checkAvailableDataSources();
return true;
}
/**
* معالجة حدث تغيير مصدر البيانات
* @param {CustomEvent} event - حدث تغيير المصدر
*/
function handleDataSourceChange(event) {
if (event.detail && event.detail.source) {
const { source, reason } = event.detail;
console.log(`تم تغيير مصدر البيانات إلى: ${source}، السبب: ${reason || 'غير محدد'}`);
// عرض إشعار للمستخدم
if (typeof showDataSourceChangedNotification === 'function') {
const sourceName = getDataSourceDisplayName(source);
showDataSourceChangedNotification(sourceName);
}
// حفظ المصدر المفضل في التخزين المحلي
localStorage.setItem('preferredDataSource', source);
// تحديث مؤشرات واجهة المستخدم
updateDataSourceIndicators(source);
// إرسال إحصائيات استخدام المصدر للتتبع (اختياري)
logDataSourceUsage(source, reason);
}
}
/**
* الحصول على اسم العرض لمصدر البيانات
* @param {string} source - كود مصدر البيانات
* @return {string} - الاسم المعروض للمستخدم
*/
function getDataSourceDisplayName(source) {
const sourceNames = {
'tradingview': 'TradingView API',
'alphavantage': 'Alpha Vantage',
'yahoo': 'Yahoo Finance',
'iex': 'IEX Cloud',
'finnhub': 'Finnhub'
};
return sourceNames[source] || source;
}
/**
* تحديث مؤشرات واجهة المستخدم لمصدر البيانات
* @param {string} source - مصدر البيانات الحالي
*/
function updateDataSourceIndicators(source) {
// تحديث مؤشر مصدر البيانات في الواجهة
const sourceNameElement = document.getElementById('currentDataSourceName');
const sourceStatusElement = document.getElementById('dataSourceStatus');
if (sourceNameElement && sourceStatusElement) {
// تحديد اسم المصدر وفئة الحالة
const sourceName = getDataSourceDisplayName(source);
const sourceClass = source === 'tradingview' ? '' :
source === 'alphavantage' ? 'secondary' :
source === 'yahoo' ? 'fallback' :
source === 'iex' ? 'historical' : 'realtime';
// تعيين النص والفئة
sourceNameElement.textContent = sourceName;
// إزالة جميع الفئات الحالية المتعلقة بالمصدر
sourceStatusElement.classList.remove('secondary', 'fallback', 'historical', 'realtime');
// إضافة الفئة المناسبة
if (sourceClass) {
sourceStatusElement.classList.add(sourceClass);
}
// تحديث النص
sourceStatusElement.textContent = source === 'tradingview' ? 'أساسي' :
source === 'alphavantage' ? 'ثانوي' :
source === 'yahoo' ? 'بديل' :
source === 'iex' ? 'تاريخي' : 'فوري';
}
// تحديث لوحة التحكم في مصادر البيانات
updateDataSourceControlPanel(source);
}
/**
* تحديث لوحة التحكم في مصادر البيانات
* @param {string} source - المصدر الحالي
*/
function updateDataSourceControlPanel(source) {
// التحقق من وجود لوحة التحكم
const sourceItems = document.querySelectorAll('.data-source-item');
if (!sourceItems || sourceItems.length === 0) return;
// إزالة الفئة النشطة من جميع العناصر
sourceItems.forEach(item => {
item.classList.remove('active');
});
// العثور على العنصر النشط وتمييزه
const activeItem = document.getElementById(`source-${source}`);
if (activeItem) {
activeItem.classList.add('active');
// تحديث معلومات المصدر
const infoElement = activeItem.querySelector('.data-source-item-info');
if (infoElement) {
infoElement.textContent = 'مصدر البيانات الحالي';
}
// تحديث حالة المصدر
const statusElement = activeItem.querySelector('.data-source-item-status');
if (statusElement) {
statusElement.textContent = 'متصل';
statusElement.classList.remove('error');
}
}
}
/**
* تسجيل استخدام مصدر البيانات للتحليلات
* @param {string} source - مصدر البيانات المستخدم
* @param {string} reason - سبب تغيير المصدر
*/
function logDataSourceUsage(source, reason) {
// تخزين إحصائيات الاستخدام محلياً
try {
const usage = JSON.parse(localStorage.getItem('dataSourceUsageStats') || '{}');
if (!usage[source]) {
usage[source] = { count: 0, reasons: {} };
}
usage[source].count++;
if (reason) {
if (!usage[source].reasons[reason]) {
usage[source].reasons[reason] = 0;
}
usage[source].reasons[reason]++;
}
// حفظ الإحصائيات
localStorage.setItem('dataSourceUsageStats', JSON.stringify(usage));
} catch (error) {
console.error('فشل في حفظ إحصائيات استخدام مصدر البيانات:', error);
}
}
/**
* التحقق من حالة مخزن التخزين المؤقت
*/
function checkCacheStorage() {
// التأكد من وجود التخزين المؤقت للبيانات
if (typeof initCacheStorage === 'function') {
initCacheStorage();
console.log('تم تهيئة مخزن التخزين المؤقت');
return true;
} else {
console.warn('وظيفة تهيئة مخزن التخزين المؤقت غير متوفرة');
return false;
}
}
/**
* استخدام البيانات المخزنة محليًا عند انقطاع الاتصال
*/
function useOfflineData() {
console.log('محاولة استخدام البيانات المخزنة محليًا...');
// محاولة استخدام البيانات المخزنة مؤقتًا
if (typeof getCachedChartData === 'function') {
// الحصول على الرمز
let symbol = 'AAPL'; // قيمة افتراضية
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('symbol')) {
symbol = urlParams.get('symbol');
} else if (window.currentSymbol) {
symbol = window.currentSymbol;
}
// محاولة استعادة البيانات المخزنة مؤقتًا
const cachedData = getCachedChartData(symbol);
if (cachedData) {
console.log(`استخدام البيانات المخزنة مؤقتًا لـ ${symbol}`);
// استخدام البيانات المخزنة
window.candleData = cachedData;
// إعادة تحميل الرسم البياني
if (typeof window.initializeChartsWithData === 'function') {
window.initializeChartsWithData();
}
return true;
} else {
console.warn('لا توجد بيانات مخزنة مؤقتًا لهذا الرمز');
}
} else {
console.warn('وظيفة استرجاع البيانات المخزنة مؤقتًا غير متاحة');
}
return false;
}
/**
* تهيئة نظام تخزين البيانات مؤقتًا
*/
function initCacheStorage() {
// التحقق من دعم واجهة Cache Storage
if (typeof caches === 'undefined') {
console.warn('Cache Storage API غير مدعومة في هذا المتصفح');
return;
}
// إنشاء وظائف التخزين المؤقت
window.cacheChartData = async function(symbol, data) {
try {
// تخزين البيانات في Local Storage
const cacheKey = `chart_data_${symbol}`;
localStorage.setItem(cacheKey, JSON.stringify({
timestamp: Date.now(),
data: data
}));
console.log(`تم تخزين بيانات ${symbol} مؤقتًا`);
return true;
} catch (error) {
console.error('خطأ في تخزين البيانات مؤقتًا:', error);
return false;
}
};
window.getCachedChartData = function(symbol) {
try {
const cacheKey = `chart_data_${symbol}`;
const cachedItem = localStorage.getItem(cacheKey);
if (cachedItem) {
const parsed = JSON.parse(cachedItem);
// التحقق من أن البيانات لم تتجاوز يوم واحد
const isExpired = (Date.now() - parsed.timestamp) > (24 * 60 * 60 * 1000);
if (!isExpired) {
return parsed.data;
} else {
console.log(`البيانات المخزنة مؤقتًا لـ ${symbol} منتهية الصلاحية`);
localStorage.removeItem(cacheKey);
}
}
} catch (error) {
console.error('خطأ في استرجاع البيانات المخزنة مؤقتًا:', error);
}
return null;
};
}
/**
* التحقق من حالة الاتصال الحالية
*/
function checkConnectionStatus() {
if (navigator.onLine) {
setOnlineStatus(true);
} else {
setOnlineStatus(false);
}
}
/**
* تحديث حالة الاتصال عند تغييرها
*/
function updateConnectionStatus(event) {
setOnlineStatus(navigator.onLine);
// إذا عاد الاتصال وكان هناك خطأ سابق، يمكن تحديث الصفحة
if (navigator.onLine && document.querySelector('.error') &&
document.querySelector('.error').textContent.includes('انتهت مهلة')) {
// عرض خيار إعادة المحاولة
showRetryOption();
}
}
/**
* تعيين حالة الاتصال في واجهة المستخدم
*/
function setOnlineStatus(isOnline) {
// التحقق من وجود عنصر حالة الاتصال
let statusElement = document.getElementById('connection-status');
// إنشاء العنصر إذا لم يكن موجوداً
if (!statusElement) {
statusElement = document.createElement('div');
statusElement.id = 'connection-status';
statusElement.className = 'connection-status';
document.body.appendChild(statusElement);
}
// تحديث حالة الاتصال
if (isOnline) {
statusElement.textContent = '✓ متصل بالإنترنت';
statusElement.className = 'connection-status online';
} else {
statusElement.textContent = '✗ غير متصل بالإنترنت';
statusElement.className = 'connection-status offline';
// عرض رسالة للمستخدم
showOfflineMessage();
}
}
/**
* عرض رسالة عدم الاتصال
*/
function showOfflineMessage() {
// التحقق من عدم وجود الرسالة بالفعل
if (document.getElementById('offline-message')) {
return;
}
// إنشاء عنصر الرسالة
const messageEl = document.createElement('div');
messageEl.id = 'offline-message';
messageEl.className = 'error-message';
messageEl.innerHTML = `
<h3>انقطع الاتصال بالإنترنت</h3>
<p>يبدو أنك غير متصل بالإنترنت حالياً. تأكد من اتصالك وحاول مرة أخرى.</p>
`;
// إدراج الرسالة في بداية المحتوى
const container = document.querySelector('.container');
if (container) {
container.insertBefore(messageEl, container.firstChild);
}
}
/**
* إزالة رسالة عدم الاتصال
*/
function removeOfflineMessage() {
const messageEl = document.getElementById('offline-message');
if (messageEl) {
messageEl.remove();
}
}
/**
* اختبار الاتصال بالخادم
*/
function pingServer() {
// إنشاء طلب بسيط للتحقق من الاتصال
fetch(window.location.origin + '/ping', {
method: 'GET',
cache: 'no-store',
headers: { 'Cache-Control': 'no-cache' },
signal: AbortSignal.timeout(5000) // وقت انتهاء مهلة 5 ثواني
})
.then(response => {
if (response.ok) {
setOnlineStatus(true);
removeOfflineMessage();
// إذا كان هناك خطأ WebSocket سابق، نحاول إعادة الاتصال
checkForWebSocketErrors();
} else {
setOnlineStatus(false);
}
})
.catch((error) => {
// في حالة فشل الاتصال
console.warn('فشل اتصال ping:', error);
setOnlineStatus(false);
// إذا كان الخطأ بسبب انتهاء المهلة، نظهر رسالة خاصة
if (error.name === 'TimeoutError' || error.name === 'AbortError') {
showTimeoutWarning();
}
});
}
/**
* إظهار تحذير بسبب انتهاء مهلة ping
*/
function showTimeoutWarning() {
// التحقق من عدم وجود تحذير بالفعل
if (document.getElementById('timeout-warning')) {
return;
}
// إنشاء عنصر التحذير
const warningEl = document.createElement('div');
warningEl.id = 'timeout-warning';
warningEl.className = 'connection-alert warning';
warningEl.innerHTML = `
<strong>⚠️ تنبيه:</strong> استغرق الاتصال بالخادم وقتًا طويلاً. قد تواجه بطئًا في تحميل البيانات.
<button id="check-connection-btn" style="margin-left: 10px; background-color: #0088cc; color: white; border: none; border-radius: 4px; padding: 3px 8px; cursor: pointer;">فحص الاتصال</button>
`;
// تنسيق التحذير
warningEl.style.backgroundColor = '#fff3cd';
warningEl.style.color = '#856404';
warningEl.style.padding = '10px 15px';
warningEl.style.borderRadius = '5px';
warningEl.style.margin = '10px 0';
warningEl.style.textAlign = 'center';
// إضافة التحذير إلى الصفحة
const container = document.querySelector('.container') || document.body;
container.insertBefore(warningEl, container.firstChild);
// إضافة معالج النقر لزر فحص الاتصال
document.getElementById('check-connection-btn').addEventListener('click', function() {
this.textContent = 'جارٍ الفحص...';
this.disabled = true;
// إجراء فحص للاتصال
pingServer();
// بعد ثانيتين، إما إزالة التحذير أو تحديثه
setTimeout(() => {
if (navigator.onLine) {
warningEl.remove();
} else {
this.textContent = 'فحص الاتصال';
this.disabled = false;
}
}, 2000);
});
}
/**
* التحقق من وجود أخطاء WebSocket سابقة
*/
function checkForWebSocketErrors() {
// التحقق من وجود أخطاء WebSocket محددة في الصفحة
const webSocketErrorEl = document.querySelector('.websocket-error');
if (webSocketErrorEl) {
console.log('تم العثور على خطأ WebSocket سابق، محاولة إصلاحه تلقائيًا...');
// إضافة ملاحظة بالمحاولة
const noteEl = document.createElement('p');
noteEl.innerHTML = '<strong>ملاحظة:</strong> الاتصال بالإنترنت مستقر الآن. جارٍ محاولة إعادة الاتصال...';
noteEl.style.color = '#27ae60';
noteEl.style.marginTop = '10px';
webSocketErrorEl.appendChild(noteEl);
// إعادة تحميل الصفحة بعد ثانيتين
setTimeout(() => {
window.location.reload();
}, 2000);
}
}
/**
* عرض خيار إعادة المحاولة
*/
function showRetryOption() {
// التحقق من وجود قسم الخطأ
const errorSection = document.querySelector('.error');
if (!errorSection || document.getElementById('retry-option')) {
return;
}
// إنشاء عنصر إعادة المحاولة
const retryEl = document.createElement('div');
retryEl.id = 'retry-option';
retryEl.className = 'timeout-counter';
retryEl.textContent = 'تم استعادة الاتصال! يمكنك إعادة المحاولة الآن.';
// إنشاء زر إعادة المحاولة
const retryButton = document.createElement('button');
retryButton.textContent = 'إعادة المحاولة';
retryButton.className = 'retry-button';
retryButton.addEventListener('click', function() {
window.location.reload();
});
retryEl.appendChild(document.createElement('br'));
retryEl.appendChild(retryButton);
// إضافة العنصر إلى قسم الخطأ
errorSection.appendChild(retryEl);
}
// عرض الوظائف للاستخدام العام
window.ConnectionManager = {
checkStatus: checkConnectionStatus,
updateStatus: updateConnectionStatus,
pingServer: pingServer
};