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

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +38 -57
server.js CHANGED
@@ -34,22 +34,18 @@ app.use('/api/', rateLimit({
34
  legacyHeaders: false,
35
  }));
36
 
37
- // ==================== متغيرات البيئة ====================
38
  const MAXMIND_ACCOUNT_ID = process.env.Account_ID;
39
  const MAXMIND_LICENSE_KEY = process.env.License_key;
40
 
41
- // ==================== المتغيرات العامة ====================
42
  let ghosteryDB = null;
43
  let ddgTrackerRadar = { domains: {} };
44
  let tosdrCache = new Map();
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 {
54
  constructor() {
55
  this.browser = null;
@@ -61,7 +57,7 @@ class BrowserPool {
61
 
62
  async getBrowser() {
63
  if (this.requestCount >= this.maxRequestsBeforeRestart) {
64
- console.log('♻️ Restarting browser to free memory...');
65
  await this.shutdown();
66
  this.requestCount = 0;
67
  }
@@ -100,9 +96,9 @@ 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;
107
  this.waiters.forEach(r => r());
108
  this.waiters = [];
@@ -139,17 +135,16 @@ class BrowserPool {
139
 
140
  const pool = new BrowserPool();
141
 
142
- // ==================== دوال التهيئة ====================
143
  async function initGhosteryDB() {
144
  try {
145
  const enginePath = path.join(__dirname, 'node_modules', '@ghostery', 'trackerdb', 'dist', 'trackerdb.engine');
146
  if (existsSync(enginePath)) {
147
  const engine = readFileSync(enginePath);
148
  ghosteryDB = await loadTrackerDB(engine);
149
- console.log('Ghostery TrackerDB initialized');
150
  }
151
  } catch (e) {
152
- console.warn('⚠️ Ghostery TrackerDB initialization failed:', e.message);
153
  }
154
  }
155
 
@@ -158,10 +153,10 @@ function loadDDGTrackerRadar() {
158
  const ddgPath = path.join(__dirname, 'data', 'ddg-tracker-radar.json');
159
  if (existsSync(ddgPath)) {
160
  ddgTrackerRadar = JSON.parse(readFileSync(ddgPath, 'utf-8'));
161
- console.log(`DDG Tracker Radar: ${Object.keys(ddgTrackerRadar.domains).length} domains`);
162
  }
163
  } catch (e) {
164
- console.warn('⚠️ DDG Tracker Radar load failed:', e.message);
165
  }
166
  }
167
 
@@ -189,9 +184,9 @@ async function downloadDDGTrackerRadar() {
189
  if (!existsSync(dir)) mkdirSync(dir);
190
  writeFileSync(path.join(dir, 'ddg-tracker-radar.json'), JSON.stringify(simplified));
191
  ddgTrackerRadar = simplified;
192
- console.log('DDG Tracker Radar updated');
193
  } catch (e) {
194
- console.error('DDG download failed:', e.message);
195
  }
196
  }
197
 
@@ -203,21 +198,21 @@ async function initGeoDatabase() {
203
  if (existsSync(dbPath)) {
204
  geoReader = await maxmind.open(dbPath);
205
  useMaxMind = true;
206
- console.log('MaxMind GeoLite2-City loaded');
207
  return;
208
  }
209
  } catch (e) {
210
- console.warn('⚠️ MaxMind initialization failed, falling back to geoip-lite:', e.message);
211
  }
212
  }
213
- console.log('Using geoip-lite for Geo mapping');
214
  }
215
 
216
  async function downloadMaxMindDatabase(dbPath) {
217
  const url = `https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=${MAXMIND_LICENSE_KEY}&suffix=tar.gz`;
218
  const dir = path.dirname(dbPath);
219
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
220
- console.log('📥 Downloading MaxMind GeoLite2-City...');
221
  const response = await axios.get(url, { responseType: 'stream', timeout: 120000 });
222
  const tarPath = path.join(dir, 'GeoLite2-City.tar.gz');
223
  const writer = createWriteStream(tarPath);
@@ -226,10 +221,9 @@ async function downloadMaxMindDatabase(dbPath) {
226
  writer.on('finish', resolve);
227
  writer.on('error', reject);
228
  });
229
- console.warn('⚠️ MaxMind DB downloaded but auto-extraction not implemented. Using geoip-lite instead.');
230
  }
231
 
