โ˜ฟ๏ธ mercury.js - signed HTTP

mercury.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.

<script src="fixi.js"></script>
<script src="helium.js"></script>
<script src="mercury.js"></script>

<button fx-action="/api/like" fx-method="post" nhg-sign>Like</button>
<input type="file" onchange="mercury.upload('https://blossom.example', this.files[0]).then((b)=>console.log(b.url))">

The 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.

Attributes

attribute description
nhg-sign fixi requests from this element or its descendants are NIP-98 signed

window.nhgCfg = {ttl} sets how long Blossom tokens stay valid (300 seconds).

Events

Dispatched on the element involved (opts.elt), else document, or globalThis outside a browser.

event cancel to detail
nhy:config cfg (elt, event, sign): the family signing hook, where signers set cfg.sign
nhg:sign abort the request (rejects with AbortError) cfg, just before cfg.sign(cfg.event)
nhg:error error from signing, the network, or an error status (verbs and Blossom helpers)

API

function returns
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
mercury.fetch(url, init?) Promise of the Response of a NIP-98 signed fetch, whatever its status
mercury.upload(server, blob, {media}?) Blossom blob descriptor {url, sha256, size, type, uploaded}, via PUT /upload or, with media, PUT /media
mercury.list(server, pubkey) descriptors from GET /list/<pubkey>
mercury.remove(server, sha256) true after DELETE /<sha256>
mercury.sign(event, elt?) "Nostr <base64>" for any unsigned event, through the signing hook

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

Notes

Every 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.

mercury.js (3.5/1.5kb raw/brotli), prefix nhg-

← ๐Ÿฅ‡ gold๐ŸŽจ cerium →