https://github.com/iprashantraj/leetcode-tracker
Chrome extension + React dashboard that tracks LeetCode time, charts your grind, and adds a friends leaderboard. Supabase backend.
https://github.com/iprashantraj/leetcode-tracker
chrome-extension javascript leetcode manifest-v3 react supabase tracker vite
Last synced: 28 days ago
JSON representation
Chrome extension + React dashboard that tracks LeetCode time, charts your grind, and adds a friends leaderboard. Supabase backend.
- Host: GitHub
- URL: https://github.com/iprashantraj/leetcode-tracker
- Owner: iprashantraj
- Created: 2026-05-15T18:29:49.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-05-23T00:39:22.000Z (29 days ago)
- Last Synced: 2026-05-23T02:23:50.201Z (29 days ago)
- Topics: chrome-extension, javascript, leetcode, manifest-v3, react, supabase, tracker, vite
- Language: JavaScript
- Size: 168 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# LeetCode Tracker
> Know how much time you actually spend on LeetCode, which topics you neglect, and which problems you keep coming back to.
A Chrome/Brave extension that quietly tracks your LeetCode practice — time spent, runs, submits, verdicts — and a web dashboard that turns it into something useful: solved-by-difficulty badges, a topic-time chart, a filterable problem list, and friend comparison.
**[→ Try the dashboard](https://theleetcodetracker.netlify.app/)** · **[→ Install the extension](https://github.com/iprashantraj/leetcode-tracker/releases/latest)** *(invite-only)*

---
## Why this exists
LeetCode shows you what you've solved. It doesn't show:
- **Where your time actually goes** — that 30 min on Two Sum vs 3 hours stuck on Trapping Rain Water
- **Which topics you avoid** — you've done 18 array problems and 0 graph problems this month
- **What your real attempt history looks like** — every run, every submit, every verdict, timestamped
- **How you compare to friends grinding the same problems**
This fills those gaps with one Chrome extension and one dashboard. No screenshots, no manual logging, no behavior change on your part — it just runs in the background while you practice as normal.
---
## How it works
```
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ Browser Extension │ │ Supabase │ │ Web Dashboard │
│ │ sync │ │ read │ │
│ • Tracks time │ ──────► │ • Postgres + RLS │ ──────► │ • Stats + charts │
│ • Detects run/sub │ every │ • Auth (invite) │ live │ • Problem list │
│ • Verdict watcher │ 60s + │ • Realtime ready │ │ • Friends compare │
│ • LeetCode meta │ events │ │ │ │
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
MV3 + JS Postgres + GoTrue React + Vite + Recharts
```
A few design choices worth calling out:
- **`chrome.webRequest` for Run/Submit detection** (not page-level fetch hooks). LeetCode caches a native fetch reference before any content script can patch it, so MAIN-world overrides silently miss every request. webRequest sits at the network layer and catches everything.
- **Shared `problems` catalogue populated organically.** When any user's extension visits "Two Sum" for the first time, it fetches difficulty + topics from LeetCode's GraphQL endpoint (using the user's own session) and writes one row to a shared table. Every other user benefits from that cache.
- **Time tracking treats "opened-and-bounced" as noise.** A visit without any run, submit, or 30+ seconds of input is discarded at the sync boundary. Your dashboard reflects real practice, not curiosity.
- **Phantom-time safeguard.** Tab closed without firing `pagehide`? The background worker caps the active interval after 90 seconds of silence so the timer doesn't accumulate hours of imaginary time.
- **Row Level Security on every table.** Anon key is shipped in the client; access control is enforced at the database layer, not the application layer.
- **Single-use invite codes** gate signups. Codes live in a table the anon key cannot read, validated by a `SECURITY DEFINER` trigger on `auth.users` insert.
---
## Tech
| Layer | Stack |
|---|---|
| Extension | Manifest V3 · vanilla JS · ES modules · `chrome.webRequest` |
| Dashboard | React 19 · Vite 8 · Recharts · date-fns |
| Backend | Supabase (Postgres 15 · GoTrue · PostgREST · pg_cron) |
| Hosting | Netlify (dashboard) · Supabase (DB + auth) · GitHub Releases (extension) |
| Distribution | Unpacked .zip (current) → Chrome Web Store (planned) |
Total footprint: ~3000 lines across extension + dashboard + SQL migrations.
---
## Install (invite-only)
> Signups require a single-use code. DM me if you'd like one.
1. Download the latest `leetcode-tracker-X.Y.Z.zip` from the **[releases page](https://github.com/iprashantraj/leetcode-tracker/releases/latest)** and unzip it.
2. Open `chrome://extensions` (or `brave://extensions`) → toggle **Developer mode** ON.
3. **Load unpacked** → pick the unzipped folder.
4. Open the **[dashboard](https://theleetcodetracker.netlify.app/)** and **Sign up** with your email, a password, and the invite code you were given.
5. Click the extension icon → **Sign in** with the same credentials.
6. Open any [leetcode.com/problems/](https://leetcode.com/problems/) page. Practice as usual. Your dashboard updates within ~60 seconds of each Run or Submit.
---
## Repo layout
```
extension/ Manifest V3 Chrome extension
├ background.js service worker (sync, webRequest, attempt lifecycle)
├ content.js isolated-world script (slug detection, idle tracking)
├ page-hook.js MAIN-world script (fetch fallback)
├ supabase.js thin REST/auth wrapper, no SDK
├ popup.html/.js extension popup UI
├ config.js public Supabase URL + anon key (safe to commit, RLS enforces)
└ build-zip.sh packages the release zip
dashboard/ React + Vite SPA
└ src/
├ pages/ Dashboard + Auth + Friends
├ components/ StatsBar, ActivityChart, Heatmap, TopicChart, ProblemList, DifficultyBadges
├ hooks/ useAttempts, useProblems, useFriendships, useSession
└ utils/stats.js pure aggregation (testable, no React, no Supabase)
supabase/ SQL migrations (idempotent, re-runnable)
├ schema.sql core tables + RLS + triggers
├ phase4.sql friends/groups
├ phase4_usernames.sql username uniqueness
├ phase5_event_log.sql granular event tracking + 90-day cron cleanup
├ phase6_invite_codes.sql single-use signup codes
└ phase7_lockdown_problems.sql immutable shared catalogue
scripts/ admin tooling (runs only on owner machine)
├ new-invite.sh generate an invite code
└ .env.example service_role config (real .env is gitignored)
```
---
## Local development
For forking or self-hosting your own instance:
Click to expand
### Supabase
1. Create a project at [supabase.com](https://supabase.com) (free tier is plenty).
2. SQL Editor → run each file in `supabase/` in numeric order (`schema.sql` first, then `phase4.sql`, etc).
3. Project Settings → API → copy the **Project URL** and **anon public** key.
### Extension
1. Edit `extension/config.js` — replace the URL and anon key with your own project's values.
2. `chrome://extensions` → Developer Mode → Load unpacked → pick the `extension/` folder.
### Dashboard
```bash
cd dashboard
cp .env.example .env
# Set VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY
npm install
npm run dev # → http://localhost:5173
```
Deploy `dashboard/` to Netlify, Vercel, or any static host. Build command: `npm run build`, output dir: `dist`.
### Generate invite codes (admin)
```bash
cp scripts/.env.example scripts/.env
# Fill in SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY (the latter NEVER leaves your machine)
./scripts/new-invite.sh "Note for this invite"
```
---
## Security model
A few notes since this is a multi-user system with a public-anon client:
- **Anon key is committed.** It's safe by design — Supabase anon keys are intended for client use; access control happens at the database via Row Level Security policies, not by hiding the key.
- **service_role key is admin-only.** Lives only in `scripts/.env` on the owner's machine. Never shipped, never committed.
- **Every table has RLS enabled.** Users can read/write only their own rows except `problems` (a shared catalogue with read-anyone / write-once / update-admin-only).
- **Invite codes** use a `SECURITY DEFINER` trigger that locks the row (`SELECT FOR UPDATE`) to prevent race conditions when two signups attempt the same code.
- **Events are auto-purged.** `pg_cron` runs nightly to delete events older than 90 days, keeping storage bounded at the free-tier limit.
---
## License
MIT — see [LICENSE](LICENSE).
---
Built by [@iprashantraj](https://github.com/iprashantraj). Open an [issue](https://github.com/iprashantraj/leetcode-tracker/issues) for bugs or feature requests — there's also a "Report a bug" link inside the extension popup.