diff --git a/tools/napplet-test/README.md b/tools/napplet-test/README.md new file mode 100644 index 0000000000..2ed5670a6e --- /dev/null +++ b/tools/napplet-test/README.md @@ -0,0 +1,70 @@ +# Napplet test harness + +A self-contained napplet for **on-device verification** of Amethyst's NIP-5D host — it calls every +`window.napplet.*` API and shows each result on screen, so you can confirm the whole +shim → shell → broker → consent round-trip (and the newer `identity.getList/getZaps/getBadges`, +`identity.onChanged`, `keys.onAction`, and `resource.bytes` `nostr:` paths) works on a real device. + +## Files + +- `index.html` — the napplet. Read-only checks run on load; publish/upload/pay are behind buttons; + live pushes (`identity.changed`, `keys.action`) land in the top banner. +- `publish.sh` — uploads `index.html` to a Blossom server and publishes the napplet event. + +## Prerequisites + +- [`nak`](https://github.com/fiatjaf/nak) (signs + publishes the events), `curl`, and `sha256sum` + (or `shasum`/`openssl`). +- A Blossom server that accepts BUD-02 uploads (e.g. `https://blossom.primal.net`, + `https://cdn.satellite.earth`, or your own). +- Your **nsec** — use the **same key you're logged in as in Amethyst**, so the napplet appears under + your account and the identity reads (`getProfile`, `getFollows`, …) have data. + +## Publish + +```bash +cd tools/napplet-test +./publish.sh --sec nsec1yourkey... --server https://blossom.primal.net \ + --relay wss://relay.damus.io --relay wss://nos.lol +``` + +Then verify it resolves (optional): + +```bash +amy napplet fetch --d napplet-test +``` + +## Open it in Amethyst + +Build & install the debug app and watch the logs: + +```bash +./gradlew :amethyst:installPlayDebug +adb logcat -s NappletHostActivity NappletBrokerService NappletContentServer +``` + +Logged in as the publishing key, find **"Napplet Test Harness"** in your Apps / Napplets list (or its +feed card) and tap **Open**. It launches in the sandboxed `:napplet` process. + +## What to verify + +- **On load:** each read row turns green. `shell.supports(identity)` = true, `(bogus)` = false. Every + capability prompts for consent the first time. +- **identity.getList/getZaps/getBadges:** open your own profile first so the cache has your lists / + zaps / badges, then relaunch — the rows show your data (empty arrays are valid if you have none). +- **identity.onChanged:** with the napplet open, switch accounts (or log out) → the banner shows + `identity.changed → `. It must NOT fire on the initial load. +- **keys.onAction:** with a hardware keyboard (or `adb shell input keyevent 47` for "S" while holding + Ctrl), press **Ctrl+S** → banner shows `keys.action → save fired`, and the keystroke is consumed. +- **resource.bytes `nostr:`:** paste a `nostr:nevent1…` / `note1…` / `naddr1…` / `npub1…` and run → + it returns the event JSON as a blob. Also try an `https://…` image URL. +- **Side effects (deliberate):** `relay.publish` signs+broadcasts a note as you; `upload.blob` uploads + a tiny blob; `value.payInvoice` pays a BOLT-11 invoice (needs a connected wallet). Each prompts. +- **Security:** the applet has no direct network (a plain `fetch()` inside it fails — CSP + `connect-src 'none'`); an undeclared capability is denied even if you'd allow it. + +## Notes + +- `publish.sh` uses `nak`'s `-t key=val1;val2` multi-element tag syntax (e.g. + `path=/index.html;`). If your `nak` version differs, adjust accordingly. +- Re-running `publish.sh` replaces the same addressable event (`d=napplet-test`). diff --git a/tools/napplet-test/index.html b/tools/napplet-test/index.html new file mode 100644 index 0000000000..6418d10735 --- /dev/null +++ b/tools/napplet-test/index.html @@ -0,0 +1,159 @@ + + + + + + + Napplet Test Harness + + + +

Napplet Test Harness

+ +
+ +
+

Side-effectful (tap to run)

