diff options
| author | Fuwn <[email protected]> | 2026-02-07 05:41:07 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-07 05:41:07 -0800 |
| commit | a1a405e56a0907ed44bfaba721e0ea632e051141 (patch) | |
| tree | 99621f7c1407eed732eeb5742fc7458c14ee6d48 /apps/web/lib | |
| parent | security: remove unsafe-eval CSP, fix host header injection, harden API routes (diff) | |
| download | asa.news-a1a405e56a0907ed44bfaba721e0ea632e051141.tar.xz asa.news-a1a405e56a0907ed44bfaba721e0ea632e051141.zip | |
fix: resolve 6 pre-ship audit bugs
- Webhook entry identifier: use entry GUID instead of feed identifier
- Optimistic rollback: add previousTimeline snapshot and onError handler
to both useToggleEntryReadState and useToggleEntrySavedState
- Rate limiter memory leak: delete Map entries when window expires,
use else-if to avoid re-setting after delete
- Entries API limit param: use Number.isFinite guard instead of falsy
coercion that treats 0 as default
- PWA manifest: add PNG raster icon routes (192x192, 512x512) for
devices that don't support SVG icons
- Billing webhook: throw on DB errors and return 500 so Stripe retries
failed events instead of silently losing them
Diffstat (limited to 'apps/web/lib')
| -rw-r--r-- | apps/web/lib/queries/use-entry-state-mutations.ts | 20 | ||||
| -rw-r--r-- | apps/web/lib/rate-limit.ts | 4 |
2 files changed, 23 insertions, 1 deletions
diff --git a/apps/web/lib/queries/use-entry-state-mutations.ts b/apps/web/lib/queries/use-entry-state-mutations.ts index a8c72d0..80bab79 100644 --- a/apps/web/lib/queries/use-entry-state-mutations.ts +++ b/apps/web/lib/queries/use-entry-state-mutations.ts @@ -65,6 +65,13 @@ export function useToggleEntryReadState() { return { previousTimeline } }, + onError: (_error, _variables, context) => { + if (context?.previousTimeline) { + for (const [queryKey, queryData] of context.previousTimeline) { + queryClient.setQueryData(queryKey, queryData) + } + } + }, onSettled: () => { queryClient.invalidateQueries({ queryKey: queryKeys.timeline.all }) queryClient.invalidateQueries({ queryKey: queryKeys.savedEntries.all }) @@ -107,6 +114,10 @@ export function useToggleEntrySavedState() { onMutate: async ({ entryIdentifier, isSaved }) => { await queryClient.cancelQueries({ queryKey: queryKeys.timeline.all }) + const previousTimeline = queryClient.getQueriesData< + InfiniteData<TimelineEntry[]> + >({ queryKey: queryKeys.timeline.all }) + queryClient.setQueriesData<InfiniteData<TimelineEntry[]>>( { queryKey: queryKeys.timeline.all }, (existingData) => { @@ -124,6 +135,15 @@ export function useToggleEntrySavedState() { } } ) + + return { previousTimeline } + }, + onError: (_error, _variables, context) => { + if (context?.previousTimeline) { + for (const [queryKey, queryData] of context.previousTimeline) { + queryClient.setQueryData(queryKey, queryData) + } + } }, onSettled: () => { queryClient.invalidateQueries({ queryKey: queryKeys.timeline.all }) diff --git a/apps/web/lib/rate-limit.ts b/apps/web/lib/rate-limit.ts index 4016781..506511d 100644 --- a/apps/web/lib/rate-limit.ts +++ b/apps/web/lib/rate-limit.ts @@ -12,7 +12,9 @@ export function rateLimit( (timestamp) => timestamp > windowStart ) - if (recentTimestamps.length >= limit) { + if (recentTimestamps.length === 0) { + requestTimestamps.delete(identifier) + } else if (recentTimestamps.length >= limit) { requestTimestamps.set(identifier, recentTimestamps) return { success: false, remaining: 0 } } |