Skip to content

Commit 62292ac

Browse files
ralyodioclaude
andcommitted
feat(store): extension store at tronbrowser.dev/store — pay $1, list MV3, go live instantly
Recreate a browser-extension store that drops the Chrome/Edge/Firefox review gauntlet (review queue, $5 dev fee, screenshots, multi-day waits) while keeping Chromium's real plumbing intact: Manifest V3, CRX3 packaging, and the gupdate update_url XML for install + auto-update. Backend (services/api/src/store, self-contained so it builds in the slim API image; mounted at /api/store): - 0003_extension_store.sql: extensions, versions, payments, scans, flags - manifest.ts: MV3 validation (pure, tested) — manifest_version must be 3 - payments.ts: $1 fee via Stripe (Checkout + HMAC-verified webhook) AND CoinPay/x402 (402 challenge + settlement confirm), dependency-free - vu1nz.ts: async, non-gating vu1nz.com security scan -> badge ('skipped' if VU1NZ_API_URL unset) - mirror.ts: git-registry mirror of each published listing (audit trail) - routes.ts: browse/detail/submit/checkout/confirm/webhook/flag, Chromium gupdate updates.xml, download redirect Frontend (apps/extensions, static, served at /srv/store): - browse, detail (install + permissions + scan badge + report), submit (paste MV3 manifest -> pay $1 -> live), honest per-browser sideload guide (TronBrowser one-click; Chrome/Edge dev-mode; Firefox AMO-unlisted) Publishing is instant after payment; scan + community flagging happen out of band. Two publish paths: upload form, or a PR to apps/extensions/registry/<slug> gated by the vu1nz GitHub Action (.github/workflows/extension-scan.yml). Store reuses the existing TronBrowser Turso/libSQL DB (FKs into users). Docs in docs/extension-store.md; env in .env.example. 14 tests pass (manifest validation + Stripe webhook verification); migration applies clean; API smoke-tested. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4c0cfaf commit 62292ac

26 files changed

Lines changed: 2055 additions & 0 deletions

‎.env.example‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,22 @@ COINPAY_REDIRECT_URI=tronbrowser://oauth/coinpay
3535
# Override only for self-hosted CoinPay:
3636
# COINPAY_AUTHORIZE_URL=https://coinpayportal.com/api/oauth/authorize
3737
# COINPAY_TOKEN_URL=https://coinpayportal.com/api/oauth/token
38+
39+
# --- Extension store (tronbrowser.dev/store) -------------------------------
40+
APP_URL=https://tronbrowser.dev
41+
# The $1 listing fee — Stripe (card):
42+
STRIPE_SECRET_KEY=
43+
STRIPE_WEBHOOK_SECRET= # for /api/store/payments/stripe/webhook
44+
# The $1 listing fee — CoinPay / x402 (1 USDC):
45+
STORE_X402_NETWORK=base
46+
STORE_X402_PAY_TO= # your USDC receiving address
47+
# COINPAY_SETTLEMENT_URL=https://coinpayportal.com/api/settlements
48+
# STORE_X402_TRUST_CLIENT=1 # DEV ONLY: accept client-reported settlement
49+
# vu1nz.com security scan (non-gating; recorded 'skipped' if unset):
50+
VU1NZ_API_URL=
51+
VU1NZ_API_KEY=
52+
# Git registry mirror (audit trail). Repo + token; no-op if unset:
53+
STORE_REGISTRY_REPO= # e.g. profullstack/tronbrowsers.dev
54+
# STORE_REGISTRY_BRANCH=main
55+
# STORE_REGISTRY_PREFIX=apps/extensions/registry
56+
GITHUB_TOKEN=
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# vu1nz.com security scan for extensions submitted via PR.
2+
#
3+
# Runs on PRs that add/modify a registry listing. This is the PR half of the
4+
# store's "scan + community flagging" model — the upload-form half runs the same
5+
# scan asynchronously inside the API (services/api/src/store/vu1nz.ts).
6+
name: extension-scan
7+
8+
on:
9+
pull_request:
10+
paths:
11+
- 'apps/extensions/registry/**/listing.json'
12+
13+
permissions:
14+
contents: read
15+
pull-requests: write
16+
17+
jobs:
18+
scan:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- name: Find changed listings
24+
id: changed
25+
run: |
26+
git fetch origin "${{ github.base_ref }}" --depth=1
27+
files=$(git diff --name-only "origin/${{ github.base_ref }}"...HEAD -- 'apps/extensions/registry/**/listing.json')
28+
echo "files<<EOF" >> "$GITHUB_OUTPUT"
29+
echo "$files" >> "$GITHUB_OUTPUT"
30+
echo "EOF" >> "$GITHUB_OUTPUT"
31+
32+
# vu1nz publishes a composite action; we invoke it per changed listing.
33+
# Configure VU1NZ_API_URL / VU1NZ_API_KEY as repo secrets to enable.
34+
- name: vu1nz scan
35+
if: ${{ steps.changed.outputs.files != '' }}
36+
env:
37+
VU1NZ_API_URL: ${{ secrets.VU1NZ_API_URL }}
38+
VU1NZ_API_KEY: ${{ secrets.VU1NZ_API_KEY }}
39+
run: |
40+
if [ -z "$VU1NZ_API_URL" ]; then
41+
echo "::warning::VU1NZ_API_URL not set — skipping scan (set it to enable the gate)"
42+
exit 0
43+
fi
44+
fail=0
45+
while IFS= read -r f; do
46+
[ -z "$f" ] && continue
47+
echo "Scanning $f"
48+
payload=$(cat "$f")
49+
resp=$(curl -sS -X POST "$VU1NZ_API_URL/scan/extension" \
50+
-H "content-type: application/json" \
51+
${VU1NZ_API_KEY:+-H "authorization: Bearer $VU1NZ_API_KEY"} \
52+
-d "{\"target\":\"browser-extension\",\"listing\":$payload}")
53+
echo "$resp"
54+
sev=$(echo "$resp" | python3 -c "import sys,json;print(json.load(sys.stdin).get('severity',''))" 2>/dev/null || echo "")
55+
if [ "$sev" = "critical" ] || [ "$sev" = "high" ]; then
56+
echo "::error::$f flagged $sev by vu1nz"
57+
fail=1
58+
fi
59+
done <<< "${{ steps.changed.outputs.files }}"
60+
exit $fail

‎Dockerfile‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ FROM caddy:2-alpine
1717
RUN apk add --no-cache nodejs
1818
COPY Caddyfile /etc/caddy/Caddyfile
1919
COPY apps/web/public/ /srv/
20+
# Extension store (tronbrowser.dev/store) — static frontend; dynamic bits hit
21+
# /api/store on the bundled API.
22+
COPY apps/extensions/public/ /srv/store/
2023
# Branding lives at the repo root (single source of truth). apps/web/public has
2124
# symlinks to them for local dev, but Docker COPY won't follow symlinks pointing
2225
# outside the copied dir — so copy the real files in (these override the links).

‎apps/extensions/README.md‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# @tronbrowser/extensions
2+
3+
The **TronBrowser extension store** — served at **tronbrowser.dev/store**.
4+
5+
Pay **$1**, list your **Manifest V3** extension, go **live instantly**. No review
6+
queue, no $5 developer fee, no screenshots, no multi-day waits. We keep
7+
Chromium's real plumbing (MV3 manifests, CRX3 packaging, `update_url`
8+
auto-update) and only delete the bureaucracy.
9+
10+
## What's here
11+
12+
This package is the **static frontend** (`public/`). All dynamic behaviour lives
13+
in the API at **`/api/store`** (see `services/api/src/store/`).
14+
15+
```
16+
public/
17+
index.html browse / search live extensions
18+
extension.html listing detail: install, permissions, scan badge, report
19+
submit.html publish flow: paste MV3 manifest → pay $1 → live
20+
install-guide.html honest per-browser sideload steps + TronBrowser one-click
21+
store.css store.js shared styles + CSP-safe client
22+
registry/ git mirror of published listings (audit trail)
23+
```
24+
25+
In production the Dockerfile copies `public/` to `/srv/store/` and Caddy serves
26+
it; `/api/*` is reverse-proxied to the bundled Hono API.
27+
28+
## Install model (universal sideload)
29+
30+
- **TronBrowser** — true one-click install + auto-update (our Chromium build).
31+
- **Chrome / Edge** — Load unpacked `.zip` (dev mode, any OS), or the
32+
auto-updating `.crx` via Linux / enterprise policy.
33+
- **Firefox** — temporary load for any build; permanent install needs Mozilla
34+
"unlisted" AMO signing.
35+
36+
See `install-guide.html` for the full steps and why stock browsers can't offer a
37+
one-click off-store install.
38+
39+
## Publish flow
40+
41+
1. `POST /api/store/extensions` — create a draft (auth required).
42+
2. `POST /api/store/extensions/:id/versions` — submit the MV3 manifest + bundle
43+
URL(s). Rejected unless it's valid Manifest V3.
44+
3. `POST /api/store/extensions/:id/checkout` — pay the $1 fee via **Stripe**
45+
(card) or **CoinPay / x402** (1 USDC).
46+
4. On payment the listing flips to **live**, is mirrored to the git registry,
47+
and a **vu1nz.com** security scan runs asynchronously (badge on the listing).
48+
49+
You can also publish **via a PR** to `registry/<slug>/listing.json` — the vu1nz
50+
scan runs as the PR's CI check.
51+
52+
## Develop
53+
54+
```bash
55+
pnpm --filter @tronbrowser/extensions serve # static preview on :4322
56+
pnpm --filter @tronbrowser/api dev # the /api/store backend
57+
```
58+
59+
See [`docs/extension-store.md`](../../docs/extension-store.md) for architecture
60+
and env vars.

‎apps/extensions/package.json‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "@tronbrowser/extensions",
3+
"version": "0.2.5",
4+
"private": true,
5+
"description": "TronBrowser extension store — pay $1, list your MV3 extension (tronbrowser.dev/store)",
6+
"type": "module",
7+
"main": "./dist/index.js",
8+
"types": "./dist/index.d.ts",
9+
"exports": {
10+
".": {
11+
"types": "./dist/index.d.ts",
12+
"default": "./dist/index.js"
13+
}
14+
},
15+
"scripts": {
16+
"build": "tsc -p tsconfig.json",
17+
"typecheck": "tsc -p tsconfig.json --noEmit",
18+
"test": "vitest run --passWithNoTests",
19+
"serve": "python3 -m http.server 4322 --directory public",
20+
"lint": "echo \"[lint] extensions: stub\""
21+
},
22+
"devDependencies": {
23+
"typescript": "^5.6.3",
24+
"vitest": "^2.1.4"
25+
}
26+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>Extension — TronBrowser Store</title>
7+
<meta name="description" content="Install this Manifest V3 extension from the TronBrowser store." />
8+
<meta name="theme-color" content="#05070d" />
9+
<link rel="icon" href="/mark.svg" type="image/svg+xml" />
10+
<link rel="stylesheet" href="/store/store.css" />
11+
</head>
12+
<body data-page="detail">
13+
<header class="nav"><div class="wrap">
14+
<a class="brand" href="/store/"><img src="/mark.svg" alt="" /> TronBrowser <span class="tag">Store</span></a>
15+
<span class="spacer"></span>
16+
<a class="navlink" href="/store/install-guide.html">Install guide</a>
17+
<a class="navlink" href="/store/submit.html">Publish</a>
18+
<a class="navlink" href="/store/">All extensions</a>
19+
</div></header>
20+
21+
<main class="wrap">
22+
<p style="margin:18px 0 0"><a href="/store/">← All extensions</a></p>
23+
<div id="paidNote" class="success hidden" style="margin-top:14px">🎉 Payment received — your extension is live!</div>
24+
<section class="panel" id="detail"><p class="muted">Loading…</p></section>
25+
</main>
26+
27+
<footer><div class="wrap">
28+
Installing outside TronBrowser? See the <a href="/store/install-guide.html">sideload guide</a> for Chrome, Edge and Firefox.
29+
</div></footer>
30+
31+
<script src="/store/store.js"></script>
32+
</body>
33+
</html>