+ + +
+ + +
+ +
+ + +
+ + + + diff --git a/tools/napplet-test/publish.sh b/tools/napplet-test/publish.sh new file mode 100755 index 0000000000..8d8f6e109b --- /dev/null +++ b/tools/napplet-test/publish.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# +# Publish the test napplet (index.html) so it can be opened in Amethyst on a device: +# 1. uploads index.html to a Blossom server (BUD-02 signed upload), +# 2. publishes a NIP-5D named-napplet event (kind 35129) whose manifest pins +# /index.html to the blob's sha256 and declares the capabilities it uses. +# +# Requirements: nak (https://github.com/fiatjaf/nak), curl, and sha256sum (or shasum/openssl). +# +# Usage: +# ./publish.sh --sec nsec1... --server https://blossom.example [--relay wss://... ]... [--id napplet-test] +# +# The secret key can also come from $NOSTR_SECRET_KEY or $NSEC. Relays default to a couple of +# public ones if none are given. Use the SAME key you are logged in as in Amethyst, so the napplet +# shows up under your own account and the identity reads have data. +set -euo pipefail + +HERE="$(cd "$(dirname "$0")" && pwd)" +HTML="$HERE/index.html" +ID="napplet-test" +SERVER="" +SEC="${NOSTR_SECRET_KEY:-${NSEC:-}}" +RELAYS=() + +while [ $# -gt 0 ]; do + case "$1" in + --sec) SEC="$2"; shift 2 ;; + --server) SERVER="${2%/}"; shift 2 ;; + --relay) RELAYS+=("$2"); shift 2 ;; + --id) ID="$2"; shift 2 ;; + -h|--help) sed -n '2,20p' "$0"; exit 0 ;; + *) echo "Unknown arg: $1" >&2; exit 2 ;; + esac +done + +[ -n "$SEC" ] || { echo "ERROR: no secret key (--sec / \$NOSTR_SECRET_KEY / \$NSEC)" >&2; exit 2; } +[ -n "$SERVER" ] || { echo "ERROR: no --server (a Blossom base URL, e.g. https://blossom.primal.net)" >&2; exit 2; } +command -v nak >/dev/null || { echo "ERROR: nak not found (https://github.com/fiatjaf/nak)" >&2; exit 2; } +command -v curl >/dev/null || { echo "ERROR: curl not found" >&2; exit 2; } +[ ${#RELAYS[@]} -gt 0 ] || RELAYS=(wss://relay.damus.io wss://nos.lol) + +sha256() { + if command -v sha256sum >/dev/null; then sha256sum | cut -d' ' -f1 + elif command -v shasum >/dev/null; then shasum -a 256 | cut -d' ' -f1 + else openssl dgst -sha256 | sed 's/.* //'; fi +} +b64() { base64 | tr -d '\n'; } + +HASH="$(sha256 < "$HTML")" +echo "index.html sha256 : $HASH" + +# --- 1. Blossom upload (BUD-02: a kind-24242 'upload' auth event in the Authorization header) --- +EXP=$(( $(date +%s) + 3600 )) +AUTH_JSON="$(nak event -k 24242 --sec "$SEC" -t "t=upload" -t "x=$HASH" -t "expiration=$EXP" -c "Upload napplet test")" +AUTH_B64="$(printf '%s' "$AUTH_JSON" | b64)" + +echo "Uploading to $SERVER/upload …" +UP="$(curl -sS -X PUT "$SERVER/upload" \ + -H "Authorization: Nostr $AUTH_B64" \ + -H "Content-Type: text/html" \ + --data-binary @"$HTML")" +echo " server said: $UP" + +echo "Verifying $SERVER/$HASH is retrievable …" +CODE="$(curl -sS -o /dev/null -w '%{http_code}' "$SERVER/$HASH")" +[ "$CODE" = "200" ] || { echo "ERROR: blob not retrievable (HTTP $CODE). Check the upload response above." >&2; exit 1; } +echo " OK (HTTP 200)" + +# --- 2. NIP-5A aggregate hash over the one path: sha256(" /index.html\n") --- +AGG="$(printf '%s /index.html\n' "$HASH" | sha256)" +echo "aggregate hash : $AGG" + +# --- 3. Publish the NIP-5D named napplet (kind 35129) --- +echo "Publishing napplet event (kind 35129, d=$ID) to: ${RELAYS[*]}" +nak event -k 35129 --sec "$SEC" \ + -d "$ID" \ + -t "path=/index.html;$HASH" \ + -t "x=$AGG;aggregate" \ + -t "server=$SERVER" \ + -t "requires=identity" \ + -t "requires=relay" \ + -t "requires=storage" \ + -t "requires=value" \ + -t "requires=resource" \ + -t "requires=upload" \ + -t "requires=keys" \ + -t "title=Napplet Test Harness" \ + -t "description=Exercises every napplet.* API for on-device verification." \ + "${RELAYS[@]}" + +PUB="$(nak key public "$SEC")" +echo +echo "Done. Author pubkey: $PUB" +echo "Verify resolution with amy:" +echo " amy napplet fetch $PUB --d $ID" +echo "Then in Amethyst (logged in as this key): open your Apps / Napplets list and tap \"Napplet Test Harness\"."