lljz66 commited on
Commit
0df0c3f
·
verified ·
1 Parent(s): f6b67d5

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +161 -245
server.js CHANGED
@@ -45,9 +45,9 @@ let tosdrCache = new Map();
45
  let geoReader = null;
46
  let useMaxMind = false;
47
 
48
- // إنشاء قفل مع مهلة زمنية (5 دقائق) لتجنب الجمود
49
  const mutex = new Mutex();
50
- const scanMutex = withTimeout(mutex, 5 * 60 * 1000); // 5 دقائق
51
 
52
  // ==================== مجمع المتصفح (Browser Pool) ====================
53
  class BrowserPool {
@@ -100,7 +100,7 @@ class BrowserPool {
100
  '--disable-background-timer-throttling',
101
  '--disable-renderer-backgrounding'
102
  ],
103
- protocolTimeout: 240000,
104
  });
105
  console.log('🚀 Browser launched');
106
  this.requestCount = 1;
@@ -288,47 +288,20 @@ async function getTosdrGrade(url) {
288
  }
289
  }
290
 
291
- // ==================== فحص سياسة الخصوصية ====================
292
- async function analyzePrivacyPolicy(url) {
293
- try {
294
- const { page, context } = await pool.newTab();
295
- await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15000 });
296
- await page.waitForTimeout(2000);
297
- const policyData = await page.evaluate(() => {
298
- const bodyText = document.body.innerText.toLowerCase();
299
- const links = Array.from(document.querySelectorAll('a'))
300
- .map(a => ({ text: a.innerText.toLowerCase(), href: a.href }))
301
- .filter(l => l.text.includes('privacy') || l.text.includes('policy') || l.text.includes('data') || l.text.includes('terms'));
302
- const hasPrivacyLink = links.length > 0;
303
- const privacyUrl = links.find(l => l.text.includes('privacy'))?.href || null;
304
- const dataSaleMentions = bodyText.includes('sell') && (bodyText.includes('data') || bodyText.includes('information'));
305
- const thirdPartyShare = bodyText.includes('third party') || bodyText.includes('third-party') || bodyText.includes('partners');
306
- const retentionMention = bodyText.match(/retain.*?(\d+)\s*(day|month|year)/i);
307
- const dataRetention = retentionMention ? `${retentionMention[1]} ${retentionMention[2]}` : 'Not specified';
308
- return {
309
- has_privacy_link: hasPrivacyLink,
310
- privacy_policy_url: privacyUrl,
311
- data_sale_indicated: dataSaleMentions,
312
- third_party_sharing: thirdPartyShare,
313
- data_retention_period: dataRetention,
314
- analyzed_text_length: bodyText.length
315
- };
316
- });
317
- await pool.closeTab(context);
318
- return policyData;
319
- } catch (e) {
320
- console.error('Privacy policy analysis failed:', e.message);
321
- return null;
322
- }
323
- }
324
 
325
- // ==================== أدوات المتصفح استخدام pool) ====================
326
  async function takeScreenshot(url) {
327
  const { page, context } = await pool.newTab();
328
  try {
329
  await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15000 });
330
- await page.waitForTimeout(800);
331
- const buffer = await page.screenshot({ type: 'jpeg', quality: 80, fullPage: false });
 
 
 
 
 
332
  return `data:image/jpeg;base64,${buffer.toString('base64')}`;
333
  } catch (e) {
334
  console.error('Screenshot failed:', e.message);
@@ -468,6 +441,55 @@ async function collectFingerprintRisks(url) {
468
  }
469
  }
470
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
471
  async function trackRedirectChain(url) {
472
  const TRACKING_PARAMS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'mc_eid', 'ref', 'affiliate', 'clickid', 'adid', 'msclkid', 'twclid', '_ga', 'igshid'];
473
  const chain = [];
@@ -540,7 +562,7 @@ async function performBlacklightScan(url) {
540
  } catch (e) {}
541
  return result;
542
  } catch (e) {
543
- console.error('Blacklight scan failed, returning empty result:', e.message);
544
  return { hosts: {}, cookies: [], error: e.message };
545
  }
