clone

tiny dependency-free nostr libraries for plain html, each under 5kb and named for an element. public domain

main
nobel / mercury / README.md
README.md 3.4K rendered
1# ☿️ mercury.js - signed HTTP23mercury.js is a miniature rexi.js with a signature on every request: each call carries a NIP-98 `Authorization` header over its URL, method and body hash, and Blossom helpers sign kind 24242 tokens to upload, list and delete blobs. It composes with fixi.js through `fx:config`, so any fixi request under `nhg-sign` is signed, and with every nobel signer through `nhy:config`, so helium.js, chlorine.js or a NIP-07 extension does the signing. It also runs in Deno, where it signs with `globalThis.nostr` or helium.js.45```html6<script src="fixi.js"></script>7<script src="helium.js"></script>8<script src="mercury.js"></script>910<button fx-action="/api/like" fx-method="post" nhg-sign>Like</button>11<input type="file" onchange="mercury.upload('https://blossom.example', this.files[0]).then((b)=>console.log(b.url))">12```1314The button's request reaches `/api/like` with a NIP-98 header proving who clicked it, and the file input stores the chosen file on a Blossom server and logs its URL.1516## Attributes1718| attribute | description |19|---|---|20| `nhg-sign` | fixi requests from this element or its descendants are NIP-98 signed |2122`window.nhgCfg = {ttl}` sets how long Blossom tokens stay valid (300 seconds).2324## Events2526Dispatched on the element involved (`opts.elt`), else `document`, or `globalThis` outside a browser.2728| event | cancel to | detail |29|---|---|---|30| `nhy:config` | | `cfg` (`elt`, `event`, `sign`): the family signing hook, where signers set `cfg.sign` |31| `nhg:sign` | abort the request (rejects with `AbortError`) | `cfg`, just before `cfg.sign(cfg.event)` |32| `nhg:error` | | `error` from signing, the network, or an error status (verbs and Blossom helpers) |3334## API3536| function | returns |37|---|---|38| `mercury.get/head/post/put/patch/del(url, body?, opts?)` | Promise of the `Response`, with `.json()` and `.text()`. Rejects on non-2xx with `status` and `response` |39| `mercury.fetch(url, init?)` | Promise of the `Response` of a NIP-98 signed `fetch`, whatever its status |40| `mercury.upload(server, blob, {media}?)` | Blossom blob descriptor `{url, sha256, size, type, uploaded}`, via `PUT /upload` or, with `media`, `PUT /media` |41| `mercury.list(server, pubkey)` | descriptors from `GET /list/<pubkey>` |42| `mercury.remove(server, sha256)` | `true` after `DELETE /<sha256>` |43| `mercury.sign(event, elt?)` | `"Nostr <base64>"` for any unsigned event, through the signing hook |4445Like rexi, `get`, `head` and `del` put `body` in the query string, and the others send plain objects and arrays as JSON, forms as `FormData`, and anything `fetch` accepts as is. `opts` are `fetch` options plus `elt`, the element whose events and signer apply. Relative URLs resolve against the page before signing. A request that already has an `Authorization` header is sent unchanged. Blossom options also take `server`, the domain in the token's `server` tag (the server URL's host).4647## Notes4849Every request is a new signature, so a NIP-07 extension or a remote signer may prompt or take a moment each time. The body is serialized once before signing, `FormData` included, and those exact bytes are hashed into the `payload` tag and sent, so servers can always check it. Streams are read into memory first. NIP-98 tokens show the full URL, including the query, to anyone who sees the header, and are good for 60 seconds on most servers. Tested against nostr-tools' NIP-98 validator, a minimal Blossom server, fixi in Chromium, and tinyrelay's NIP-86 management API and Blossom store.