1# 🔌 chlorine.js - remote signing23chlorine.js is a miniature NIP-46 client: it talks to a signer that lives elsewhere, a bunker or a phone app, over relays. It connects with a `bunker://` URI or by showing a `nostrconnect://` URI for the signer to scan, and remembers the session so reloads reconnect without a new handshake. It composes by wearing the NIP-07 face: once connected, `window.nostr` is chlorine's signer, so xenon.js publishes through it, helium.js steps aside, and every other nobel library just works.45```html6<script src="helium.js"></script>7<script src="fluorine.js"></script>8<script src="chlorine.js"></script>9<script src="xenon.js"></script>1011<form ncl-connect><input name="bunker" placeholder="bunker://..."><button>Connect</button></form>12<form nhy-relay="wss://relay.example" nxe-publish><input name="content"><button>Post</button></form>13```1415Pasting a bunker URI connects to the remote signer, and from then on the post form's events are signed by it.1617## Attributes1819| attribute | description |20|---|---|21| `ncl-connect` | A form that connects on submit, using its `bunker` field |22| `ncl-nostrconnect` | On load (when not connected), gets a `nostrconnect://` URI as its `href` (links) or text, and connects when a signer answers |23| `ncl-relay` | Space-separated relays for `ncl-nostrconnect`, from the closest ancestor. Default `nclCfg.relays` |24| `ncl-name`, `ncl-perms` | App name (default the page title) and requested permissions in the `nostrconnect://` URI |25| `ncl-disconnect` | Clicking it disconnects |26| `ncl-connected` | Set on `<html>` to the user's pubkey while connected |2728`window.nclCfg = {relays, timeout}` sets default relays for `listen` and the request timeout (30000 ms).2930## Events3132Dispatched on `document` (or the form or link involved), or on `globalThis` outside a browser.3334| event | cancel to | detail |35|---|---|---|36| `ncl:listen` | set the `href` or text yourself (to draw a QR code) | `uri` |37| `ncl:connected` | | `pubkey` (also fired when a stored session is restored) |38| `ncl:auth` | skip `window.open(url)` | `url` the signer asks the user to visit. The request keeps waiting |39| `ncl:error` | | `error` from a form or link connection |40| `ncl:disconnected` | | |4142## API4344| function | returns |45|---|---|46| `chlorine.connect(bunkerUri)` | Promise of the user's pubkey. Sends `connect` with the URI's secret, then `get_public_key` |47| `chlorine.listen({relays, name, perms, ...})` | `{uri, done}`: a `nostrconnect://` URI (extra keys become query parameters) and a Promise of the user's pubkey once a signer answers with the secret |48| `chlorine.disconnect()` | forgets the session and restores the previous `window.nostr` |49| `chlorine.rpc(method, params)` | Promise of the result of any NIP-46 method, such as `ping` |50| `chlorine.signer` | the NIP-07 face `{getPublicKey, signEvent, nip44, nip04}` set as `window.nostr` |51| `chlorine.previous` | the `window.nostr` that was there before |52| `chlorine.session` | `{sk, pk, remote, relays, secret, user}` or `null` |5354Each signature, encryption and decryption is a relay round trip to the signer, so expect it to take a moment rather than be instant. The session, including the throwaway client key, is kept in `localStorage` under `ncl:session`. Requires helium.js and fluorine.js. Tested against `nak bunker` (both flows) on tinyrelay and the dev relay.