fix: resolve 500s on notifications, cache errors in SW, and [object Object] on-ramp alert
- Notification routes: wrap GET / and GET /count in try-catch, return
graceful fallbacks instead of 500s when DB table is missing/unavailable
- getUnreadCount: add null safety (row?.count ?? 0) and catch DB errors
- Service worker: add .catch(() => {}) to all cache.put() calls to
suppress NetworkError on quota-exceeded or corrupted cache entries
- On-ramp error display: coerce err.error to string so alerts show the
actual message instead of [object Object]
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
97da6c729f
commit
49f55dffc8
|
|
@ -3873,7 +3873,9 @@ class FolkFlowsApp extends HTMLElement {
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const err = await res.json().catch(() => ({ error: "Unknown error" }));
|
const err = await res.json().catch(() => ({ error: "Unknown error" }));
|
||||||
alert(`On-ramp failed: ${err.error || res.statusText}`);
|
const msg = typeof err.error === 'string' ? err.error : JSON.stringify(err.error) || res.statusText;
|
||||||
|
console.error("[UserOnRamp] Server error:", res.status, err);
|
||||||
|
alert(`On-ramp failed: ${msg}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3894,8 +3896,9 @@ class FolkFlowsApp extends HTMLElement {
|
||||||
// Open on-ramp widget
|
// Open on-ramp widget
|
||||||
this.openWidgetModal(data.widgetUrl);
|
this.openWidgetModal(data.widgetUrl);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[UserOnRamp] Error:", err);
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
alert("Failed to start on-ramp. Check console for details.");
|
console.error("[UserOnRamp] Error:", msg, err);
|
||||||
|
alert(`On-ramp failed: ${msg}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3926,7 +3929,9 @@ class FolkFlowsApp extends HTMLElement {
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const err = await res.json().catch(() => ({ error: "Unknown error" }));
|
const err = await res.json().catch(() => ({ error: "Unknown error" }));
|
||||||
alert(`On-ramp failed: ${err.error || res.statusText}`);
|
const msg = typeof err.error === 'string' ? err.error : JSON.stringify(err.error) || res.statusText;
|
||||||
|
console.error("[QuickFund] Server error:", res.status, err);
|
||||||
|
alert(`On-ramp failed: ${msg}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,32 +34,42 @@ async function requireAuth(req: Request) {
|
||||||
|
|
||||||
// ── GET / — Paginated notification list ──
|
// ── GET / — Paginated notification list ──
|
||||||
notificationRouter.get("/", async (c) => {
|
notificationRouter.get("/", async (c) => {
|
||||||
const claims = await requireAuth(c.req.raw);
|
try {
|
||||||
if (!claims) return c.json({ error: "Authentication required" }, 401);
|
const claims = await requireAuth(c.req.raw);
|
||||||
|
if (!claims) return c.json({ error: "Authentication required" }, 401);
|
||||||
|
|
||||||
const url = new URL(c.req.url);
|
const url = new URL(c.req.url);
|
||||||
const unreadOnly = url.searchParams.get("unread") === "true";
|
const unreadOnly = url.searchParams.get("unread") === "true";
|
||||||
const category = url.searchParams.get("category") || undefined;
|
const category = url.searchParams.get("category") || undefined;
|
||||||
const limit = Math.min(Number(url.searchParams.get("limit")) || 50, 100);
|
const limit = Math.min(Number(url.searchParams.get("limit")) || 50, 100);
|
||||||
const offset = Number(url.searchParams.get("offset")) || 0;
|
const offset = Number(url.searchParams.get("offset")) || 0;
|
||||||
|
|
||||||
const notifications = await getUserNotifications(claims.sub, {
|
const notifications = await getUserNotifications(claims.sub, {
|
||||||
unreadOnly,
|
unreadOnly,
|
||||||
category,
|
category,
|
||||||
limit,
|
limit,
|
||||||
offset,
|
offset,
|
||||||
});
|
});
|
||||||
|
|
||||||
return c.json({ notifications });
|
return c.json({ notifications });
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[notifications] GET / failed:", err instanceof Error ? err.message : err);
|
||||||
|
return c.json({ notifications: [] });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── GET /count — Lightweight unread count (polling fallback) ──
|
// ── GET /count — Lightweight unread count (polling fallback) ──
|
||||||
notificationRouter.get("/count", async (c) => {
|
notificationRouter.get("/count", async (c) => {
|
||||||
const claims = await requireAuth(c.req.raw);
|
try {
|
||||||
if (!claims) return c.json({ error: "Authentication required" }, 401);
|
const claims = await requireAuth(c.req.raw);
|
||||||
|
if (!claims) return c.json({ error: "Authentication required" }, 401);
|
||||||
|
|
||||||
const count = await getUnreadCount(claims.sub);
|
const count = await getUnreadCount(claims.sub);
|
||||||
return c.json({ unreadCount: count });
|
return c.json({ unreadCount: count });
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[notifications] GET /count failed:", err instanceof Error ? err.message : err);
|
||||||
|
return c.json({ unreadCount: 0 });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── PATCH /:id/read — Mark one notification as read ──
|
// ── PATCH /:id/read — Mark one notification as read ──
|
||||||
|
|
|
||||||
|
|
@ -1156,11 +1156,16 @@ export async function getUserNotifications(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUnreadCount(userDid: string): Promise<number> {
|
export async function getUnreadCount(userDid: string): Promise<number> {
|
||||||
const [row] = await sql`
|
try {
|
||||||
SELECT COUNT(*)::int as count FROM notifications
|
const [row] = await sql`
|
||||||
WHERE user_did = ${userDid} AND NOT read AND NOT dismissed
|
SELECT COUNT(*)::int as count FROM notifications
|
||||||
`;
|
WHERE user_did = ${userDid} AND NOT read AND NOT dismissed
|
||||||
return row.count;
|
`;
|
||||||
|
return row?.count ?? 0;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[notifications] getUnreadCount failed:", err instanceof Error ? err.message : err);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function markNotificationRead(id: string, userDid: string): Promise<boolean> {
|
export async function markNotificationRead(id: string, userDid: string): Promise<boolean> {
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ self.addEventListener("fetch", (event) => {
|
||||||
statusText: response.statusText,
|
statusText: response.statusText,
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
cache.put(event.request, timedResponse);
|
cache.put(event.request, timedResponse).catch(() => {});
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
})
|
})
|
||||||
|
|
@ -171,7 +171,7 @@ self.addEventListener("fetch", (event) => {
|
||||||
return fetch(event.request).then((response) => {
|
return fetch(event.request).then((response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const clone = response.clone();
|
const clone = response.clone();
|
||||||
caches.open(STATIC_CACHE).then((cache) => cache.put(event.request, clone));
|
caches.open(STATIC_CACHE).then((cache) => cache.put(event.request, clone).catch(() => {}));
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
|
|
@ -190,7 +190,7 @@ self.addEventListener("fetch", (event) => {
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const clone = response.clone();
|
const clone = response.clone();
|
||||||
caches.open(HTML_CACHE).then((cache) => cache.put(event.request, clone));
|
caches.open(HTML_CACHE).then((cache) => cache.put(event.request, clone).catch(() => {}));
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
})
|
})
|
||||||
|
|
@ -210,7 +210,7 @@ self.addEventListener("fetch", (event) => {
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const clone = response.clone();
|
const clone = response.clone();
|
||||||
caches.open(STATIC_CACHE).then((cache) => cache.put(event.request, clone));
|
caches.open(STATIC_CACHE).then((cache) => cache.put(event.request, clone).catch(() => {}));
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue