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) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -3894,8 +3896,9 @@ class FolkFlowsApp extends HTMLElement {
|
|||
// Open on-ramp widget
|
||||
this.openWidgetModal(data.widgetUrl);
|
||||
} catch (err) {
|
||||
console.error("[UserOnRamp] Error:", err);
|
||||
alert("Failed to start on-ramp. Check console for details.");
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
console.error("[UserOnRamp] Error:", msg, err);
|
||||
alert(`On-ramp failed: ${msg}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3926,7 +3929,9 @@ class FolkFlowsApp extends HTMLElement {
|
|||
|
||||
if (!res.ok) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,32 +34,42 @@ async function requireAuth(req: Request) {
|
|||
|
||||
// ── GET / — Paginated notification list ──
|
||||
notificationRouter.get("/", async (c) => {
|
||||
const claims = await requireAuth(c.req.raw);
|
||||
if (!claims) return c.json({ error: "Authentication required" }, 401);
|
||||
try {
|
||||
const claims = await requireAuth(c.req.raw);
|
||||
if (!claims) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
const url = new URL(c.req.url);
|
||||
const unreadOnly = url.searchParams.get("unread") === "true";
|
||||
const category = url.searchParams.get("category") || undefined;
|
||||
const limit = Math.min(Number(url.searchParams.get("limit")) || 50, 100);
|
||||
const offset = Number(url.searchParams.get("offset")) || 0;
|
||||
const url = new URL(c.req.url);
|
||||
const unreadOnly = url.searchParams.get("unread") === "true";
|
||||
const category = url.searchParams.get("category") || undefined;
|
||||
const limit = Math.min(Number(url.searchParams.get("limit")) || 50, 100);
|
||||
const offset = Number(url.searchParams.get("offset")) || 0;
|
||||
|
||||
const notifications = await getUserNotifications(claims.sub, {
|
||||
unreadOnly,
|
||||
category,
|
||||
limit,
|
||||
offset,
|
||||
});
|
||||
const notifications = await getUserNotifications(claims.sub, {
|
||||
unreadOnly,
|
||||
category,
|
||||
limit,
|
||||
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) ──
|
||||
notificationRouter.get("/count", async (c) => {
|
||||
const claims = await requireAuth(c.req.raw);
|
||||
if (!claims) return c.json({ error: "Authentication required" }, 401);
|
||||
try {
|
||||
const claims = await requireAuth(c.req.raw);
|
||||
if (!claims) return c.json({ error: "Authentication required" }, 401);
|
||||
|
||||
const count = await getUnreadCount(claims.sub);
|
||||
return c.json({ unreadCount: count });
|
||||
const count = await getUnreadCount(claims.sub);
|
||||
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 ──
|
||||
|
|
|
|||
|
|
@ -1156,11 +1156,16 @@ export async function getUserNotifications(
|
|||
}
|
||||
|
||||
export async function getUnreadCount(userDid: string): Promise<number> {
|
||||
const [row] = await sql`
|
||||
SELECT COUNT(*)::int as count FROM notifications
|
||||
WHERE user_did = ${userDid} AND NOT read AND NOT dismissed
|
||||
`;
|
||||
return row.count;
|
||||
try {
|
||||
const [row] = await sql`
|
||||
SELECT COUNT(*)::int as count FROM notifications
|
||||
WHERE user_did = ${userDid} AND NOT read AND NOT dismissed
|
||||
`;
|
||||
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> {
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ self.addEventListener("fetch", (event) => {
|
|||
statusText: response.statusText,
|
||||
headers,
|
||||
});
|
||||
cache.put(event.request, timedResponse);
|
||||
cache.put(event.request, timedResponse).catch(() => {});
|
||||
}
|
||||
return response;
|
||||
})
|
||||
|
|
@ -171,7 +171,7 @@ self.addEventListener("fetch", (event) => {
|
|||
return fetch(event.request).then((response) => {
|
||||
if (response.ok) {
|
||||
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;
|
||||
});
|
||||
|
|
@ -190,7 +190,7 @@ self.addEventListener("fetch", (event) => {
|
|||
.then((response) => {
|
||||
if (response.ok) {
|
||||
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;
|
||||
})
|
||||
|
|
@ -210,7 +210,7 @@ self.addEventListener("fetch", (event) => {
|
|||
.then((response) => {
|
||||
if (response.ok) {
|
||||
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;
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue