167 lines
5.9 KiB
TypeScript
167 lines
5.9 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { spawn } from 'child_process';
|
|
|
|
// Prevent static generation of this route
|
|
export const dynamic = 'force-dynamic';
|
|
export const runtime = 'nodejs';
|
|
|
|
// Store connected clients
|
|
const clients = new Set<ReadableStreamDefaultController>();
|
|
|
|
// Handle CORS preflight requests
|
|
export async function OPTIONS(request: NextRequest) {
|
|
return new NextResponse(null, {
|
|
status: 200,
|
|
headers: {
|
|
'Access-Control-Allow-Origin': '*',
|
|
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
|
'Access-Control-Allow-Headers': 'Cache-Control, Content-Type',
|
|
'Access-Control-Max-Age': '86400',
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
const stream = new ReadableStream({
|
|
start(controller) {
|
|
// Add this client to the set
|
|
clients.add(controller);
|
|
|
|
// Send initial connection message
|
|
try {
|
|
controller.enqueue(`data: ${JSON.stringify({ type: 'connected', message: 'SSE connection established' })}\n\n`);
|
|
} catch (error) {
|
|
console.error('Error sending initial message:', error);
|
|
clients.delete(controller);
|
|
return;
|
|
}
|
|
|
|
// Set up Rust file watcher if not already set up
|
|
if (clients.size === 1) {
|
|
try {
|
|
const rustWatcher = spawn(
|
|
process.cwd() + '/markdown_backend/target/release/markdown_backend',
|
|
['watch'],
|
|
{ stdio: ['pipe', 'pipe', 'pipe'] }
|
|
);
|
|
|
|
rustWatcher.stdout.on('data', (data) => {
|
|
const message = data.toString().trim();
|
|
console.log('Rust watcher output:', message);
|
|
|
|
if (message.includes('Posts directory changed!')) {
|
|
// Notify all connected clients about the update
|
|
const updateMessage = JSON.stringify({ type: 'update', timestamp: new Date().toISOString() });
|
|
const clientsToRemove: ReadableStreamDefaultController[] = [];
|
|
|
|
clients.forEach(client => {
|
|
try {
|
|
client.enqueue(`data: ${updateMessage}\n\n`);
|
|
} catch (error) {
|
|
// Mark client for removal
|
|
clientsToRemove.push(client);
|
|
}
|
|
});
|
|
|
|
// Remove disconnected clients
|
|
clientsToRemove.forEach(client => {
|
|
clients.delete(client);
|
|
});
|
|
|
|
// Stop watching if no clients are connected
|
|
if (clients.size === 0) {
|
|
console.log('No clients connected, stopping watcher');
|
|
rustWatcher.kill();
|
|
}
|
|
}
|
|
});
|
|
|
|
rustWatcher.stderr.on('data', (data) => {
|
|
const errorMessage = data.toString().trim();
|
|
console.error('Rust watcher error:', errorMessage);
|
|
|
|
// Don't treat RecvError as a real error - it's expected when the process is terminated
|
|
if (!errorMessage.includes('RecvError')) {
|
|
// Send error to clients
|
|
const errorData = JSON.stringify({ type: 'error', message: errorMessage });
|
|
const clientsToRemove: ReadableStreamDefaultController[] = [];
|
|
|
|
clients.forEach(client => {
|
|
try {
|
|
client.enqueue(`data: ${errorData}\n\n`);
|
|
} catch (error) {
|
|
clientsToRemove.push(client);
|
|
}
|
|
});
|
|
|
|
clientsToRemove.forEach(client => {
|
|
clients.delete(client);
|
|
});
|
|
}
|
|
});
|
|
|
|
rustWatcher.on('error', (error) => {
|
|
console.error('Rust watcher spawn error:', error);
|
|
});
|
|
|
|
rustWatcher.on('close', (code) => {
|
|
console.log('Rust watcher closed with code:', code);
|
|
// Only restart if we still have clients
|
|
if (clients.size > 0) {
|
|
console.log('Restarting watcher due to unexpected close');
|
|
// The watcher will be restarted when the next client connects
|
|
}
|
|
});
|
|
|
|
// Store the watcher process for cleanup
|
|
(controller as any).rustWatcher = rustWatcher;
|
|
} catch (error) {
|
|
console.error('Error setting up Rust file watcher:', error);
|
|
}
|
|
}
|
|
|
|
// Clean up when client disconnects
|
|
request.signal.addEventListener('abort', () => {
|
|
clients.delete(controller);
|
|
|
|
// Stop watching if no clients are connected
|
|
if (clients.size === 0) {
|
|
const rustWatcher = (controller as any).rustWatcher;
|
|
if (rustWatcher) {
|
|
console.log('Last client disconnected, stopping watcher');
|
|
rustWatcher.kill();
|
|
}
|
|
}
|
|
});
|
|
},
|
|
cancel() {
|
|
// Handle stream cancellation - this is called when the stream is cancelled
|
|
// We can't access the specific controller here, so we'll handle cleanup in the abort event
|
|
}
|
|
});
|
|
|
|
return new NextResponse(stream, {
|
|
headers: {
|
|
'Content-Type': 'text/event-stream',
|
|
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
'Connection': 'keep-alive',
|
|
'Access-Control-Allow-Origin': '*',
|
|
'Access-Control-Allow-Headers': 'Cache-Control, Content-Type',
|
|
'X-Accel-Buffering': 'no', // Disable nginx buffering
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error('SSE route error:', error);
|
|
return new NextResponse(
|
|
JSON.stringify({ error: 'Internal server error' }),
|
|
{
|
|
status: 500,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Access-Control-Allow-Origin': '*',
|
|
},
|
|
}
|
|
);
|
|
}
|
|
}
|