‎apps/extensions/public/index.html‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>TronBrowser Store — pay $1, list your extension</title>
7+
<meta name="description" content="The TronBrowser extension store. Manifest V3 extensions, one-click install in TronBrowser, sideload guides for Chrome/Edge/Firefox. Publishers pay $1 and go live instantly — no review queue, no $5 dev fee, no multi-day waits." />
8+
<meta name="theme-color" content="#05070d" />
9+
<link rel="canonical" href="https://tronbrowser.dev/store/" />
10+
<link rel="icon" href="/mark.svg" type="image/svg+xml" />
11+
<link rel="stylesheet" href="/store/store.css" />
12+
</head>
13+
<body data-page="browse">
14+
<header class="nav"><div class="wrap">
15+
<a class="brand" href="/store/"><img src="/mark.svg" alt="" /> TronBrowser <span class="tag">Store</span></a>
16+
<span class="spacer"></span>
17+
<a class="navlink" href="/store/install-guide.html">Install guide</a>
18+
<a class="navlink" href="/store/submit.html">Publish</a>
19+
<a class="navlink" href="/">tronbrowser.dev</a>
20+
</div></header>
21+
22+
<main class="wrap">
23+
<section class="hero">
24+
<span class="pill">Manifest V3 · $1 to list · live instantly</span>
25+
<h1>Browser extensions, without the gatekeepers.</h1>
26+
<p>Real Chromium plumbing — MV3 bundles, CRX packaging, <code>update_url</code> auto-update — minus the Web Store bureaucracy. One-click install in TronBrowser; sideload anywhere. Every listing carries a vu1nz.com security scan and community flagging.</p>
27+
<div class="searchbar">
28+
<input type="search" id="q" placeholder="Search extensions…" autocomplete="off" />
29+
<a class="btn" href="/store/submit.html">Publish yours</a>
30+
</div>
31+
</section>
32+
33+
<div id="grid" class="grid"></div>
34+
<p id="empty" class="muted center hidden" style="padding:40px 0">No extensions yet. <a href="/store/submit.html">Be the first to publish →</a></p>
35+
</main>
36+
37+
<footer><div class="wrap">
38+
TronBrowser Store · <a href="/">Profullstack, Inc.</a> · <a href="/store/install-guide.html">Install guide</a> · listings carry a vu1nz.com scan; report abuse from any listing.
39+
</div></footer>
40+
41+
<script src="/store/store.js"></script>
42+
</body>
43+
</html>
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>Install guide — TronBrowser Store</title>
7+
<meta name="description" content="How to install TronBrowser store extensions: one-click in TronBrowser, and sideload steps for Chrome, Edge and Firefox." />
8+
<meta name="theme-color" content="#05070d" />
9+
<link rel="icon" href="/mark.svg" type="image/svg+xml" />
10+
<link rel="stylesheet" href="/store/store.css" />
11+
</head>
12+
<body data-page="guide">
13+
<header class="nav"><div class="wrap">
14+
<a class="brand" href="/store/"><img src="/mark.svg" alt="" /> TronBrowser <span class="tag">Store</span></a>
15+
<span class="spacer"></span>
16+
<a class="navlink" href="/store/submit.html">Publish</a>
17+
<a class="navlink" href="/store/">All extensions</a>
18+
</div></header>
19+
20+
<main class="wrap">
21+
<section class="hero" style="padding:38px 0 6px">
22+
<h1>Installing store extensions</h1>
23+
<p>TronBrowser gives you a real one-click install. Other browsers actively block third-party extension installs, so they need a sideload step — here's the honest path for each.</p>
24+
</section>
25+
26+
<div class="tabs">
27+
<div class="tab active" data-tab="tron">TronBrowser</div>
28+
<div class="tab" data-tab="chrome">Chrome</div>
29+
<div class="tab" data-tab="edge">Edge</div>
30+
<div class="tab" data-tab="firefox">Firefox</div>
31+
</div>
32+
33+
<section id="tron" class="tabpane active panel">
34+
<h2>TronBrowser — one click</h2>
35+
<p>Because TronBrowser is our own Chromium build, store extensions install and auto-update directly — no developer mode, no warnings.</p>
36+
<ol class="steps">
37+
<li>Open the extension's page in the store and press <b>Install</b>.</li>
38+
<li>Confirm the permission prompt. Done — it auto-updates via the store's <code>update_url</code>.</li>
39+
</ol>
40+
</section>
41+
42+
<section id="chrome" class="tabpane panel">
43+
<h2>Chrome — sideload (developer mode)</h2>
44+
<p class="hint">Chrome disables extensions installed from outside the Web Store on Windows/macOS. Use developer mode (works everywhere) or enterprise policy / Linux for the auto-updating <code>.crx</code>.</p>
45+
<h3>Unpacked (.zip) — any OS</h3>
46+
<ol class="steps">
47+
<li><b>Download</b> the <code>.zip</code> from the listing and unzip it.</li>
48+
<li>Go to <code>chrome://extensions</code> and toggle <b>Developer mode</b> (top-right).</li>
49+
<li>Click <b>Load unpacked</b> and select the unzipped folder.</li>
50+
</ol>
51+
<h3>Auto-updating .crx — Linux / enterprise</h3>
52+
<p class="hint">On Linux, or via the <code>ExtensionInstallForcelist</code> / <code>ExtensionSettings</code> enterprise policy, point Chrome at the listing's <code>.crx</code> and its <code>update_url</code> (shown on each listing) for silent auto-updates.</p>
53+
</section>
54+
55+
<section id="edge" class="tabpane panel">
56+
<h2>Edge — sideload (developer mode)</h2>
57+
<p class="hint">Edge is Chromium too, so it mirrors Chrome.</p>
58+
<ol class="steps">
59+
<li>Download and unzip the bundle.</li>
60+
<li>Open <code>edge://extensions</code> and enable <b>Developer mode</b>.</li>
61+
<li>Click <b>Load unpacked</b> and choose the folder.</li>
62+
</ol>
63+
</section>
64+
65+
<section id="firefox" class="tabpane panel">
66+
<h2>Firefox</h2>
67+
<p class="hint">Release Firefox only runs add-ons signed by Mozilla. Two paths:</p>
68+
<h3>Temporary (any build) — until restart</h3>
69+
<ol class="steps">
70+
<li>Open <code>about:debugging#/runtime/this-firefox</code>.</li>
71+
<li><b>Load Temporary Add-on…</b> and pick the <code>manifest.json</code> from the unzipped bundle.</li>
72+
</ol>
73+
<h3>Permanent — needs Mozilla "unlisted" signing</h3>
74+
<p class="hint">For a permanent install in release Firefox the <code>.xpi</code> must be signed by Mozilla's AMO (the "unlisted" path — no public listing/review). Developer Edition / Nightly / ESR-unbranded can disable signing and install the <code>.xpi</code> directly via <code>about:addons → Install Add-on From File</code>.</p>
75+
</section>
76+
77+
<section id="publish" class="panel">
78+
<h2>Publishing via a PR (alternative to the upload form)</h2>
79+
<p>The store DB is the source of truth, but every published listing is also mirrored to a public git registry. If you prefer git, open a PR adding <code>registry/&lt;your-slug&gt;/listing.json</code> (name, MV3 manifest, bundle/CRX URLs). The vu1nz.com scan runs as the PR's CI check; once the $1 fee clears, it merges and goes live.</p>
80+
</section>
81+
</main>
82+
83+
<footer><div class="wrap">Questions? <a href="/">tronbrowser.dev</a></div></footer>
84+
85+
<script src="/store/store.js"></script>
86+
</body>
87+
</html>

0 commit comments

Comments
 (0)