546
  }
@@ -612,178 +634,15 @@ function calculatePrivacyScore(blacklight, enrichedTrackers, tosdrGrade, securit
612
  return { score, grade };
613
  }
614
 
615
- // ==================== نقطة نهاية API عادية (JSON) ====================
616
  app.get('/api/scan', async (req, res) => {
617
- const url = normalizeUrl(req.query.url);
618
- if (!url) return res.status(400).json({ error: 'Invalid URL' });
619
-
620
- // استخدام runExclusive لضمان تحرير القفل تلقائيًا
621
- try {
622
- const result = await scanMutex.runExclusive(async () => {
623
- const startTime = Date.now();
624
-
625
- const [
626
- screenshotResult, hiddenStorageResult, cookieConsentResult,
627
- fingerprintResult, redirectChainResult, blacklightResult,
628
- tosdrResult, securityResult, privacyPolicyResult
629
- ] = await Promise.allSettled([
630
- takeScreenshot(url),
631
- checkHiddenStorage(url),
632
- analyzeCookieConsent(url),
633
- collectFingerprintRisks(url),
634
- trackRedirectChain(url),
635
- performBlacklightScan(url),
636
- getTosdrGrade(url),
637
- performSecurityCheck(url),
638
- analyzePrivacyPolicy(url)
639
- ]);
640
-
641
- const screenshotData = screenshotResult.status === 'fulfilled' ? screenshotResult.value : null;
642
- const blacklightData = blacklightResult.status === 'fulfilled' ? blacklightResult.value : { hosts: {}, cookies: [] };
643
- const tosdrData = tosdrResult.status === 'fulfilled' ? tosdrResult.value : null;
644
- const securityData = securityResult.status === 'fulfilled' ? securityResult.value : { ssl: { valid: false } };
645
- const hiddenData = hiddenStorageResult.status === 'fulfilled' ? hiddenStorageResult.value : null;
646
- const cookieConsentData = cookieConsentResult.status === 'fulfilled' ? cookieConsentResult.value : null;
647
- const fingerprintData = fingerprintResult.status === 'fulfilled' ? fingerprintResult.value : null;
648
- const redirectData = redirectChainResult.status === 'fulfilled' ? redirectChainResult.value : null;
649
- const privacyPolicyData = privacyPolicyResult.status === 'fulfilled' ? privacyPolicyResult.value : null;
650
-
651
- // تجميع المتتبعين
652
- const thirdPartyDomains = [
653
- ...(blacklightData.hosts?.thirdParty || []),
654
- ...(blacklightData.hosts?.requests?.third_party || [])
655
- ];
656
- const uniqueDomains = [...new Set(thirdPartyDomains)];
657
- const enrichedTrackers = [];
658
- for (const domain of uniqueDomains) {
659
- try {
660
- const ddgInfo = getDDGInfo(domain);
661
- const ghosteryInfo = await getGhosteryInfo(domain);
662
- enrichedTrackers.push({
663
- domain,
664
- owner: ghosteryInfo?.organization || ddgInfo?.owner || getBaseDomain(domain),
665
- category: ghosteryInfo?.category || ddgInfo?.category || 'unknown',
666
- prevalence: ddgInfo?.prevalence || 0
667
- });
668
- } catch (e) {
669
- enrichedTrackers.push({ domain, owner: getBaseDomain(domain), category: 'unknown', prevalence: 0 });
670
- }
671
- }
672
-
673
- // Geo mapping
674
- const uniqueIps = [...new Set((blacklightData.hosts?.requests?.third_party || []).map(r => r.ip_addr).filter(Boolean))];
675
- const geoDestinations = [];
676
- for (const ip of uniqueIps) {
677
- try {
678
- let geoData = null;
679
- if (useMaxMind && geoReader) {
680
- const mmGeo = geoReader.get(ip);
681
- if (mmGeo) {
682
- geoData = {
683
- ip,
684
- country: mmGeo.country?.names?.en || 'Unknown',
685
- city: mmGeo.city?.names?.en || 'Unknown',
686
- latitude: mmGeo.location?.latitude,
687
- longitude: mmGeo.location?.longitude
688
- };
689
- }
690
- }
691
- if (!geoData) {
692
- const geoLite = geoip.lookup(ip);
693
- if (geoLite) {
694
- geoData = {
695
- ip,
696
- country: geoLite.country,
697
- city: geoLite.city,
698
- latitude: geoLite.ll?.[0] || null,
699
- longitude: geoLite.ll?.[1] || null
700
- };
701
- }
702
- }
703
- if (geoData) geoDestinations.push(geoData);
704
- } catch (e) {}
705
- }
706
-
707
- // PII Leakage
708
- const leakageAlerts = [];
709
- for (const r of (blacklightData.hosts?.requests?.third_party || [])) {
710
- if ((r.method === 'POST' || r.method === 'PUT') && r.body) {
711
- try {
712
- const detected = piiFilter.detect(r.body);
713
- if (detected?.length > 0) {
714
- leakageAlerts.push({
715
- severity: 'high',
716
- destination: r.url,
717
- method: r.method,
718
- types: detected.map(p => p.type),
719
- message: 'Potential PII detected in request to third-party domain.'
720
- });
721
- }
722
- } catch (e) {}
723
- }
724
- }
725
-
726
- const { score, grade } = calculatePrivacyScore(
727
- blacklightData, enrichedTrackers, tosdrData,
728
- securityData, cookieConsentData, fingerprintData,
729
- redirectData, privacyPolicyData
730
- );
731
-
732
- return {
733
- success: true,
734
- url,
735
- final_url: blacklightData.uri_dest || url,
736
- scan_time_sec: (Date.now() - startTime) / 1000,
737
- privacy_score: { score, grade },
738
- trackers: { count: enrichedTrackers.length, list: enrichedTrackers.slice(0, 20) },
739
- cookies: {
740
- total: blacklightData.cookies?.length || 0,
741
- third_party: blacklightData.cookies?.filter(c => c.thirdParty)?.length || 0
742
- },
743
- fingerprinting: {
744
- canvas: !!(blacklightData.canvasFingerprinters?.length),
745
- fonts: !!(blacklightData.canvasFontFingerprinters?.length),
746
- live_risks: fingerprintData
747
- },
748
- session_recording: !!(blacklightData.sessionRecorders?.length),
749
- key_logging: !!(blacklightData.keyLogging?.length),
750
- hidden_storage: hiddenData ? {
751
- localStorage: hiddenData.localStorage?.length || 0,
752
- sessionStorage: hiddenData.sessionStorage?.length || 0,
753
- indexedDB: hiddenData.indexedDB
754
- } : null,
755
- cookie_consent: cookieConsentData,
756
- redirect_chain: redirectData,
757
- security: securityData,
758
- tosdr: tosdrData,
759
- privacy_policy_analysis: privacyPolicyData,
760
- geo_mapping: { data_destinations: geoDestinations },
761
- leakage_detection: { alerts: leakageAlerts },
762
- screenshot: screenshotData,
763
- raw: blacklightData
764
- };
765
- });
766
-
767
- res.json(result);
768
- } catch (e) {
769
- console.error('Scan error:', e);
770
- // التحقق مما إذا كان الخطأ بسبب انتهاء مهلة القفل
771
- if (e.message.includes('timeout')) {
772
- res.status(503).json({ error: 'Server is busy, please try again later.' });
773
- } else {
774
- res.status(500).json({ success: false, error: e.message });
775
- }
776
- }
777
- });
778
-
779
- // ==================== نقطة نهاية SSE (تقدم حي) ====================
780
- app.get('/api/scan/live', async (req, res) => {
781
  const url = normalizeUrl(req.query.url);
782
  if (!url) {
783
  res.status(400).send('Invalid URL');
784
  return;
785
  }
786
 
 
787
  res.writeHead(200, {
788
  'Content-Type': 'text/event-stream',
789
  'Cache-Control': 'no-cache',
@@ -797,33 +656,86 @@ app.get('/api/scan/live', async (req, res) => {
797
 
798
  sendEvent('start', { url, message: 'Scan started...' });
799
 
 
800
  try {
801
  await scanMutex.runExclusive(async () => {
802
  const startTime = Date.now();
803
 
804
- const promises = [
805
- takeScreenshot(url).then(r => { sendEvent('progress', { step: 'screenshot', status: 'done' }); return r; }).catch(() => null),
806
- checkHiddenStorage(url).then(r => { sendEvent('progress', { step: 'hidden_storage', status: 'done' }); return r; }).catch(() => null),
807
- analyzeCookieConsent(url).then(r => { sendEvent('progress', { step: 'cookie_consent', status: 'done' }); return r; }).catch(() => null),
808
- collectFingerprintRisks(url).then(r => { sendEvent('progress', { step: 'fingerprint_risks', status: 'done' }); return r; }).catch(() => null),
809
- trackRedirectChain(url).then(r => { sendEvent('progress', { step: 'redirect_chain', status: 'done' }); return r; }).catch(() => null),
810
- performBlacklightScan(url).then(r => { sendEvent('progress', { step: 'blacklight_scan', status: 'done' }); return r; }).catch(() => null),
811
- getTosdrGrade(url).then(r => { sendEvent('progress', { step: 'tosdr', status: 'done' }); return r; }).catch(() => null),
812
- performSecurityCheck(url).then(r => { sendEvent('progress', { step: 'security', status: 'done' }); return r; }).catch(() => null),
813
- analyzePrivacyPolicy(url).then(r => { sendEvent('progress', { step: 'privacy_policy', status: 'done' }); return r; }).catch(() => null),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
814
  ];
815
 
816
- const results = await Promise.allSettled(promises);
817
-
818
- const screenshotData = results[0].status === 'fulfilled' ? results[0].value : null;
819
- const hiddenData = results[1].status === 'fulfilled' ? results[1].value : null;
820
- const cookieConsentData = results[2].status === 'fulfilled' ? results[2].value : null;
821
- const fingerprintData = results[3].status === 'fulfilled' ? results[3].value : null;
822
- const redirectData = results[4].status === 'fulfilled' ? results[4].value : null;
823
- const blacklightData = results[5].status === 'fulfilled' ? results[5].value : { hosts: {}, cookies: [] };
824
- const tosdrData = results[6].status === 'fulfilled' ? results[6].value : null;
825
- const securityData = results[7].status === 'fulfilled' ? results[7].value : { ssl: { valid: false } };
826
- const privacyPolicyData = results[8].status === 'fulfilled' ? results[8].value : null;
 
 
827
 
828
  // تجميع المتتبعين
829
  const thirdPartyDomains = [
@@ -900,12 +812,20 @@ app.get('/api/scan/live', async (req, res) => {
900
  }
901
  }
902
 
 
 
903
  const { score, grade } = calculatePrivacyScore(
904
- blacklightData, enrichedTrackers, tosdrData,
905
- securityData, cookieConsentData, fingerprintData,
906
- redirectData, privacyPolicyData
 
 
 
 
 
907
  );
908
 
 
909
  const finalResult = {
910
  success: true,
911
  url,
@@ -920,23 +840,23 @@ app.get('/api/scan/live', async (req, res) => {
920
  fingerprinting: {
921
  canvas: !!(blacklightData.canvasFingerprinters?.length),
922
  fonts: !!(blacklightData.canvasFontFingerprinters?.length),
923
- live_risks: fingerprintData
924
  },
925
  session_recording: !!(blacklightData.sessionRecorders?.length),
926
  key_logging: !!(blacklightData.keyLogging?.length),
927
- hidden_storage: hiddenData ? {
928
- localStorage: hiddenData.localStorage?.length || 0,
929
- sessionStorage: hiddenData.sessionStorage?.length || 0,
930
- indexedDB: hiddenData.indexedDB
931
  } : null,
932
- cookie_consent: cookieConsentData,
933
- redirect_chain: redirectData,
934
  security: securityData,
935
- tosdr: tosdrData,
936
- privacy_policy_analysis: privacyPolicyData,
937
  geo_mapping: { data_destinations: geoDestinations },
938
  leakage_detection: { alerts: leakageAlerts },
939
- screenshot: screenshotData,
940
  raw: blacklightData
941
  };
942
 
@@ -945,11 +865,7 @@ app.get('/api/scan/live', async (req, res) => {
945
  });
946
  } catch (e) {
947
  console.error('Live scan error:', e);
948
- if (e.message.includes('timeout')) {
949
- sendEvent('error', { error: 'Server is busy, please try again later.' });
950
- } else {
951
- sendEvent('error', { error: e.message });
952
- }
953
  } finally {
954
  res.end();
955
  }
@@ -970,6 +886,6 @@ app.get('/health', (req, res) => res.json({ status: 'ok' }));
970
  await downloadDDGTrackerRadar();
971
  }
972
  app.listen(PORT, '0.0.0.0', () => {
973
- console.log(`🚀 Private Eye API running on port ${PORT}`);
974
  });
975
  })();
 
45
  let geoReader = null;
46
  let useMaxMind = false;
47
 
48
+ // قفل لمنع تداخل الطلبات (مهم لـ Hugging Face)
49
  const mutex = new Mutex();
50
+ const scanMutex = withTimeout(mutex, 5 * 60 * 1000); // 5 دقائق كحد أقصى
51
 
52
  // ==================== مجمع المتصفح (Browser Pool) ====================
53
  class BrowserPool {
 
100
  '--disable-background-timer-throttling',
101
  '--disable-renderer-backgrounding'
102
  ],
103
+ protocolTimeout: 240000, // 4 دقائق
104
  });
105
  console.log('🚀 Browser launched');
106
  this.requestCount = 1;
 
288
  }
289
  }
290
 
291
+ // ==================== أدوات المتصفح (كل منها تفتح وتغلق context فورًا) ====================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
 
293
+ /** لقطة شاشة سريعة وخفيفة */
294
  async function takeScreenshot(url) {
295
  const { page, context } = await pool.newTab();
296
  try {
297
  await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15000 });
298
+ await page.waitForTimeout(500);
299
+ const buffer = await page.screenshot({
300
+ type: 'jpeg',
301
+ quality: 50,
302
+ fullPage: false,
303
+ clip: { x: 0, y: 0, width: 1024, height: 768 }
304
+ });
305
  return `data:image/jpeg;base64,${buffer.toString('base64')}`;
306
  } catch (e) {
307
  console.error('Screenshot failed:', e.message);
 
441
  }
442
  }
443
 
444
+ async function analyzePrivacyPolicy(url) {
445
+ const { page, context } = await pool.newTab();
446
+ try {
447
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15000 });
448
+ await page.waitForTimeout(2000);
449
+ const privacyLink = await page.evaluate(() => {
450
+ const links = Array.from(document.querySelectorAll('a'));
451
+ const privacyLink = links.find(a =>
452
+ a.innerText.toLowerCase().includes('privacy') ||
453
+ a.innerText.toLowerCase().includes('policy')
454
+ );
455
+ return privacyLink ? privacyLink.href : null;
456
+ });
457
+
458
+ let policyUrl = privacyLink;
459
+ let analysisText = '';
460
+
461
+ if (policyUrl) {
462
+ await page.goto(policyUrl, { waitUntil: 'domcontentloaded', timeout: 15000 });
463
+ await page.waitForTimeout(2000);
464
+ analysisText = await page.evaluate(() => document.body.innerText.toLowerCase());
465
+ } else {
466
+ analysisText = await page.evaluate(() => document.body.innerText.toLowerCase());
467
+ }
468
+
469
+ const dataSaleMentions = analysisText.includes('sell') && (analysisText.includes('data') || analysisText.includes('information'));
470
+ const thirdPartyShare = analysisText.includes('third party') || analysisText.includes('third-party') || analysisText.includes('partners');
471
+ const retentionMention = analysisText.match(/retain.*?(\d+)\s*(day|month|year)/i);
472
+ const dataRetention = retentionMention ? `${retentionMention[1]} ${retentionMention[2]}` : 'Not specified';
473
+
474
+ return {
475
+ has_privacy_link: !!privacyLink,
476
+ privacy_policy_url: policyUrl || null,
477
+ data_sale_indicated: dataSaleMentions,
478
+ third_party_sharing: thirdPartyShare,
479
+ data_retention_period: dataRetention,
480
+ analyzed_text_length: analysisText.length,
481
+ source: policyUrl ? 'privacy_policy_page' : 'homepage'
482
+ };
483
+ } catch (e) {
484
+ console.error('Privacy policy analysis failed:', e.message);
485
+ return null;
486
+ } finally {
487
+ await pool.closeTab(context);
488
+ }
489
+ }
490
+
491
+ // ==================== أدوات لا تحتاج متصفح ====================
492
+
493
  async function trackRedirectChain(url) {
494
  const TRACKING_PARAMS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'mc_eid', 'ref', 'affiliate', 'clickid', 'adid', 'msclkid', 'twclid', '_ga', 'igshid'];
