A Chrome extension that summarises sales data from a Google Sheet (revenue totals, top products, a sales-over-time chart, revenue by category and segment, recent activity), plus a small Express backend that brokers Google Sheets. The extension authenticates you with Google OAuth — no service-account sharing required.
repo/
+-- manifest.json # Chrome MV3 manifest (with OAuth2 config)
+-- popup.html # Popup UI shell
+-- popup.js # Orchestrator + card renderers + click handler
+-- config.example.js # Template for config.js (git-ignored)
+-- .gitignore
+-- README.md
+-- popup/
| +-- style.css # All popup CSS
| +-- features.js # Getting Started card (schema reference)
+-- backend/
+-- server.js
+-- routes/
| +-- analyze.js # POST /api/analyze
| +-- setupInfo.js # GET /api/setup-info
+-- services/
| +-- analyze.js # Pure analyzer
| +-- googleSheets.js # JWT + OAuth-backed sheet fetching
+-- package.json
+-- README.md
+-- config/
+-- middleware/
You run the SheetPulse backend at a single URL. An API key is
generated server-side and bundled into every install of the
extension through /config.js.
End users install the extension and just click Analyze Sheet Trends — on first click, Google asks them to grant read-only sheet access. After that, every sheet they open can be analyzed instantly. No service-account sharing needed.
See backend/README.md for full setup instructions, including
OAuth client ID creation in Google Cloud Console.
After analyze, the popup shows in order: sheet header → totals grid (revenue / quantity / avg unit price) → sales-over-time area chart → top products by revenue → revenue by category → revenue by segment → recent activity. Notes appear as a small admonition when the analyzer ran but couldn't produce all sections (e.g. partial schema mismatch).
On first click, Google asks the user to grant read-only sheet access. Once approved, every sheet the user opens can be analyzed without sharing anything with a service account. The backend falls back to its SA for users who decline OAuth or run older extension versions.
The popup is split into two scripts:
popup.js— orchestrator. Click handler, OAuth token acquisition (chrome.identity.getAuthToken), network (analyze()), every data-card renderer, plusrenderResults(). After a successful analyze it dispatches asheetpulse:analyzedCustomEvent ondocument.popup/features.js— shows a Getting Started card with the canonical schema on first open. Listens for the CustomEvent and hides the card after the first successful analyze.popup/style.css— all popup styles (dark theme, green accent).
- Google OAuth (primary): the extension gets a user-scoped access
token (
chrome.identity.getAuthToken). The backend uses that token to read sheets directly — no service-account sharing. Scope isspreadsheets.readonly. - CORS allowlist locked to
chrome-extension://<your-id>so only the real extension can hit the backend. - API key gate (
X-API-Key) on every/api/*endpoint except/api/setup-info. Used as a fallback for users who decline OAuth. - Google service account with
spreadsheets.readonlyscope as fallback. - Rate limit of 30 req/min/IP on every
/api/*path. - Centralized error handling — clients never see stack traces or upstream internals.
Rotate the API key (in both backend/.env and /config.js) if you
suspect abuse. See backend/README.md for full backend details.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /healthz |
No | Uptime probe. |
| GET | /api/setup-info |
No* | Returns {schemaColumns} for the Getting Started card. |
| POST | /api/analyze |
Yes | Returns the analyzer payload (totals, charts, top products, byDate, etc.). Accepts optional Authorization: Bearer for Google OAuth. |
* /api/setup-info is rate-limited and CORS-gated but not
auth-gated.