Fix: stabilize ActivityFeed UI on reload
Avoid shared dev rate-limit bucket for n8n status and fall back to a stable offline state when the status call fails, preventing the widget from getting stuck in the small translucent loading UI.
This commit is contained in:
@@ -6,10 +6,21 @@ export const revalidate = 30;
|
|||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
// Rate limiting for n8n status endpoint
|
// Rate limiting for n8n status endpoint
|
||||||
const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown';
|
const ip =
|
||||||
|
request.headers.get("x-forwarded-for") ||
|
||||||
|
request.headers.get("x-real-ip") ||
|
||||||
|
"unknown";
|
||||||
|
const ua = request.headers.get("user-agent") || "unknown";
|
||||||
const { checkRateLimit } = await import('@/lib/auth');
|
const { checkRateLimit } = await import('@/lib/auth');
|
||||||
|
|
||||||
if (!checkRateLimit(ip, 30, 60000)) { // 30 requests per minute for status
|
// In dev, many requests can share ip=unknown; use UA to avoid a shared bucket.
|
||||||
|
const rateKey =
|
||||||
|
process.env.NODE_ENV === "development" && ip === "unknown"
|
||||||
|
? `ua:${ua.slice(0, 120)}`
|
||||||
|
: ip;
|
||||||
|
const maxPerMinute = process.env.NODE_ENV === "development" ? 300 : 30;
|
||||||
|
|
||||||
|
if (!checkRateLimit(rateKey, maxPerMinute, 60000)) { // requests per minute
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Rate limit exceeded. Please try again later.' },
|
{ error: 'Rate limit exceeded. Please try again later.' },
|
||||||
{ status: 429 }
|
{ status: 429 }
|
||||||
|
|||||||
@@ -84,8 +84,17 @@ export default function ActivityFeed() {
|
|||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
|
const fallback: StatusData = {
|
||||||
|
status: { text: "offline", color: "gray" },
|
||||||
|
music: null,
|
||||||
|
gaming: null,
|
||||||
|
coding: null,
|
||||||
|
};
|
||||||
|
|
||||||
// Check if fetch is available (should be, but safety check)
|
// Check if fetch is available (should be, but safety check)
|
||||||
if (typeof fetch === 'undefined') {
|
if (typeof fetch === 'undefined') {
|
||||||
|
setData(fallback);
|
||||||
|
setHasActivity(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +113,9 @@ export default function ActivityFeed() {
|
|||||||
if (process.env.NODE_ENV === 'development' && res) {
|
if (process.env.NODE_ENV === 'development' && res) {
|
||||||
console.warn('ActivityFeed: API returned non-OK status:', res.status);
|
console.warn('ActivityFeed: API returned non-OK status:', res.status);
|
||||||
}
|
}
|
||||||
|
// Don't stay in tiny "loading" state forever; show stable fallback UI.
|
||||||
|
setData(fallback);
|
||||||
|
setHasActivity(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +126,8 @@ export default function ActivityFeed() {
|
|||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
console.warn('ActivityFeed: Failed to parse JSON response:', parseError);
|
console.warn('ActivityFeed: Failed to parse JSON response:', parseError);
|
||||||
}
|
}
|
||||||
|
setData(fallback);
|
||||||
|
setHasActivity(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +145,8 @@ export default function ActivityFeed() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!json || typeof json !== 'object') {
|
if (!json || typeof json !== 'object') {
|
||||||
|
setData(fallback);
|
||||||
|
setHasActivity(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +184,14 @@ export default function ActivityFeed() {
|
|||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
console.error("Failed to fetch activity:", error);
|
console.error("Failed to fetch activity:", error);
|
||||||
}
|
}
|
||||||
// Don't set error state - just fail silently
|
// Don't set error state - show stable fallback
|
||||||
|
setData({
|
||||||
|
status: { text: "offline", color: "gray" },
|
||||||
|
music: null,
|
||||||
|
gaming: null,
|
||||||
|
coding: null,
|
||||||
|
});
|
||||||
|
setHasActivity(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user