495
  const chain = [];
 
562
  } catch (e) {}
563
  return result;
564
  } catch (e) {
565
+ console.error('Blacklight scan failed:', e.message);
566
  return { hosts: {}, cookies: [], error: e.message };
567
  }
568
  }
 
634
  return { score, grade };
635
  }
636
 
637
+ // ==================== نقطة النهاية الوحيدة: SSE Live Scan ====================
638
  app.get('/api/scan', async (req, res) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
639
  const url = normalizeUrl(req.query.url);
640
  if (!url) {
641
  res.status(400).send('Invalid URL');
642
  return;
643
  }
644
 
645
+ // إعداد رؤوس SSE
646
  res.writeHead(200, {
647
  'Content-Type': 'text/event-stream',
648
  'Cache-Control': 'no-cache',
 
656
 
657
  sendEvent('start', { url, message: 'Scan started...' });
658
 
659
+ // استخدام القفل لضمان طلب واحد فقط في وقت واحد
660
  try {
661
  await scanMutex.runExclusive(async () => {
662
  const startTime = Date.now();
663
 
664
+ // تخزين النتائج مؤقتًا
665
+ const results = {
666
+ screenshot: null,
667
+ hiddenStorage: null,
668
+ cookieConsent: null,
669
+ fingerprint: null,
670
+ redirectChain: null,
671
+ blacklight: null,
672
+ tosdr: null,
673
+ security: null,
674
+ privacyPolicy: null
675
+ };
676
+
677
+ // تعريف الأدوات مع وظيفة إرسال التقدم فور الانتهاء
678
+ const tools = [
679
+ {
680
+ name: 'screenshot',
681
+ fn: () => takeScreenshot(url),
682
+ onDone: (data) => { results.screenshot = data; sendEvent('progress', { step: 'screenshot', status: 'done' }); }
683
+ },
684
+ {
685
+ name: 'hidden_storage',
686
+ fn: () => checkHiddenStorage(url),
687
+ onDone: (data) => { results.hiddenStorage = data; sendEvent('progress', { step: 'hidden_storage', status: 'done' }); }
688
+ },
689
+ {
690
+ name: 'cookie_consent',
691
+ fn: () => analyzeCookieConsent(url),
692
+ onDone: (data) => { results.cookieConsent = data; sendEvent('progress', { step: 'cookie_consent', status: 'done' }); }
693
+ },
694
+ {
695
+ name: 'fingerprint_risks',
696
+ fn: () => collectFingerprintRisks(url),
697
+ onDone: (data) => { results.fingerprint = data; sendEvent('progress', { step: 'fingerprint_risks', status: 'done' }); }
698
+ },
699
+ {
700
+ name: 'redirect_chain',
701
+ fn: () => trackRedirectChain(url),
702
+ onDone: (data) => { results.redirectChain = data; sendEvent('progress', { step: 'redirect_chain', status: 'done' }); }
703
+ },
704
+ {
705
+ name: 'blacklight_scan',
706
+ fn: () => performBlacklightScan(url),
707
+ onDone: (data) => { results.blacklight = data; sendEvent('progress', { step: 'blacklight_scan', status: 'done' }); }
708
+ },
709
+ {
710
+ name: 'tosdr',
711
+ fn: () => getTosdrGrade(url),
712
+ onDone: (data) => { results.tosdr = data; sendEvent('progress', { step: 'tosdr', status: 'done' }); }
713
+ },
714
+ {
715
+ name: 'security',
716
+ fn: () => performSecurityCheck(url),
717
+ onDone: (data) => { results.security = data; sendEvent('progress', { step: 'security', status: 'done' }); }
718
+ },
719
+ {
720
+ name: 'privacy_policy',
721
+ fn: () => analyzePrivacyPolicy(url),
722
+ onDone: (data) => { results.privacyPolicy = data; sendEvent('progress', { step: 'privacy_policy', status: 'done' }); }
723
+ }
724
  ];
725
 
726
+ // بدء جميع الأدوات بالتوازي
727
+ const promises = tools.map(tool =>
728
+ tool.fn()
729
+ .then(data => { tool.onDone(data); return data; })
730
+ .catch(err => { console.error(`${tool.name} failed:`, err.message); tool.onDone(null); return null; })
731
+ );
732
+
733
+ // انتظار انتهاء الجميع
734
+ const settled = await Promise.allSettled(promises);
735
+ // (القيم الفعلية موجودة في results لأننا خزناها عبر onDone)
736
+
737
+ // معالجة البيانات النهائية (خاصة blacklight)
738
+ const blacklightData = results.blacklight || { hosts: {}, cookies: [] };
739
 
740
  // تجميع المتتبعين
741
  const thirdPartyDomains = [
 
812
  }
813
  }
814
 
815
+ // حساب النتيجة
816
+ const securityData = results.security || { ssl: { valid: false } };
817
  const { score, grade } = calculatePrivacyScore(
818
+ blacklightData,
819
+ enrichedTrackers,
820
+ results.tosdr,
821
+ securityData,
822
+ results.cookieConsent,
823
+ results.fingerprint,
824
+ results.redirectChain,
825
+ results.privacyPolicy
826
  );
827
 
828
+ // بناء النتيجة النهائية
829
  const finalResult = {
830
  success: true,
831
  url,
 
840
  fingerprinting: {
841
  canvas: !!(blacklightData.canvasFingerprinters?.length),
842
  fonts: !!(blacklightData.canvasFontFingerprinters?.length),
843
+ live_risks: results.fingerprint
844
  },
845
  session_recording: !!(blacklightData.sessionRecorders?.length),
846
  key_logging: !!(blacklightData.keyLogging?.length),
847
+ hidden_storage: results.hiddenStorage ? {
848
+ localStorage: results.hiddenStorage.localStorage?.length || 0,
849
+ sessionStorage: results.hiddenStorage.sessionStorage?.length || 0,
850
+ indexedDB: results.hiddenStorage.indexedDB
851
  } : null,
852
+ cookie_consent: results.cookieConsent,
853
+ redirect_chain: results.redirectChain,
854
  security: securityData,
855
+ tosdr: results.tosdr,
856
+ privacy_policy_analysis: results.privacyPolicy,
857
  geo_mapping: { data_destinations: geoDestinations },
858
  leakage_detection: { alerts: leakageAlerts },
859
+ screenshot: results.screenshot,
860
  raw: blacklightData
861
  };
862
 
 
865
  });
866
  } catch (e) {
867
  console.error('Live scan error:', e);
868
+ sendEvent('error', { error: e.message });
 
 
 
 
869
  } finally {
870
  res.end();
871
  }
 
886
  await downloadDDGTrackerRadar();
887
  }
888
  app.listen(PORT, '0.0.0.0', () => {
889
+ console.log(`🚀 Private Eye Live API running on port ${PORT}`);
890
  });
891
  })();