Update server.js
Browse files
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);
|
| 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('
|
| 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,
|
| 104 |
});
|
| 105 |
-
console.log('
|
| 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('
|
| 150 |
}
|
| 151 |
} catch (e) {
|
| 152 |
-
console.warn('
|
| 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(`
|
| 162 |
}
|
| 163 |
} catch (e) {
|
| 164 |
-
console.warn('
|
| 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('
|
| 193 |
} catch (e) {
|
| 194 |
-
console.error('
|
| 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('
|
| 207 |
return;
|
| 208 |
}
|
| 209 |
} catch (e) {
|
| 210 |
-
console.warn('
|
| 211 |
}
|
| 212 |
}
|
| 213 |
-
console.log('
|
| 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('
|
| 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('
|
| 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;
|
| 683 |
},
|
| 684 |
{
|
| 685 |
name: 'hidden_storage',
|
| 686 |
fn: () => checkHiddenStorage(url),
|
| 687 |
-
onDone: (data) => { results.hiddenStorage = data;
|
| 688 |
},
|
| 689 |
{
|
| 690 |
name: 'cookie_consent',
|
| 691 |
fn: () => analyzeCookieConsent(url),
|
| 692 |
-
onDone: (data) => { results.cookieConsent = data;
|
| 693 |
},
|
| 694 |
{
|
| 695 |
name: 'fingerprint_risks',
|
| 696 |
fn: () => collectFingerprintRisks(url),
|
| 697 |
-
onDone: (data) => { results.fingerprint = data;
|
| 698 |
},
|
| 699 |
{
|
| 700 |
name: 'redirect_chain',
|
| 701 |
fn: () => trackRedirectChain(url),
|
| 702 |
-
onDone: (data) => { results.redirectChain = data;
|
| 703 |
},
|
| 704 |
{
|
| 705 |
name: 'blacklight_scan',
|
| 706 |
fn: () => performBlacklightScan(url),
|
| 707 |
-
onDone: (data) => { results.blacklight = data;
|
| 708 |
},
|
| 709 |
{
|
| 710 |
name: 'tosdr',
|
| 711 |
fn: () => getTosdrGrade(url),
|
| 712 |
-
onDone: (data) => { results.tosdr = data;
|
| 713 |
},
|
| 714 |
{
|
| 715 |
name: 'security',
|
| 716 |
fn: () => performSecurityCheck(url),
|
| 717 |
-
onDone: (data) => { results.security = data;
|
| 718 |
},
|
| 719 |
{
|
| 720 |
name: 'privacy_policy',
|
| 721 |
fn: () => analyzePrivacyPolicy(url),
|
| 722 |
-
onDone: (data) => { results.privacyPolicy = data;
|
| 723 |
}
|
| 724 |
];
|
| 725 |
|
| 726 |
-
|
| 727 |
-
const promises = tools.map(tool =>
|
| 728 |
tool.fn()
|
| 729 |
-
.then(data => {
|
| 730 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(`
|
| 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 |
})();
|