26-source OSINT intelligence engine with live Jarvis dashboard, auto-refresh via SSE, optional LLM layer (4 providers), delta/memory system, and Telegram breaking news alerts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
43 lines
1.4 KiB
JavaScript
43 lines
1.4 KiB
JavaScript
// Shared fetch utility with timeout, retries, and error handling
|
|
|
|
export async function safeFetch(url, opts = {}) {
|
|
const { timeout = 15000, retries = 1, headers = {} } = opts;
|
|
let lastError;
|
|
for (let i = 0; i <= retries; i++) {
|
|
try {
|
|
const controller = new AbortController();
|
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
const res = await fetch(url, {
|
|
signal: controller.signal,
|
|
headers: { 'User-Agent': 'Crucix/1.0', ...headers },
|
|
});
|
|
clearTimeout(timer);
|
|
if (!res.ok) {
|
|
const body = await res.text().catch(() => '');
|
|
throw new Error(`HTTP ${res.status}: ${body.slice(0, 200)}`);
|
|
}
|
|
const text = await res.text();
|
|
try { return JSON.parse(text); } catch { return { rawText: text.slice(0, 500) }; }
|
|
} catch (e) {
|
|
lastError = e;
|
|
// GDELT needs 5s between requests, others are fine with shorter delays
|
|
if (i < retries) await new Promise(r => setTimeout(r, 2000 * (i + 1)));
|
|
}
|
|
}
|
|
return { error: lastError?.message || 'Unknown error', source: url };
|
|
}
|
|
|
|
export function ago(hours) {
|
|
return new Date(Date.now() - hours * 3600000).toISOString();
|
|
}
|
|
|
|
export function today() {
|
|
return new Date().toISOString().split('T')[0];
|
|
}
|
|
|
|
export function daysAgo(n) {
|
|
const d = new Date();
|
|
d.setDate(d.getDate() - n);
|
|
return d.toISOString().split('T')[0];
|
|
}
|