CCCPaste

063377232d38a

import type { NextRequest } from "next/server";
import { redisClient } from "@/repo/RedisClient";
import { FlagsService } from "@/services/FlagsService";

const subscriber = redisClient.createSubscriber();

// Map: topic -> Set<{controller, connectionId}> (still pod-local)
const connections = new Map<
	string,
	Set<{ controller: ReadableStreamDefaultController; connectionId: string }>
>();

function findConnectionByController(
	controller: ReadableStreamDefaultController,
) {
	for (const [topic, connectionSet] of connections) {
		for (const connection of connectionSet) {
			if (connection.controller === controller) {
				return { connection, topic };
			}
		}
	}
	return null;
}

subscriber.subscribe("sse-broadcast");
subscriber.on("message", (channel, message) => {
	if (channel === "sse-broadcast") {
		try {
			const event = JSON.parse(message);
			broadcastToLocalConnections(event);
		} catch (error) {
			console.error("Error processing Redis message:", error);
		}
	}
});

// biome-ignore lint/suspicious/noExplicitAny: SSE data can contain anything
function broadcastToLocalConnections(event: any) {
	const {
		topic,
		broadcastToAll,
		broadcastToOtherUsers,
		excludeConnectionId,
		targetConnectionId,
	} = event;

	const eventString = `data: ${JSON.stringify(event)}\n\n`;
	const encoder = new TextEncoder();
	const eventData = encoder.encode(eventString);

	const connectionsToRemove: Array<{
		connection: {
			controller: ReadableStreamDefaultController;
			connectionId: string;
		};
		topic: string;
	}> = [];

	if (targetConnectionId) {
		// Send to specific connection
		connections.forEach((connectionSet, currentTopic) => {
			if (currentTopic === topic || topic === "*") {
				connectionSet.forEach((connection) => {
					if (connection.connectionId === targetConnectionId) {
						try {
							connection.controller.enqueue(eventData);
						} catch {
							connectionsToRemove.push({ connection, topic: currentTopic });
						}
					}
				});
			}
		});
	} else if (broadcastToOtherUsers && excludeConnectionId) {
		// Broadcast to all connections except the one with excludeConnectionId
		connections.forEach((connectionSet, currentTopic) => {
			if (currentTopic === topic || topic === "*") {
				connectionSet.forEach((connection) => {
					if (connection.connectionId !== excludeConnectionId) {
						try {
							connection.controller.enqueue(eventData);
						} catch {
							connectionsToRemove.push({ connection, topic: currentTopic });
						}
					}
				});
			}
		});
	} else if (broadcastToAll) {
		// Broadcast to all connections in the topic
		connections.forEach((connectionSet, currentTopic) => {
			if (currentTopic === topic || topic === "*") {
				connectionSet.forEach((connection) => {
					try {
						connection.controller.enqueue(eventData);
					} catch {
						connectionsToRemove.push({ connection, topic: currentTopic });
					}
				});
			}
		});
	} else {
		// Broadcast to specific topic connections
		const connectionSet = connections.get(topic);
		if (connectionSet) {
			connectionSet.forEach((connection) => {
				try {
					connection.controller.enqueue(eventData);
				} catch {
					connectionsToRemove.push({ connection, topic });
				}
			});
		}
	}

	// Clean up dead connections
	cleanupConnections(connectionsToRemove);
}

function cleanupConnections(
	connectionsToRemove: Array<{
		connection: {
			controller: ReadableStreamDefaultController;
			connectionId: string;
		};
		topic: string;
	}>,
) {
	connectionsToRemove.forEach(({ connection, topic: topicToClean }) => {
		const connectionSet = connections.get(topicToClean);
		if (connectionSet) {
			connectionSet.delete(connection);
			if (connectionSet.size === 0) {
				connections.delete(topicToClean);
			}
		}
	});
}

export async function GET(request: NextRequest) {
	// Check if SSE feature is enabled
	const isEnabled = await FlagsService.isFeatureEnabled("enable_sse");
	if (!isEnabled) {
		return new Response(
			JSON.stringify({ error: "SSE is currently disabled" }),
			{
				status: 418, // I'm a teapot - indicates SSE feature is disabled
				headers: { "Content-Type": "application/json" },
			},
		);
	}

	const { searchParams } = new URL(request.url);
	const topic = searchParams.get("topic") || "*";

	const stream = new ReadableStream({
		start(controller) {
			if (!connections.has(topic)) {
				connections.set(topic, new Set());
			}
			const connectionSet = connections.get(topic);

			const connectionId = crypto.randomUUID();
			const connection = { controller, connectionId };
			connectionSet?.add(connection);

			const connectionEvent = {
				id: crypto.randomUUID(),
				topic: "connection",
				data: {
					message: "Connected to SSE",
					connectionId,
				},
				timestamp: Date.now(),
			};

			const eventString = `data: ${JSON.stringify(connectionEvent)}\n\n`;
			const encoder = new TextEncoder();
			const eventData = encoder.encode(eventString);

			try {
				controller.enqueue(eventData);
			} catch (err) {
				console.error("Failed to send connection event:", err);
			}
		},

		cancel(controller) {
			// Find and remove the connection
			const found = findConnectionByController(controller);
			if (found) {
				const connectionSet = connections.get(found.topic);
				if (connectionSet) {
					connectionSet.delete(found.connection);
					if (connectionSet.size === 0) {
						connections.delete(found.topic);
					}
				}
			}
		},
	});

	return new Response(stream, {
		headers: {
			"Content-Type": "text/event-stream",
			"Cache-Control": "no-cache",
			Connection: "keep-alive",
		},
	});
}

export async function POST(request: NextRequest) {
	try {
		// Check if SSE feature is enabled
		const isEnabled = await FlagsService.isFeatureEnabled("enable_sse");
		if (!isEnabled) {
			return Response.json(
				{ error: "SSE is currently disabled" },
				{ status: 418 }, // I'm a teapot - indicates SSE feature is disabled
			);
		}

		const {
			topic,
			data,
			targetConnectionId,
			excludeConnectionId,
			broadcastToAll,
			broadcastToOtherUsers,
		} = await request.json();

		if (!topic) {
			return Response.json({ error: "topic is required" }, { status: 400 });
		}

		const event = {
			id: crypto.randomUUID(),
			topic,
			data: data || {},
			timestamp: Date.now(),
			targetConnectionId,
			excludeConnectionId,
			broadcastToAll,
			broadcastToOtherUsers,
		};

		await redisClient.publish("sse-broadcast", JSON.stringify(event));

		return Response.json({
			success: true,
			eventId: event.id,
			message: "Event published to all connections",
		});
	} catch (error) {
		console.error("Error processing SSE event:", error);
		return Response.json({ error: "Internal server error" }, { status: 500 });
	}
}