lljz66 commited on
Commit
cb86b85
·
verified ·
1 Parent(s): bb35a0b

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +264 -98
server.js CHANGED
@@ -11,6 +11,8 @@ import { parse } from 'tldts';
11
  import { chromium } from 'playwright';
12
  import axios from 'axios';
13
  import cron from 'node-cron';
 
 
14
 
15
  const __filename = fileURLToPath(import.meta.url);
16
  const __dirname = path.dirname(__filename);
@@ -91,7 +93,6 @@ function normalizeUrl(inputUrl) {
91
  try { return new URL(url).toString(); } catch { return null; }
92
  }
93
 
94
- // استخراج النطاق الأساسي (محسّن لتقليل "Unknown")
95
  function getBaseDomain(hostname) {
96
  const parsed = parse(hostname);
97
  if (parsed.subdomain) {
@@ -164,6 +165,34 @@ async function getTosdrGrade(url) {
164
  }
165
  }
166
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  // ==================== لقطة شاشة (WebP) ====================
168
  async function takeScreenshot(url) {
169
  let browser = null;
@@ -191,7 +220,8 @@ async function performBlacklightScan(url) {
191
  numPages: 1,
192
  defaultWaitUntil: 'networkidle2',
193
  captureHar: true,
194
- saveScreenshots: false,
 
195
  headless: true,
196
  defaultTimeout: 60000,
197
  extraChromiumArgs: ['--disable-blink-features=AutomationControlled','--no-sandbox','--disable-setuid-sandbox','--disable-dev-shm-usage','--disable-gpu','--ignore-certificate-errors']
@@ -203,7 +233,50 @@ async function performBlacklightScan(url) {
203
  }
204
  }
205
 
206
- // ==================== فحص Hidden Storage (wearehere) ====================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  async function checkHiddenStorage(url) {
208
  let browser = null;
209
  try {
@@ -238,7 +311,7 @@ async function checkHiddenStorage(url) {
238
  }
239
 
240
  // ==================== حساب درجة الخصوصية ====================
241
- function calculatePrivacyScore(blacklight, enrichedTrackers, tosdrGrade) {
242
  let score = 100;
243
 
244
  const trackerCount = enrichedTrackers.length;
@@ -254,6 +327,11 @@ function calculatePrivacyScore(blacklight, enrichedTrackers, tosdrGrade) {
254
  if (blacklight?.sessionRecorders?.length > 0) score -= 10;
255
  if (blacklight?.keyLogging?.length > 0) score -= 10;
256
 
 
 
 
 
 
257
  if (tosdrGrade) {
258
  if (tosdrGrade.grade === 'A') score += 5;
259
  else if (tosdrGrade.grade === 'B') score += 2;
@@ -274,49 +352,186 @@ function calculatePrivacyScore(blacklight, enrichedTrackers, tosdrGrade) {
274
  return { score, grade };
275
  }
276
 
277
- // ==================== نقطة نهاية الفحص الفردي (متوازية) ====================
278
- app.get('/api/scan/stream', async (req, res) => {
279
  const url = normalizeUrl(req.query.url);
280
  if (!url) return res.status(400).json({ error: 'Invalid URL' });
281
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  res.writeHead(200, {
283
  'Content-Type': 'text/event-stream',
284
  'Cache-Control': 'no-cache',
285
  'Connection': 'keep-alive'
286
  });
287
  const send = (event, data) => res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
288
-
289
  const startTime = Date.now();
290
  try {
291
- send('step', { step: 'start', message: '🚀 Starting parallel scan (V7)...' });
292
 
293
- // تنفيذ جميع المهام بالتوازي
294
- const [screenshotResult, blacklightResult, tosdrResult, hiddenStorageResult] = await Promise.allSettled([
295
  takeScreenshot(url),
296
  performBlacklightScan(url),
297
  getTosdrGrade(url),
 
298
  checkHiddenStorage(url)
299
  ]);
300
 
301
- const screenshot = screenshotResult.status === 'fulfilled' ? screenshotResult.value : null;
302
- const blacklight = blacklightResult.status === 'fulfilled' ? blacklightResult.value : { error: blacklightResult.reason?.message };
303
- const tosdrGrade = tosdrResult.status === 'fulfilled' ? tosdrResult.value : null;
304
- const hiddenStorage = hiddenStorageResult.status === 'fulfilled' ? hiddenStorageResult.value : null;
 
305
 
306
- send('step', { step: 'screenshot', message: screenshot ? '✅ Screenshot captured (WebP)' : '⚠️ Screenshot failed' });
307
- send('step', { step: 'blacklight', message: blacklight.error ? '⚠️ Blacklight scan had issues' : ' Page analysis complete' });
308
- send('step', { step: 'tosdr', message: tosdrGrade ? `✅ ToS;DR rating: ${tosdrGrade.grade}` : 'ℹ️ No ToS;DR rating available' });
309
- send('step', { step: 'storage', message: hiddenStorage ? `✅ Hidden storage checked` : '⚠️ Storage check skipped' });
310
 
311
  const thirdPartyDomains = [
312
- ...(blacklight.hosts?.thirdParty || []),
313
- ...(blacklight.hosts?.requests?.third_party || [])
314
  ];
315
  const uniqueDomains = [...new Set(thirdPartyDomains)];
316
 
317
- send('step', { step: 'cookies', message: `🍪 Found ${blacklight.cookies?.length || 0} cookies` });
318
- send('step', { step: 'trackers', message: `🎯 Analyzing ${uniqueDomains.length} third-party domains...` });
319
-
320
  const enrichedTrackers = [];
321
  for (const domain of uniqueDomains) {
322
  const ddgInfo = getDDGInfo(domain);
@@ -325,105 +540,56 @@ app.get('/api/scan/stream', async (req, res) => {
325
  domain,
326
  owner: ghosteryInfo?.organization || ddgInfo?.owner || getBaseDomain(domain),
327
  category: ghosteryInfo?.category || ddgInfo?.category || 'unknown',
328
- prevalence: ddgInfo?.prevalence || 0,
329
- fingerprinting: ddgInfo?.fingerprinting || 0
330
  });
331
  }
332
 
333
- const { score, grade } = calculatePrivacyScore(blacklight, enrichedTrackers, tosdrGrade);
334
 
335
- // تضمين الدرجة في JSON الخام
336
- blacklight.privacy_grade = grade;
337
- blacklight.privacy_score = score;
338
- if (hiddenStorage) {
339
- blacklight.hidden_storage = hiddenStorage;
340
  }
341
 
342
  const summary = {
 
343
  url,
344
- final_url: blacklight.uri_dest || url,
345
  scan_time_sec: (Date.now() - startTime) / 1000,
346
- grade,
347
- score,
348
  trackers: { count: enrichedTrackers.length, list: enrichedTrackers.slice(0, 20) },
349
  cookies: {
350
- total: blacklight.cookies?.length || 0,
351
- third_party: blacklight.cookies?.filter(c => c.thirdParty)?.length || 0
 
 
 
 
352
  },
353
- fingerprinting: !!(blacklight.canvasFingerprinters?.length || blacklight.canvasFontFingerprinters?.length),
354
- session_recording: !!blacklight.sessionRecorders?.length,
355
- key_logging: !!blacklight.keyLogging?.length,
356
- hidden_storage: hiddenStorage ? {
357
- localStorage: hiddenStorage.localStorage?.length || 0,
358
- sessionStorage: hiddenStorage.sessionStorage?.length || 0,
359
- indexedDB: hiddenStorage.indexedDB
360
  } : null,
361
- tosdr: tosdrGrade,
362
- screenshot
 
363
  };
364
 
365
- send('result', { success: true, summary, raw: blacklight });
366
 
367
  } catch (e) {
368
- console.error('Scan error:', e);
369
  send('error', { error: e.message });
370
  } finally {
371
  res.end();
372
  }
373
  });
374
 
375
- // ==================== نقطة نهاية الفحص المجمع ====================
376
- app.post('/api/scan/bulk', async (req, res) => {
377
- const { urls } = req.body;
378
- if (!urls || !Array.isArray(urls) || urls.length === 0) {
379
- return res.status(400).json({ error: 'Invalid or missing "urls" array' });
380
- }
381
-
382
- const results = [];
383
- for (const inputUrl of urls) {
384
- const url = normalizeUrl(inputUrl);
385
- if (!url) {
386
- results.push({ url: inputUrl, error: 'Invalid URL' });
387
- continue;
388
- }
389
-
390
- try {
391
- const options = {
392
- inUrl: url,
393
- blTests: ['cookies', 'third_party_trackers', 'fb_pixel_events', 'canvas_fingerprinters', 'canvas_font_fingerprinters'],
394
- numPages: 1,
395
- defaultWaitUntil: 'networkidle2',
396
- headless: true,
397
- defaultTimeout: 45000,
398
- extraChromiumArgs: ['--no-sandbox','--disable-dev-shm-usage','--disable-gpu']
399
- };
400
- const blacklightResult = await collect(url, options);
401
- const thirdPartyDomains = [...(blacklightResult.hosts?.thirdParty || [])];
402
- const enriched = thirdPartyDomains.map(d => {
403
- const ddg = getDDGInfo(d);
404
- return { domain: d, owner: ddg?.owner || getBaseDomain(d), category: ddg?.category || 'unknown' };
405
- });
406
- const tosdr = await getTosdrGrade(url);
407
- results.push({
408
- url,
409
- final_url: blacklightResult.uri_dest || url,
410
- trackers: enriched.length,
411
- cookies: blacklightResult.cookies?.length || 0,
412
- fingerprinting: !!(blacklightResult.canvasFingerprinters?.length),
413
- tosdr_grade: tosdr?.grade || null
414
- });
415
- } catch (e) {
416
- results.push({ url, error: e.message });
417
- }
418
- }
419
-
420
- res.json({ success: true, results });
421
- });
422
-
423
  app.get('/health', (req, res) => res.json({ status: 'ok' }));
424
  app.use(express.static('public'));
425
 
426
- // ==================== بدء التشغيل ====================
427
  (async () => {
428
  await initGhosteryDB();
429
  loadDDGTrackerRadar();
@@ -432,5 +598,5 @@ app.use(express.static('public'));
432
  await downloadDDGTrackerRadar();
433
  }
434
 
435
- app.listen(PORT, '0.0.0.0', () => console.log(`🚀 Private Eye V7 running on ${PORT}`));
436
  })();
 
11
  import { chromium } from 'playwright';
12
  import axios from 'axios';
13
  import cron from 'node-cron';
14
+ import tlsing from 'tlsing';
15
+ import SecureCheck from 'securecheck';
16
 
17
  const __filename = fileURLToPath(import.meta.url);
18
  const __dirname = path.dirname(__filename);
 
93
  try { return new URL(url).toString(); } catch { return null; }
94
  }
95
 
 
96
  function getBaseDomain(hostname) {
97
  const parsed = parse(hostname);
98
  if (parsed.subdomain) {
 
165
  }
166
  }
167
 
168
+ // ==================== فحص SSL/TLS و هيدرات الأمان ====================
169
+ async function performSecurityCheck(url) {
170
+ try {
171
+ const tlsResults = await tlsing.check(url);
172
+ const secureCheck = new SecureCheck(url);
173
+ const headerResults = await secureCheck.scan();
174
+
175
+ return {
176
+ ssl: {
177
+ valid: tlsResults.valid,
178
+ issuer: tlsResults.issuer,
179
+ expires_in_days: tlsResults.daysRemaining,
180
+ protocol: tlsResults.protocol,
181
+ grade: tlsResults.grade
182
+ },
183
+ headers: {
184
+ csp: !!headerResults.headers?.contentSecurityPolicy,
185
+ hsts: !!headerResults.headers?.strictTransportSecurity,
186
+ xfo: headerResults.headers?.xFrameOptions || 'Missing',
187
+ xcto: headerResults.headers?.xContentTypeOptions || 'Missing'
188
+ }
189
+ };
190
+ } catch (e) {
191
+ console.error('Security check failed:', e.message);
192
+ return { ssl: { valid: false }, headers: {} };
193
+ }
194
+ }
195
+
196
  // ==================== لقطة شاشة (WebP) ====================
197
  async function takeScreenshot(url) {
198
  let browser = null;
 
220
  numPages: 1,
221
  defaultWaitUntil: 'networkidle2',
222
  captureHar: true,
223
+ saveScreenshots: true,
224
+ screenshotOptions: { type: 'jpeg', quality: 80, fullPage: false },
225
  headless: true,
226
  defaultTimeout: 60000,
227
  extraChromiumArgs: ['--disable-blink-features=AutomationControlled','--no-sandbox','--disable-setuid-sandbox','--disable-dev-shm-usage','--disable-gpu','--ignore-certificate-errors']
 
233
  }
234
  }
235
 
236
+ // ==================== فحص سريع (Fast) ====================
237
+ async function performFastScan(url) {
238
+ const hostname = new URL(url).hostname;
239
+ const baseDomain = getBaseDomain(hostname);
240
+
241
+ // جمع النطاقات الخارجية من قواعد البيانات المحلية فقط
242
+ const allDomains = new Set();
243
+
244
+ // إضافة نطاقات معروفة من Ghostery (بشكل محدود)
245
+ if (ghosteryDB) {
246
+ // هذه محاكاة - نحتاج لاستخراج النطاقات المرتبطة بالنطاق الأساسي
247
+ // في الواقع، Ghostery لا يوفر API لاستخراج جميع النطاقات، لذا سنعتمد على DDG بشكل أساسي
248
+ }
249
+
250
+ // استخدام DDG Tracker Radar
251
+ for (const domain in ddgTrackerRadar.domains) {
252
+ if (domain.includes(baseDomain) || baseDomain.includes(domain)) {
253
+ allDomains.add(domain);
254
+ }
255
+ }
256
+
257
+ const trackers = [];
258
+ for (const domain of allDomains) {
259
+ const ddgInfo = ddgTrackerRadar.domains[domain];
260
+ const ghosteryInfo = await getGhosteryInfo(domain);
261
+ trackers.push({
262
+ domain,
263
+ owner: ghosteryInfo?.organization || ddgInfo?.owner || domain,
264
+ category: ghosteryInfo?.category || ddgInfo?.category || 'unknown',
265
+ prevalence: ddgInfo?.prevalence || 0
266
+ });
267
+ }
268
+
269
+ const security = await performSecurityCheck(url);
270
+
271
+ return {
272
+ mode: 'fast',
273
+ trackers,
274
+ security,
275
+ scan_time_sec: (Date.now() - startTime) / 1000
276
+ };
277
+ }
278
+
279
+ // ==================== فحص التخزين الخفي ====================
280
  async function checkHiddenStorage(url) {
281
  let browser = null;
282
  try {
 
311
  }
312
 
313
  // ==================== حساب درجة الخصوصية ====================
314
+ function calculatePrivacyScore(blacklight, enrichedTrackers, tosdrGrade, security) {
315
  let score = 100;
316
 
317
  const trackerCount = enrichedTrackers.length;
 
327
  if (blacklight?.sessionRecorders?.length > 0) score -= 10;
328
  if (blacklight?.keyLogging?.length > 0) score -= 10;
329
 
330
+ // تأثير SSL
331
+ if (!security?.ssl?.valid) score -= 20;
332
+ if (!security?.headers?.hsts) score -= 5;
333
+ if (!security?.headers?.csp) score -= 5;
334
+
335
  if (tosdrGrade) {
336
  if (tosdrGrade.grade === 'A') score += 5;
337
  else if (tosdrGrade.grade === 'B') score += 2;
 
352
  return { score, grade };
353
  }
354
 
355
+ // ==================== نقطة نهاية الفحص الموحدة ====================
356
+ app.get('/api/scan', async (req, res) => {
357
  const url = normalizeUrl(req.query.url);
358
  if (!url) return res.status(400).json({ error: 'Invalid URL' });
359
+
360
+ const mode = req.query.mode || 'deep';
361
+ const format = req.query.format || 'sse';
362
+
363
+ // وضع الفحص السريع
364
+ if (mode === 'fast') {
365
+ try {
366
+ const startTime = Date.now();
367
+ const security = await performSecurityCheck(url);
368
+ const tosdr = await getTosdrGrade(url);
369
+
370
+ // جمع النطاقات المحتملة من DDG (محاكاة)
371
+ const hostname = new URL(url).hostname;
372
+ const baseDomain = getBaseDomain(hostname);
373
+ const trackers = [];
374
+
375
+ for (const domain in ddgTrackerRadar.domains) {
376
+ if (domain.includes(baseDomain) || baseDomain.includes(domain)) {
377
+ const info = ddgTrackerRadar.domains[domain];
378
+ trackers.push({
379
+ domain,
380
+ owner: info.owner || domain,
381
+ category: info.category || 'unknown',
382
+ prevalence: info.prevalence || 0
383
+ });
384
+ }
385
+ }
386
+
387
+ const result = {
388
+ success: true,
389
+ mode: 'fast',
390
+ url,
391
+ final_url: url,
392
+ scan_time_sec: (Date.now() - startTime) / 1000,
393
+ trackers: { count: trackers.length, list: trackers.slice(0, 20) },
394
+ security,
395
+ tosdr,
396
+ privacy_score: calculatePrivacyScore({}, trackers, tosdr, security)
397
+ };
398
+
399
+ if (format === 'json') {
400
+ return res.json(result);
401
+ }
402
+
403
+ // SSE
404
+ res.writeHead(200, {
405
+ 'Content-Type': 'text/event-stream',
406
+ 'Cache-Control': 'no-cache',
407
+ 'Connection': 'keep-alive'
408
+ });
409
+ res.write(`event: result\ndata: ${JSON.stringify(result)}\n\n`);
410
+ res.end();
411
+
412
+ } catch (e) {
413
+ res.status(500).json({ error: e.message });
414
+ }
415
+ return;
416
+ }
417
+
418
+ // وضع الفحص العميق (deep)
419
+ if (format === 'json') {
420
+ try {
421
+ const startTime = Date.now();
422
+ const [screenshot, blacklight, tosdr, security, hiddenStorage] = await Promise.allSettled([
423
+ takeScreenshot(url),
424
+ performBlacklightScan(url),
425
+ getTosdrGrade(url),
426
+ performSecurityCheck(url),
427
+ checkHiddenStorage(url)
428
+ ]);
429
+
430
+ const screenshotData = screenshot.status === 'fulfilled' ? screenshot.value : null;
431
+ const blacklightData = blacklight.status === 'fulfilled' ? blacklight.value : { error: blacklight.reason?.message };
432
+ const tosdrData = tosdr.status === 'fulfilled' ? tosdr.value : null;
433
+ const securityData = security.status === 'fulfilled' ? security.value : { ssl: { valid: false } };
434
+ const hiddenData = hiddenStorage.status === 'fulfilled' ? hiddenStorage.value : null;
435
+
436
+ // استخراج النطاقات
437
+ const thirdPartyDomains = [
438
+ ...(blacklightData.hosts?.thirdParty || []),
439
+ ...(blacklightData.hosts?.requests?.third_party || [])
440
+ ];
441
+ const uniqueDomains = [...new Set(thirdPartyDomains)];
442
+
443
+ const enrichedTrackers = [];
444
+ for (const domain of uniqueDomains) {
445
+ const ddgInfo = getDDGInfo(domain);
446
+ const ghosteryInfo = await getGhosteryInfo(domain);
447
+ enrichedTrackers.push({
448
+ domain,
449
+ owner: ghosteryInfo?.organization || ddgInfo?.owner || getBaseDomain(domain),
450
+ category: ghosteryInfo?.category || ddgInfo?.category || 'unknown',
451
+ prevalence: ddgInfo?.prevalence || 0
452
+ });
453
+ }
454
+
455
+ const { score, grade } = calculatePrivacyScore(blacklightData, enrichedTrackers, tosdrData, securityData);
456
+
457
+ // استخدام لقطة Blacklight كاحتياط
458
+ let finalScreenshot = screenshotData;
459
+ if (!finalScreenshot && blacklightData.screenshots?.[0]) {
460
+ finalScreenshot = `data:image/jpeg;base64,${blacklightData.screenshots[0].toString('base64')}`;
461
+ }
462
+
463
+ const result = {
464
+ success: true,
465
+ mode: 'deep',
466
+ url,
467
+ final_url: blacklightData.uri_dest || url,
468
+ scan_time_sec: (Date.now() - startTime) / 1000,
469
+ privacy_score: { score, grade },
470
+ trackers: { count: enrichedTrackers.length, list: enrichedTrackers.slice(0, 20) },
471
+ cookies: {
472
+ total: blacklightData.cookies?.length || 0,
473
+ third_party: blacklightData.cookies?.filter(c => c.thirdParty)?.length || 0
474
+ },
475
+ fingerprinting: {
476
+ canvas: !!(blacklightData.canvasFingerprinters?.length),
477
+ fonts: !!(blacklightData.canvasFontFingerprinters?.length)
478
+ },
479
+ session_recording: !!blacklightData.sessionRecorders?.length,
480
+ key_logging: !!blacklightData.keyLogging?.length,
481
+ hidden_storage: hiddenData ? {
482
+ localStorage: hiddenData.localStorage?.length || 0,
483
+ sessionStorage: hiddenData.sessionStorage?.length || 0,
484
+ indexedDB: hiddenData.indexedDB
485
+ } : null,
486
+ security: securityData,
487
+ tosdr: tosdrData,
488
+ screenshot: finalScreenshot,
489
+ raw: blacklightData
490
+ };
491
+
492
+ res.json(result);
493
+
494
+ } catch (e) {
495
+ res.status(500).json({ error: e.message });
496
+ }
497
+ return;
498
+ }
499
+
500
+ // وضع SSE للفحص العميق
501
  res.writeHead(200, {
502
  'Content-Type': 'text/event-stream',
503
  'Cache-Control': 'no-cache',
504
  'Connection': 'keep-alive'
505
  });
506
  const send = (event, data) => res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
507
+
508
  const startTime = Date.now();
509
  try {
510
+ send('step', { step: 'start', message: '🚀 Starting V8 deep scan...' });
511
 
512
+ const [screenshot, blacklight, tosdr, security, hiddenStorage] = await Promise.allSettled([
 
513
  takeScreenshot(url),
514
  performBlacklightScan(url),
515
  getTosdrGrade(url),
516
+ performSecurityCheck(url),
517
  checkHiddenStorage(url)
518
  ]);
519
 
520
+ const screenshotData = screenshot.status === 'fulfilled' ? screenshot.value : null;
521
+ const blacklightData = blacklight.status === 'fulfilled' ? blacklight.value : { error: blacklight.reason?.message };
522
+ const tosdrData = tosdr.status === 'fulfilled' ? tosdr.value : null;
523
+ const securityData = security.status === 'fulfilled' ? security.value : { ssl: { valid: false } };
524
+ const hiddenData = hiddenStorage.status === 'fulfilled' ? hiddenStorage.value : null;
525
 
526
+ send('step', { step: 'security', message: securityData?.ssl?.valid ? '✅ SSL valid' : '⚠️ SSL issues' });
527
+ send('step', { step: 'tosdr', message: tosdrData ? `✅ ToS;DR: ${tosdrData.grade}` : 'ℹ️ No ToS;DR' });
 
 
528
 
529
  const thirdPartyDomains = [
530
+ ...(blacklightData.hosts?.thirdParty || []),
531
+ ...(blacklightData.hosts?.requests?.third_party || [])
532
  ];
533
  const uniqueDomains = [...new Set(thirdPartyDomains)];
534
 
 
 
 
535
  const enrichedTrackers = [];
536
  for (const domain of uniqueDomains) {
537
  const ddgInfo = getDDGInfo(domain);
 
540
  domain,
541
  owner: ghosteryInfo?.organization || ddgInfo?.owner || getBaseDomain(domain),
542
  category: ghosteryInfo?.category || ddgInfo?.category || 'unknown',
543
+ prevalence: ddgInfo?.prevalence || 0
 
544
  });
545
  }
546
 
547
+ const { score, grade } = calculatePrivacyScore(blacklightData, enrichedTrackers, tosdrData, securityData);
548
 
549
+ let finalScreenshot = screenshotData;
550
+ if (!finalScreenshot && blacklightData.screenshots?.[0]) {
551
+ finalScreenshot = `data:image/jpeg;base64,${blacklightData.screenshots[0].toString('base64')}`;
 
 
552
  }
553
 
554
  const summary = {
555
+ mode: 'deep',
556
  url,
557
+ final_url: blacklightData.uri_dest || url,
558
  scan_time_sec: (Date.now() - startTime) / 1000,
559
+ privacy_score: { score, grade },
 
560
  trackers: { count: enrichedTrackers.length, list: enrichedTrackers.slice(0, 20) },
561
  cookies: {
562
+ total: blacklightData.cookies?.length || 0,
563
+ third_party: blacklightData.cookies?.filter(c => c.thirdParty)?.length || 0
564
+ },
565
+ fingerprinting: {
566
+ canvas: !!(blacklightData.canvasFingerprinters?.length),
567
+ fonts: !!(blacklightData.canvasFontFingerprinters?.length)
568
  },
569
+ session_recording: !!blacklightData.sessionRecorders?.length,
570
+ key_logging: !!blacklightData.keyLogging?.length,
571
+ hidden_storage: hiddenData ? {
572
+ localStorage: hiddenData.localStorage?.length || 0,
573
+ sessionStorage: hiddenData.sessionStorage?.length || 0,
574
+ indexedDB: hiddenData.indexedDB
 
575
  } : null,
576
+ security: securityData,
577
+ tosdr: tosdrData,
578
+ screenshot: finalScreenshot
579
  };
580
 
581
+ send('result', { success: true, summary, raw: blacklightData });
582
 
583
  } catch (e) {
 
584
  send('error', { error: e.message });
585
  } finally {
586
  res.end();
587
  }
588
  });
589
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590
  app.get('/health', (req, res) => res.json({ status: 'ok' }));
591
  app.use(express.static('public'));
592
 
 
593
  (async () => {
594
  await initGhosteryDB();
595
  loadDDGTrackerRadar();
 
598
  await downloadDDGTrackerRadar();
599
  }
600
 
601
+ app.listen(PORT, '0.0.0.0', () => console.log(`🚀 Private Eye V8 running on ${PORT}`));
602
  })();