From 696938eebafa83f1812695c4585260657c8116b1 Mon Sep 17 00:00:00 2001 From: Kashvi Garg <97344852+kashvigarg@users.noreply.github.com> Date: Thu, 10 Apr 2025 13:42:17 +0000 Subject: [PATCH] Untested Changes --- web2/app/api/[...path]/route.ts | 149 +++++++++++---------- web2/app/api/sse/[...path]/route.ts | 38 ++++++ web2/app/page.tsx | 4 +- web2/app/timeline/page.tsx | 10 ++ web2/components/comments-list.tsx | 2 +- web2/components/notification-indicator.tsx | 57 +++++++- web2/components/notifications-list.tsx | 2 +- web2/components/timeline.tsx | 5 +- web2/components/users-list.tsx | 1 + web2/lib/use-sse.tsx | 65 ++++++--- 10 files changed, 234 insertions(+), 99 deletions(-) create mode 100644 web2/app/api/sse/[...path]/route.ts create mode 100644 web2/app/timeline/page.tsx diff --git a/web2/app/api/[...path]/route.ts b/web2/app/api/[...path]/route.ts index 51d7c71..4c1684d 100644 --- a/web2/app/api/[...path]/route.ts +++ b/web2/app/api/[...path]/route.ts @@ -8,80 +8,81 @@ export async function GET(request: NextRequest, { params }: { params: { path: st const token = searchParams.get("token") || request.headers.get("Authorization")?.split(" ")[1]; // Check if this is an SSE endpoint - const isSSE = path === "timeline" || path === "notifications" || path.includes("/comments"); - - if (isSSE) { - try { - // Try SSE first - const response = await fetch(`${process.env.API_URL}/api/${path}`, { - headers: { - Authorization: `Bearer ${token}`, - Accept: "text/event-stream", - }, - }); - - const text = await response.text(); - - if (response.ok && text.trim()) { - try { - const data = JSON.parse(text); - - // Set up SSE response - const encoder = new TextEncoder(); - const stream = new ReadableStream({ - async start(controller) { - controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`)); - - // Keep the connection open - const interval = setInterval(() => { - controller.enqueue(encoder.encode(": keepalive\n\n")); - }, 30000); - - // Clean up on close - request.signal.addEventListener("abort", () => { - clearInterval(interval); - controller.close(); - }); - }, - }); - - return new NextResponse(stream, { - headers: { - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache", - Connection: "keep-alive", - }, - }); - } catch (jsonError) { - console.error("SSE JSON parse error:", jsonError); - return NextResponse.json({ error: "Invalid JSON response from SSE" }, { status: 502 }); - } - } else { - console.log("SSE response empty, falling back to basic fetch..."); - - const fallbackResponse = await fetch(`${process.env.API_URL}/api/${path}`, { - headers: { Authorization: `Bearer ${token}` }, - }); - - const fallbackText = await fallbackResponse.text(); - - if (!fallbackText.trim()) { - return NextResponse.json({ error: "Empty response from server" }, { status: 502 }); - } - - try { - return NextResponse.json(JSON.parse(fallbackText), { status: fallbackResponse.status }); - } catch { - return NextResponse.json({ error: "Invalid JSON from fallback" }, { status: 502 }); - } - } - } catch (error: any) { - console.error("SSE error:", error); - if (error.code !== "UND_ERR_HEADERS_TIMEOUT") { - return NextResponse.json({ error: "Failed to connect to SSE" }, { status: 500 }); - } - } - } + // const isSSE = path === "timeline" || path === "notifications" ||path.includes("/comments"); + + // if (isSSE) { + // try { + // // Try SSE first + // const response = await fetch(`${process.env.API_URL}/api/${path}`, { + // headers: { + // Authorization: `Bearer ${token}`, + // Accept: "text/event-stream", + // }, + // }); + // console.log(response) + + // const text = await response.text(); + + // if (response.ok && !text.trim()) { + // try { + // const data = JSON.parse(text); + + // // Set up SSE response + // const encoder = new TextEncoder(); + // const stream = new ReadableStream({ + // async start(controller) { + // controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`)); + + // // Keep the connection open + // const interval = setInterval(() => { + // controller.enqueue(encoder.encode(": keepalive\n\n")); + // }, 30000); + + // // Clean up on close + // request.signal.addEventListener("abort", () => { + // clearInterval(interval); + // controller.close(); + // }); + // }, + // }); + + // return new NextResponse(stream, { + // headers: { + // "Content-Type": "text/event-stream", + // "Cache-Control": "no-cache", + // Connection: "keep-alive", + // }, + // }); + // } catch (jsonError) { + // console.error("SSE JSON parse error:", jsonError); + // return NextResponse.json({ error: "Invalid JSON response from SSE" }, { status: 502 }); + // } + // } else { + // console.log("SSE response empty, falling back to basic fetch..."); + + // const fallbackResponse = await fetch(`${process.env.API_URL}/api/${path}`, { + // headers: { Authorization: `Bearer ${token}` }, + // }); + + // const fallbackText = await fallbackResponse.text(); + + // if (!fallbackText.trim()) { + // return NextResponse.json({ error: "Empty response from server" }, { status: 502 }); + // } + + // try { + // return NextResponse.json(JSON.parse(fallbackText), { status: fallbackResponse.status }); + // } catch { + // return NextResponse.json({ error: "Invalid JSON from fallback" }, { status: 502 }); + // } + // } + // } catch (error: any) { + // console.error("SSE error:", error); + // if (error.code !== "UND_ERR_HEADERS_TIMEOUT") { + // return NextResponse.json({ error: "Failed to connect to SSE" }, { status: 500 }); + // } + // } + // } // Regular API request try { diff --git a/web2/app/api/sse/[...path]/route.ts b/web2/app/api/sse/[...path]/route.ts new file mode 100644 index 0000000..8e61a83 --- /dev/null +++ b/web2/app/api/sse/[...path]/route.ts @@ -0,0 +1,38 @@ +// app/api/sse/[...path]/route.ts +import { type NextRequest } from "next/server"; + +export async function GET(request: NextRequest) { + const url = new URL(request.url); + const path = url.pathname.replace("/api/sse/", ""); // e.g., "timeline" or "comments/123" + const token = + url.searchParams.get("token") || + request.headers.get("Authorization")?.split(" ")[1]; + + try { + const backendResponse = await fetch( + `${process.env.API_URL}/api/${path}?${url.searchParams.toString()}`, + { + method: "GET", + headers: { + Authorization: token ? `Bearer ${token}` : "", + Accept: "text/event-stream", + }, + } + ); + + // Proxy the backend SSE stream directly to the client + return new Response(backendResponse.body, { + status: 200, + headers: { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + // Optional headers for CORS (if needed): + // "Access-Control-Allow-Origin": "*", + }, + }); + } catch (error) { + console.error("SSE proxy error:", error); + return new Response("Failed to connect to SSE", { status: 502 }); + } +} diff --git a/web2/app/page.tsx b/web2/app/page.tsx index f84db81..66cf2ad 100644 --- a/web2/app/page.tsx +++ b/web2/app/page.tsx @@ -1,7 +1,7 @@ import { redirect } from "next/navigation" import { cookies } from "next/headers" -import { Timeline } from "@/components/timeline" import { MainLayout } from "@/components/main-layout" +import TimelinePage from "./timeline/page" export default async function Home() { const cookieStore = await cookies() @@ -13,7 +13,7 @@ export default async function Home() { return ( - + ) } diff --git a/web2/app/timeline/page.tsx b/web2/app/timeline/page.tsx new file mode 100644 index 0000000..40d80c6 --- /dev/null +++ b/web2/app/timeline/page.tsx @@ -0,0 +1,10 @@ +import { Timeline } from "@/components/timeline"; +import React from "react"; + +export default function TimelinePage(){ + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/web2/components/comments-list.tsx b/web2/components/comments-list.tsx index c41ff9c..4db12a9 100644 --- a/web2/components/comments-list.tsx +++ b/web2/components/comments-list.tsx @@ -34,7 +34,7 @@ export function CommentsList({ proseId }: { proseId: string }) { const { toast } = useToast() // Try to use SSE for comments, fall back to regular fetch - const { data, error: sseError } = useSSE(`/api/${proseId}/comments`, token, { + const { data, error: sseError } = useSSE(`/api/sse/${proseId}/comments`, token, "comment", { onMessage: (data) => { if (data) { console.log("SSE CHECK FOR COMMENTS") diff --git a/web2/components/notification-indicator.tsx b/web2/components/notification-indicator.tsx index 247b7fe..76eac1e 100644 --- a/web2/components/notification-indicator.tsx +++ b/web2/components/notification-indicator.tsx @@ -13,7 +13,7 @@ export function NotificationIndicator({ className = "" }: NotificationIndicatorP const { token } = useAuth() // Use SSE to check for unread notifications - const { data } = useSSE<{ has_unread: boolean }>("/api/notifications", token, { + const { data } = useSSE<{ has_unread: boolean }>("/api/sse/notifications", token, "notification", { onMessage: (data) => { if (data && Array.isArray(data)) { setHasUnread(data.some((notification) => !notification.read)) @@ -38,13 +38,14 @@ export function NotificationIndicator({ className = "" }: NotificationIndicatorP if (response.ok) { const text = await response.text() - const data = JSON.parse(text) + if (text.trim()){ + const data = JSON.parse(text) if (data!=null){ setHasUnread(data.some((notification: any) => !notification.read)) - } else { + } + } + else { setHasUnread(false) - console.log("EMPTY") - console.log(data) } } } catch (err) { @@ -60,3 +61,49 @@ export function NotificationIndicator({ className = "" }: NotificationIndicatorP return
} +// "use client"; + +// import { useState, useEffect } from "react"; +// import { useAuth } from "@/lib/auth-hooks"; + +// type NotificationIndicatorProps = { +// className?: string; +// }; + +// export function NotificationIndicator({ className = "" }: NotificationIndicatorProps) { +// const [hasUnread, setHasUnread] = useState(false); +// const { token } = useAuth(); + +// // Fallback: check for unread notifications on mount (SSE disabled) +// useEffect(() => { +// const checkUnread = async () => { +// try { +// const response = await fetch("/api/notifications", { +// headers: { +// Authorization: `Bearer ${token}`, +// }, +// }); + +// if (response.ok) { +// const text = await response.text(); +// if (text.trim()) { +// const data = JSON.parse(text); +// if (data != null) { +// setHasUnread(data.some((notification: any) => !notification.read)); +// } +// } else { +// setHasUnread(false); +// } +// } +// } catch (err) { +// console.error("Failed to check unread notifications", err); +// } +// }; + +// checkUnread(); +// }, [token]); + +// if (!hasUnread) return null; + +// return
; +// } diff --git a/web2/components/notifications-list.tsx b/web2/components/notifications-list.tsx index 6513588..eb3dd06 100644 --- a/web2/components/notifications-list.tsx +++ b/web2/components/notifications-list.tsx @@ -28,7 +28,7 @@ export function NotificationsList() { const { toast } = useToast() // Try to use SSE for notifications, fall back to regular fetch - const { data, error: sseError } = useSSE("/api/notifications", token, { + const { data, error: sseError } = useSSE("/api/sse/notifications", token, "notification", { onMessage: (data) => { if (data) { console.log("SSE CHECK FOR NOTIFS") diff --git a/web2/components/timeline.tsx b/web2/components/timeline.tsx index f40a382..20fb235 100644 --- a/web2/components/timeline.tsx +++ b/web2/components/timeline.tsx @@ -34,7 +34,7 @@ export function Timeline() { const { toast } = useToast(); // Use SSE to get timeline updates - const { data, error: sseError } = useSSE("/api/timeline", token, { + const { data, error: sseError } = useSSE("/api/sse/timeline", token, "timeline",{ onMessage: (data) => { if (data) { console.log("SSE CHECK FOR TL") @@ -192,3 +192,6 @@ export function Timeline() {
); } + + + diff --git a/web2/components/users-list.tsx b/web2/components/users-list.tsx index f095bde..2242ef7 100644 --- a/web2/components/users-list.tsx +++ b/web2/components/users-list.tsx @@ -85,6 +85,7 @@ export function UsersList() { ); } + // console.log(filteredUsers) return (