232
- // ==================== دوال مساعدة ====================
233
  function normalizeUrl(inputUrl) {
234
  let url = inputUrl.trim();
235
  if (!url.startsWith('http://') && !url.startsWith('https://')) url = 'https://' + url;
@@ -288,9 +282,6 @@ async function getTosdrGrade(url) {
288
  }
289
  }
290
 
291
- // ==================== أدوات المتصفح (كل منها تفتح وتغلق context فورًا) ====================
292
-
293
- /** لقطة شاشة سريعة وخفيفة */
294
  async function takeScreenshot(url) {
295
  const { page, context } = await pool.newTab();
296
  try {
@@ -488,8 +479,6 @@ async function analyzePrivacyPolicy(url) {
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 = [];
@@ -597,7 +586,6 @@ async function performSecurityCheck(url) {
597
  }
598
  }
599
 
600
- // ==================== حساب درجة الخصوصية ====================
601
  function calculatePrivacyScore(blacklight, enrichedTrackers, tosdrGrade, security, cookieConsent, fingerprintRisks, redirectChain, privacyPolicy) {
602
  let score = 100;
603
  score -= Math.min(enrichedTrackers.length * 4, 30);
@@ -634,7 +622,6 @@ function calculatePrivacyScore(blacklight, enrichedTrackers, tosdrGrade, securit
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) {
@@ -642,7 +629,6 @@ app.get('/api/scan', async (req, res) => {
642
  return;
643
  }
644
 
645
- // إعداد رؤوس SSE
646
  res.writeHead(200, {
647
  'Content-Type': 'text/event-stream',
648
  'Cache-Control': 'no-cache',
@@ -656,12 +642,10 @@ app.get('/api/scan', async (req, res) => {
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,
@@ -674,70 +658,72 @@ app.get('/api/scan', async (req, res) => {
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 = [
742
  ...(blacklightData.hosts?.thirdParty || []),
743
  ...(blacklightData.hosts?.requests?.third_party || [])
@@ -759,7 +745,6 @@ app.get('/api/scan', async (req, res) => {
759
  }
760
  }
761
 
762
- // Geo mapping
763
  const uniqueIps = [...new Set((blacklightData.hosts?.requests?.third_party || []).map(r => r.ip_addr).filter(Boolean))];
764
  const geoDestinations = [];
765
  for (const ip of uniqueIps) {
@@ -793,7 +778,6 @@ app.get('/api/scan', async (req, res) => {
793
  } catch (e) {}
794
  }
795
 
796
- // PII Leakage
797
  const leakageAlerts = [];
798
  for (const r of (blacklightData.hosts?.requests?.third_party || [])) {
799
  if ((r.method === 'POST' || r.method === 'PUT') && r.body) {
@@ -812,7 +796,6 @@ app.get('/api/scan', async (req, res) => {
812
  }
813
  }
814
 
815
- // حساب النتيجة
816
  const securityData = results.security || { ssl: { valid: false } };
817
  const { score, grade } = calculatePrivacyScore(
818
  blacklightData,
@@ -825,7 +808,6 @@ app.get('/api/scan', async (req, res) => {
825
  results.privacyPolicy
826
  );
827
 
828
- // بناء النتيجة النهائية
829
  const finalResult = {
830
  success: true,
831
  url,
@@ -873,7 +855,6 @@ app.get('/api/scan', async (req, res) => {
873
 
874
  app.get('/health', (req, res) => res.json({ status: 'ok' }));
875
 
876
- // ==================== بدء التشغيل ====================
877
  (async () => {
878
  const dirs = [path.join(__dirname, 'data'), path.join(__dirname, 'bl-tmp')];
879
  for (const dir of dirs) {
@@ -886,6 +867,6 @@ app.get('/health', (req, res) => res.json({ status: 'ok' }));
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
  })();
 
34
  legacyHeaders: false,
35
  }));
36
 
 
37
  const MAXMIND_ACCOUNT_ID = process.env.Account_ID;
38
  const MAXMIND_LICENSE_KEY = process.env.License_key;
39
 
 
40
  let ghosteryDB = null;
41
  let ddgTrackerRadar = { domains: {} };
42
  let tosdrCache = new Map();
43
  let geoReader = null;
44
  let useMaxMind = false;
45
 
 
46
  const mutex = new Mutex();
47
+ const scanMutex = withTimeout(mutex, 5 * 60 * 1000);
48
 
 
49
  class BrowserPool {
50
  constructor() {
51
  this.browser = null;
 
57
 
58
  async getBrowser() {
59
  if (this.requestCount >= this.maxRequestsBeforeRestart) {
60
+ console.log('Restarting browser to free memory...');
61
  await this.shutdown();
62
  this.requestCount = 0;
63
  }
 
96
  '--disable-background-timer-throttling',
97
  '--disable-renderer-backgrounding'
98
  ],
99
+ protocolTimeout: 240000,
100
  });
101
+ console.log('Browser launched');
102
  this.requestCount = 1;
103
  this.waiters.forEach(r => r());
104
  this.waiters = [];
 
135
 
136
  const pool = new BrowserPool();
137
 
 
138
  async function initGhosteryDB() {
139
  try {
140
  const enginePath = path.join(__dirname, 'node_modules', '@ghostery', 'trackerdb', 'dist', 'trackerdb.engine');
141
  if (existsSync(enginePath)) {
142
  const engine = readFileSync(enginePath);
143
  ghosteryDB = await loadTrackerDB(engine);
144
+ console.log('Ghostery TrackerDB initialized');
145
  }
146
  } catch (e) {
147
+ console.warn('Ghostery TrackerDB initialization failed:', e.message);
148
  }
149
  }
150
 
 
153
  const ddgPath = path.join(__dirname, 'data', 'ddg-tracker-radar.json');
154
  if (existsSync(ddgPath)) {
155
  ddgTrackerRadar = JSON.parse(readFileSync(ddgPath, 'utf-8'));
156
+ console.log(`DDG Tracker Radar: ${Object.keys(ddgTrackerRadar.domains).length} domains`);
157
  }
158
  } catch (e) {
159
+ console.warn('DDG Tracker Radar load failed:', e.message);
160
  }
161
  }
162
 
 
184
  if (!existsSync(dir)) mkdirSync(dir);
185
  writeFileSync(path.join(dir, 'ddg-tracker-radar.json'), JSON.stringify(simplified));
186
  ddgTrackerRadar = simplified;
187
+ console.log('DDG Tracker Radar updated');
188
  } catch (e) {
189
+ console.error('DDG download failed:', e.message);
190
  }
191
  }
192
 
 
198
  if (existsSync(dbPath)) {
199
  geoReader = await maxmind.open(dbPath);
200
  useMaxMind = true;
201
+ console.log('MaxMind GeoLite2-City loaded');
202
  return;
203
  }
204
  } catch (e) {
205
+ console.warn('MaxMind initialization failed, falling back to geoip-lite:', e.message);
206
  }
207
  }
208
+ console.log('Using geoip-lite for Geo mapping');
209
  }
210
 
211
  async function downloadMaxMindDatabase(dbPath) {
212
  const url = `https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=${MAXMIND_LICENSE_KEY}&suffix=tar.gz`;
213
  const dir = path.dirname(dbPath);
214
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
215
+ console.log('Downloading MaxMind GeoLite2-City...');
216
  const response = await axios.get(url, { responseType: 'stream', timeout: 120000 });
217
  const tarPath = path.join(dir, 'GeoLite2-City.tar.gz');
218
  const writer = createWriteStream(tarPath);
 
221
  writer.on('finish', resolve);
222
  writer.on('error', reject);
223
  });
224
+ console.warn('MaxMind DB downloaded but auto-extraction not implemented. Using geoip-lite instead.');
225
  }
226
 
 
227
  function normalizeUrl(inputUrl) {
228
  let url = inputUrl.trim();
229
  if (!url.startsWith('http://') && !url.startsWith('https://')) url = 'https://' + url;
 
282
  }
283
  }
284
 
 
 
 
285
  async function takeScreenshot(url) {
286
  const { page, context } = await pool.newTab();
287
  try {
 
479
  }
480
  }
481
 
 
 
482
  async function trackRedirectChain(url) {
483
  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'];
484
  const chain = [];
 
586
  }
587
  }
588
 
 
589
  function calculatePrivacyScore(blacklight, enrichedTrackers, tosdrGrade, security, cookieConsent, fingerprintRisks, redirectChain, privacyPolicy) {
590
  let score = 100;
591
  score -= Math.min(enrichedTrackers.length * 4, 30);
 
622
  return { score, grade };
623
  }
624
 
 
625
  app.get('/api/scan', async (req, res) => {
626
  const url = normalizeUrl(req.query.url);
627
  if (!url) {
 
629
  return;
630
  }
631
 
 
632
  res.writeHead(200, {
633
  'Content-Type': 'text/event-stream',
634
  'Cache-Control': 'no-cache',
 
642
 
643
  sendEvent('start', { url, message: 'Scan started...' });
644
 
 
645
  try {
646
  await scanMutex.runExclusive(async () => {
647
  const startTime = Date.now();
648
 
 
649
  const results = {
650
  screenshot: null,
651
  hiddenStorage: null,
 
658
  privacyPolicy: null
659
  };
660
 
 
661
  const tools = [
662
  {
663
  name: 'screenshot',
664
  fn: () => takeScreenshot(url),
665
+ onDone: (data) => { results.screenshot = data; }
666
  },
667
  {
668
  name: 'hidden_storage',
669
  fn: () => checkHiddenStorage(url),
670
+ onDone: (data) => { results.hiddenStorage = data; }
671
  },
672
  {
673
  name: 'cookie_consent',
674
  fn: () => analyzeCookieConsent(url),
675
+ onDone: (data) => { results.cookieConsent = data; }
676
  },
677
  {
678
  name: 'fingerprint_risks',
679
  fn: () => collectFingerprintRisks(url),
680
+ onDone: (data) => { results.fingerprint = data; }
681
  },
682
  {
683
  name: 'redirect_chain',
684
  fn: () => trackRedirectChain(url),
685
+ onDone: (data) => { results.redirectChain = data; }
686
  },
687
  {
688
  name: 'blacklight_scan',
689
  fn: () => performBlacklightScan(url),
690
+ onDone: (data) => { results.blacklight = data; }
691
  },
692
  {
693
  name: 'tosdr',
694
  fn: () => getTosdrGrade(url),
695
+ onDone: (data) => { results.tosdr = data; }
696
  },
697
  {
698
  name: 'security',
699
  fn: () => performSecurityCheck(url),
700
+ onDone: (data) => { results.security = data; }
701
  },
702
  {
703
  name: 'privacy_policy',
704
  fn: () => analyzePrivacyPolicy(url),
705
+ onDone: (data) => { results.privacyPolicy = data; }
706
  }
707
  ];
708
 
709
+ const toolPromises = tools.map(tool =>
 
710
  tool.fn()
711
+ .then(data => {
712
+ tool.onDone(data);
713
+ sendEvent('progress', { step: tool.name, status: 'completed' });
714
+ return data;
715
+ })
716
+ .catch(err => {
717
+ console.error(`${tool.name} failed:`, err.message);
718
+ tool.onDone(null);
719
+ sendEvent('progress', { step: tool.name, status: 'failed', error: err.message });
720
+ return null;
721
+ })
722
  );
723
 
724
+ await Promise.all(toolPromises);
 
 
725
 
 
726
  const blacklightData = results.blacklight || { hosts: {}, cookies: [] };
 
 
727
  const thirdPartyDomains = [
728
  ...(blacklightData.hosts?.thirdParty || []),
729
  ...(blacklightData.hosts?.requests?.third_party || [])
 
745
  }
746
  }
747
 
 
748
  const uniqueIps = [...new Set((blacklightData.hosts?.requests?.third_party || []).map(r => r.ip_addr).filter(Boolean))];
749
  const geoDestinations = [];
750
  for (const ip of uniqueIps) {
 
778
  } catch (e) {}
779
  }
780
 
 
781
  const leakageAlerts = [];
782
  for (const r of (blacklightData.hosts?.requests?.third_party || [])) {
783
  if ((r.method === 'POST' || r.method === 'PUT') && r.body) {
 
796
  }
797
  }
798
 
 
799
  const securityData = results.security || { ssl: { valid: false } };
800
  const { score, grade } = calculatePrivacyScore(
801
  blacklightData,
 
808
  results.privacyPolicy
809
  );
810
 
 
811
  const finalResult = {
812
  success: true,
813
  url,
 
855
 
856
  app.get('/health', (req, res) => res.json({ status: 'ok' }));
857
 
 
858
  (async () => {
859
  const dirs = [path.join(__dirname, 'data'), path.join(__dirname, 'bl-tmp')];
860
  for (const dir of dirs) {
 
867
  await downloadDDGTrackerRadar();
868
  }
869
  app.listen(PORT, '0.0.0.0', () => {
870
+ console.log(`Private Eye Live API running on port ${PORT}`);
871
  });
872
  })();