import { watch, readFileSync } from 'fs'; import { readdir, readFile, stat } from 'fs/promises'; import { resolve, basename, extname, join, dirname } from 'path'; import { fileURLToPath } from 'url'; // --- Configuration --- const __dirname = dirname(fileURLToPath(import.meta.url)); function loadEnv() { const envPath = resolve(__dirname, '.env'); try { const envFileContent = readFileSync(envPath, 'utf-8'); envFileContent.split(/\r?\n/).forEach(line => { const trimmedLine = line.trim(); if (trimmedLine && !trimmedLine.startsWith('#')) { const parts = trimmedLine.split('=', 2); if (parts.length === 2) { const [key, value] = parts; process.env[key.trim()] = value.trim().replace(/^"|"$/g, ''); } } }); console.log('Successfully loaded configuration from .env file.'); } catch (error) { console.error('FATAL: Could not find or read the .env file. Please ensure it exists in the same directory as this script.'); process.exit(1); } } loadEnv(); const { WEBHOOK_BASE_URL, BASIC_AUTH_USERNAME, BASIC_AUTH_PASSWORD } = process.env; if (!WEBHOOK_BASE_URL || !BASIC_AUTH_USERNAME || !BASIC_AUTH_PASSWORD) { console.error('FATAL: Please define WEBHOOK_BASE_URL, BASIC_AUTH_USERNAME, and BASIC_AUTH_PASSWORD in your .env file.'); process.exit(1); } const WATCH_PATH = resolve(__dirname, '..', '..', '..', 'script-output', 'signal_export'); const DEBOUNCE_DELAY_MS = 250; const debounceTimers = new Map(); // --- Core Logic --- async function sendToWebhook(filePath: string) { try { const combinatorName = basename(filePath, extname(filePath)); const url = `${WEBHOOK_BASE_URL}/${encodeURIComponent(combinatorName)}`; const content = await readFile(filePath, 'utf-8'); // Silently ignore empty files, which can occur during file writing. if (!content.trim()) return; JSON.parse(content); const authHeader = 'Basic ' + Buffer.from(`${BASIC_AUTH_USERNAME}:${BASIC_AUTH_PASSWORD}`).toString('base64'); console.log(`Sending data for '${combinatorName}'...`); const response = await fetch(url, { method: 'POST', // Using POST to send a request body, as is standard for webhooks. headers: { 'Content-Type': 'application/json', 'Authorization': authHeader, }, body: content, }); if (!response.ok) { throw new Error(`HTTP Error: ${response.status} ${response.statusText}`); } console.log(`Successfully sent data for '${combinatorName}'. Server responded with ${response.status}.`); } catch (err: any) { const fileName = basename(filePath); if (err instanceof SyntaxError) { console.error(`Skipping ${fileName}: Invalid JSON content. The file might be incomplete.`); } else { console.error(`Error sending webhook for ${fileName}:`, err.message); } } } function handleFileChange(filename: string | null) { if (!filename || !filename.endsWith('.json')) return; if (debounceTimers.has(filename)) { clearTimeout(debounceTimers.get(filename)!); } const newTimer = setTimeout(async () => { const fullPath = join(WATCH_PATH, filename); try { const stats = await stat(fullPath); if (stats.isFile()) { await sendToWebhook(fullPath); } } catch { // Error (e.g., file deleted before processing), ignore. } finally { debounceTimers.delete(filename); } }, DEBOUNCE_DELAY_MS); debounceTimers.set(filename, newTimer); } async function initialScanAndProcess() { console.log(`Performing initial scan of directory: ${WATCH_PATH}`); try { const files = await readdir(WATCH_PATH); const jsonFiles = files.filter(f => f.endsWith('.json')); console.log(`Found ${jsonFiles.length} existing JSON file(s). Processing...`); for (const file of jsonFiles) { await sendToWebhook(join(WATCH_PATH, file)); } } catch (err: any) { console.error(`Error during initial scan: ${err.message}. Ensure the directory exists.`); } } // --- Script Execution --- (async () => { await initialScanAndProcess(); console.log(`\nWatching for file changes in: ${WATCH_PATH}`); try { watch(WATCH_PATH, (eventType, filename) => { if (filename) { console.log(`[${new Date().toLocaleTimeString()}] Event '${eventType}' on '${filename}'`); handleFileChange(filename); } }); console.log("Watcher is now running. Press Ctrl+C to stop."); } catch (err: any) { console.error(`FATAL: Failed to start watcher. Please check if the path is correct. Error: ${err.message}`); process.exit(1); } })();