122 lines
10 KiB
JavaScript
122 lines
10 KiB
JavaScript
import { streamSSE } from 'hono/streaming';
|
|
import { AbstractProduct } from '../AbstractProduct.js';
|
|
import { getAnalyticsRoute, getAnalyticsStreamRoute, deleteAnalyticsRoute } from './routes.js';
|
|
import { analyticsEmitter } from '../../lib/analytics-emitter.js';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import readline from 'readline';
|
|
const ANALYTICS_FILE = path.resolve(process.cwd(), 'logs/analytics.jsonl');
|
|
export class AnalyticsProduct extends AbstractProduct {
|
|
id = 'analytics';
|
|
jobOptions = {};
|
|
actions = {}; // Optional: Add actions if needed for jobs
|
|
workers = [];
|
|
routes = [];
|
|
hash = () => 'analytics-hash';
|
|
meta = () => ({});
|
|
constructor() {
|
|
super();
|
|
this.initializeRoutes();
|
|
}
|
|
initializeRoutes() {
|
|
this.routes.push({
|
|
definition: getAnalyticsRoute,
|
|
handler: this.handleGetAnalytics.bind(this)
|
|
});
|
|
this.routes.push({
|
|
definition: getAnalyticsStreamRoute,
|
|
handler: this.handleGetAnalyticsStream.bind(this)
|
|
});
|
|
this.routes.push({
|
|
definition: deleteAnalyticsRoute,
|
|
handler: this.handleDeleteAnalytics.bind(this)
|
|
});
|
|
}
|
|
// ... existing handlers
|
|
async handleDeleteAnalytics(c) {
|
|
try {
|
|
if (fs.existsSync(ANALYTICS_FILE)) {
|
|
// Truncate file
|
|
await fs.promises.truncate(ANALYTICS_FILE, 0);
|
|
}
|
|
return c.json({ success: true });
|
|
}
|
|
catch (err) {
|
|
console.error('Error clearing analytics:', err);
|
|
return c.json({ error: 'Internal Server Error' }, 500);
|
|
}
|
|
}
|
|
async handleGetAnalyticsStream(c) {
|
|
return streamSSE(c, async (stream) => {
|
|
const listener = async (entry) => {
|
|
await stream.writeSSE({
|
|
data: JSON.stringify(entry),
|
|
event: 'log',
|
|
});
|
|
};
|
|
analyticsEmitter.on('log', listener);
|
|
// Keep connection alive or handle disconnect
|
|
// Hono's streamSSE handles closing the stream when the connection drops,
|
|
// but we need to remove the listener to avoid leaks.
|
|
stream.onAbort(() => {
|
|
analyticsEmitter.off('log', listener);
|
|
});
|
|
// Wait forever (or until client disconnects)
|
|
while (true) {
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
}
|
|
});
|
|
}
|
|
async handleGetAnalytics(c) {
|
|
try {
|
|
const limit = parseInt(c.req.query('limit') || '100', 10);
|
|
const startDateStr = c.req.query('startDate');
|
|
const endDateStr = c.req.query('endDate');
|
|
const startDate = startDateStr ? new Date(startDateStr).getTime() : 0;
|
|
const endDate = endDateStr ? new Date(endDateStr).getTime() : Date.now();
|
|
if (!fs.existsSync(ANALYTICS_FILE)) {
|
|
return c.json([]);
|
|
}
|
|
// Efficiently read last N lines would be better, but for "filtered" queries we generally need to scan.
|
|
// If file is huge, this is slow.
|
|
// However, typical usage for "analytics middleware... for now" implies simple logging.
|
|
// We will stream the file from the beginning (or end if we could) and collect matching entries.
|
|
// To respect 'limit' effectively with date filters, we ideally want the *latest* entries.
|
|
// So reading from end or collecting all and sorting/slicing is needed.
|
|
// Collecting all in memory is dangerous for large files.
|
|
// But implementing reverse line reading is complex without a library.
|
|
// Compromise: Read all, parse, filter, take last N.
|
|
// Optimization: If no date filter, reasonable to assume we want latest.
|
|
const logs = [];
|
|
const fileStream = fs.createReadStream(ANALYTICS_FILE);
|
|
const rl = readline.createInterface({
|
|
input: fileStream,
|
|
crlfDelay: Infinity
|
|
});
|
|
for await (const line of rl) {
|
|
if (!line.trim())
|
|
continue;
|
|
try {
|
|
const entry = JSON.parse(line);
|
|
const timestamp = new Date(entry.timestamp).getTime();
|
|
if (timestamp >= startDate && timestamp <= endDate) {
|
|
logs.push(entry);
|
|
}
|
|
}
|
|
catch (e) {
|
|
// Ignore bad lines
|
|
}
|
|
}
|
|
// Sort by timestamp desc
|
|
logs.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
// Limit
|
|
const result = logs.slice(0, limit);
|
|
return c.json(result);
|
|
}
|
|
catch (err) {
|
|
console.error('Error reading analytics:', err);
|
|
return c.json({ error: 'Internal Server Error' }, 500);
|
|
}
|
|
}
|
|
}
|
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvcHJvZHVjdHMvYW5hbHl0aWNzL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUMzQyxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDeEQsT0FBTyxFQUFFLGlCQUFpQixFQUFFLHVCQUF1QixFQUFFLG9CQUFvQixFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQy9GLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBRWxFLE9BQU8sRUFBRSxNQUFNLElBQUksQ0FBQztBQUNwQixPQUFPLElBQUksTUFBTSxNQUFNLENBQUM7QUFDeEIsT0FBTyxRQUFRLE1BQU0sVUFBVSxDQUFDO0FBRWhDLE1BQU0sY0FBYyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRSxFQUFFLHNCQUFzQixDQUFDLENBQUM7QUFFM0UsTUFBTSxPQUFPLGdCQUFpQixTQUFRLGVBQW9CO0lBQ3RELEVBQUUsR0FBRyxXQUFXLENBQUM7SUFDakIsVUFBVSxHQUFHLEVBQUUsQ0FBQztJQUNoQixPQUFPLEdBQUcsRUFBRSxDQUFDLENBQUMsMkNBQTJDO0lBQ3pELE9BQU8sR0FBVSxFQUFFLENBQUM7SUFDcEIsTUFBTSxHQUFVLEVBQUUsQ0FBQztJQUNuQixJQUFJLEdBQUcsR0FBRyxFQUFFLENBQUMsZ0JBQWdCLENBQUM7SUFDOUIsSUFBSSxHQUFHLEdBQUcsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUM7SUFFbEI7UUFDSSxLQUFLLEVBQUUsQ0FBQztRQUNSLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO0lBQzVCLENBQUM7SUFFRCxnQkFBZ0I7UUFDWixJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQztZQUNiLFVBQVUsRUFBRSxp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
|