feature: Top Categories#2
Conversation
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@components/home/Categories.tsx`:
- Line 31: The cellClassName value contains an extraneous trailing space
('change-header-cell '); update the assignment for cellClassName in
Categories.tsx to remove the trailing space so it becomes 'change-header-cell',
and verify any class concatenation logic uses explicit separators instead of
relying on that trailing space.
- Around line 18-26: The map rendering in category.top_3_coins (inside
Categories.tsx) uses the image URL for both key and alt, which is fragile and
inaccessible; update the Image usage in the category.top_3_coins.map callback to
use a stable unique key (e.g., combine category id/name with the map index or a
coin id if available — e.g., `${category.id}-${index}`) and replace the alt prop
with a meaningful description (e.g., the coin name or a human-friendly string
like `${category.name} coin ${index + 1}` or extract the filename from the URL)
so screen readers get useful text; ensure you update the JSX that returns the
Image component (the Image element inside category.top_3_coins.map) accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ab67eba4-4bb3-409b-ba0a-215f390344e2
📒 Files selected for processing (4)
app/page.tsxcomponents/DataTable.tsxcomponents/home/Categories.tsxcomponents/home/TrendingCoins.tsx
| return category.top_3_coins.map((coin, index) => ( | ||
| <Image | ||
| key={category.top_3_coins[index]} | ||
| src={coin} | ||
| alt={category.top_3_coins[index]} | ||
| width={24} | ||
| height={24} | ||
| /> | ||
| )); |
There was a problem hiding this comment.
Fix Image key and improve alt text for accessibility.
Two issues with the Image rendering:
-
Key: Using the URL (
category.top_3_coins[index]) as the React key is fragile. If the same coin URL appears multiple times or positions change, React's reconciliation may behave unexpectedly. -
Alt text: The
altprop is set to the URL string, which provides no meaningful description for screen readers and violates accessibility best practices.
♿ Proposed fix for key and alt attributes
- return category.top_3_coins.map((coin, index) => (
+ return category.top_3_coins.map((coin, index) => {
+ const coinName = category.name || 'coin';
+ return (
<Image
- key={category.top_3_coins[index]}
+ key={`${category.name}-coin-${index}`}
src={coin}
- alt={category.top_3_coins[index]}
+ alt={`Top coin ${index + 1} in ${coinName} category`}
width={24}
height={24}
/>
- ));
+ );
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return category.top_3_coins.map((coin, index) => ( | |
| <Image | |
| key={category.top_3_coins[index]} | |
| src={coin} | |
| alt={category.top_3_coins[index]} | |
| width={24} | |
| height={24} | |
| /> | |
| )); | |
| return category.top_3_coins.map((coin, index) => { | |
| const coinName = category.name || 'coin'; | |
| return ( | |
| <Image | |
| key={`${category.name}-coin-${index}`} | |
| src={coin} | |
| alt={`Top coin ${index + 1} in ${coinName} category`} | |
| width={24} | |
| height={24} | |
| /> | |
| ); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/home/Categories.tsx` around lines 18 - 26, The map rendering in
category.top_3_coins (inside Categories.tsx) uses the image URL for both key and
alt, which is fragile and inaccessible; update the Image usage in the
category.top_3_coins.map callback to use a stable unique key (e.g., combine
category id/name with the map index or a coin id if available — e.g.,
`${category.id}-${index}`) and replace the alt prop with a meaningful
description (e.g., the coin name or a human-friendly string like
`${category.name} coin ${index + 1}` or extract the filename from the URL) so
screen readers get useful text; ensure you update the JSX that returns the Image
component (the Image element inside category.top_3_coins.map) accordingly.
| }, | ||
| { | ||
| header: '24h Change', | ||
| cellClassName: 'change-header-cell ', |
There was a problem hiding this comment.
Remove trailing space from className.
The string 'change-header-cell ' contains an unnecessary trailing space.
🧹 Proposed fix
- cellClassName: 'change-header-cell ',
+ cellClassName: 'change-header-cell',📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| cellClassName: 'change-header-cell ', | |
| cellClassName: 'change-header-cell', |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/home/Categories.tsx` at line 31, The cellClassName value contains
an extraneous trailing space ('change-header-cell '); update the assignment for
cellClassName in Categories.tsx to remove the trailing space so it becomes
'change-header-cell', and verify any class concatenation logic uses explicit
separators instead of relying on that trailing space.
feat: create CoinHeader component for displaying live coin data feat: implement CoinsPagination component for navigating through coin pages feat: add Converter component for currency conversion feat: create LiveDataWrapper component to handle live data updates feat: add TopGainersLosers component to display top gainers and losers feat: implement reusable UI components: Badge, Button, Input, Pagination, Select, Separator, Tabs feat: add useCoinGeckoWebSocket hook for real-time data fetching
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (5)
components/home/CoinOverview.tsx (1)
8-22: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winFailures are now silently swallowed with no logging.
Previously wrapped in try/catch with
console.error; now each fetch fails silently intonull/[]. If/coins/bitcoinor its OHLC endpoint start failing, there's no signal in logs to diagnose it — the UI simply falls back to the skeleton with no trace.CandlestickChart's ownfetchOHLCData(components/CandlestickChart.tsx) still logs its failures viaconsole.error, so this is now inconsistent within the same feature.♻️ Suggested fix
const getCoinData = async () => { const [coin, coinOHLCData] = await Promise.all([ fetcher<CoinDetailsData>('/coins/bitcoin', { dex_pair_format: 'symbol', - }).catch(() => null), + }).catch((e) => { + console.error('Failed to fetch coin details', e); + return null; + }), fetcher<OHLCData[]>('/coins/bitcoin/ohlc', { vs_currency: 'usd', days: 1, precision: 'full', - }).catch(() => []), + }).catch((e) => { + console.error('Failed to fetch OHLC data', e); + return []; + }), ]); return { coin, coinOHLCData }; };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/home/CoinOverview.tsx` around lines 8 - 22, The fetch failures in getCoinData are being swallowed by Promise.all(...).catch(() => null/[]) with no logging, so restore error reporting while keeping the fallback values. Update getCoinData in CoinOverview to log failures for the /coins/bitcoin and /coins/bitcoin/ohlc fetcher calls (similar to CandlestickChart.fetchOHLCData) before returning null or [] so the feature remains debuggable.components/CoinsPagination.tsx (1)
15-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Paginationtype name collides with the importedPaginationcomponent.
Paginationis imported as a component from@/components/ui/pagination(Line 4) and then reused as a prop type annotation here. TypeScript resolves this via separate value/type namespaces (assuming a globalPaginationinterface exists intype.d.ts), so it compiles, but the shared name is confusing to readers and easy to break if either symbol is refactored.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/CoinsPagination.tsx` at line 15, The `Pagination` name is being used both for the imported UI component and the prop type in `CoinsPagination`, which is confusing and fragile. Rename the props type annotation in `CoinsPagination` to a more specific identifier (or inline the shape) and update the component signature so it no longer relies on the shared `Pagination` symbol. Keep the imported `Pagination` component name as-is and use distinct, descriptive type names to avoid future collisions.components/CandlestickChart.tsx (1)
78-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate millisecond-to-second conversion logic.
The same
convertedToSecondsmapping is repeated in both the init effect and the update effect. Extracting a shared helper (e.g.toSecondsOHLC(data: OHLCData[])) would reduce duplication and the risk of the two copies drifting apart.Also applies to: 112-121
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/CandlestickChart.tsx` around lines 78 - 89, The millisecond-to-second mapping is duplicated in both the init and update paths of CandlestickChart. Extract the repeated ohlcData.map conversion into a shared helper (for example, a toSecondsOHLC function near convertOHLCData) and reuse it in both effects so the series.setData flow stays consistent and there is only one place to maintain the conversion logic.hooks/useCoinGeckoWebSocket.ts (1)
77-88: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy liftNo reconnection logic on socket close/error.
ws.onclose/ws.onerroronly flipisWsReadyto false; there's no retry/backoff to re-establish the connection. For a live-data feature this means a dropped connection silently stops updates until the user reloads.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@hooks/useCoinGeckoWebSocket.ts` around lines 77 - 88, The WebSocket lifecycle in useCoinGeckoWebSocket only toggles isWsReady off in ws.onclose and ws.onerror, so add reconnection handling there instead of stopping after a disconnect. Update the hook to schedule a retry with backoff and reconnect the socket from the same setup path used by ws.onopen/ws.onmessage, and make sure the cleanup returned by the effect cancels any pending retry when unmounting.app/coins/[id]/page.tsx (1)
14-24: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winNo 404 handling for invalid/unknown coin IDs.
If
iddoesn't exist,fetcherthrows (perlib/coingecko.actions.ts), and there's nonotFound()call or try/catch here—users hit the generic error boundary instead of a proper 404 page.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/coins/`[id]/page.tsx around lines 14 - 24, The coin detail page currently lets an unknown or invalid id bubble up as a generic fetch error instead of rendering a 404. Update the logic in the page component that calls Promise.all and fetcher for CoinDetailsData and OHLCData to catch not-found cases and call notFound() from the Next.js page flow when the coin cannot be resolved. Keep the fix localized to the coin page’s data-loading path so valid coins still render normally and only missing coins route to the 404 page.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/coins/page.tsx`:
- Around line 10-16: The page parsing in the coins page is accepting negative
values because `Number(page) || 1` only falls back on falsy values, so update
the `currentPage` logic in `page.tsx` to validate that the parsed page is a
positive integer before using it, otherwise default to 1. Also remove the
hardcoded `totalCoins = 1000`/derived `totalPages` assumption and compute
pagination from the API response or a shared source of truth so
`CoinsPagination` and this page stay in sync, especially the `isLastPage` logic.
In `@components/CandlestickChart.tsx`:
- Around line 26-30: Make liveInterval and setLiveInterval optional in
CandlestickChartProps so CoinOverview can render CandlestickChart without
passing them. Update the CandlestickChartProps definition in type.d.ts to align
with the runtime guards already present in CandlestickChart, and ensure the
component signature still destructures liveInterval and setLiveInterval safely.
- Around line 40-57: The period fetch in `fetchOHLCData` is happening outside
`startTransition`, so `isPending` only covers `setOhlcData` and the buttons
remain enabled while `/coins/${coinId}/ohlc` is loading. Move the async fetch
and state update into the same transition flow used by
`fetchOHLCData`/`startTransition`, so the pending state covers the request and
prevents overlapping clicks from older responses overwriting newer
`selectedPeriod` data.
In `@components/CoinHeader.tsx`:
- Around line 60-80: The CoinHeader stats icon rendering always shows an icon
because the ternary in the stats list only switches between TrendingUp and
TrendingDown instead of skipping rendering when showIcon is false. Update the
JSX in CoinHeader’s stats map so the icon block is conditionally rendered only
when stat.showIcon is true, and then choose TrendingUp or TrendingDown based on
stat.isUp for the remaining cases.
In `@components/CoinsPagination.tsx`:
- Around line 15-23: The CoinsPagination component’s isLastPage logic is using a
hardcoded page limit instead of the totalPages prop. Update CoinsPagination so
the last-page check is derived from currentPage and totalPages, and keep the
next-control disabling logic aligned with buildPageNumbers and handlePageChange.
This will ensure pagination behavior stays correct if the coin count or page
size changes.
- Around line 29-34: The pagination controls rendered via PaginationPrevious,
PaginationNext, and PaginationLink are not keyboard-accessible because they are
currently behaving like bare anchors without real link semantics. Update the
pagination components in components/ui/pagination.tsx so these controls render
as proper buttons or include valid href-based navigation, and make sure
CoinsPagination keeps using those updated primitives for page changes and
previous/next actions.
In `@hooks/useCoinGeckoWebSocket.ts`:
- Around line 28-39: The handleMessage function in useCoinGeckoWebSocket can
throw on malformed WebSocket payloads because both JSON.parse(event.data) and
JSON.parse(msg?.identifier ?? '') are unguarded. Wrap the parsing and the
confirm_subscription identifier extraction in try/catch, and only process ping
or update subscribed.current when parsing succeeds; if parsing fails, safely
ignore or log the bad message so onmessage keeps working. Refer to
handleMessage, WebSocketMessage, and subscribed.current when updating the logic.
- Around line 123-147: Move the pool-related subscriptions in
useCoinGeckoWebSocket into the same queueMicrotask block as the CGSimplePrice
reset/subscribe flow. The current OnchainTrade and OnchainOHLCV subscriptions
run after unsubscribeAll(), which can cause a newly selected pool to be
immediately unsubscribed if the channel already exists; update the effect so
poolAddress handling happens inside the microtask after unsubscribeAll() and
before the new set_pools subscriptions are issued.
- Line 5: The websocket URL in useCoinGeckoWebSocket currently inlines
NEXT_PUBLIC_COINGECKO_API_KEY into the browser bundle, exposing the secret to
clients. Update the CoinGecko handshake so useCoinGeckoWebSocket no longer
builds a client-side URL with the API key; instead route the connection through
a server-side proxy or a backend endpoint that attaches the credential, or
switch to a tightly scoped demo token. Make sure WS_BASE is removed or replaced
with a server-derived endpoint and that no browser-accessible code references
the secret key directly.
In `@lib/coingecko.actions.ts`:
- Around line 55-61: The fast-path branch in getPools that calls fetcher for a
specific network and contractAddress can throw on common non-2xx responses,
unlike the fallback lookup. Wrap that fetcher call in the same try/catch pattern
used later in getPools, and return the provided fallback when the request fails;
keep the existing behavior of returning poolData.data?.[0] when successful.
---
Nitpick comments:
In `@app/coins/`[id]/page.tsx:
- Around line 14-24: The coin detail page currently lets an unknown or invalid
id bubble up as a generic fetch error instead of rendering a 404. Update the
logic in the page component that calls Promise.all and fetcher for
CoinDetailsData and OHLCData to catch not-found cases and call notFound() from
the Next.js page flow when the coin cannot be resolved. Keep the fix localized
to the coin page’s data-loading path so valid coins still render normally and
only missing coins route to the 404 page.
In `@components/CandlestickChart.tsx`:
- Around line 78-89: The millisecond-to-second mapping is duplicated in both the
init and update paths of CandlestickChart. Extract the repeated ohlcData.map
conversion into a shared helper (for example, a toSecondsOHLC function near
convertOHLCData) and reuse it in both effects so the series.setData flow stays
consistent and there is only one place to maintain the conversion logic.
In `@components/CoinsPagination.tsx`:
- Line 15: The `Pagination` name is being used both for the imported UI
component and the prop type in `CoinsPagination`, which is confusing and
fragile. Rename the props type annotation in `CoinsPagination` to a more
specific identifier (or inline the shape) and update the component signature so
it no longer relies on the shared `Pagination` symbol. Keep the imported
`Pagination` component name as-is and use distinct, descriptive type names to
avoid future collisions.
In `@components/home/CoinOverview.tsx`:
- Around line 8-22: The fetch failures in getCoinData are being swallowed by
Promise.all(...).catch(() => null/[]) with no logging, so restore error
reporting while keeping the fallback values. Update getCoinData in CoinOverview
to log failures for the /coins/bitcoin and /coins/bitcoin/ohlc fetcher calls
(similar to CandlestickChart.fetchOHLCData) before returning null or [] so the
feature remains debuggable.
In `@hooks/useCoinGeckoWebSocket.ts`:
- Around line 77-88: The WebSocket lifecycle in useCoinGeckoWebSocket only
toggles isWsReady off in ws.onclose and ws.onerror, so add reconnection handling
there instead of stopping after a disconnect. Update the hook to schedule a
retry with backoff and reconnect the socket from the same setup path used by
ws.onopen/ws.onmessage, and make sure the cleanup returned by the effect cancels
any pending retry when unmounting.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 034472de-6cde-4f6d-a54b-21ad371712e9
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (24)
app/coins/[id]/page.tsxapp/coins/page.tsxapp/globals.csscomponents/CandlestickChart.tsxcomponents/CoinHeader.tsxcomponents/CoinsPagination.tsxcomponents/Converter.tsxcomponents/DataTable.tsxcomponents/LiveDataWrapper.tsxcomponents/TopGainersLosers.tsxcomponents/home/Categories.tsxcomponents/home/CoinOverview.tsxcomponents/ui/badge.tsxcomponents/ui/button.tsxcomponents/ui/input.tsxcomponents/ui/pagination.tsxcomponents/ui/select.tsxcomponents/ui/separator.tsxcomponents/ui/tabs.tsxconstants.tshooks/useCoinGeckoWebSocket.tslib/coingecko.actions.tspackage.jsontype.d.ts
💤 Files with no reviewable changes (1)
- components/DataTable.tsx
✅ Files skipped from review due to trivial changes (2)
- package.json
- constants.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- components/home/Categories.tsx
| const CoinsPagination = ({ currentPage, totalPages }: Pagination) => { | ||
| const router = useRouter(); | ||
|
|
||
| const handlePageChange = (page: number) => { | ||
| router.push(`/coins?page=${page}`); | ||
| }; | ||
|
|
||
| const pageNumbers = buildPageNumbers(currentPage, totalPages); | ||
| const isLastPage = currentPage >= 100; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
isLastPage ignores the totalPages prop.
totalPages is passed in explicitly but isLastPage uses a hardcoded currentPage >= 100. It only coincidentally matches today because app/coins/page.tsx computes totalPages = 1000 / 10 = 100. If the total coin count or page size ever changes, this component will disable/enable the "next" control based on stale, unrelated logic instead of the actual page count it was given.
🐛 Suggested fix
- const isLastPage = currentPage >= 100;
+ const isLastPage = currentPage >= totalPages;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const CoinsPagination = ({ currentPage, totalPages }: Pagination) => { | |
| const router = useRouter(); | |
| const handlePageChange = (page: number) => { | |
| router.push(`/coins?page=${page}`); | |
| }; | |
| const pageNumbers = buildPageNumbers(currentPage, totalPages); | |
| const isLastPage = currentPage >= 100; | |
| const CoinsPagination = ({ currentPage, totalPages }: Pagination) => { | |
| const router = useRouter(); | |
| const handlePageChange = (page: number) => { | |
| router.push(`/coins?page=${page}`); | |
| }; | |
| const pageNumbers = buildPageNumbers(currentPage, totalPages); | |
| const isLastPage = currentPage >= totalPages; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/CoinsPagination.tsx` around lines 15 - 23, The CoinsPagination
component’s isLastPage logic is using a hardcoded page limit instead of the
totalPages prop. Update CoinsPagination so the last-page check is derived from
currentPage and totalPages, and keep the next-control disabling logic aligned
with buildPageNumbers and handlePageChange. This will ensure pagination behavior
stays correct if the coin count or page size changes.
|
|
||
| import { useEffect, useRef, useState } from 'react'; | ||
|
|
||
| const WS_BASE = `${process.env.NEXT_PUBLIC_COINGECKO_WEBSOCKET_URL}?x_cg_pro_api_key=${process.env.NEXT_PUBLIC_COINGECKO_API_KEY}`; |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the file structure and inspect the hook with line numbers
git ls-files hooks/useCoinGeckoWebSocket.ts
wc -l hooks/useCoinGeckoWebSocket.ts
cat -n hooks/useCoinGeckoWebSocket.ts | sed -n '1,260p'
# Find related CoinGecko websocket/env usages
rg -n "NEXT_PUBLIC_COINGECKO|useCoinGeckoWebSocket|queueMicrotask|JSON.parse\\(msg\\?\\.identifier|ws\\.onclose|ws\\.onerror|unsubscribeAll|OnchainTrade|OnchainOHLCV|CGSimplePrice" -S .Repository: markvalenzuela72/coin-pulse
Length of output: 6283
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the hook's message types and surrounding consumer
rg -n "type WebSocketMessage|interface WebSocketMessage|UseCoinGeckoWebSocketProps|UseCoinGeckoWebSocketReturn|ExtendedPriceData|Trade|OHLCData|LiveDataWrapper" hooks components types . -S
# Read the relevant type definitions and consumer usage if present
for f in \
hooks/useCoinGeckoWebSocket.ts \
components/LiveDataWrapper.tsx \
types.ts \
src/types.ts \
lib/types.ts
do
if [ -f "$f" ]; then
echo "===== $f ====="
cat -n "$f" | sed -n '1,260p'
fi
doneRepository: markvalenzuela72/coin-pulse
Length of output: 6298
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "===== type.d.ts (relevant ranges) ====="
cat -n type.d.ts | sed -n '140,270p'
echo
echo "===== env/docs references ====="
rg -n "NEXT_PUBLIC_COINGECKO_API_KEY|COINGECKO_WEBSOCKET|coingecko" -S . \
-g '!node_modules' -g '!dist' -g '!build'Repository: markvalenzuela72/coin-pulse
Length of output: 4385
🌐 Web query:
CoinGecko websocket x_cg_pro_api_key public client-side exposure documentation restricted demo key
💡 Result:
CoinGecko documentation and security best practices explicitly advise against exposing any API keys in client-side code [1][2][3]. Even if you are using a demo key or a restricted API key, you should avoid embedding it directly into frontend applications (such as JavaScript in browsers or mobile apps) because it can be easily extracted by third parties [2][3]. Official documentation and guidance consistently recommend the following security practices: 1. Secure Storage: Store your API key in a secure backend server environment, such as an environment variable (.env file), rather than hardcoding it into your codebase [4][1][2]. 2. Backend Proxy: All requests that require authentication should be sent from your frontend to your own backend server. The backend then attaches the API key and forwards the request to the CoinGecko API [1][2][5]. This prevents your key from ever being exposed to the client-side browser or application [2][6]. 3. Header Authentication: When connecting to CoinGecko APIs (including WebSocket and REST endpoints), using the HTTP header x-cg-pro-api-key (for Pro/paid plans) or x-cg-demo-api-key (for demo plans) is preferred over passing the key as a query string parameter [7][8][9][2]. Passing keys in query strings is risky because they may be captured in browser history, server logs, or proxy logs [9][2][3]. Even for development or demo purposes, exposing a key in the browser creates a security risk where an attacker can monitor and potentially misuse your key, potentially leading to rate limiting or unauthorized consumption of your API credits [10][2][3]. Always prioritize server-side proxying for any application involving API authentication [1][5][3].
Citations:
- 1: https://www.coingecko.com/learn/build-crypto-token-search-engine
- 2: https://apispine.com/coingecko-api/authentication
- 3: https://apispine.com/coingecko/authentication
- 4: https://docs.coingecko.com/docs/sdk-typescript
- 5: https://support.coingecko.com/hc/en-us/articles/4538879945625-API-key-can-I-store-the-key-in-my-headers
- 6: https://www.coingecko.com/en/api
- 7: https://docs.coingecko.com/websocket/cgsimpleprice
- 8: https://docs.coingecko.com/websocket/onchainsimpletokenprice
- 9: https://docs.coingecko.com/v3.0.1/reference/authentication
- 10: https://docs.coingecko.com/websocket/index
Do not expose the CoinGecko API key in the browser. NEXT_PUBLIC_COINGECKO_API_KEY is inlined into the client bundle and sent on the websocket URL, so anyone can extract and reuse it. Move this handshake behind a server-side proxy or replace it with a tightly scoped demo token.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@hooks/useCoinGeckoWebSocket.ts` at line 5, The websocket URL in
useCoinGeckoWebSocket currently inlines NEXT_PUBLIC_COINGECKO_API_KEY into the
browser bundle, exposing the secret to clients. Update the CoinGecko handshake
so useCoinGeckoWebSocket no longer builds a client-side URL with the API key;
instead route the connection through a server-side proxy or a backend endpoint
that attaches the credential, or switch to a tightly scoped demo token. Make
sure WS_BASE is removed or replaced with a server-derived endpoint and that no
browser-accessible code references the secret key directly.
|
Warning Review limit reached
Next review available in: 41 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary by CodeRabbit
New Features
Improvements