Find Users to Follow

diff --git a/web2/lib/use-sse.tsx b/web2/lib/use-sse.tsx index 35554f6..bba54d0 100644 --- a/web2/lib/use-sse.tsx +++ b/web2/lib/use-sse.tsx @@ -4,11 +4,12 @@ import { useState, useEffect } from "react" type SSEOptions = { onMessage?: (data: any) => void + addEventListener?: (data: any) => void onError?: (error: any) => void fallbackToFetch?: boolean } -export function useSSE(url: string, token: string | null, options: SSEOptions = {}) { +export function useSSE(url: string, token: string | null, eventListener: string, options: SSEOptions = {}) { const [data, setData] = useState(null) const [error, setError] = useState(null) const [isConnected, setIsConnected] = useState(false) @@ -22,27 +23,58 @@ export function useSSE(url: string, token: string | null, options: SSEOptions const connectSSE = () => { try { // Try to use SSE - eventSource = new EventSource(`${url}?token=${token}&sse=true`) + console.log("Trying SSE") + console.log(url) + eventSource = new EventSource(`${url}?token=${token}`) eventSource.onopen = () => { + console.log("connected") setIsConnected(true) } + // eventSource.onmessage = (event) => { + // console.log("TEST") + // console.log(event.data) + // if (event.data.startsWith("data:")) { + // try { + // const parsedData = JSON.parse(event.data.replace(/^data: /, "")); + // setData(parsedData); + // options.onMessage?.(parsedData); + // } catch (err) { + // console.error("Error parsing SSE data", err); + // } + // } else { + // console.warn("Received non-SSE data", event.data); + // } + // }; + eventSource.onmessage = (event) => { - if (event.data.startsWith("data:")) { - try { - const parsedData = JSON.parse(event.data.replace(/^data: /, "")); - setData(parsedData); - options.onMessage?.(parsedData); - } catch (err) { - console.error("Error parsing SSE data", err); - } - } else { - console.warn("Received non-SSE data", event.data); + console.log("DEFAULT SSE"); + console.log(event.data); + + try { + const parsedData = JSON.parse(event.data); + setData(parsedData); + options.onMessage?.(parsedData); + } catch (err) { + console.error("Error parsing SSE data", err); } }; + // eventSource.addEventListener(eventListener, (event) => { + // console.log("ADDING LISTENER ", eventListener) + // try { + // const parsedData = JSON.parse(event.data); + // setData(parsedData); + // options.onMessage?.(parsedData); + // } catch (err) { + // console.error("Error parsing SSE data", err); + // } + // }); + + eventSource.onerror = (err) => { + console.log("ERRRRRRRR: ", url) console.error("SSE error", err) setError(new Error("SSE connection failed")) options.onError?.(err) @@ -81,9 +113,12 @@ export function useSSE(url: string, token: string | null, options: SSEOptions throw new Error("Failed to fetch data") } - const fetchedData = await response.json() - setData(fetchedData) - options.onMessage?.(fetchedData) + const fetchedText = await response.text() + if (fetchedText.trim()) { + const fetchedData = JSON.parse(fetchedText); + setData(fetchedData) + options.onMessage?.(fetchedData) + } } catch (err) { if (err instanceof Error && err.name !== "AbortError") { console.error("Fallback fetch error", err)