Refresh checkpoint with NIP-46 remote signer
This commit is contained in:
parent
73cb08d856
commit
593fc6af8b
1 changed files with 50 additions and 87 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
# Checkpoint — Compose preview + open-source docs (2026-08-04)
|
# Checkpoint — NIP-46 remote signer (2026-08-05)
|
||||||
|
|
||||||
A stopping point you can return to if this session is closed. Everything below was
|
A stopping point you can return to if this session is closed. Everything below was
|
||||||
verified green at the moment this file was written.
|
verified green at the moment this file was written.
|
||||||
|
|
@ -6,119 +6,82 @@ verified green at the moment this file was written.
|
||||||
## Where things are
|
## Where things are
|
||||||
|
|
||||||
- Project: `/home/avi/Projects/skills/nost-feed-manager`
|
- Project: `/home/avi/Projects/skills/nost-feed-manager`
|
||||||
- Git repo: `master` @ `3eb9fa9` ("Rewrite README for Forgejo hosting"), on top of
|
- Git repo: `master` @ `73cb08d` ("Add NIP-46 remote signer (external signing)"), on top of
|
||||||
`dbc0974` ("Add open-source README and MIT license"), `46d5da7` ("Point checkpoint at current
|
`13efee6` ("Refresh checkpoint with Forgejo README"), `3eb9fa9` ("Rewrite README for Forgejo
|
||||||
HEAD"), `9ec01c9` ("Add AGENTS.md checkpoint workflow rule"), `a052851` (checkpoint refresh),
|
hosting"), and the history described in the previous checkpoint.
|
||||||
`d677125` ("Show preview images and link cards in Compose"), `03f6877` (NIP-98 upload auth),
|
|
||||||
`3e3467b` (compose preview + attachments), `17e8ace` (Profiles subtitle copy), and
|
|
||||||
`8eb6685` (reveal-secret-key).
|
|
||||||
- The working tree is **clean** — everything from this session is committed.
|
- The working tree is **clean** — everything from this session is committed.
|
||||||
- `AGENTS.md` now records the standing rule: every addition must end with an up-to-date checkpoint.
|
- The signer feature is split across one commit (`73cb08d`) that contains both the Rust backend
|
||||||
- `README.md` is an open-source-ready document rewritten for **Forgejo** hosting (no GitHub
|
and the Electron/React frontend.
|
||||||
references; clone/issues/wiki links use the `[YOUR_FORGEJO_INSTANCE_URL]/<OWNER>/<REPO>` Forgejo
|
|
||||||
format; no git remote is configured in the repo yet, so the instance URL is a labeled
|
|
||||||
placeholder). The new `LICENSE` file grants MIT (Copyright (c) 2026 Avi). The vault is gitignored,
|
|
||||||
so no secret material is tracked.
|
|
||||||
- `/home/avi/Projects/nostr_backend/nostr_backendmanager.md` (old-CLI docs) has been updated
|
|
||||||
to match reality; it lives outside this repo so it is not part of any commit.
|
|
||||||
- `/home/avi/Projects/nostr_backend/vlog-website/` (separate, untouched).
|
|
||||||
|
|
||||||
## What was completed: compose preview + image attachments
|
## What was completed: NIP-46 remote signer (bunker role)
|
||||||
|
|
||||||
The Compose screen now has a **Write / Preview** tab split. The preview renders the note as it
|
The app can now act as a **remote signer** (NIP-46, "bunker") for another Nostr app. Instead of
|
||||||
will appear (profile avatar + npub, the text, and any images), without publishing anything.
|
pasting your nsec into other apps, they send signing requests to this app, which approves them
|
||||||
|
with the active profile's keys. Decisions made this session: the connected peer is
|
||||||
|
**auto-approved** once the handshake succeeds, the signer runs **inside the `serve` process as a
|
||||||
|
concurrent tokio task** (sharing `Arc<Mutex<App>>`), and the frontend gets a **dedicated Signer
|
||||||
|
screen** as its own sidebar nav item.
|
||||||
|
|
||||||
- **Photos from hyperlinks**: pasting an image URL (`.jpg/.jpeg/.png/.gif/.webp/.avif`) anywhere in
|
- **Backend** (`src/signer.rs`): a hand-rolled NIP-46 implementation (the SDK has no ready-made
|
||||||
the note shows the picture in the preview. On publish, the backend detects the same URLs and tags
|
NIP-46 server). `Signer` exposes `status()`, `connect()`, `disconnect()`, `fail()`,
|
||||||
the event with NIP-92 `imeta` (`url`, `m`) plus the legacy `image` tag, so both new and older
|
`set_connected()`. `parse_connect_uri` accepts `nostrconnect://` links (and clearly rejects the
|
||||||
Nostr clients render the images. Detection is by extension only — nothing is downloaded to decide.
|
opposite-role `bunker://` scheme). The async `run_sign_task` connects over the chosen relays,
|
||||||
- **Attach images**: an "Attach image" button opens the native file dialog (multi-select),
|
listens for kind 24133 requests decrypted with a NIP-44 v2 conversation key, and answers
|
||||||
uploads each file to nostr.build (anonymous `POST /api/v2/upload/files`, field `fileToUpload`),
|
`get_public_key`, `sign_event`, and `nip44_encrypt`/`nip44_decrypt`. Responses are NIP-44-encrypted
|
||||||
and shows a removable thumbnail chip. The hosted URLs are appended to the note on publish.
|
and published back to the peer. Six unit tests cover URI parsing, bunker rejection, NIP-44
|
||||||
- **Where upload lives**: `pick_image` / `upload_image` are handled by the Electron main process
|
round-trip, get_public_key, sign_event, and unknown-method error responses.
|
||||||
(native dialog + `fetch`), not the Rust backend — they need a file dialog and a one-off HTTP
|
- **IPC** (`src/ipc.rs`): `serve`/`handle`/`run` now share `Arc<Mutex<App>>`; new variants
|
||||||
upload. The renderer still calls them through the same `window.backend.request` envelope.
|
`SignerConnect { uri }`, `SignerDisconnect`, `SignerStatus`. The connect/disconnect/status
|
||||||
- The reveal-secret-key feature from the previous commit (`8eb6685`) is unchanged and still works.
|
requests are serviced by `run()` before the (long-lived) vault lock so a slow signer task never
|
||||||
|
blocks ordinary commands.
|
||||||
|
- **CLI** (`src/main.rs`): `signer status` and `signer connect <uri>`.
|
||||||
|
- **Electron/React frontend**: new **Signer screen** (sidebar nav item with the key icon) where you
|
||||||
|
paste a `nostrconnect://` link, Connect/Disconnect, and see the phase (Not connected /
|
||||||
|
Connecting… / Connected), the connected client's hex pubkey (shortened), the relays in use, and
|
||||||
|
any error. Wired through `api.ts` (`signerConnect`/`signerDisconnect`/`signerStatus`) and
|
||||||
|
`AppProvider`. When the vault is locked, a warning explains the signer needs an unlocked vault.
|
||||||
|
Tests: `SignerScreen.test.tsx` (new) + `apiMock.ts`/`fakeBackend.ts` signer methods.
|
||||||
|
|
||||||
## Commits this session (newest first)
|
## Commits this session (newest first)
|
||||||
|
|
||||||
- `3eb9fa9` "Rewrite README for Forgejo hosting" — README now targets Forgejo: zero GitHub URLs;
|
- `73cb08d` "Add NIP-46 remote signer (external signing)" — everything above in one commit.
|
||||||
clone / issues / wiki links use the generic Forgejo format `[YOUR_FORGEJO_INSTANCE_URL]/<OWNER>/<REPO>`
|
Full verification suite re-run green (below).
|
||||||
with the placeholder labeled as such (repo has no git remote configured, so the instance URL
|
|
||||||
couldn't be auto-detected). Forgejo Actions CI badge left as a commented placeholder. Full
|
|
||||||
verification suite re-run green (docs-only change): cargo 64, npm 65, clippy/fmt/typecheck/
|
|
||||||
lint/format/builds clean.
|
|
||||||
- `dbc0974` "Add open-source README and MIT license" — `README.md` rewritten for a technical /
|
|
||||||
open-source audience (badges, feature list incl. compose preview + media, tech-stack tables,
|
|
||||||
quick start + `.env` example, CLI/IPC usage, screenshots placeholders, dev guide, contributing,
|
|
||||||
community, license). New `LICENSE` (MIT, Copyright (c) 2026 Avi). Docs-only change; full
|
|
||||||
verification suite re-run green anyway (cargo 64, npm 65, all lint/format/builds clean).
|
|
||||||
- `46d5da7` "Point checkpoint at current HEAD" — checkpoint aligned with HEAD before the docs work.
|
|
||||||
- `9ec01c9` "Add AGENTS.md checkpoint workflow rule" — new `AGENTS.md` records that every addition
|
|
||||||
must end with an up-to-date checkpoint.
|
|
||||||
- `a052851` "Refresh checkpoint with preview image/link fixes"
|
|
||||||
- `d677125` "Show preview images and link cards in Compose" — two fixes from GUI testing:
|
|
||||||
1. Remote images never rendered because the CSP was `img-src 'self' data:` — added `https:`
|
|
||||||
(`frontend/index.html`). Attached images and pasted image URLs now appear in the preview.
|
|
||||||
2. Link previews: a pasted web-page link (not a direct image URL) now fetches the page in the
|
|
||||||
Electron main process (`link_preview` IPC) and shows an OpenGraph/Twitter card (image, title,
|
|
||||||
description, site, hostname) in the preview. New `src/lib/media.ts::extractLinkUrls`,
|
|
||||||
`LinkPreview` type, `api.linkPreview`, `AppProvider.linkPreview`, `LinkPreviewCard` in
|
|
||||||
ComposeScreen, `styles.css` card styles. Publish is unchanged — link cards are a preview-only
|
|
||||||
feature (Nostr clients render their own link previews from the URL in the note).
|
|
||||||
Tests: `media.test.ts` (new), `ComposeScreen.test.tsx`, `apiMock.ts`, `fakeBackend.ts`.
|
|
||||||
- `03f6877` "Sign NIP-98 auth for nostr.build image uploads" — fixes the attach-image flow, which
|
|
||||||
failed with "Unauthorized, please provide a valid nip-98 token". nostr.build now requires a
|
|
||||||
NIP-98 `Authorization` header. Backend: new `src/uploads.rs` (`nip98_authorization` signs a kind
|
|
||||||
27235 auth event with the active profile's key and returns `Nostr <base64>`), IPC method
|
|
||||||
`upload_auth { url, http_method }`, `nostr-sdk` feature `nip98` added. Electron:
|
|
||||||
`frontend/electron/main.ts` fetches the header from the backend and sends it on the upload.
|
|
||||||
Verified live: throwaway profile → IPC `upload_auth` → `curl` upload to nostr.build returned
|
|
||||||
`status: success` (temp data cleaned up).
|
|
||||||
- `3e3467b` "Add compose preview with image attachments (NIP-92 imeta)" — 11 files, 1 new.
|
|
||||||
Backend: `src/publish.rs` (`extract_image_urls`, `image_mime_from_url`, `image_tags` + tests).
|
|
||||||
Electron: `frontend/electron/main.ts` (`pickImage`, `uploadImage`, routed from `backend:request`).
|
|
||||||
Renderer: `frontend/src/lib/media.ts` (new), `api.ts`, `types.ts`, `AppProvider.tsx`,
|
|
||||||
`ComposeScreen.tsx`, `styles.css`. Tests: `ComposeScreen.test.tsx`, `apiMock.ts`, `fakeBackend.ts`
|
|
||||||
(adds request recording + `pick_image`/`upload_image`).
|
|
||||||
- `17e8ace` "Clarify the Profiles subtitle about private keys" — `ProfilesScreen.tsx` copy.
|
|
||||||
- `8eb6685` "Add reveal-secret-key after unlock (CLI + GUI)" — previous feature, see history.
|
|
||||||
|
|
||||||
## How it was verified (all green)
|
## How it was verified (all green)
|
||||||
|
|
||||||
```
|
```
|
||||||
cargo test # 64 passed
|
cargo test # 70 passed
|
||||||
cargo clippy --all-targets # clean
|
cargo clippy --all-targets # clean
|
||||||
cargo fmt --check # clean
|
cargo fmt --check # clean
|
||||||
cargo build --release # builds (rebuilt so the GUI runs the new backend)
|
cargo build --release # builds (rebuilt so the GUI runs the new backend)
|
||||||
npm run typecheck # clean (frontend/)
|
npm run typecheck # clean (frontend/)
|
||||||
npm run lint # clean (pre-existing module warning only)
|
npm run lint # clean (pre-existing module warning only)
|
||||||
npm run format:check # clean
|
npm run format:check # clean
|
||||||
npm test # 65 passed (12 files)
|
npm test # 69 passed (13 files)
|
||||||
npm run electron:build # compiles the Electron main process
|
npm run electron:build # compiles the Electron main process
|
||||||
npm run build # rebuilds the React bundle (dist/)
|
npm run build # rebuilds the React bundle (dist/)
|
||||||
```
|
```
|
||||||
|
|
||||||
Plus live checks: a throwaway-profile `upload_auth` + `curl` upload to nostr.build returned
|
CLI smoke checks run earlier in the session: `signer status` printed the active profile + relays;
|
||||||
`status: success`; the link-preview parser (same code as Electron) fetched Wikipedia's og:title +
|
a `bunker://` link and a non-`nostrconnect` URI were both rejected with clear errors.
|
||||||
og:image and example.com's title correctly. Temp files cleaned up.
|
|
||||||
|
|
||||||
## How to resume
|
## How to resume
|
||||||
|
|
||||||
1. Open the repo: `cd /home/avi/Projects/skills/nost-feed-manager`
|
1. Open the repo: `cd /home/avi/Projects/skills/nost-feed-manager`
|
||||||
2. State is committed: `git status` should be clean; `git log --oneline -5` shows the three commits.
|
2. State is committed: `git status` should be clean; `git log --oneline -5` shows
|
||||||
3. To try it:
|
`73cb08d` at the top.
|
||||||
- GUI: `cd frontend && npm start` — Compose → "Attach image" or paste an image URL → Preview tab.
|
3. To try the signer:
|
||||||
(Note: `npm start` alone does NOT rebuild the React bundle — run `npm run build` first, or use
|
- GUI: `cd frontend && npm start`, then the **Signer** screen in the sidebar. Paste a
|
||||||
`npm run dev` + `NOSTR_GUI_DEV_URL=http://localhost:5173 npm start` for live reload.)
|
`nostrconnect://` link from a Nostr app to sign for it. (Note: `npm start` alone does NOT
|
||||||
- CLI: `cargo build --release`, then `./target/release/nostr-manager-backend list` and
|
rebuild the React bundle — run `npm run build` first, or use `npm run dev` +
|
||||||
`./target/release/nostr-manager-backend show-secret <npub>` (no angle brackets).
|
`NOSTR_GUI_DEV_URL=http://localhost:5173 npm start` for live reload.)
|
||||||
|
- CLI: `cargo build --release`, then `./target/release/nostr-manager-backend signer status`.
|
||||||
4. Re-run verification with the commands above.
|
4. Re-run verification with the commands above.
|
||||||
|
|
||||||
## Outstanding / next steps (if you continue)
|
## Outstanding / next steps (if you continue)
|
||||||
|
|
||||||
- Nothing uncommitted. Possible follow-ups: a real feed view (currently Home only shows the last
|
- Nothing uncommitted. Possible follow-ups: a real feed view (currently Home only shows the last
|
||||||
publication); the upload uses the active profile's key for NIP-98 (so an encrypted, locked vault
|
publication); a NIP-46 **client** role (initiating `bunker://` from this app is intentionally
|
||||||
needs unlocking before attaching an image — the attach error explains this); relay defaults in code
|
not implemented — only the signer role is). Live cross-client verification against an actual
|
||||||
still point at `relay.damus.io` (503 upstream) and `relay.nostr.band` (timeout) — user's local
|
Nostr app (e.g. a NIP-46-compatible client that produces a `nostrconnect://` link) has not yet
|
||||||
settings already use `nos.lol` + `relay.primal.net` instead.
|
been done end-to-end, only the unit and CLI smoke tests.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue