← 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).