Skip to content

Engineering pass: fix polling leak, security hardening, and migrate to Next 16 conventions - #1

Open
arena-ai-coding-agent[bot] wants to merge 1 commit into
mainfrom
arena/019fb324-valencystudio
Open

Engineering pass: fix polling leak, security hardening, and migrate to Next 16 conventions#1
arena-ai-coding-agent[bot] wants to merge 1 commit into
mainfrom
arena/019fb324-valencystudio

Conversation

@arena-ai-coding-agent

Copy link
Copy Markdown

Summary

Full engineering pass on the Valency Studio codebase. Fixes one real bug that was leaking intervals in production, hardens API-key handling, and migrates to Next 16 conventions. All changes typecheck cleanly and the dev server starts in ~400ms.

Critical bug fixes

  • <roblox-panel.tsx> polling interval leak — the polling useEffect had [uploadMap, account, ...] as deps and a cleanup that only cleared the interval when !needsPolling, so on every state change a new interval was created while the previous one kept running (multiplied polling, duplicate toasts, memory growth). Rewrote to a single setInterval with useRef for the live account/setters; cleanup always runs.
  • <history-list.tsx> polling — same pattern: refs so the interval is created once and doesn't recreate on every state change.
  • <roblox-api.ts> verifyApiKey was a no-op — it called /assets/v1/operations/verify-${Date.now()} (which doesn't exist) and treated the resulting 404 as valid. Any key would pass. Now probes a real asset path and discriminates 401/403 vs 200/404.
  • <audio-processor.ts> getWaveform returned Math.random() on ffmpeg failure, so corrupt files displayed as plausible waveforms. Now returns [] so the UI can show a clear no-data state.
  • <audio-processor.ts> ensureDirs moved off module top-level — was creating directories on every import, which breaks on read-only filesystems and trips Turbopack's file tracer.

Security

  • Roblox API key is no longer persisted to localStorage. The account identity is remembered; the user must re-verify with the key on reload. README claimed the key was session-only — now the code matches. Includes a one-time migration that scrubs any pre-existing keys from localStorage on first load.
  • /api/roblox/status — API key moved from URL query string to POST body / X-Roblox-Api-Key header. The key no longer leaks into reverse-proxy access logs, browser history, or referer headers.
  • /api/roblox/verify and /api/roblox/upload — lightweight in-memory rate limiting (10/min and 20/min respectively) to prevent DoS and getting our server throttled by Roblox.
  • /api/roblox/upload — explicit 100 MB cap on the in-memory audio buffer with a 413 response.

Next.js 16 migration

  • src/middleware.tssrc/proxy.ts — Next 16 deprecated the old convention; the dev server was emitting a warning. The semantics are identical.
  • next.config.ts: reactStrictMode: true to catch effect-related bugs earlier; outputFileTracingExcludes for ./.tmp-audio/** and bot-config.json so standalone output doesn't pull the whole repo; removed the untyped eslint.ignoreDuringBuilds option.

TypeScript / code quality

  • audio/file/route.ts: wrap Buffers as Uint8Array for NextResponse BodyInit typing.
  • waveform-player.tsx: remove invalid volume prop on <audio>, apply via ref.
  • auth.ts: NextAuthOptions type, Session module augmentation for user.id, drop invalid trustHost config key (now set via AUTH_TRUST_HOST env, documented in .env.example).
  • discord-bot.ts: clear error messages for privileged-intent failures (the Engineering pass: fix polling leak, security hardening, and migrate to Next 16 conventions #1 reason a fresh bot silently fails to start) and invalid tokens; process.exit(1) on login failure; unhandledRejection / uncaughtException handlers; fixed a console-log template-string typo.
  • eslint.config.mjs: re-enabled react-hooks/exhaustive-deps as a warning. Disabling it was the root cause of the polling leak — the original effect deps looked right, but the rule was off so the cleanup-vs-interval mismatch wasn't flagged.
  • New src/lib/rate-limit.ts — in-memory token-bucket limiter with self-sweeping storage.
  • .env.example: documented AUTH_TRUST_HOST.
  • .gitignore: ignore npm/yarn/pnpm lockfiles (project uses bun).

Verification

  • npx tsc --noEmit --skipLibCheck — clean, zero errors (was 6 pre-existing errors; this PR also fixed those in auth.ts, waveform-player.tsx, audio/file/route.ts, and the original roblox-panel.tsx typecheck quirks).
  • npx next dev — starts in ~400ms, / returns 307 → /login, /login returns 200, no deprecation warnings.
  • npx next build — only fails on Google Fonts fetch + Prisma binary download in the sandboxed CI; both work in normal environments. The Turbopack compile and standalone output work.

Files changed

.env.example                       |   3 +
.gitignore                         |   9 +
eslint.config.mjs                  |   4 +-
next.config.ts                     |  16 ++-
src/app/api/audio/file/route.ts    |   6 +-
src/app/api/roblox/status/route.ts |  79 +++++++++++----
src/app/api/roblox/upload/route.ts |  19 ++++
src/app/api/roblox/verify/route.ts |  11 ++
src/components/history-list.tsx    |  76 ++++++++------
src/components/roblox-panel.tsx    | 202 +++++++++++++++++++++----------------
src/components/waveform-player.tsx |   8 +-
src/discord-bot.ts                 |  47 +++++++--
src/lib/audio-processor.ts         |  64 ++++++++----
src/lib/auth.ts                    |  41 ++++++--
src/lib/rate-limit.ts              |  55 ++++++++++ (new)
src/lib/roblox-api.ts              |  40 ++++++--
src/lib/store.ts                   |  42 +++++++-
src/middleware.ts → src/proxy.ts   |  (renamed)

18 files changed, +538 / −193 lines.

… to Next 16 conventions

Critical bug fixes:
- roblox-panel.tsx: fix polling interval leak (effect deps caused
  multiple intervals to stack). Now uses single setInterval with
  proper cleanup; uses refs to avoid effect re-creation.
- history-list.tsx: same pattern — ref-based polling that doesn't
  recreate on every state update.
- roblox-api.ts: verifyApiKey was a no-op (404 from a non-existent
  endpoint was treated as 'valid'). Now probes a real asset endpoint
  and uses 401/403 vs 200/404 to discriminate.
- audio-processor.ts: getWaveform was returning Math.random() on
  ffmpeg failure, masking broken files. Now returns empty array so
  the UI can show a clear 'no waveform' state.
- audio-processor.ts: ensureDirs moved off module top-level to
  prevent import-time side effects on read-only filesystems and to
  avoid Turbopack NFT issues. All UPLOAD_DIR/PROCESSED_DIR uses
  replaced with lazy getters.

Security:
- store.ts: Roblox API key is NO LONGER persisted to localStorage.
  Identity is remembered; the user must re-verify with their key
  before any upload. Includes one-time migration that scrubs
  legacy keys from existing localStorage entries.
- /api/roblox/status: API key moved from URL query string to
  request body (POST) or X-Roblox-Api-Key header. The key is no
  longer logged in reverse-proxy access logs or browser history.
- /api/roblox/verify and /api/roblox/upload: added lightweight
  in-memory rate limiting (10 req/min for verify, 20 req/min for
  upload) to prevent trivial DoS and Roblox API throttling.
- /api/roblox/upload: explicit 100 MB cap on in-memory audio
  buffer with proper 413 response.

Next.js 16 migration:
- src/middleware.ts → src/proxy.ts: Next 16 deprecated the
  middleware file convention in favor of proxy (same semantics).
- next.config.ts: reactStrictMode enabled to catch effect bugs;
  ignoreBuildErrors kept for legacy reasons. Removed untyped
  eslint.ignoreDuringBuilds option.
- outputFileTracingExcludes for ./.tmp-audio/** and bot-config.json
  to avoid Turbopack NFT pulling in the whole project.

TypeScript / code quality:
- audio/file/route.ts: Buffer → Uint8Array wrap for NextResponse
  BodyInit compatibility.
- waveform-player.tsx: removed invalid 'volume' prop from
  <audio>, apply volume via ref.
- auth.ts: NextAuthOptions type, module augmentation for
  session.user.id, dropped invalid 'trustHost' config key.
- discord-bot.ts: clearer error messages for privileged-intent
  and invalid-token failures; process.exit(1) on login failure
  so process managers can detect; unhandled rejection / uncaught
  exception handlers; fixed a console.log template-string typo.

- eslint.config.mjs: react-hooks/exhaustive-deps re-enabled as
  'warn' — disabling it was the root cause of the polling leak.

- .env.example: documented AUTH_TRUST_HOST for non-localhost deploys.
- .gitignore: ignore npm/yarn/pnpm lockfiles (project uses bun).
- new src/lib/rate-limit.ts: in-memory token-bucket limiter.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants