← back to Govarbitrage
extension/README.md
93 lines
# GovArbitrage Capture — Chrome Extension
A standalone Manifest V3 Chrome extension that scrapes the government-surplus
auction page you're viewing (GovDeals, Public Surplus, GSA Auctions, or any
generic auction page) and POSTs the extracted listing into the GovArbitrage app.
No build step, no npm — it's plain vanilla JS and loads directly as an unpacked
extension.
## Load it (unpacked)
1. Open `chrome://extensions` in Chrome (or any Chromium browser).
2. Toggle **Developer mode** on (top-right).
3. Click **Load unpacked**.
4. Select this `extension/` folder.
5. The **GovArbitrage Capture** icon appears in the toolbar. Pin it for easy
access.
After editing any file, click the **↻ reload** icon on the extension card in
`chrome://extensions` to pick up changes.
## Use it
1. Navigate to an auction/item page on GovDeals, Public Surplus, or GSA
Auctions (or any other auction page — it falls back to generic extraction).
2. Click the extension icon to open the popup.
3. Confirm the **API base URL** (defaults to `http://localhost:3000`; your
choice is saved to `chrome.storage.local` and persists).
4. Click **Capture this page** — the extracted JSON appears in the preview.
5. Review it, then click **Send to GovArbitrage** to POST it to the app.
## How it works
- **`popup.js`** uses `chrome.scripting.executeScript` to inject
**`content.js`** into the active tab, then calls its
`extractGovArbitrageListing()` function and displays the returned object.
Extraction is done programmatically (no persistent content script) so the
extension only touches a page when you explicitly ask it to.
- **`content.js`** detects the source from `location.hostname`, parses the
auction/lot id from the URL (query string first, then path, then visible
label), and pulls title, description, category, current bid, bid count,
closing date, location, images, and terms with site-specific selectors plus
defensive fallbacks. It never throws — every access is guarded — so a partial
page still yields a valid (partial) object.
- **`background.js`** is a near-empty MV3 service worker (logs install,
optionally relays a `PING`/`PONG` message).
## App endpoint
The popup POSTs the captured JSON to:
```
POST {apiBase}/api/import/extension
Content-Type: application/json
```
**Note:** this import endpoint is being added on the GovArbitrage app side. The
extension does not depend on the app internals — it just sends the JSON shape
below. On success it looks for `{ id }` or `{ listing: { id } }` in the
response and shows the listing id; any non-2xx response is surfaced as an error.
## Captured JSON shape
The object `content.js` emits (field names deliberately match the app's Prisma
`Listing` model; `source` uses the `AuctionSource` enum, and the extension's
values are `EXTENSION`-origin listings):
```json
{
"source": "GOVDEALS | PUBLIC_SURPLUS | GSA_AUCTIONS | OTHER",
"sourceAuctionId": "string (auction/lot number)",
"sourceUrl": "string (full page URL)",
"title": "string",
"description": "string",
"category": "string",
"currentBid": 0,
"bidCount": 0,
"closingAt": "ISO-8601 string | null",
"locationCity": "string",
"locationState": "string",
"locationZip": "string",
"imageUrls": ["string", "..."],
"auctionTerms": "string",
"capturedAt": "ISO-8601 string"
}
```
Types: `currentBid` and `bidCount` are numbers; `closingAt` is an ISO string or
`null`; `imageUrls` is an array of absolute URL strings; all other fields are
strings (possibly empty). The app should map `source` into the `AuctionSource`
enum, store the values as an `EXTENSION`-origin `Listing`, and enforce the
`@@unique([source, sourceAuctionId])` constraint (upsert on re-capture).