clone

nostr relays on demand at <name>.bind.ws: claim one with your key, pay in zaps past the free allowance

scripts: build, check, dev and ops folders, so a generator, a typecheck guard, a dev fixture and the weekly report are told apart by their path; zaptest gets the header it lacked

Dami committed b865a74
commit
b865a74eaeb1afb6a893a78fd65b8e78e6ffba5d
parent
82984df8d92a1fb31a8ae245810c16b6a75d5562
author
Dami <dami.etoile@gmail.com>
20 files changed, +38 -33
M.github/workflows/margin.yml2+-
Mdocs/11-hosting-bindws.md4++--
Mdocs/12-develop-extend.md16+++++++++-------
Mdocs/15-costs-and-margins.md6+++---
Mdocs/16-hosting-without-cloudflare.md2+-
Mpackage.json8++++----
Rscripts/build-console.mjs → scripts/build/build-console.mjs8++++----
Rscripts/build-signer.mjs → scripts/build/build-signer.mjs6+++---
Rscripts/signer-entry.js → scripts/build/signer-entry.js2+-
Rscripts/check-celld.mjs → scripts/check/check-celld.mjs4++--
Rscripts/check-console.mjs → scripts/check/check-console.mjs2+-
Rscripts/dev-signer.mjs → scripts/dev/dev-signer.mjs0
Rscripts/junk.mjs → scripts/dev/junk.mjs2+-
Rscripts/seed.mjs → scripts/dev/seed.mjs0
Rscripts/shot.mjs → scripts/dev/shot.mjs0
Rscripts/stage.mjs → scripts/dev/stage.mjs0
Rscripts/zaptest.mjs → scripts/dev/zaptest.mjs3+++
Rscripts/margin.mjs → scripts/ops/margin.mjs2+-
Msrc/gen/console.ts2+-
Msrc/gen/signer.ts2+-
M .github/workflows/margin.yml +1 -1
M docs/11-hosting-bindws.md +2 -2
@@ -22,7 +22,7 @@ Edit `wrangler.jsonc`:
2222 | `LEASE_DAYS` | how long a temporary relay from `POST /lease` lives; 14 if unset |2323 | `ratelimits` | how many leases a minute, per address and overall; the defaults are 5 and 60 |2424 25-The prices carry a comment with the Cloudflare rates they were set from. `node scripts/margin.mjs` compares them with those rates at the current bitcoin price, and a weekly workflow does the same; see [Costs and margins](15-costs-and-margins.md).25+The prices carry a comment with the Cloudflare rates they were set from. `node scripts/ops/margin.mjs` compares them with those rates at the current bitcoin price, and a weekly workflow does the same; see [Costs and margins](15-costs-and-margins.md).2626 2727 Point `routes` at your zone: the apex as a custom domain and `*.<domain>/*` as a zone route. Create the R2 bucket named in `r2_buckets`.2828
@@ -122,4 +122,4 @@ npm run dev
122122 123123 `wrangler dev --env dev` serves `http://<name>.localhost:8787` as the relay named `<name>`, `http://<domain>.localhost:8787` as the apex and anything else as `DEV_RELAY`. The dev environment has no routes, so hostnames pass through.124124 125-To use the console without a browser extension, run `node scripts/dev-signer.mjs` and paste the snippet from that file into the devtools console before clicking sign in. `node scripts/seed.mjs <name>` publishes sample events.125+To use the console without a browser extension, run `node scripts/dev/dev-signer.mjs` and paste the snippet from that file into the devtools console before clicking sign in. `node scripts/seed.mjs <name>` publishes sample events.
M docs/12-develop-extend.md +9 -7
@@ -49,9 +49,11 @@ test/
4949 helpers/ the socket client, the management call, media tokens, the QR reader5050 conformance/ black-box suite for any relay URL, one file per NIP, files included5151 scripts/52- dev-signer.mjs seed.mjs junk.mjs zaptest.mjs shot.mjs53- build-signer.mjs signer-entry.js bundle nostr-tools for the console's remote signing54- check-celld.mjs wrangler.celld.jsonc stays in step with wrangler.jsonc52+ build/ build-signer.mjs signer-entry.js bundle nostr-tools for the console's remote signing53+ build-console.mjs fold src/console into src/gen/console.ts54+ check/ check-console.mjs check-celld.mjs run by npm run typecheck55+ dev/ dev-signer.mjs seed.mjs stage.mjs junk.mjs shot.mjs zaptest.mjs for a dev relay56+ ops/ margin.mjs fuel prices against Cloudflare's rates, weekly in CI5557 wrangler.jsonc the Worker on Cloudflare5658 wrangler.celld.jsonc the same Worker on celld (docs/16)5759 ```
@@ -98,19 +100,19 @@ A NIP that changes the query surface touches `store.ts`. Add the number to `SUPP
98100 99101 ## The signer bundle100102 101-Remote signing needs NIP-44 and secp256k1, which browsers do not ship. `scripts/signer-entry.js` imports the pieces of nostr-tools the console uses and `npm run build:signer` bundles them with esbuild into `src/gen/signer.ts`, a string the relay serves at `/signer.js`. The console loads it only when someone picks a remote signer. The generated file is committed; `npm run typecheck` fails when it is stale.103+Remote signing needs NIP-44 and secp256k1, which browsers do not ship. `scripts/build/signer-entry.js` imports the pieces of nostr-tools the console uses and `npm run build:signer` bundles them with esbuild into `src/gen/signer.ts`, a string the relay serves at `/signer.js`. The console loads it only when someone picks a remote signer. The generated file is committed; `npm run typecheck` fails when it is stale.102104 103105 ## The console104106 105-The console is three ordinary files in `src/console`: `console.html` (the body), `console.css` and `console.js`. `npm run build:console` folds them into `src/gen/console.ts`, the strings the relay serves; `npm run dev` runs it first, and `npm run typecheck` fails when the generated file is stale. It is a generated module rather than a text-module rule in `wrangler.jsonc` because celld deploy refuses a config with `rules`. `scripts/check-console.mjs` runs the script's synchronous start against a stub document, so a handler wired above the helper it calls fails typecheck instead of a live page. The shared shell in `ui.ts` provides fonts, colors, buttons, inputs and the sticker vocabulary; keep new pages inside it.107+The console is three ordinary files in `src/console`: `console.html` (the body), `console.css` and `console.js`. `npm run build:console` folds them into `src/gen/console.ts`, the strings the relay serves; `npm run dev` runs it first, and `npm run typecheck` fails when the generated file is stale. It is a generated module rather than a text-module rule in `wrangler.jsonc` because celld deploy refuses a config with `rules`. `scripts/check/check-console.mjs` runs the script's synchronous start against a stub document, so a handler wired above the helper it calls fails typecheck instead of a live page. The shared shell in `ui.ts` provides fonts, colors, buttons, inputs and the sticker vocabulary; keep new pages inside it.106108 107-To look at what you changed: `npm run dev`, then `node scripts/dev-signer.mjs`, open `http://<name>.localhost:8787/`, paste the `window.nostr` snippet from that script into the devtools console, and sign in or claim. Playwright's CLI can drive the same loop: open the page, inject the snippet with `eval`, click, screenshot at a phone width and at a desktop width. `scripts/shot.mjs` renders a page over the Chrome debugging protocol when Playwright is not around.109+To look at what you changed: `npm run dev`, then `node scripts/dev/dev-signer.mjs`, open `http://<name>.localhost:8787/`, paste the `window.nostr` snippet from that script into the devtools console, and sign in or claim. Playwright's CLI can drive the same loop: open the page, inject the snippet with `eval`, click, screenshot at a phone width and at a desktop width. `scripts/dev/shot.mjs` renders a page over the Chrome debugging protocol when Playwright is not around.108110 109111 Rules that hold across pages: no middle dots, no purposeless subtext, tables scroll inside their card on narrow screens, decorative elements are not selectable, a multi-field form uses the labelled grid rather than one row of inputs, and the copy above a block is one sentence.110112 111113 ## Screenshots112114 113-`node scripts/shot.mjs <url> <out.png> [width] [height]` renders a page with phone or desktop emulation over the Chrome debugging protocol and prints the layout's scroll width, which should equal the viewport width.115+`node scripts/dev/shot.mjs <url> <out.png> [width] [height]` renders a page with phone or desktop emulation over the Chrome debugging protocol and prints the layout's scroll width, which should equal the viewport width.114116 115117 ## Pull requests116118
M docs/15-costs-and-margins.md +3 -3
M docs/16-hosting-without-cloudflare.md +1 -1
M package.json +4 -4
R scripts/build-console.mjs → scripts/build/build-console.mjs +4 -4
R scripts/build-signer.mjs → scripts/build/build-signer.mjs +3 -3
R scripts/signer-entry.js → scripts/build/signer-entry.js +1 -1
R scripts/check-celld.mjs → scripts/check/check-celld.mjs +2 -2
R scripts/check-console.mjs → scripts/check/check-console.mjs +1 -1
R scripts/dev-signer.mjs → scripts/dev/dev-signer.mjs +0 -0

No content changes.

R scripts/junk.mjs → scripts/dev/junk.mjs +1 -1
R scripts/seed.mjs → scripts/dev/seed.mjs +0 -0

No content changes.

R scripts/shot.mjs → scripts/dev/shot.mjs +0 -0

No content changes.

R scripts/stage.mjs → scripts/dev/stage.mjs +0 -0

No content changes.

R scripts/zaptest.mjs → scripts/dev/zaptest.mjs +3 -0
R scripts/margin.mjs → scripts/ops/margin.mjs +1 -1
M src/gen/console.ts +1 -1
@@ -1,4 +1,4 @@
1-// Generated by scripts/build-console.mjs from src/console. Do not edit; run npm run build:console.1+// Generated by scripts/build/build-console.mjs from src/console. Do not edit; run npm run build:console.22 export const CONSOLE_HTML = "<main>\n <header class=\"mast\">\n <div class=\"banner hidden\" id=\"banner\"><img id=\"bannerimg\" alt=\"\"></div>\n <div class=\"wordmark\"><img id=\"iconimg\" class=\"icon hidden\" alt=\"\"><h1 id=\"title\"></h1></div>\n <div class=\"left\"><div class=\"care\" id=\"care\"></div></div>\n <div class=\"right\">\n <span class=\"urlrow\"><code class=\"pill\" id=\"url\"></code><button class=\"ib\" id=\"copy\" title=\"Copy relay URL\" aria-label=\"Copy relay URL\"><svg viewBox=\"0 0 24 24\"><rect x=\"9\" y=\"9\" width=\"11\" height=\"11\" rx=\"2\"/><path d=\"M5 15V6a2 2 0 0 1 2-2h9\"/></svg></button></span>\n <span class=\"owner\"><i class=\"av hidden\" id=\"owner-av\"></i><span id=\"ownerline\"></span><em id=\"who\"></em>\n <button id=\"signin\" class=\"btn hidden\">Sign in with extension</button>\n <button id=\"signin46\" class=\"btn hidden\">Remote signer</button>\n <button id=\"signout\" class=\"ib hidden\" title=\"Sign out\" aria-label=\"Sign out\"><svg viewBox=\"0 0 24 24\"><path d=\"M10 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h4M15 8l5 4-5 4M20 12H9\"/></svg></button>\n </span>\n </div>\n </header>\n\n <section id=\"unclaimed\" class=\"hidden\">\n <h2>Nobody owns this relay yet.</h2>\n <p class=\"lead\">Claim it and it's yours: you decide who can post, who can read, and what stays. One signature with a nostr browser extension; no account, no email, no card.</p>\n <div class=\"row\"><button id=\"claim\" class=\"btn pri\">Claim this relay</button><button class=\"btn remote\" data-note=\"claimnote\">Use a remote signer</button><span class=\"note\" id=\"claimnote\"></span></div>\n </section>\n\n <section id=\"leased\" class=\"hidden\">\n <h2>A temporary relay, for now.</h2>\n <p class=\"lead\">Anyone can read and write here until <b id=\"lease-until\"></b>. Then everything on it is deleted and the name is freed. Claim it and it stays, events and files included: one signature with a nostr browser extension. Or claim a new name and pull this one into it from its Storage tab.</p>\n <div class=\"row\"><button id=\"claimlease\" class=\"btn pri\">Claim this relay</button><button class=\"btn remote\" data-note=\"leasenote\">Use a remote signer</button><span class=\"note\" id=\"leasenote\"></span></div>\n </section>\n\n <section id=\"remotesec\" class=\"hidden\">\n <h2>Connect a remote signer.</h2>\n <p class=\"lead\">Your key stays in a signer app such as Amber or nsec.app; this page asks it to sign. On a phone, open the link and approve there. On a computer, paste the <span class=\"mono\">bunker://</span> URL the app gives you.</p>\n <div class=\"row\"><a id=\"nclink\" class=\"btn pri\" href=\"#\">Open in your signer app</a><button id=\"nccopy\" class=\"btn\">Copy link</button><span class=\"note\" id=\"ncnote\"></span></div>\n <form id=\"bunkerform\" class=\"addrow\"><input class=\"txt mono\" name=\"url\" placeholder=\"bunker://…\" required><button class=\"btn\">Connect</button></form>\n <img id=\"ncqr\" class=\"hidden\" alt=\"QR code of the signer link\" width=\"180\" height=\"180\">\n <p class=\"note\" id=\"remotenote\"></p>\n </section>\n\n <section id=\"foldsec\" class=\"folds\">\n <div id=\"frontsec\" class=\"hidden\">\n <p class=\"lead hidden\" id=\"desc\"></p>\n <p class=\"metarow hidden\" id=\"metarow\"></p>\n </div>\n <details class=\"disclosure\" id=\"aboutsec\" name=\"page\"><summary>About, for clients</summary><div id=\"about\" class=\"about\"></div></details>\n <details class=\"disclosure\" id=\"connectsec\" name=\"page\"><summary>Connect</summary>\n <div class=\"doors\">\n <div class=\"door\"><small>nostr relay</small><div class=\"v\"><span id=\"d-ws\"></span><button class=\"ib\" data-copy=\"ws\" title=\"Copy\" aria-label=\"Copy\"><svg viewBox=\"0 0 24 24\"><rect x=\"9\" y=\"9\" width=\"11\" height=\"11\" rx=\"2\"/><path d=\"M5 15V6a2 2 0 0 1 2-2h9\"/></svg></button></div></div>\n <div class=\"door\"><small>Blossom media</small><div class=\"v\"><span id=\"d-blossom\"></span><button class=\"ib\" data-copy=\"blossom\" title=\"Copy\" aria-label=\"Copy\"><svg viewBox=\"0 0 24 24\"><rect x=\"9\" y=\"9\" width=\"11\" height=\"11\" rx=\"2\"/><path d=\"M5 15V6a2 2 0 0 1 2-2h9\"/></svg></button></div></div>\n <div class=\"door\"><small>names</small><div class=\"v\"><span id=\"d-nip05\"></span></div></div>\n <div class=\"door\"><small>HTTP bridge, NIP-98</small><div class=\"v\"><span>POST /events, /query, /count</span></div></div>\n </div>\n <div class=\"block\">\n <h3>Open it in an app</h3>\n <p class=\"note\">Relay apps open this relay as a place. Feed apps do not: they find the owner here through a profile link that carries this relay as the hint, and learn the relay from that. Either way, add <span class=\"mono\" id=\"apps-ws\"></span> under the app's relay settings to post here.</p>\n <div class=\"apps\" id=\"apps\"></div>\n <div class=\"phones\" id=\"phones\"></div>\n </div>\n </details>\n </section>\n\n <section id=\"peoplesec\" class=\"hidden\">\n <h2>People <span class=\"count\" id=\"people-count\"></span></h2>\n <p class=\"note hidden\" id=\"people-note\">Hidden from visitors. Only you see this list.</p>\n <div class=\"dir\" id=\"dir\"></div>\n </section>\n\n <section id=\"fuelsec\" class=\"hidden\">\n <h2>Fuel</h2>\n <p class=\"lead\">Free allowance first; past it, usage burns sats. Anyone can zap a top-up.</p>\n <div class=\"gauges\">\n <div class=\"gauge\"><small>Events stored</small><div class=\"bar\"><i id=\"g-events\"></i><b id=\"p-events\"></b></div><span id=\"t-events\"></span></div>\n <div class=\"gauge\"><small>Files stored</small><div class=\"bar\"><i id=\"g-media\"></i><b id=\"p-media\"></b></div><span id=\"t-media\"></span></div>\n <div class=\"gauge\"><small>Awake this month</small><div class=\"bar\"><i id=\"g-active\"></i><b id=\"p-active\"></b></div><span id=\"t-active\"></span></div>\n <div class=\"gauge\"><small>Rows written this month</small><div class=\"bar\"><i id=\"g-rows\"></i><b id=\"p-rows\"></b></div><span id=\"t-rows\"></span></div>\n </div>\n <p class=\"balance\" id=\"fuel-balance\"></p>\n <form id=\"topup\" class=\"topup hidden\"><span class=\"sats\"><input type=\"number\" name=\"sats\" min=\"1\" value=\"1000\"><span>sats</span></span><button class=\"btn pri\">&#9889; Zap to top up</button><span class=\"note\" id=\"topup-note\"></span></form>\n <div id=\"invoice\" class=\"invoice hidden\">\n <p>Pay from any lightning wallet. The receipt lands on this relay and credits it automatically.</p>\n <div class=\"row\"><a id=\"inv-link\" class=\"btn\" href=\"#\">Open in wallet</a><button id=\"inv-copy\" class=\"ib\" title=\"Copy invoice\" aria-label=\"Copy invoice\"><svg viewBox=\"0 0 24 24\"><rect x=\"9\" y=\"9\" width=\"11\" height=\"11\" rx=\"2\"/><path d=\"M5 15V6a2 2 0 0 1 2-2h9\"/></svg></button><span class=\"state\" id=\"inv-state\"></span></div>\n <textarea id=\"inv-text\" readonly></textarea>\n </div>\n </section>\n\n <section id=\"myinvites\" class=\"hidden\">\n <h2>Your invites</h2>\n <p class=\"lead\">The owner lets members bring people in. Each link admits one person and lasts three days.</p>\n <div class=\"row\"><button id=\"mintmine\" class=\"btn pri\">New invite link</button><span class=\"note\" id=\"minenote\"></span></div>\n <ul id=\"mine\" class=\"plain\"></ul>\n </section>\n\n\n <div class=\"console hidden\" id=\"console\">\n <nav class=\"tabs\" id=\"tabs\">\n <a href=\"#people\" data-tab=\"people\">People<span class=\"count\" id=\"tc-people\"></span></a>\n <a href=\"#moderation\" data-tab=\"moderation\">Moderation<span class=\"count\" id=\"tc-reports\"></span></a>\n <a href=\"#rules\" data-tab=\"rules\">Rules</a>\n <a href=\"#identity\" data-tab=\"identity\">Identity</a>\n <a href=\"#data\" data-tab=\"data\">Data</a>\n <a href=\"#sync\" data-tab=\"sync\">Sync</a>\n <a href=\"#views\" data-tab=\"views\">Views</a>\n <a href=\"#health\" data-tab=\"health\">Health</a>\n <a href=\"#owner\" data-tab=\"owner\">Owner</a>\n </nav>\n\n <section class=\"panel\" data-panel=\"people\">\n <h2>People</h2>\n <p class=\"lead\">The member list is published as a signed roster; a name makes someone <span class=\"mono\" id=\"nip05-example\"></span>.</p>\n <div class=\"block\">\n <form id=\"addmember\" class=\"addrow\"><input class=\"txt mono\" name=\"pubkey\" placeholder=\"hex pubkey or npub\" required><input class=\"txt\" name=\"name\" placeholder=\"name (optional)\" pattern=\"[a-z0-9._-]{1,64}\" style=\"max-width:9rem\"><input class=\"txt\" name=\"note\" placeholder=\"note\" style=\"max-width:9rem\"><button class=\"ib pri\" title=\"Add member\" aria-label=\"Add member\"><svg viewBox=\"0 0 24 24\"><path d=\"M12 5v14M5 12h14\"/></svg></button></form>\n <div class=\"scroll\"><table id=\"members\" class=\"people-table\"><thead><tr><th>Who</th><th>Name</th><th>Note</th><th>Limits</th><th>Joined</th><th></th></tr></thead><tbody></tbody></table></div>\n </div>\n <div class=\"block\">\n <div class=\"two\">\n <div>\n <h3>Invites</h3>\n <form id=\"mintinvite\" class=\"addrow\"><select class=\"txt\" name=\"ttl\" style=\"max-width:8rem\"><option value=\"3600\">1 hour</option><option value=\"86400\">1 day</option><option value=\"259200\" selected>3 days</option><option value=\"2592000\">30 days</option></select><input class=\"txt num\" type=\"number\" name=\"max\" min=\"0\" value=\"0\" title=\"uses, 0 = unlimited\" style=\"width:5rem;flex:0 0 auto\"><input class=\"txt\" name=\"note\" placeholder=\"note\"><button class=\"ib pri\" title=\"New invite\" aria-label=\"New invite\"><svg viewBox=\"0 0 24 24\"><path d=\"M12 5v14M5 12h14\"/></svg></button></form>\n <ul id=\"invites\" class=\"plain\"></ul>\n <form id=\"treeform\" class=\"addrow hidden\" title=\"Members invite members: how many hops from you the tree reaches, and how many live invites each member may hold\"><span class=\"dim\">members invite</span><input class=\"txt num\" type=\"number\" name=\"depth\" min=\"0\" max=\"10\" value=\"0\" style=\"width:4rem;flex:0 0 auto\"><span class=\"dim\">hops deep,</span><input class=\"txt num\" type=\"number\" name=\"quota\" min=\"0\" max=\"100\" value=\"0\" style=\"width:4rem;flex:0 0 auto\"><span class=\"dim\">each</span><button class=\"btn\">Save</button></form>\n </div>\n <div>\n <h3>Joining</h3>\n <form id=\"joinform\">\n <div class=\"form\">\n <label class=\"wide\"><span>Shown before someone accepts an invite</span><textarea name=\"joinTerms\" placeholder=\"House rules, expectations, anything a newcomer should agree to.\" maxlength=\"20000\"></textarea></label>\n <label class=\"wide switch\"><input type=\"checkbox\" name=\"directoryPublic\"> Show the people directory to visitors</label>\n </div>\n <div class=\"actions\"><button class=\"btn pri\">Save</button></div>\n </form>\n </div>\n </div>\n </div>\n </section>\n\n <section class=\"panel\" data-panel=\"moderation\">\n <h2>Moderation</h2>\n <p class=\"lead\">Reports never show in the feed. Banning also deletes the reported thing.</p>\n <div class=\"block\">\n <h3>Reports <span class=\"count\" id=\"reports-count\"></span></h3>\n <form id=\"thresholdform\" class=\"addrow\"><span class=\"dim\">hide an event once</span><input class=\"txt num\" type=\"number\" name=\"reportThreshold\" min=\"0\" max=\"100\" style=\"width:4.5rem;flex:0 0 auto\"><span class=\"dim\">different people report it; 0 never</span><button class=\"btn\">Save</button></form>\n <div class=\"scroll\"><table id=\"reports\" class=\"events\"><thead><tr><th>Time</th><th>Type</th><th>About</th><th>Reason</th><th></th></tr></thead><tbody></tbody></table></div>\n </div>\n <div class=\"block\">\n <h3>Log</h3>\n <p class=\"dim\">Every change made here or by a moderation event, newest first, the last 5,000.</p>\n <div class=\"scroll\"><table id=\"audit\" class=\"events\"><thead><tr><th>Time</th><th>Who</th><th>Action</th><th>Target</th><th>Detail</th></tr></thead><tbody></tbody></table></div>\n <button class=\"btn\" id=\"audit-more\" type=\"button\">Older</button>\n </div>\n <div class=\"block two\">\n <div>\n <h3>Bans</h3>\n <form id=\"addban\" class=\"addrow\"><input class=\"txt mono\" name=\"pubkey\" placeholder=\"hex pubkey or npub\" required><input class=\"txt\" name=\"reason\" placeholder=\"reason\" style=\"max-width:8rem\"><label class=\"note\" title=\"Also delete everything they wrote and uploaded\"><input type=\"checkbox\" name=\"erase\"> erase</label><button class=\"ib danger\" title=\"Ban\" aria-label=\"Ban\"><svg viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"8.5\"/><path d=\"M6 6l12 12\"/></svg></button></form>\n <ul id=\"bans\" class=\"plain\"></ul>\n <ul id=\"banned-events\" class=\"plain\"></ul>\n </div>\n <div>\n <h3>Blocked addresses</h3>\n <form id=\"addblock\" class=\"addrow\"><input class=\"txt mono\" name=\"ip\" placeholder=\"IP address\" required><input class=\"txt\" name=\"reason\" placeholder=\"reason\" style=\"max-width:8rem\"><button class=\"ib danger\" title=\"Block\" aria-label=\"Block\"><svg viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"8.5\"/><path d=\"M6 6l12 12\"/></svg></button></form>\n <ul id=\"blocks\" class=\"plain\"></ul>\n </div>\n </div>\n <div class=\"block\">\n <h3>Recent events</h3>\n <p class=\"note\">Delete removes one thing. Ban also refuses it forever. Search covers notes, articles and profiles.</p>\n <form id=\"searchform\" class=\"addrow\"><input class=\"txt\" name=\"q\" placeholder=\"search\" maxlength=\"200\"><button class=\"btn\">Search</button><button type=\"button\" id=\"searchclear\" class=\"btn hidden\">Clear</button></form>\n <div class=\"scroll\"><table id=\"events\" class=\"events\"><thead><tr><th>Time</th><th>Kind</th><th>Author</th><th>Content</th><th></th></tr></thead><tbody></tbody></table></div>\n <div class=\"actions left\"><button id=\"more\" class=\"btn\">Load more</button></div>\n </div>\n <div class=\"block\">\n <h3>Pinned <span class=\"count\" id=\"pins-count\"></span></h3>\n <p class=\"note\">Group clients show these at the top. Up to 20, in this order.</p>\n <form id=\"pinform\" class=\"addrow\"><input class=\"txt mono\" name=\"id\" placeholder=\"event id or address kind:pubkey:d\" required><button class=\"btn\">Pin</button></form>\n <ul id=\"pins\" class=\"plain\"></ul>\n </div>\n </section>\n\n <section class=\"panel\" data-panel=\"rules\">\n <h2>Rules</h2>\n <p class=\"lead\">Bans apply regardless of these.</p>\n <div class=\"block\">\n <h3>Presets</h3>\n <p class=\"note\">One click sets writes, reads, kinds and keep-for together. Limits, identity and people stay.</p>\n <div id=\"presets\"></div>\n <div class=\"row hidden\" id=\"presetsourcerow\"><input class=\"txt mono\" id=\"presetsource\" placeholder=\"wss://source.relay to mirror\" style=\"max-width:24rem\"><span class=\"note\">Replica presets keep a standing pull of their kinds from this relay.</span></div>\n <p class=\"note\" id=\"presetnote\"></p>\n </div>\n <form id=\"access\">\n <div class=\"two\">\n <div><h3>Writes</h3><div class=\"choices\">\n <label><input type=\"radio\" name=\"writes\" value=\"open\"><b>Anyone</b><small>Every valid event is accepted, unless banned.</small></label>\n <label><input type=\"radio\" name=\"writes\" value=\"allowlist\"><b>Members</b><small>Only people on the member list, and you.</small></label>\n <label><input type=\"radio\" name=\"writes\" value=\"wot\"><b>Members and their follows</b><small>Members, plus everyone in a member's contact list.</small></label>\n <label><input type=\"radio\" name=\"writes\" value=\"owner\"><b>Only me</b><small>Nobody else can post.</small></label>\n </div></div>\n <div><h3>Reads</h3><div class=\"choices\">\n <label><input type=\"radio\" name=\"reads\" value=\"open\"><b>Anyone</b><small>DMs and gift wraps still go only to their parties.</small></label>\n <label><input type=\"radio\" name=\"reads\" value=\"auth\"><b>Authenticated only</b><small>Every query needs a NIP-42 sign-in first.</small></label>\n <label><input type=\"radio\" name=\"reads\" value=\"members\"><b>Members only</b><small>Removing a member ends their subscriptions.</small></label>\n </div></div>\n </div>\n <div class=\"form\" style=\"margin-top:1rem\">\n <label><span>Open to anyone <small>kinds anyone may write, whatever the rule above says</small></span><input class=\"txt mono\" name=\"openKinds\" placeholder=\"7, 9735\"></label>\n <label class=\"switch\"><input type=\"checkbox\" name=\"guestReplies\"> Anyone may reply to a member's note</label>\n </div>\n <div class=\"limits\">\n <label><span>Minimum proof of work <small>bits</small></span><input class=\"txt num\" type=\"number\" name=\"minPow\" min=\"0\" max=\"64\"></label>\n <label><span>Reject events dated in the future by more than <small>seconds</small></span><input class=\"txt num\" type=\"number\" name=\"maxFuture\" min=\"0\"></label>\n <label><span>Max events per query</span><input class=\"txt num\" type=\"number\" name=\"maxLimit\" min=\"1\" max=\"5000\"></label>\n <label><span>Max subscriptions per connection</span><input class=\"txt num\" type=\"number\" name=\"maxSubs\" min=\"1\" max=\"200\"></label>\n <label><span>Max message size <small>KB</small></span><input class=\"txt num\" type=\"number\" name=\"maxMessageKB\" min=\"16\" max=\"1024\"></label>\n <label><span>Events per minute <small>per connection</small></span><input class=\"txt num\" type=\"number\" name=\"eventsPerMinute\" min=\"1\"></label>\n <label><span>Queries per minute <small>per connection</small></span><input class=\"txt num\" type=\"number\" name=\"reqsPerMinute\" min=\"1\"></label>\n <label><span>Max upload size <small>MB</small></span><input class=\"txt num\" type=\"number\" name=\"maxBlobMB\" min=\"1\" max=\"95\"></label>\n </div>\n <div class=\"actions\"><button class=\"btn pri\">Save rules</button></div>\n </form>\n <div class=\"block\">\n <h3>Kinds</h3>\n <p class=\"note\">An empty allow list means every kind. Blocks always win.</p>\n <form id=\"kindform\" class=\"addrow\"><input class=\"txt num\" type=\"number\" name=\"kind\" min=\"0\" max=\"65535\" placeholder=\"kind\" required style=\"width:7rem;flex:0 0 auto\"><button class=\"ib\" name=\"rule\" value=\"allow\" title=\"Allow kind\" aria-label=\"Allow kind\"><svg viewBox=\"0 0 24 24\"><path d=\"M5 12l5 5L20 7\"/></svg></button><button class=\"ib danger\" name=\"rule\" value=\"block\" title=\"Block kind\" aria-label=\"Block kind\"><svg viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"8.5\"/><path d=\"M6 6l12 12\"/></svg></button></form>\n <p class=\"kindline\">Allowed: <span id=\"kinds-allow\"></span></p>\n <p class=\"kindline\">Blocked: <span id=\"kinds-block\"></span></p>\n </div>\n <div class=\"block\">\n <h3>Blocked words</h3>\n <p class=\"note\">Content containing one is refused. You and your moderators are exempt. An entry written /like this/ is a regular expression.</p>\n <form id=\"wordsform\"><textarea class=\"txt\" name=\"words\" rows=\"4\" placeholder=\"one word, phrase or /pattern/ per line\"></textarea><label class=\"switch\"><input type=\"checkbox\" name=\"inTags\"> Also search tags</label><div class=\"actions\"><button class=\"btn\">Save words</button></div></form>\n </div>\n </section>\n\n <section class=\"panel\" data-panel=\"identity\">\n <h2>Identity</h2>\n <form id=\"identity\">\n <h3>Profile</h3>\n <div class=\"form\">\n <label><span>Name</span><input class=\"txt\" name=\"name\" maxlength=\"200\"></label>\n <label><span>Contact</span><input class=\"txt\" name=\"contact\" placeholder=\"mailto:you@example.com\" maxlength=\"500\"></label>\n <label class=\"wide\"><span>Description</span><textarea name=\"description\" maxlength=\"2000\"></textarea></label>\n <label class=\"wide\"><span>Icon URL</span><input class=\"txt\" name=\"icon\" placeholder=\"https://…/icon.png\" maxlength=\"2000\"></label>\n <label class=\"wide\"><span>Banner URL</span><input class=\"txt\" name=\"banner\" placeholder=\"https://…/banner.png\" maxlength=\"2000\"></label>\n </div>\n <h3 class=\"gap\">For directories</h3>\n <div class=\"form\">\n <label><span>Posting policy URL</span><input class=\"txt\" name=\"postingPolicy\" placeholder=\"https://…/posting\" maxlength=\"2000\"></label>\n <label><span>Privacy policy URL</span><input class=\"txt\" name=\"privacyPolicy\" placeholder=\"https://…/privacy\" maxlength=\"2000\"></label>\n <label><span>Tags</span><input class=\"txt\" name=\"tags\" placeholder=\"bitcoin, cats\"></label>\n <label><span>Languages</span><input class=\"txt\" name=\"languageTags\" placeholder=\"en, pt-BR\"></label>\n <label><span>Countries</span><input class=\"txt\" name=\"relayCountries\" placeholder=\"US, DE\"></label>\n </div>\n <div class=\"actions\"><button class=\"btn pri\">Save identity</button></div>\n </form>\n <div class=\"block\">\n <h3>Your own domain</h3>\n <p class=\"note\">Point a hostname you control at this relay, such as <span class=\"mono\">relay.example.com</span>. Add it here, then create the record shown at your DNS. The certificate is issued on its own once the record resolves.</p>\n <form id=\"adddomain\" class=\"addrow\"><input class=\"txt mono\" name=\"host\" placeholder=\"relay.example.com\" required><button class=\"btn\">Add</button></form>\n <div id=\"domains\"></div>\n <p class=\"note\" id=\"domainnote\"></p>\n </div>\n <div class=\"block\">\n <h3>Your relay lists</h3>\n <p class=\"note\">Clients find your relays through these lists. Add this relay to each so they use it. What is already listed stays.</p>\n <div id=\"wire\"></div>\n </div>\n <div class=\"block\">\n <h3>Share</h3>\n <p class=\"note\">A card for links and profiles, and the group address that group-aware clients open. Both are public and refresh every five minutes.</p>\n <div class=\"share\">\n <a href=\"/card.svg\" target=\"_blank\" rel=\"noopener\"><img id=\"cardimg\" src=\"/card.svg\" alt=\"relay card\" width=\"600\" height=\"315\"></a>\n <div class=\"share-side\">\n <img id=\"naddrqr\" class=\"hidden\" alt=\"QR code of the group address\" width=\"150\" height=\"150\">\n <code class=\"mono naddr\" id=\"naddr\"></code>\n <div class=\"row\"><button class=\"btn\" id=\"copynaddr\">Copy naddr</button><button class=\"btn\" id=\"copyembed\">Copy embed</button></div>\n </div>\n </div>\n </div>\n </section>\n\n <section class=\"panel\" data-panel=\"data\">\n <h2>Data</h2>\n <p class=\"lead\">Keep-for rules run once a day. Purges happen now and cannot be undone.</p>\n <div class=\"kbar\" id=\"s-bar\"></div><span class=\"legend slegend\" id=\"s-legend\"></span>\n <div class=\"usage\" id=\"s-totals\"></div>\n <div class=\"block\">\n <h3>By kind</h3>\n <div class=\"scroll\"><table id=\"kinds\" class=\"events\"><thead><tr><th>Kind</th><th class=\"r\">Count</th><th class=\"r\">Size</th><th>Oldest</th><th>Keep for</th><th></th></tr></thead><tbody></tbody></table></div>\n </div>\n <div class=\"block\">\n <h3>Files <span class=\"count\" id=\"blobs-count\"></span></h3>\n <div class=\"scroll\"><table id=\"blobs\" class=\"events\"><thead><tr><th>Time</th><th>File</th><th>Size</th><th>Uploader</th><th></th></tr></thead><tbody></tbody></table></div>\n </div>\n <div class=\"block\">\n <h3>Dumps <span class=\"count\" id=\"dumps-count\"></span></h3>\n <p class=\"note\">Every event as one JSONL file on a schedule, kept for a few runs and counted as files. Downloads need your signature.</p>\n <form id=\"dumpform\" class=\"addrow\"><select class=\"txt\" name=\"dumps\" style=\"max-width:8rem\"><option value=\"off\">off</option><option value=\"daily\">daily</option><option value=\"weekly\">weekly</option></select><input class=\"txt num\" type=\"number\" name=\"keep\" min=\"1\" max=\"60\" value=\"7\" title=\"how many to keep\" style=\"width:5rem;flex:0 0 auto\"><button class=\"btn\">Save</button><button type=\"button\" id=\"dumpnow\" class=\"btn\">Dump now</button></form>\n <ul id=\"dumps\" class=\"plain\"></ul>\n </div>\n <div class=\"block\">\n <h3>Import a file</h3>\n <p class=\"note\">A JSONL of events, one per line, such as a dump or a strfry export, or a JSON array. Up to 64 MB. Signatures are checked; bans and kind rules apply; the write rule does not. Progress shows under Jobs.</p>\n <form id=\"importform\" class=\"addrow\"><input class=\"txt\" type=\"file\" name=\"file\" accept=\".jsonl,.json,.ndjson,application/json\" required><button class=\"btn\">Import</button></form>\n </div>\n </section>\n\n <section class=\"panel\" data-panel=\"sync\">\n <h2>Sync</h2>\n <p class=\"lead\">Events in and out, one round at a time, while the relay sleeps between rounds. Jobs spend awake time, which fuel counts.</p>\n <div class=\"block\">\n <h3>Jobs</h3>\n <p class=\"note\">Work the relay does on its own, one round at a time. A pull copies what another relay has and this one lacks. Fetch my history pulls your own events from the relays in your relay list. Rebroadcast sends what is here to other relays. Bans and kind rules apply to what arrives.</p>\n <div class=\"scroll\"><table id=\"jobs\" class=\"events\"><thead><tr><th>Job</th><th>Relays</th><th>Filter</th><th>Schedule</th><th>Result</th><th></th></tr></thead><tbody></tbody></table></div>\n <form id=\"pullform\" class=\"addrow\"><input class=\"txt mono\" name=\"url\" placeholder=\"wss://other.relay\" required><select class=\"txt\" name=\"every\" style=\"max-width:9rem\"><option value=\"0\">once</option><option value=\"1\">every hour</option><option value=\"6\">every 6 hours</option><option value=\"24\">every day</option></select><button class=\"btn\">Pull</button></form>\n <form id=\"backfillform\" class=\"addrow\"><input class=\"txt mono\" name=\"relays\" placeholder=\"relays, comma separated; blank uses your relay list\"><button class=\"btn\">Fetch my history</button></form>\n <form id=\"pushform\">\n <div class=\"form\">\n <label class=\"wide\"><span>Rebroadcast to</span><input class=\"txt mono\" name=\"targets\" placeholder=\"wss://a.relay, wss://b.relay\" required></label>\n <label><span>Kinds <small>blank for all</small></span><input class=\"txt mono\" name=\"kinds\" placeholder=\"1, 30023\"></label>\n <label><span>Newer than <small>days, blank for all</small></span><input class=\"txt num\" type=\"number\" name=\"days\" min=\"0\"></label>\n <label><span>Schedule</span><select class=\"txt\" name=\"every\"><option value=\"0\">once</option><option value=\"1\">every hour</option><option value=\"6\">every 6 hours</option><option value=\"24\">every day</option></select></label>\n </div>\n <div class=\"actions\"><button class=\"btn\">Rebroadcast</button></div>\n </form>\n </div>\n <div class=\"block\">\n <h3>Fork this relay</h3>\n <p class=\"note\">A new name, temporary until claimed, filled from this relay. Yours to split a name by job, or somebody else's to hand them a community with its history. One fork an hour.</p>\n <form id=\"forkform\">\n <div class=\"form\">\n <label><span>Name <small>optional</small></span><input class=\"txt mono\" name=\"name\" placeholder=\"a memorable one if blank\" pattern=\"[a-z0-9-]{3,32}\"></label>\n <label><span>Who claims it <small>npub or hex, you if blank</small></span><input class=\"txt mono\" name=\"holder\"></label>\n <label><span>What to copy</span><select class=\"txt\" name=\"scope\"><option value=\"all\">everything</option><option value=\"mine\">only my events</option><option value=\"kinds\">these kinds</option></select></label>\n <label id=\"forkkinds\" class=\"hidden\"><span>Kinds <small>comma separated</small></span><input class=\"txt mono\" name=\"kinds\" placeholder=\"1, 30023\"></label>\n <label class=\"wide switch\"><input type=\"checkbox\" name=\"people\" checked> Copy the people</label>\n </div>\n <div class=\"actions\"><button class=\"btn pri\">Fork</button></div>\n </form>\n <p class=\"note\" id=\"forknote\"></p>\n <div class=\"row hidden\" id=\"forkresult\"><span class=\"mono\" id=\"forkurl\"></span><button class=\"ib\" id=\"forkcopy\" title=\"Copy\" aria-label=\"Copy\"><svg viewBox=\"0 0 24 24\"><rect x=\"9\" y=\"9\" width=\"11\" height=\"11\" rx=\"2\"/><path d=\"M5 15V6a2 2 0 0 1 2-2h9\"/></svg></button></div>\n </div>\n </section>\n\n <section class=\"panel\" data-panel=\"views\">\n <h2>Views</h2>\n <p class=\"lead\">Records the relay computes and signs, for clients and for anyone. Each run costs the rows it writes.</p>\n <div class=\"block\">\n <div id=\"views\"></div>\n </div>\n </section>\n\n <section class=\"panel\" data-panel=\"health\">\n <h2>Health</h2>\n <div class=\"counters\">\n <div class=\"counter\"><b class=\"big\" id=\"h-last\">–</b><small>since last event</small><span class=\"sub\" id=\"h-last-sub\"></span></div>\n <div class=\"counter\"><b class=\"big\" id=\"h-conns\">–</b><small>connected now</small><span class=\"sub\">websockets open</span></div>\n <div class=\"counter\"><b class=\"big\" id=\"h-fuel\">–</b><small id=\"h-fuel-label\">fuel</small><span class=\"sub\" id=\"h-fuel-sub\"></span></div>\n <div class=\"counter\"><div class=\"kbar\" id=\"h-kinds\"></div><small>used for, last 30 days</small><span class=\"sub legend\" id=\"h-kinds-legend\"></span></div>\n </div>\n <div class=\"usage\" id=\"usage\"></div>\n <div class=\"block\">\n <h3>Zaps received</h3>\n <div class=\"scroll\"><table id=\"credits\"><thead><tr><th>When</th><th>From</th><th class=\"r\">Sats</th></tr></thead><tbody></tbody></table></div>\n </div>\n <div class=\"block\">\n <h3>Notifications</h3>\n <p class=\"note\">The relay writes you a private message (NIP-17) with its own key. It lands here, in your inbox on your own relay, and on your DM relays when this relay holds your kind 10050.</p>\n <form id=\"notify\">\n <div class=\"form\">\n <label class=\"wide switch\"><input type=\"checkbox\" name=\"reports\"> A report arrives</label>\n <label class=\"wide switch\"><input type=\"checkbox\" name=\"fuel\"> Fuel runs low</label>\n <label class=\"wide switch\"><input type=\"checkbox\" name=\"jobs\"> A pull finishes</label>\n <label class=\"wide switch\"><input type=\"checkbox\" name=\"succession\"> The handover clock is running</label>\n <label class=\"wide switch\"><input type=\"checkbox\" name=\"digest\"> A note every week on how the relay is doing</label>\n </div>\n <div class=\"actions\"><button class=\"btn pri\">Save notifications</button><button type=\"button\" id=\"notifytest\" class=\"btn\">Send a test message</button></div>\n </form>\n </div>\n </section>\n\n <section class=\"panel\" data-panel=\"owner\">\n <h2>Owner</h2>\n <p class=\"lead\">The relay's key, events, files and fuel stay put through everything here except delete.</p>\n <div class=\"block\">\n <h3>Configuration</h3>\n <p class=\"note\">Rules, identity, members, bans, address blocks and kind rules as a file. Importing replaces those lists; it never touches events, files, or the owner.</p>\n <div class=\"row\"><button id=\"exportcfg\" class=\"btn\">Export configuration</button><button id=\"importcfg\" class=\"btn\">Import configuration</button><input type=\"file\" id=\"cfgfile\" accept=\"application/json,.json\" class=\"hidden\"></div>\n </div>\n <div class=\"block danger-zone\">\n <h3>Transfer ownership</h3>\n <p class=\"note\">Hands the relay to a member. You stay on as a moderator. The relay's key, events, files and fuel do not change. There is no undo.</p>\n <form id=\"transfer\" class=\"addrow\"><select class=\"txt mono\" name=\"pubkey\" required></select><button class=\"btn danger\">Transfer</button></form>\n </div>\n <div class=\"block danger-zone\">\n <h3>If I lose my key</h3>\n <p class=\"note\">Name a member as your heir. If you do not sign in here for the time you pick, the relay writes to you once a week for a month, then hands itself to the heir and keeps you on as a moderator. Any signed action on the relay resets the clock.</p>\n <form id=\"succession\">\n <div class=\"form\">\n <label><span>Heir <small>a member</small></span><select class=\"txt mono\" name=\"heir\" required></select></label>\n <label><span>Hand over</span><select class=\"txt\" name=\"afterDays\"><option value=\"90\">after 3 months away</option><option value=\"180\" selected>after 6 months away</option><option value=\"365\">after a year away</option></select></label>\n </div>\n <div class=\"actions\"><button class=\"btn\">Set heir</button><button type=\"button\" id=\"clearsuccession\" class=\"btn hidden\">Clear</button></div>\n </form>\n <p class=\"note\" id=\"successionnote\"></p>\n </div>\n <div class=\"block danger-zone\">\n <h3>Delete this relay</h3>\n <p class=\"note\">Deletes every event, file, member, invite and setting, closes every connection, and returns the name to unclaimed for anyone to take. There is no undo.</p>\n <button id=\"deleterelay\" class=\"btn danger\">Delete this relay</button>\n </div>\n </section>\n </div>\n\n <footer class=\"pg\"><p>a bind.ws relay</p><a href=\"https://github.com/FelineStateMachine/bindws\">source</a></footer>\n</main>\n";33 export const CONSOLE_CSS = "main { max-width: 64rem; }\n.mast { display: grid; grid-template-columns: 1fr auto; gap: 1.5rem 2rem; align-items: end; margin-bottom: 2rem; }\n.mast .wordmark { grid-column: 1 / -1; justify-self: center; margin-bottom: .5rem; }\n.mast .right { display: grid; gap: .7rem; justify-items: end; text-align: right; }\n.urlrow, .owner { display: inline-flex; align-items: center; gap: .4rem; color: var(--ink-2); }\n.owner b { color: var(--ink); } .owner em { font-style: normal; color: var(--forest); }\n.urlrow .ib, .owner .ib { width: 26px; height: 26px; margin-left: .2rem; box-shadow: 1px 1px 0 var(--ink); } .urlrow .ib svg, .owner .ib svg { width: 13px; height: 13px; }\n.care { display: flex; gap: .9rem; flex-wrap: wrap; }\n.care .g { display: grid; justify-items: center; gap: .3rem; width: 5.4rem; }\n.care .g i { display: grid; place-items: center; width: 44px; height: 44px; border: 2px solid var(--ink); border-radius: 10px; background: var(--paper); box-shadow: 2px 2px 0 var(--ink); position: relative; }\n.care .g i svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }\n.care .g.off i::after { content: \"\"; position: absolute; width: 3px; height: 40px; background: var(--ink); transform: rotate(45deg); border-radius: 2px; box-shadow: 0 0 0 2px var(--paper); }\n.care .g small { font: 500 10px/1.2 var(--mono); text-transform: uppercase; letter-spacing: .04em; color: var(--ink-2); text-align: center; }\n#unclaimed { background: var(--butter); }\n.doors { display: grid; grid-template-columns: repeat(4, 1fr); gap: .8rem; } @media (max-width: 52rem) { .doors { grid-template-columns: 1fr 1fr; } } @media (max-width: 30rem) { .doors { grid-template-columns: 1fr; } }\n.door { display: grid; grid-template-rows: auto 1fr; align-content: start; gap: .35rem; padding: .8rem .9rem; border: 2px solid var(--ink); border-radius: 12px; background: var(--paper); box-shadow: 3px 3px 0 var(--ink); min-width: 0; }\n.apps { display: grid; gap: 1.4rem; margin-top: 1rem; } .appgroup h4 { margin: 0 0 .2rem; font-size: 1rem; } .appgroup > .note { margin-top: 0; }\n.appgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: .8rem; } @media (max-width: 52rem) { .appgrid { grid-template-columns: 1fr 1fr; } } @media (max-width: 30rem) { .appgrid { grid-template-columns: 1fr; } }\n.app { display: grid; align-content: start; gap: .4rem; padding: .8rem .9rem; border: 2px solid var(--ink); border-radius: 12px; background: var(--paper); box-shadow: 3px 3px 0 var(--ink); min-width: 0; }\n.app-head { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; } .app-head b { font-size: 15px; } .app-head small { color: var(--ink-3); } .app p { margin: 0; font-size: 13px; color: var(--ink-2); }\n.app-acts { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .2rem; } .app-acts .btn { padding: .3rem .7rem; font-size: 13px; }\n.phones { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, max-content)); gap: .8rem; margin-top: 1rem; } .phones img { display: block; width: 150px; height: 150px; } .phones .door { justify-items: start; }\n.door small { font: 700 11px var(--sans); text-transform: uppercase; letter-spacing: .06em; color: var(--ink-2); }\n.door .v { display: grid; grid-template-columns: 1fr auto; align-items: start; gap: .5rem; font: 500 13px/1.45 var(--mono); } .door .v span { min-width: 0; overflow-wrap: anywhere; }\n.door .ib { width: 24px; height: 24px; box-shadow: 1px 1px 0 var(--ink); flex: 0 0 auto; } .door .ib svg { width: 12px; height: 12px; }\n#peoplesec { background: var(--peach); }\n.dir { display: flex; flex-wrap: wrap; gap: .5rem; }\n.who { display: inline-flex; align-items: center; gap: .1rem; padding: .3rem .7rem .3rem .4rem; border: 2px solid var(--ink); border-radius: 999px; background: var(--paper); box-shadow: 2px 2px 0 var(--ink); font-size: 14px; }\n.who .role { font: 700 10px var(--mono); text-transform: uppercase; letter-spacing: .05em; color: var(--forest); margin-left: .4rem; }\n.who.me { background: var(--sun); }\n#fuelsec { background: var(--mint); }\n.gauges { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }\n.gauge small { display: block; font-weight: 600; font-size: 13px; }\n.gauge .bar { position: relative; height: 22px; border: 2px solid var(--ink); border-radius: 999px; margin: .5rem 0 .35rem; overflow: hidden; padding: 3px; background: var(--paper) linear-gradient(90deg, transparent calc(25% - 1px), var(--line) calc(25% - 1px) 25%, transparent 25% calc(50% - 1px), var(--line) calc(50% - 1px) 50%, transparent 50% calc(75% - 1px), var(--line) calc(75% - 1px) 75%, transparent 75%); }\n.gauge .bar b { position: absolute; right: .6rem; top: 0; bottom: 0; display: flex; align-items: center; font: 600 11px var(--mono); color: var(--ink-3); pointer-events: none; }\n.gauge .bar i { display: block; height: 100%; width: 0; min-width: 14px; border-radius: 999px; background: repeating-linear-gradient(-45deg, var(--forest) 0 8px, var(--forest-2) 8px 16px); transition: width .4s; }\n.gauge .bar i.warm { background: repeating-linear-gradient(-45deg, #d9a52a 0 8px, var(--sun) 8px 16px); }\n.gauge .bar i.over { background: repeating-linear-gradient(-45deg, var(--red) 0 8px, #d4614c 8px 16px); }\n.gauge span { font-size: 13px; color: var(--ink-3); }\n.balance { margin: 1.2rem 0 0; color: var(--ink-2); } .balance b { color: var(--ink); font-weight: 700; }\n.sats { display: inline-flex; align-items: center; width: auto; } .sats input { font: 15px var(--sans); width: 6rem; text-align: right; border: 0; outline: none; padding: 0; background: transparent; color: var(--ink); } .sats span { color: var(--ink-3); padding-left: .4rem; }\n.topup { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-top: .8rem; }\n.invoice { margin-top: 1rem; border: 2px dashed var(--ink); border-radius: 12px; padding: .9rem 1rem; background: var(--paper); }\n.invoice p { margin: 0 0 .6rem; color: var(--ink-2); font-size: 14px; } .invoice .state { font-size: 13px; color: var(--forest); }\n.invoice textarea { margin-top: .7rem; min-height: 3.4rem; font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); }\ndetails.disclosure summary { cursor: pointer; font: 400 1.6rem/1 var(--display); list-style: none; display: flex; align-items: center; gap: .5rem; }\ndetails.disclosure summary::-webkit-details-marker { display: none; }\n.folds details + details, .folds #frontsec:not(.hidden) + details { border-top: 2px solid var(--line); margin-top: 1.2rem; padding-top: 1.2rem; } .folds #frontsec .metarow { margin-bottom: 0; } .folds .doors { margin-top: 1rem; } .folds > details > .block:first-of-type { margin-top: 1.4rem; }\ndetails.disclosure summary::before { content: \"+\"; font: 700 1.4rem var(--mono); width: 1.4rem; } details.disclosure[open] summary::before { content: \"–\"; }\n.about { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: .6rem 1.2rem; margin-top: 1rem; font-size: 14px; }\n.about small { display: block; color: var(--ink-3); font-size: 12px; } .about div > div { overflow-wrap: anywhere; }\n.console { margin-top: 3rem; }\n.tabs { display: flex; gap: .45rem; flex-wrap: nowrap; align-items: flex-end; margin: 0 0 -2px 1rem; padding: 2px 3rem 2px 0; overflow-x: auto; overflow-y: hidden; scrollbar-width: none; }\n.tabs.fade-r { -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 2.5rem), transparent); mask-image: linear-gradient(90deg, #000 calc(100% - 2.5rem), transparent); }\n.tabs.fade-l { -webkit-mask-image: linear-gradient(90deg, transparent, #000 2.5rem); mask-image: linear-gradient(90deg, transparent, #000 2.5rem); }\n.tabs.fade-l.fade-r { -webkit-mask-image: linear-gradient(90deg, transparent, #000 2.5rem, #000 calc(100% - 2.5rem), transparent); mask-image: linear-gradient(90deg, transparent, #000 2.5rem, #000 calc(100% - 2.5rem), transparent); } .tabs::-webkit-scrollbar { display: none; } .tabs a { flex: 0 0 auto; white-space: nowrap; }\n.tabs a { font: 600 14px var(--sans); padding: .55rem 1rem; border: 2px solid var(--ink); border-bottom: 0; border-radius: 12px 12px 0 0; background: var(--head); color: var(--ink-2); text-decoration: none; margin-bottom: -2px; position: relative; z-index: 1; }\n.tabs a.on { color: var(--ink); z-index: 3; padding-top: .7rem; } .tabs a.on:nth-child(4n+1) { background: var(--sun); } .tabs a.on:nth-child(4n+2) { background: var(--peach); } .tabs a.on:nth-child(4n+3) { background: var(--sky); } .tabs a.on:nth-child(4n+4) { background: var(--mint); }\n.count { display: inline-grid; place-items: center; min-width: 1.5em; height: 1.5em; padding: 0 .4em; margin-left: .45rem; border: 1.5px solid var(--ink); border-radius: 999px; background: var(--sun); color: var(--ink); font: 700 11px/1 var(--mono); vertical-align: middle; box-shadow: 1px 1px 0 var(--ink); } .count:empty { display: none; } h2 .count { font-size: 12px; vertical-align: .45em; }\n.panel { display: none; margin: 0; border-radius: 0 18px 18px 18px; position: relative; z-index: 2; }\n.panel.on { display: block; }\n.panel h2 { font-size: 2rem; }\n.block { margin-top: 1.8rem; }\n.two { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; } .two > * { min-width: 0; }\n.actions { display: flex; justify-content: flex-end; margin-top: 1.1rem; } .actions.left { justify-content: flex-start; }\n.addrow { display: flex; gap: .5rem; align-items: center; margin-bottom: .9rem; } .addrow input { flex: 1; min-width: 0; } .addrow label { display: inline-flex; align-items: center; gap: .35rem; flex: 0 0 auto; white-space: nowrap; font-size: 14px; color: var(--ink-2); cursor: pointer; } .addrow label input { flex: none; width: auto; margin: 0; accent-color: var(--forest); } .addrow .ib { width: 38px; height: 38px; flex: 0 0 auto; border-radius: 10px; }\n.choices { display: grid; gap: .5rem; }\n.choices label { display: grid; grid-template-columns: auto 1fr; gap: 0 .7rem; cursor: pointer; padding: .7rem .8rem; border: 2px solid var(--line-2); border-radius: 12px; background: var(--paper); }\n.choices label:has(:checked) { border-color: var(--ink); box-shadow: 3px 3px 0 var(--ink); }\n.choices input { grid-row: span 2; margin: .2rem 0 0; accent-color: var(--forest); }\n.choices b { font-weight: 600; } .choices small { color: var(--ink-2); font-size: 13px; }\n.limits { display: grid; grid-template-columns: 1fr 1fr; gap: .6rem 1.5rem; margin-top: 1.3rem; }\n.limits label { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: .6rem; } .limits label small { color: var(--ink-3); }\n.form { display: grid; grid-template-columns: 1fr 1fr; gap: .9rem 1.5rem; } .form label { display: grid; gap: .3rem; font-weight: 600; font-size: 14px; } .form .wide { grid-column: 1 / -1; }\n.danger-zone { border: 2px solid var(--red); border-radius: 12px; padding: 1rem 1.1rem; background: var(--red-soft); } .danger-zone h3 { color: var(--red); } .form label.switch { display: flex; flex-direction: row; align-items: center; gap: .6rem; font-weight: 600; font-size: 14px; } .switch input { accent-color: var(--forest); width: 18px; height: 18px; }\ntable { width: 100%; border-collapse: collapse; }\nth { text-align: left; font-size: 12px; font-weight: 600; color: var(--ink-3); padding: .4rem .5rem; border-bottom: 2px solid var(--ink); white-space: nowrap; }\ntd { padding: .5rem .5rem; vertical-align: middle; border-bottom: 1px solid var(--line); }\ntbody tr:nth-child(even) td { background: rgba(255,255,255,.55); }\nth.r, td.r { text-align: right; white-space: nowrap; } td.mono, td.dim { white-space: nowrap; } .events td.c:empty::before { content: \"(no content)\"; color: var(--ink-3); }\ntd .ib { width: 26px; height: 26px; box-shadow: 1px 1px 0 var(--ink); } td .ib svg { width: 13px; height: 13px; }\ntd input.txt { padding: .25rem .5rem; font-size: 14px; } td select.role { width: auto; min-width: 7rem; padding: .25rem .4rem; font-size: 13px; } .people-table input.name { width: 7rem; } .people-table input.note { width: 9.5rem; }\n.kind { font-family: var(--mono); font-size: 12px; background: var(--sun); border: 1.5px solid var(--ink); padding: 0 .5rem; border-radius: 999px; font-weight: 500; white-space: nowrap; }\n.events td.c { max-width: 0; width: 100%; overflow-x: auto; white-space: nowrap; scrollbar-width: thin; -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 2rem), transparent); mask-image: linear-gradient(90deg, #000 calc(100% - 2rem), transparent); }\n.people-table td.name { width: 12rem; }\n.ev { display: inline-grid; place-items: center; width: 20px; height: 20px; border-radius: 4px; vertical-align: -5px; margin-right: .45rem; font: 500 9px var(--mono); background: var(--line); color: var(--ink-2); font-style: normal; }\n.plain { list-style: none; margin: 0; padding: 0; } .plain li { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: .5rem; padding: .5rem 0; border-bottom: 2px dotted var(--line-2); } .plain li > span { min-width: 0; overflow-wrap: anywhere; } .plain li:last-child { border-bottom: 0; } .plain li.empty { grid-template-columns: 1fr; color: var(--ink-3); }\n.kindline { margin: .4rem 0; color: var(--ink-2); display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; }\n.tag { display: inline-flex; align-items: center; gap: .2rem; font-family: var(--mono); font-size: 13px; padding: .05rem .2rem .05rem .55rem; border: 2px solid var(--ink); border-radius: 999px; background: var(--paper); box-shadow: 1px 1px 0 var(--ink); }\n.tag.plain { padding-right: .55rem; box-shadow: none; border-color: var(--line-2); color: var(--ink-3); } .tag.blk { color: var(--red); }\n.tag .ib { width: 18px; height: 18px; border-width: 1.5px; box-shadow: none; margin-left: .2rem; } .tag .ib svg { width: 9px; height: 9px; }\n.counters { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.2rem; margin-bottom: 1.6rem; }\n.counter { display: grid; justify-items: center; align-content: center; gap: .15rem; min-height: 7.2rem; text-align: center; padding: 1rem .8rem; border: 2px solid var(--ink); border-radius: 16px; box-shadow: 4px 4px 0 var(--ink); background: var(--paper); }\n.counter:nth-child(1) { background: var(--sun); } .counter:nth-child(2) { background: var(--peach); } .counter:nth-child(3) { background: var(--sky); } .counter:nth-child(4) { background: var(--mint); }\n.counter .big { font: 400 2.6rem/1 var(--display); white-space: nowrap; } .counter small { display: block; margin-top: .35rem; font: 700 12px var(--sans); text-transform: uppercase; letter-spacing: .08em; } .counter .sub { font-size: 12px; color: var(--ink-2); }\n.kbar { display: flex; width: 100%; height: 22px; border: 2px solid var(--ink); border-radius: 999px; overflow: hidden; background: var(--paper); margin-bottom: .35rem; } .kbar i { display: block; height: 100%; border-right: 2px solid var(--ink); } .kbar i:last-child { border-right: 0; }\n.k1 { background: var(--ink); } .k2 { background: var(--forest); } .k3 { background: var(--id-3); } .k4 { background: var(--id-2); } .k5 { background: var(--line-2); }\n.legend { display: flex; flex-wrap: wrap; justify-content: center; gap: .1rem .6rem; font-family: var(--mono); font-size: 10.5px; text-transform: uppercase; } .legend i { display: inline-block; width: 9px; height: 9px; border: 1.5px solid var(--ink); border-radius: 2px; margin-right: .3rem; vertical-align: -1px; }\n.usage { display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); gap: .8rem; } .usage div { padding: .7rem .9rem; border: 2px solid var(--ink); border-radius: 12px; background: var(--paper); } .usage small { display: block; color: var(--ink-3); font-size: 12px; } .usage b { font: 400 1.5rem/1.2 var(--display); }\n@media (max-width: 52rem) { .two, .gauges, .limits, .form, .counters { grid-template-columns: 1fr !important; } .addrow { flex-wrap: wrap; } .mast { grid-template-columns: 1fr; } .mast .right { justify-items: start; text-align: left; } }\n@media (max-width: 40rem) { .events:not(#kinds):not(#jobs) th:first-child, .events:not(#kinds):not(#jobs) td:first-child { display: none; } }\n\n.slegend { justify-content: flex-start; margin: 0 0 1.2rem; }\n#s-totals { margin-bottom: .4rem; }\ntd.keep { white-space: nowrap; } td.keep input { width: 5.5rem; display: inline-block; margin-right: .3rem; } td.keep input::placeholder { color: var(--ink-3); }\n#kinds tr.any td:first-child { font-weight: 600; }\n#kinds td:first-child .kind { display: inline-block; min-width: 5.2em; text-align: center; margin-right: .35rem; }\n/* Tables keep their columns and scroll inside the card on narrow screens instead of pushing the page sideways. */\n.scroll { overflow-x: auto; max-width: 100%; scrollbar-width: thin; } .scroll table { min-width: 100%; }\nsection, .card { overflow-x: clip; }\n.kind.sys { background: var(--mint); border-color: var(--forest); color: var(--forest); }\ntd.keep.sys { color: var(--ink-3); font-size: 13px; white-space: normal; }\n\n#console.mod .tabs a:not([data-tab=people]):not([data-tab=moderation]) { display: none; } #console.mod #thresholdform { display: none; }\n#members select.role { width: auto; padding: .1rem .3rem; font-size: 12px; margin-left: .3rem; }\n.wire-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: .5rem 1.2rem; padding: .8rem 0; border-bottom: 1px solid var(--line); align-items: start; } .wire-row:first-child { border-top: 2px solid var(--ink); }\n.wire-main { min-width: 0; } .wire-side { display: grid; justify-items: end; gap: .45rem; max-width: 26rem; }\n.wire-acts { display: flex; gap: .4rem; white-space: nowrap; } .wire-acts .btn { padding: .35rem .75rem; font-size: 13px; }\n.wire-meta { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: .35rem .5rem; align-items: center; font-size: 13px; color: var(--ink-2); text-align: right; } .wire-meta:empty { display: none; }\n.pill { display: inline-block; padding: .1rem .6rem; border: 1.5px solid var(--ink); border-radius: 999px; font: 600 12px var(--sans); color: var(--ink); background: var(--paper); } .pill.on { background: var(--mint); } .pill.off { color: var(--ink-3); } .pill.bad { color: var(--red); }\n.chip { display: inline-block; padding: .05rem .45rem; border: 1px solid var(--forest); border-radius: 6px; font: 12px var(--mono); color: var(--forest); } .chip.bad { border-color: var(--red); color: var(--red); }\n.share { display: grid; grid-template-columns: minmax(0, 3fr) minmax(14rem, 2fr); gap: 1.4rem; align-items: start; } .share #cardimg { width: 100%; height: auto; display: block; }\n.share-side { display: grid; gap: .8rem; justify-items: start; } .naddr { display: block; font-size: 12px; line-height: 1.5; word-break: break-all; color: var(--ink-2); }\n@media (max-width: 52rem) { .share { grid-template-columns: 1fr; } .wire-side { display: contents; } .wire-acts { justify-self: end; } .wire-meta { grid-column: 1 / -1; justify-content: flex-start; text-align: left; } }\n#presets .btn { margin: 0 .4rem .4rem 0; }\n.panel form h3.gap { margin-top: 1.4rem; }\n.key { position: relative; display: inline-block; font-family: var(--mono); } .key::before { content: attr(data-short); } .key .full { position: absolute; left: 0; top: 0; width: 1px; height: 1px; overflow: hidden; opacity: 0; white-space: nowrap; }\n.mast .banner { grid-column: 1 / -1; width: 100%; aspect-ratio: 4 / 1; overflow: hidden; border: 2px solid var(--ink); border-radius: 18px; box-shadow: 4px 4px 0 var(--ink); background: var(--paper); margin-bottom: .5rem; } .mast .banner img { width: 100%; height: 100%; object-fit: cover; display: block; }\n.mast .wordmark { display: flex; align-items: center; gap: 1rem; } .mast .icon { width: 64px; height: 64px; border-radius: 50%; border: 2px solid var(--ink); box-shadow: 3px 3px 0 var(--ink); background: var(--paper); object-fit: cover; }\n.metarow { display: flex; flex-wrap: wrap; gap: .4rem .6rem; align-items: center; margin: -.4rem 0 1rem; font-size: 13px; color: var(--ink-2); }\n.metarow .tag { display: inline-block; padding: .1rem .6rem; border: 1.5px solid var(--ink); border-radius: 999px; font: 600 12px var(--sans); color: var(--ink); background: var(--sun); }\n.metarow .sep { color: var(--ink-3); } .metarow a { color: var(--forest); text-decoration: none; border-bottom: 1.5px solid var(--forest); } .metarow a:hover { border-bottom-width: 2px; }\n.about a { color: var(--forest); text-decoration: none; border-bottom: 1.5px solid var(--forest); }\n.wire-acts label { display: inline-flex; align-items: center; gap: .35rem; font-size: 14px; color: var(--ink-2); cursor: pointer; } .wire-acts label input { margin: 0; accent-color: var(--forest); } .wire-row.dim .wire-main { color: var(--ink-3); }\n";44 export const CONSOLE_JS = "(async () => {\n const $ = (s) => document.querySelector(s);\n const $$ = (s) => document.querySelectorAll(s);\n const host = location.host;\n const wsURL = (location.protocol === \"https:\" ? \"wss://\" : \"ws://\") + host;\n const rpcURL = location.origin + \"/\";\n let info = null, me = null, owner = \"\", policy = null, fuel = null, people = null, myRole = \"\";\n\n const toast = (msg) => { const t = $(\"#toast\"); t.textContent = msg; t.classList.add(\"show\"); clearTimeout(t._t); t._t = setTimeout(() => t.classList.remove(\"show\"), 2600); };\n // guard wraps a handler: disables the button, shows errors as toasts. Defined first, since handlers below use it.\n const guard = (fn) => async (ev) => { ev.preventDefault(); const b = ev.submitter || ev.target; if (b) b.disabled = true; try { await fn(ev); } catch (e) { toast(e.message); } finally { if (b) b.disabled = false; } };\n const short = (hex) => hex ? hex.slice(0, 8) + \"…\" + hex.slice(-4) : \"\";\n // key shows a key short but keeps the whole of it in the DOM: selecting, double-clicking or clicking it copies the full hex.\n const key = (hex) => hex ? '<span class=\"key\" data-short=\"' + short(hex) + '\" title=\"' + hex + '\"><span class=\"full\">' + hex + '</span></span>' : '';\n const hue = (hex) => (parseInt(hex.slice(0, 2), 16) * 360 / 256).toFixed(0) + \"deg\";\n const av = (hex) => '<i class=\"av\" style=\"--h:' + hue(hex) + '\"></i>';\n const fmtBytes = (n) => n < 1e6 ? (n / 1e3).toFixed(0) + \" KB\" : n < 1e9 ? (n / 1e6).toFixed(1) + \" MB\" : (n / 1e9).toFixed(2) + \" GB\";\n const fmtHours = (ms) => ms < 3600e3 ? Math.round(ms / 60e3) + \" min\" : (ms / 3600e3).toFixed(ms < 36e6 ? 1 : 0) + \" h\";\n const fuelOver = () => !!fuel && (fuel.eventBytes > fuel.freeEventBytes || fuel.mediaBytes > fuel.freeMediaBytes || fuel.activeMs > fuel.freeActiveMs || fuel.rowsWritten > fuel.freeRowsWritten);\n const fmtTime = (t) => t ? new Date(t * 1000).toLocaleString(undefined, { month: \"short\", day: \"numeric\", hour: \"2-digit\", minute: \"2-digit\" }) : \"–\";\n const fmtDay = (t) => t ? new Date(t * 1000).toLocaleDateString(undefined, { month: \"short\", day: \"numeric\" }) : \"–\";\n const ago = (t) => { const s = Math.max(0, Math.floor(Date.now() / 1000) - t); return s < 60 ? \"just now\" : s < 3600 ? Math.floor(s / 60) + \" min\" : s < 86400 ? Math.floor(s / 3600) + \" h\" : Math.floor(s / 86400) + \" d\"; };\n const esc = (s) => String(s ?? \"\").replace(/[&<>\"']/g, (c) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" })[c]);\n\n const IC = {\n x: '<svg viewBox=\"0 0 24 24\"><path d=\"M6 6l12 12M18 6L6 18\"/></svg>',\n undo: '<svg viewBox=\"0 0 24 24\"><path d=\"M4 10h10a5 5 0 0 1 0 10h-3M4 10l4-4M4 10l4 4\"/></svg>',\n check: '<svg viewBox=\"0 0 24 24\"><path d=\"M5 12l5 5L20 7\"/></svg>',\n trash: '<svg viewBox=\"0 0 24 24\"><path d=\"M4 7h16M6 7l1.2 13h9.6L18 7\"/></svg>',\n ban: '<svg viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"8.5\"/><path d=\"M6 6l12 12\"/></svg>',\n banuser: '<svg viewBox=\"0 0 24 24\"><circle cx=\"10\" cy=\"8\" r=\"4\"/><path d=\"M3 21a7 7 0 0 1 11-5.7M15 15l6 6M21 15l-6 6\"/></svg>',\n eye: '<svg viewBox=\"0 0 24 24\"><path d=\"M2 12s4-6 10-6 10 6 10 6-4 6-10 6S2 12 2 12z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg>',\n lock: '<svg viewBox=\"0 0 24 24\"><rect x=\"5\" y=\"10\" width=\"14\" height=\"10\" rx=\"2\"/><path d=\"M8 10V7a4 4 0 0 1 8 0v3\"/></svg>',\n pen: '<svg viewBox=\"0 0 24 24\"><path d=\"M4 20l4-1 11-11-3-3L5 16l-1 4zM13 8l3 3\"/></svg>',\n people: '<svg viewBox=\"0 0 24 24\"><circle cx=\"9\" cy=\"8\" r=\"3.5\"/><circle cx=\"17\" cy=\"9\" r=\"2.5\"/><path d=\"M3 20a6 6 0 0 1 12 0M15 20a4.5 4.5 0 0 1 7-3.5\"/></svg>',\n person: '<svg viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"8\" r=\"4\"/><path d=\"M4 21a8 8 0 0 1 16 0\"/></svg>',\n gauge: '<svg viewBox=\"0 0 24 24\"><path d=\"M4 16a8 8 0 0 1 16 0M12 16l4-5\"/><circle cx=\"12\" cy=\"16\" r=\"1.2\"/></svg>',\n bolt: '<svg viewBox=\"0 0 24 24\"><path d=\"M13 3L5 14h6l-1 7 8-11h-6z\"/></svg>',\n copy: '<svg viewBox=\"0 0 24 24\"><rect x=\"9\" y=\"9\" width=\"11\" height=\"11\" rx=\"2\"/><path d=\"M5 15V6a2 2 0 0 1 2-2h9\"/></svg>',\n pin: '<svg viewBox=\"0 0 24 24\"><path d=\"M9 4h6l-1 6 3 3v2H7v-2l3-3-1-6zM12 15v5\"/></svg>',\n };\n const ib = (icon, label, act, id, extra) => '<button class=\"ib' + (extra ? \" \" + extra : \"\") + '\" title=\"' + label + '\" aria-label=\"' + label + '\" data-act=\"' + act + '\" data-id=\"' + esc(id) + '\">' + IC[icon] + \"</button>\";\n\n const CH = \"qpzry9x8gf2tvdw0s3jn54khce6mua7l\";\n function npubToHex(s) {\n s = s.trim().toLowerCase();\n if (/^[0-9a-f]{64}$/.test(s)) return s;\n if (!s.startsWith(\"npub1\")) return null;\n const data = s.slice(5, -6).split(\"\").map((c) => CH.indexOf(c));\n if (data.some((d) => d < 0)) return null;\n let bits = 0, acc = 0, out = [];\n for (const d of data) { acc = (acc << 5) | d; bits += 5; if (bits >= 8) { bits -= 8; out.push((acc >> bits) & 255); } }\n return out.length === 32 ? out.map((b) => b.toString(16).padStart(2, \"0\")).join(\"\") : null;\n }\n\n async function sha256hex(s) {\n const b = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(s));\n return [...new Uint8Array(b)].map((x) => x.toString(16).padStart(2, \"0\")).join(\"\");\n }\n // ---- signing: a NIP-07 extension, or a NIP-46 remote signer ----\n // The remote path loads the bundled library on first use; its session\n // (client key, signer pubkey, relays) lives in localStorage until sign-out.\n let remote = null, lib = null, pendingNote = null;\n const NO_SIGNER = \"Install a nostr extension (Alby, nos2x, …) or connect a remote signer.\";\n const withTimeout = (p, ms, what) => Promise.race([p, new Promise((_, rej) => setTimeout(() => rej(new Error(what + \" did not answer; is the signer app open?\")), ms))]);\n const signer = {\n ready: () => !!remote || !!window.nostr,\n async getPublicKey() { if (remote) return withTimeout(remote.getPublicKey(), 20000, \"The remote signer\"); if (window.nostr) return window.nostr.getPublicKey(); throw new Error(NO_SIGNER); },\n async signEvent(ev) { if (remote) return withTimeout(remote.signEvent(ev), 60000, \"The remote signer\"); if (window.nostr) return window.nostr.signEvent(ev); throw new Error(NO_SIGNER); },\n };\n async function signerLib() {\n if (lib) return lib;\n if (!window.NostrSigner) await new Promise((res, rej) => { const s = document.createElement(\"script\"); s.src = window.SIGNER_URL || \"/signer.js\"; s.onload = res; s.onerror = () => rej(new Error(\"Could not load the signer library.\")); document.head.appendChild(s); });\n return (lib = window.NostrSigner);\n }\n const onauth = (u) => window.open(u, \"_blank\");\n // The signer conversation rides this relay, and a relay may ask the socket to\n // authenticate before it delivers kind 24133 to its parties; answer with the\n // session's own key.\n const authPool = (L, sk) => new L.SimplePool({ automaticallyAuth: () => (evt) => Promise.resolve(L.finalizeEvent(evt, sk)) });\n const saveSession = (sk, s, secret) => localStorage.setItem(\"nip46\", JSON.stringify({ sk: lib.bytesToHex(sk), pubkey: s.bp.pubkey, relays: s.bp.relays, secret: secret || null }));\n async function resumeRemote() {\n const raw = localStorage.getItem(\"nip46\");\n if (!raw) return false;\n try {\n const s = JSON.parse(raw); const L = await signerLib();\n remote = L.BunkerSigner.fromBunker(L.hexToBytes(s.sk), { pubkey: s.pubkey, relays: s.relays, secret: s.secret }, { onauth, pool: authPool(L, L.hexToBytes(s.sk)) });\n return true;\n } catch { localStorage.removeItem(\"nip46\"); return false; }\n }\n async function connectBunker(input) {\n const L = await signerLib();\n const bp = await L.parseBunkerInput(input.trim());\n if (!bp) throw new Error(\"That is not a bunker:// URL.\");\n if (!bp.relays.length) throw new Error(\"The bunker URL names no relay.\");\n const sk = L.generateSecretKey();\n const s = L.BunkerSigner.fromBunker(sk, bp, { onauth, pool: authPool(L, sk) });\n await withTimeout(s.connect({ name: host, url: location.origin }), 60000, \"The signer\");\n await Promise.race([s.switchRelays(), new Promise((r) => setTimeout(r, 3000))]);\n remote = s; saveSession(sk, s, bp.secret);\n }\n // The nostrconnect:// flow: this relay carries the traffic, so no third relay is involved.\n let ncPending = null;\n async function offerNostrConnect() {\n if (ncPending) return ncPending;\n const L = await signerLib();\n const sk = L.generateSecretKey();\n const secret = L.bytesToHex(crypto.getRandomValues(new Uint8Array(8)));\n const uri = L.createNostrConnectURI({ clientPubkey: L.getPublicKey(sk), relays: [wsURL], secret, name: host, url: location.origin, perms: [\"sign_event:27235\", \"sign_event:9734\"] });\n $(\"#nclink\").href = uri; $(\"#ncnote\").textContent = \"\";\n $(\"#ncqr\").src = \"/qr.svg?text=\" + encodeURIComponent(uri); $(\"#ncqr\").classList.remove(\"hidden\");\n ncPending = L.BunkerSigner.fromURI(sk, uri, { onauth, pool: authPool(L, sk) }, 600000).then((s) => { remote = s; saveSession(sk, s, secret); ncPending = null; return s; }, (e) => { ncPending = null; throw e; });\n return ncPending;\n }\n async function remoteDone() {\n me = await signer.getPublicKey();\n localStorage.setItem(\"me\", me);\n $(\"#remotesec\").classList.add(\"hidden\");\n toast(\"Remote signer connected\");\n if (pendingNote && !owner) { const n = pendingNote; pendingNote = null; await claimNow(n); return; }\n pendingNote = null;\n renderHeader(); await loadAdmin(); await loadPeople();\n }\n function showRemote(note) {\n pendingNote = note || null;\n $(\"#remotesec\").classList.remove(\"hidden\");\n $(\"#remotesec\").scrollIntoView({ behavior: \"smooth\", block: \"start\" });\n offerNostrConnect().then(remoteDone).catch((e) => { $(\"#ncnote\").textContent = e.message; });\n }\n\n async function rpc(method, ...params) {\n if (!signer.ready()) throw new Error(NO_SIGNER);\n const body = JSON.stringify({ method, params });\n const ev = await signer.signEvent({ kind: 27235, created_at: Math.floor(Date.now() / 1000), content: \"\", tags: [[\"u\", rpcURL], [\"method\", \"POST\"], [\"payload\", await sha256hex(body)]] });\n const resp = await fetch(rpcURL, { method: \"POST\", headers: { \"content-type\": \"application/nostr+json+rpc\", authorization: \"Nostr \" + btoa(JSON.stringify(ev)) }, body });\n const json = await resp.json();\n if (json.error) throw new Error(json.error);\n return json.result;\n }\n\n // ---- front of house ----\n // urlish shows a URL as a short link: the host and the start of the path, the whole thing on hover and as the target.\n class Html { constructor(s) { this.s = s; } }\n const urlish = (u) => {\n if (!u) return \"\";\n let label = u; try { const x = new URL(u); label = x.host + (x.pathname.length > 1 ? x.pathname.slice(0, 18) + (x.pathname.length > 18 ? \"\\u2026\" : \"\") : \"\"); } catch { /* not a URL */ }\n if (!/^https?:\\/\\//.test(u)) return u;\n return new Html('<a href=\"' + esc(u) + '\" target=\"_blank\" rel=\"noopener\" title=\"' + esc(u) + '\">' + esc(label) + \"</a>\");\n };\n function renderHeader() {\n const name = (info && info.name) || host.split(\".\")[0];\n $(\"#title\").textContent = name;\n document.title = name + \" - relay\";\n $(\"#url\").textContent = wsURL;\n $(\"#d-ws\").textContent = wsURL; $(\"#d-blossom\").textContent = location.origin; $(\"#d-nip05\").textContent = \"you@\" + host;\n $(\"#nip05-example\").textContent = \"alice@\" + host;\n const desc = (info && info.description) || \"\";\n $(\"#desc\").textContent = desc; $(\"#desc\").classList.toggle(\"hidden\", !desc);\n // What the owner declared about the relay, on the page, not only in the document clients read.\n const img = (id, url) => { const el = $(id); if (url) el.src = url; el.classList.toggle(\"hidden\", !url); };\n img(\"#bannerimg\", info?.banner); $(\"#banner\").classList.toggle(\"hidden\", !info?.banner);\n img(\"#iconimg\", info?.icon);\n const meta = [];\n for (const t of info?.tags || []) meta.push('<span class=\"tag\">' + esc(t) + \"</span>\");\n const where = [...(info?.language_tags || []), ...(info?.relay_countries || [])];\n if (where.length) meta.push(\"<span>\" + esc(where.join(\", \")) + \"</span>\");\n const link = (label, href) => href ? '<a href=\"' + esc(href) + '\" target=\"_blank\" rel=\"noopener\">' + label + \"</a>\" : \"\";\n const contact = info?.contact ? (/^(mailto:|https?:\\/\\/)/.test(info.contact) ? link(\"Contact\", info.contact) : \"<span>\" + esc(info.contact) + \"</span>\") : \"\";\n for (const x of [contact, link(\"Terms\", info?.terms_of_service), link(\"Posting policy\", info?.posting_policy), link(\"Privacy\", info?.privacy_policy)]) if (x) meta.push(x);\n $(\"#metarow\").innerHTML = meta.join('<span class=\"sep\">|</span>'); $(\"#metarow\").classList.toggle(\"hidden\", meta.length === 0);\n $(\"#frontsec\").classList.toggle(\"hidden\", !desc && meta.length === 0);\n owner = (info && info.pubkey) || \"\";\n const isOwner = !!me && me === owner;\n const lease = !owner && info && info.lease ? info.lease : null;\n $(\"#ownerline\").innerHTML = owner ? 'run by <b>' + key(owner) + \"</b>\" : lease ? \"temporary until \" + fmtDay(lease.expires_at) : \"unclaimed\";\n $(\"#owner-av\").classList.toggle(\"hidden\", !owner);\n if (owner) $(\"#owner-av\").style.setProperty(\"--h\", hue(owner));\n $(\"#who\").textContent = me ? (isOwner ? \"(that's you)\" : \"\") : \"\";\n $(\"#unclaimed\").classList.toggle(\"hidden\", !!owner || !!lease);\n $(\"#leased\").classList.toggle(\"hidden\", !lease);\n if (lease) {\n $(\"#lease-until\").textContent = fmtTime(lease.expires_at);\n $(\"#leasenote\").textContent = lease.holder ? \"Reserved for the key that asked for it; sign with that key.\" : \"\";\n }\n $(\"#signin\").classList.toggle(\"hidden\", !!me || !owner);\n $(\"#signin46\").classList.toggle(\"hidden\", !!me || !owner);\n $(\"#signout\").classList.toggle(\"hidden\", !me);\n $(\"#about\").innerHTML = [\n [\"Name\", info?.name], [\"Description\", info?.description], [\"Contact\", info?.contact], [\"Owner\", owner ? short(owner) : \"\"], [\"Relay key\", info?.self ? short(info.self) : \"\"], [\"Software\", urlish(info?.software)], [\"Version\", info?.version],\n [\"Max query\", info?.limitation?.max_limit], [\"Auth required\", String(!!info?.limitation?.auth_required)], [\"Restricted writes\", String(!!info?.limitation?.restricted_writes)], [\"Min PoW\", info?.limitation?.min_pow_difficulty || 0],\n [\"Tags\", info?.tags?.join(\", \")], [\"Languages\", info?.language_tags?.join(\", \")], [\"Countries\", info?.relay_countries?.join(\", \")],\n [\"Terms\", urlish(info?.terms_of_service)], [\"Posting policy\", urlish(info?.posting_policy)], [\"Privacy policy\", urlish(info?.privacy_policy)], [\"Icon\", urlish(info?.icon)], [\"Banner\", urlish(info?.banner)],\n [\"NIPs\", info?.supported_nips?.join(\" \")],\n ].map(([k, v]) => \"<div><small>\" + k + \"</small><div\" + ([\"Owner\", \"NIPs\", \"Relay key\"].includes(k) ? ' class=\"mono\"' : \"\") + \">\" + (v === undefined || v === \"\" ? '<span class=\"muted\">–</span>' : v instanceof Html ? v.s : esc(v)) + \"</div></div>\").join(\"\");\n renderCare();\n }\n\n function renderCare() {\n const g = (icon, label, off) => '<span class=\"g' + (off ? \" off\" : \"\") + '\" title=\"' + label + '\"><i>' + IC[icon] + \"</i><small>\" + label + \"</small></span>\";\n const writes = policy ? policy.writes : (info?.limitation?.restricted_writes ? \"allowlist\" : \"open\");\n const reads = policy ? policy.reads : (info?.limitation?.auth_required ? \"auth\" : \"open\");\n let out = writes === \"owner\" ? g(\"person\", \"only owner writes\") : writes === \"allowlist\" ? g(\"people\", \"members write\") : writes === \"wot\" ? g(\"people\", \"members and follows write\") : g(\"pen\", \"anyone writes\");\n out += reads === \"members\" ? g(\"people\", \"members read\") : reads === \"auth\" ? g(\"lock\", \"sign in to read\") : g(\"eye\", \"anyone reads\");\n if (fuel) {\n const over = fuelOver();\n out += fuel.outOfFuel ? g(\"gauge\", \"out of fuel\", true) : over ? g(\"bolt\", \"burning sats\") : g(\"gauge\", \"on free allowance\");\n }\n $(\"#care\").innerHTML = out;\n }\n\n async function loadPeople() {\n try { people = await (await fetch(\"/people\")).json(); } catch { return; }\n const isOwner = !!me && me === owner;\n let list = people.people || [];\n if (!people.public && isOwner && policy) list = (window.__members || []);\n $(\"#peoplesec\").classList.toggle(\"hidden\", !owner || list.length === 0);\n $(\"#people-note\").classList.toggle(\"hidden\", people.public);\n $(\"#people-count\").textContent = list.length || \"\";\n $(\"#dir\").innerHTML = list.map((m) => '<span class=\"who' + (m.pubkey === me ? \" me\" : \"\") + '\" title=\"' + esc(m.pubkey) + '\">' + av(m.pubkey) + '<span class=\"mono\">' + (m.name ? esc(m.name) + \"@\" + host : key(m.pubkey)) + \"</span>\" + (m.role === \"owner\" ? '<span class=\"role\">owner</span>' : \"\") + \"</span>\").join(\"\");\n }\n\n async function loadInfo() {\n info = await (await fetch(\"/\", { headers: { accept: \"application/nostr+json\" } })).json();\n renderHeader();\n }\n\n async function loadFuel() {\n try { fuel = await (await fetch(\"/fuel\")).json(); } catch { return; }\n $(\"#fuelsec\").classList.toggle(\"hidden\", !owner);\n const pct = (used, free) => Math.min(100, Math.round((used / Math.max(free, 1)) * 100));\n const gauge = (id, used, free, text) => { const g = $(\"#g-\" + id), share = free ? used / free : 0; g.style.width = pct(used, free) + \"%\"; g.classList.toggle(\"over\", share > 1); g.classList.toggle(\"warm\", share > 0.75 && share <= 1); $(\"#p-\" + id).textContent = share > 1 ? \"over\" : Math.round(share * 100) + \"%\"; $(\"#t-\" + id).textContent = text; };\n gauge(\"events\", fuel.eventBytes, fuel.freeEventBytes, fmtBytes(fuel.eventBytes) + \" of \" + fmtBytes(fuel.freeEventBytes) + \" free\");\n gauge(\"media\", fuel.mediaBytes, fuel.freeMediaBytes, fmtBytes(fuel.mediaBytes) + \" of \" + fmtBytes(fuel.freeMediaBytes) + \" free\");\n gauge(\"active\", fuel.activeMs, fuel.freeActiveMs, fmtHours(fuel.activeMs) + \" of \" + fmtHours(fuel.freeActiveMs) + \" free\");\n gauge(\"rows\", fuel.rowsWritten, fuel.freeRowsWritten, fuel.rowsWritten.toLocaleString() + \" of \" + fuel.freeRowsWritten.toLocaleString() + \" free\");\n const sats = Math.floor(fuel.balanceMsats / 1000);\n const r = fuel.rates;\n $(\"#fuel-balance\").innerHTML = fuel.outOfFuel\n ? '<span class=\"chip\">out of fuel</span> Writes are paused until someone tops up.'\n : \"Balance <b>\" + sats.toLocaleString() + \" sats</b>. Past the allowances, prices track what the hosting costs: \" + r.satsPerGBMonthEvents.toLocaleString() + \" sats per GB-month of events, \" + r.satsPerGBMonthMedia.toLocaleString() + \" per GB-month of files, \" + r.satsPerActiveHour.toLocaleString() + \" per hour awake, \" + r.satsPerMillionRows.toLocaleString() + \" per million rows written. Traffic is free.\";\n $(\"#topup\").classList.toggle(\"hidden\", !fuel.enabled);\n // Who paid is the owner's to see: it comes with the signed stats call.\n let credits = [];\n if (owner && signer.ready()) { try { credits = (await rpc(\"stats\")).credits || []; } catch { credits = []; } }\n $(\"#credits tbody\").innerHTML = credits.length ? credits.map((c) => '<tr><td class=\"dim\">' + esc(fmtTime(c.at)) + '</td><td class=\"mono\" title=\"' + esc(c.payer) + '\">' + av(c.payer) + key(c.payer) + '</td><td class=\"mono r\">' + Math.floor(c.msats / 1000).toLocaleString() + \"</td></tr>\").join(\"\") : '<tr><td colspan=\"3\" class=\"muted\">no zaps yet</td></tr>';\n if (!fuel.enabled) $(\"#topup-note\").textContent = \"Top-ups are not enabled on this service yet.\";\n renderCare();\n renderFuelTile();\n }\n\n async function topUp(sats) {\n if (!signer.ready()) throw new Error(NO_SIGNER);\n const msats = Math.round(sats * 1000);\n const zapRequest = await signer.signEvent({ kind: 9734, created_at: Math.floor(Date.now() / 1000), content: \"fuel for \" + host, tags: [[\"p\", fuel.servicePubkey], [\"amount\", String(msats)], [\"relays\", wsURL]] });\n const r = await (await fetch(\"/fuel/invoice\", { method: \"POST\", body: JSON.stringify({ zapRequest }) })).json();\n if (r.error) throw new Error(r.error);\n const inv = r.invoice;\n $(\"#invoice\").classList.remove(\"hidden\");\n $(\"#inv-text\").value = inv; $(\"#inv-link\").href = \"lightning:\" + inv; $(\"#inv-state\").textContent = \"waiting for payment…\";\n if (window.webln) { try { await window.webln.enable(); await window.webln.sendPayment(inv); } catch (e) { toast(e.message || \"wallet declined\"); } }\n const before = fuel.creditedMsats;\n for (let i = 0; i < 120; i++) {\n await new Promise((r) => setTimeout(r, 2500));\n await loadFuel();\n if (fuel.creditedMsats > before) { $(\"#inv-state\").textContent = \"paid, thank you\"; toast(\"Fuel credited\"); return; }\n }\n $(\"#inv-state\").textContent = \"no receipt yet; it can take a minute after paying\";\n }\n\n // ---- console ----\n function showTab(name) {\n if (name === \"content\" || name === \"storage\") name = \"data\";\n const known = myRole === \"moderator\" ? [\"people\", \"moderation\"] : [\"people\", \"moderation\", \"rules\", \"identity\", \"data\", \"sync\", \"views\", \"health\", \"owner\"];\n if (!known.includes(name)) name = \"people\";\n $$(\".tabs a\").forEach((a) => a.classList.toggle(\"on\", a.dataset.tab === name));\n $$(\".panel\").forEach((p) => p.classList.toggle(\"on\", p.dataset.panel === name));\n }\n // Tabs switch in place. A fragment still opens a tab when someone arrives with one,\n // but clicking never writes one, and any fragment already there is dropped.\n $$(\".tabs a\").forEach((a) => a.addEventListener(\"click\", (ev) => { ev.preventDefault(); showTab(a.dataset.tab); if (location.hash) history.replaceState(null, \"\", location.pathname + location.search); }));\n window.addEventListener(\"hashchange\", () => showTab(location.hash.slice(1)));\n // The strip fades on whichever side has more tabs off screen.\n const tabsFade = () => { const t = $(\"#tabs\"); t.classList.toggle(\"fade-l\", t.scrollLeft > 4); t.classList.toggle(\"fade-r\", t.scrollLeft + t.clientWidth < t.scrollWidth - 4); };\n $(\"#tabs\").addEventListener(\"scroll\", tabsFade); window.addEventListener(\"resize\", tabsFade); new ResizeObserver(tabsFade).observe($(\"#tabs\"));\n\n function renderFuelTile() {\n if (!fuel) return;\n const over = fuelOver();\n const sats = Math.max(0, Math.floor(fuel.balanceMsats / 1000));\n const d = new Date(), end = Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + 1, 1), days = Math.max(1, Math.ceil((end - Date.now()) / 86400000));\n if (fuel.outOfFuel) { $(\"#h-fuel\").textContent = \"0 sats\"; $(\"#h-fuel-label\").textContent = \"out of fuel\"; $(\"#h-fuel-sub\").textContent = \"writes are paused\"; }\n else if (over) { $(\"#h-fuel\").textContent = sats.toLocaleString() + \" sats\"; $(\"#h-fuel-label\").textContent = \"fuel left\"; $(\"#h-fuel-sub\").textContent = \"past the free allowance\"; }\n else { $(\"#h-fuel\").textContent = days + (days === 1 ? \" day\" : \" days\"); $(\"#h-fuel-label\").textContent = \"free allowance left\"; $(\"#h-fuel-sub\").textContent = sats ? \"then \" + sats.toLocaleString() + \" sats\" : \"then a top-up is needed\"; }\n $(\"#usage\").innerHTML = [[\"events stored\", fmtBytes(fuel.eventBytes)], [\"files stored\", fmtBytes(fuel.mediaBytes)], [\"awake this month\", fmtHours(fuel.activeMs)], [\"rows written\", fuel.rowsWritten.toLocaleString()], [\"rows read\", fuel.rowsRead.toLocaleString()], [\"received this month\", fmtBytes(fuel.bytesIn)], [\"served this month\", fmtBytes(fuel.bytesOut)], [\"charged\", Math.floor(fuel.chargedMsats / 1000).toLocaleString() + \" sats\"]]\n .map(([k, v]) => \"<div><small>\" + k + \"</small><b>\" + v + \"</b></div>\").join(\"\");\n }\n\n function renderHealth(stats) {\n $(\"#h-last\").textContent = stats.newest ? ago(stats.newest) : \"none\";\n $(\"#h-last-sub\").textContent = stats.newest ? fmtTime(stats.newest) : \"no events yet\";\n $(\"#h-conns\").textContent = stats.connections;\n const buckets = [[\"notes\", \"k1\", [1]], [\"reactions\", \"k2\", [7]], [\"DMs\", \"k3\", [4, 14, 1059]], [\"long-form\", \"k4\", [30023]], [\"other\", \"k5\", null]];\n const sums = buckets.map(() => 0); let total = 0;\n for (const { kind, n } of stats.kinds || []) { total += n; const i = buckets.findIndex((b) => b[2] && b[2].includes(kind)); sums[i < 0 ? 4 : i] += n; }\n const parts = buckets.map((b, i) => [b[0], b[1], sums[i]]).filter((p) => p[2] > 0);\n $(\"#h-kinds\").innerHTML = total ? parts.map(([name, cls, n]) => '<i class=\"' + cls + '\" style=\"flex:' + n + '\" title=\"' + name + \" \" + Math.round(n * 100 / total) + '%\"></i>').join(\"\") : '<i class=\"k5\" style=\"flex:1\"></i>';\n $(\"#h-kinds-legend\").innerHTML = total ? parts.map(([name, cls, n]) => '<span><i class=\"' + cls + '\"></i>' + name + \" \" + Math.round(n * 100 / total) + \"%</span>\").join(\"\") : \"<span>no events yet</span>\";\n renderFuelTile();\n }\n\n async function loadAdmin() {\n const isOwner = !!me && me === owner;\n myRole = isOwner ? \"owner\" : \"\";\n let stats = null, p = null;\n // A signed-in member may be a moderator; the relay says by answering stats.\n if (me && owner && !isOwner) { try { [stats, p] = await Promise.all([rpc(\"stats\"), rpc(\"getpolicy\")]); myRole = \"moderator\"; } catch { myRole = \"\"; } }\n $(\"#console\").classList.toggle(\"hidden\", !myRole);\n loadMine();\n $(\"#console\").classList.toggle(\"mod\", myRole === \"moderator\");\n if (!myRole) { policy = null; renderCare(); return; }\n if (isOwner) [stats, p] = await Promise.all([rpc(\"stats\"), rpc(\"getpolicy\")]);\n policy = p;\n renderHealth(stats);\n renderCare();\n const fa = $(\"#access\"), fi = $(\"#identity\");\n fa.writes.value = p.writes; fa.reads.value = p.reads; fa.openKinds.value = (p.openKinds || []).join(\", \"); fa.guestReplies.checked = !!p.guestReplies;\n $(\"#wordsform\").words.value = (p.blockedWords || []).join(\"\\n\"); $(\"#wordsform\").inTags.checked = !!p.blockedWordsInTags; $(\"#thresholdform\").reportThreshold.value = p.reportThreshold || 0; fa.minPow.value = p.minPow; fa.maxFuture.value = p.maxFuture; fa.maxLimit.value = p.maxLimit; fa.maxSubs.value = p.maxSubs; fa.maxMessageKB.value = p.maxMessageKB;\n fa.eventsPerMinute.value = p.eventsPerMinute; fa.reqsPerMinute.value = p.reqsPerMinute; fa.maxBlobMB.value = p.maxBlobMB;\n fi.name.value = p.name; fi.contact.value = p.contact; fi.description.value = p.description; fi.icon.value = p.icon;\n const fj = $(\"#joinform\"); fj.joinTerms.value = p.joinTerms; fj.directoryPublic.checked = !!p.directoryPublic;\n loadCard();\n fi.banner.value = p.banner || \"\"; fi.postingPolicy.value = p.postingPolicy || \"\"; fi.privacyPolicy.value = p.privacyPolicy || \"\";\n fi.tags.value = (p.tags || []).join(\", \"); fi.languageTags.value = (p.languageTags || []).join(\", \"); fi.relayCountries.value = (p.relayCountries || []).join(\", \");\n const fn = $(\"#notify\"), nt = p.notify || {};\n fn.reports.checked = !!nt.reports; fn.fuel.checked = !!nt.fuel; fn.jobs.checked = !!nt.jobs; fn.succession.checked = !!nt.succession; fn.digest.checked = !!nt.digest;\n showTab(location.hash.slice(1));\n if (isOwner) { await renderPresets(); renderWire(); renderSuccession(); loadDomains(); }\n await Promise.all([loadLists(), loadEvents(true), loadPins(), ...(isOwner ? [loadStorage()] : [])]);\n }\n\n async function loadAudit(before) {\n const rows = await rpc(\"listaudit\", before || 0);\n const html = rows.map((r) => '<tr data-seq=\"' + r.seq + '\"><td class=\"dim\">' + esc(fmtTime(r.at)) + '</td><td class=\"mono\" title=\"' + esc(r.actor) + '\">' + av(r.actor) + key(r.actor) + '</td><td><span class=\"kind\">' + esc(r.action) + '</span></td><td class=\"mono\">' + (/^[0-9a-f]{64}$/.test(r.target) ? key(r.target) : esc(r.target)) + '</td><td class=\"c\">' + esc(r.detail) + '</td></tr>').join(\"\");\n const tb = $(\"#audit tbody\");\n if (before) tb.insertAdjacentHTML(\"beforeend\", html); else tb.innerHTML = html || '<tr><td class=\"dim\" colspan=\"5\">Nothing yet</td></tr>';\n $(\"#audit-more\").style.display = rows.length < 100 ? \"none\" : \"\";\n }\n $(\"#audit-more\").onclick = guard(async () => { const last = $(\"#audit tbody tr:last-child\"); await loadAudit(last ? +last.dataset.seq : 0); });\n async function loadLists() {\n loadAudit(0);\n const [mem, bans, bannedEvents, allow, block, invites, reports, blobs, blocks] = await Promise.all([rpc(\"listmembers\"), rpc(\"listbannedpubkeys\"), rpc(\"listbannedevents\"), rpc(\"listallowedkinds\"), rpc(\"listblockedkinds\"), rpc(\"listinvites\"), rpc(\"listreports\"), rpc(\"listblobs\", 100), rpc(\"listblockedips\")]);\n const members = mem.members;\n window.__members = members;\n const roleCell = (m) => m.role === \"owner\" ? ' <span class=\"chip\">owner</span>' : myRole === \"owner\" ? ' <select class=\"txt role\" title=\"Role\"><option value=\"member\"' + (m.role === \"member\" ? \" selected\" : \"\") + '>member</option><option value=\"moderator\"' + (m.role === \"moderator\" ? \" selected\" : \"\") + '>moderator</option></select>' : m.role === \"moderator\" ? ' <span class=\"chip\">moderator</span>' : \"\";\n const untouchable = (m) => m.role === \"owner\" || (myRole !== \"owner\" && m.role === \"moderator\");\n // The tree: everyone under whoever invited them, the owner and the owner's own additions at the root.\n const known = new Set(members.map((m) => m.pubkey));\n const byInviter = new Map();\n for (const m of members) { const k = m.role !== \"owner\" && known.has(m.invited_by) ? m.invited_by : \"\"; if (!byInviter.has(k)) byInviter.set(k, []); byInviter.get(k).push(m); }\n const ordered = [], placed = new Set();\n const walk = (k, depth) => { for (const m of byInviter.get(k) || []) { if (placed.has(m.pubkey)) continue; placed.add(m.pubkey); m.depth = depth; ordered.push(m); walk(m.pubkey, depth + 1); } };\n walk(\"\", 0);\n for (const m of members) if (!placed.has(m.pubkey)) { m.depth = 0; ordered.push(m); }\n const nameOf = (pk) => { const x = members.find((m) => m.pubkey === pk); return x && x.name ? x.name : short(pk); };\n const limits = (m) => m.role === \"owner\" ? \"\" : myRole === \"owner\"\n ? '<input class=\"txt num keep\" type=\"number\" min=\"0\" placeholder=\"days\" title=\"Keep this member\\'s events for this many days; blank follows the relay rules\" value=\"' + (m.keep_days || \"\") + '\"><input class=\"txt num cap\" type=\"number\" min=\"0\" placeholder=\"KB\" title=\"Cap on stored bytes, in KB; blank is unlimited\" value=\"' + (m.max_bytes ? Math.round(m.max_bytes / 1024) : \"\") + '\">'\n : '<span class=\"dim\">' + (m.keep_days ? m.keep_days + \" d\" : \"\") + (m.max_bytes ? \" \" + fmtBytes(m.max_bytes) : \"\") + \"</span>\";\n $(\"#members tbody\").innerHTML = ordered.map((m) => '<tr data-pk=\"' + m.pubkey + '\"><td class=\"mono\" title=\"' + esc(m.pubkey) + '\" style=\"padding-left:' + (0.5 + m.depth * 1.1) + 'rem\">' + av(m.pubkey) + key(m.pubkey) + roleCell(m) + (m.depth ? ' <span class=\"dim\">via ' + esc(nameOf(m.invited_by)) + \"</span>\" : \"\") + (m.invites ? ' <span class=\"chip\" title=\"live invites\">' + m.invites + \" inv</span>\" : \"\") + '</td><td class=\"name\"><input class=\"txt name\" value=\"' + esc(m.name || \"\") + '\" placeholder=\"name\" pattern=\"[a-z0-9._-]{1,64}\"></td><td><input class=\"txt note\" value=\"' + esc(m.note || \"\") + '\" placeholder=\"note\"></td><td class=\"lim\">' + limits(m) + '</td><td class=\"dim\">' + esc(fmtDay(m.joined_at)) + \", \" + esc(m.via) + '</td><td class=\"r\">' + ib(\"check\", \"Save\", \"savemember\", m.pubkey) + (untouchable(m) ? \"\" : ib(\"x\", \"Remove\", \"removemember\", m.pubkey) + ib(\"banuser\", \"Ban\", \"banpubkey\", m.pubkey, \"danger\")) + \"</td></tr>\").join(\"\");\n const tf = $(\"#treeform\");\n tf.classList.toggle(\"hidden\", myRole !== \"owner\");\n if (policy && policy.memberInvites) { tf.depth.value = policy.memberInvites.depth; tf.quota.value = policy.memberInvites.quota; }\n $(\"#transfer [name=pubkey]\").innerHTML = members.filter((m) => m.role !== \"owner\").map((m) => '<option value=\"' + m.pubkey + '\">' + esc(m.name ? m.name + \"@\" + host : key(m.pubkey)) + \"</option>\").join(\"\");\n $(\"#succession [name=heir]\").innerHTML = $(\"#transfer [name=pubkey]\").innerHTML;\n $(\"#tc-people\").textContent = members.length;\n const link = (code) => location.origin + \"/invite/\" + code;\n $(\"#invites\").innerHTML = invites.length ? invites.map((i) => '<li><i class=\"ev\">inv</i><span><a class=\"mono\" href=\"/invite/' + i.code + '\">…' + i.code.slice(-8) + \"</a> \" + esc(i.note || \"\") + ' <span class=\"dim\">' + i.uses + (i.max_uses ? \"/\" + i.max_uses : \"\") + \" used, until \" + esc(fmtDay(i.expires_at)) + \"</span></span><span>\" + ib(\"copy\", \"Copy link\", \"copy\", link(i.code)) + ib(\"x\", \"Revoke\", \"revokeinvite\", i.code) + \"</span></li>\").join(\"\") : '<li class=\"empty\">no invites</li>';\n const person = (r, icon, label, act) => \"<li>\" + av(r.pubkey) + '<span><b class=\"mono\" title=\"' + esc(r.pubkey) + '\">' + short(r.pubkey) + \"</b> \" + esc(r.reason || \"\") + \"</span>\" + ib(icon, label, act, r.pubkey) + \"</li>\";\n $(\"#bans\").innerHTML = bans.length ? bans.map((r) => person(r, \"undo\", \"Unban\", \"unrulepubkey\")).join(\"\") : '<li class=\"empty\">nobody banned</li>';\n $(\"#blocks\").innerHTML = blocks.length ? blocks.map((r) => '<li><i class=\"ev\">ip</i><span><b class=\"mono\">' + esc(r.ip) + \"</b> \" + esc(r.reason || \"\") + \"</span>\" + ib(\"undo\", \"Unblock\", \"unblockip\", r.ip) + \"</li>\").join(\"\") : '<li class=\"empty\">no addresses blocked</li>';\n $(\"#banned-events\").innerHTML = bannedEvents.map((r) => '<li><i class=\"ev\">ev</i><span><b class=\"mono\" title=\"' + esc(r.id) + '\">' + key(r.id) + \"</b> \" + esc(r.reason || \"\") + \"</span>\" + ib(\"check\", \"Allow again\", \"allowevent\", r.id) + \"</li>\").join(\"\");\n $(\"#reports tbody\").innerHTML = reports.length ? reports.map((r) => '<tr><td class=\"dim\">' + esc(fmtTime(r.at)) + '</td><td><span class=\"kind\">' + esc(r.type || \"report\") + '</span></td><td class=\"mono\" title=\"' + esc(r.target_pubkey) + '\">' + av(r.target_pubkey) + key(r.target_pubkey) + (r.target_event ? (r.blob ? ' <a class=\"dim\" href=\"/' + esc(r.target_event) + '\" target=\"_blank\" rel=\"noopener\">file ' + key(r.target_event) + \"</a>\" : ' <span class=\"dim\">ev ' + key(r.target_event) + (r.hidden ? ' <span class=\"tag\">hidden</span>' : \"\") + \"</span>\") : \"\") + '</td><td class=\"c\">' + esc(r.content) + '</td><td class=\"r\">' + ib(\"check\", \"Dismiss\", \"resolve:dismiss\", r.id) + ib(\"trash\", r.blob ? \"Delete the file\" : \"Delete the event\", \"resolve:delete\", r.id) + ib(\"banuser\", \"Ban the author\", \"resolve:ban\", r.id, \"danger\") + \"</td></tr>\").join(\"\") : '<tr><td colspan=\"5\" class=\"muted\">nothing reported</td></tr>';\n $(\"#reports-count\").textContent = reports.length || \"\"; $(\"#tc-reports\").textContent = reports.length || \"\";\n $(\"#blobs tbody\").innerHTML = blobs.length ? blobs.map((b) => '<tr><td class=\"dim\">' + esc(fmtTime(b.uploaded)) + '</td><td class=\"mono\"><a href=\"' + esc(b.url) + '\" target=\"_blank\" rel=\"noopener\">' + key(b.sha256) + '</a> <span class=\"dim\">' + esc(b.type) + '</span></td><td class=\"mono\">' + fmtBytes(b.size) + '</td><td class=\"mono\" title=\"' + esc(b.uploader) + '\">' + av(b.uploader) + key(b.uploader) + '</td><td class=\"r\">' + ib(\"trash\", \"Delete file\", \"deleteblob\", b.sha256, \"danger\") + \"</td></tr>\").join(\"\") : '<tr><td colspan=\"5\" class=\"muted\">no uploads</td></tr>';\n $(\"#blobs-count\").textContent = blobs.length || \"\";\n const tag = (k, cls) => '<span class=\"tag ' + cls + '\">' + k + ib(\"x\", \"Remove rule\", \"unrulekind\", String(k)) + \"</span>\";\n $(\"#kinds-allow\").innerHTML = allow.length ? allow.map((k) => tag(k, \"ok\")).join(\"\") : '<span class=\"tag plain\">all</span>';\n $(\"#kinds-block\").innerHTML = block.length ? block.map((k) => tag(k, \"blk\")).join(\"\") : '<span class=\"tag plain\">none</span>';\n await loadPeople();\n }\n\n const KIND_NAMES = { 0: \"profiles\", 1: \"notes\", 3: \"contacts\", 4: \"DMs\", 5: \"deletions\", 6: \"reposts\", 7: \"reactions\", 16: \"reposts\", 1059: \"gift wraps\", 1063: \"file headers\", 1111: \"comments\", 1984: \"reports\", 9734: \"zap requests\", 9735: \"zap receipts\", 9802: \"highlights\", 10002: \"relay lists\", 13534: \"roster\", 9000: \"group adds\", 9001: \"group removals\", 9021: \"join requests\", 9022: \"leave requests\", 39000: \"group info\", 39001: \"group admins\", 39002: \"group members\", 39003: \"group roles\", 30023: \"articles\", 30024: \"drafts\", 30078: \"app data\", 30311: \"live events\", 30818: \"wiki\" };\n const kindName = (k) => KIND_NAMES[k] || (k >= 20000 && k < 30000 ? \"ephemeral\" : k >= 30000 && k < 40000 ? \"addressable\" : k >= 10000 && k < 20000 ? \"replaceable\" : \"kind \" + k);\n const SYS_KINDS = new Set([0, 3, 10002, 9735, 13534, 8000, 8001, 9000, 9001, 39000, 39001, 39002, 39003]);\n let storage = null;\n\n async function loadViews() {\n let views;\n try { views = await rpc(\"listviews\"); } catch { return; }\n $(\"#views\").innerHTML = views.map((v) => {\n const runs = v.trigger === \"live\" ? \"live, from memory\" : v.trigger === \"write\" ? \"on write and daily\" : v.trigger;\n const who = v.audience === \"members\" ? \"members\" + (v.stored ? \"\" : \", on request\") : \"anyone\";\n const last = v.trigger === \"live\" ? \"\" : v.last ? \"last run \" + fmtTime(v.last.at) : \"not run yet\";\n const rows = v.trigger === \"live\" ? \"no rows\" : v.last ? v.last.rows.toLocaleString() + \" rows\" : \"\";\n const meta = [runs, who, last, rows].filter(Boolean).map((x) => \"<span>\" + esc(x) + \"</span>\").join('<span class=\"sep\">|</span>');\n return '<div class=\"wire-row' + (v.on ? \"\" : \" dim\") + '\"><div class=\"wire-main\"><b>' + esc(v.name) + '</b><br><small>' + esc(v.about) + '</small></div><div class=\"wire-side\"><div class=\"wire-acts\"><label><input type=\"checkbox\" data-view=\"' + esc(v.name) + '\"' + (v.on ? \" checked\" : \"\") + \"> on</label>\" + (v.on ? '<a class=\"btn\" href=\"' + esc(v.path) + '\" target=\"_blank\" rel=\"noopener\">Open</a>' : \"\") + '</div><div class=\"wire-meta\">' + meta + \"</div></div></div>\";\n }).join(\"\");\n }\n $(\"#views\").addEventListener(\"change\", guard(async (ev) => {\n const box = ev.target.closest(\"input[data-view]\"); if (!box) return;\n await rpc(\"setpolicy\", { views: { [box.dataset.view]: box.checked } });\n toast((box.checked ? \"Publishing \" : \"Took down \") + box.dataset.view); await loadViews(); await loadInfo();\n }));\n async function loadDumps() {\n const list = await rpc(\"listdumps\");\n const f = $(\"#dumpform\");\n if (policy) { f.dumps.value = policy.dumps || \"off\"; f.keep.value = policy.dumpsKeep || 7; }\n $(\"#dumps-count\").textContent = list.length || \"\";\n $(\"#dumps\").innerHTML = list.length ? list.map((d) => '<li><i class=\"ev\">jsonl</i><span><b class=\"mono\">' + esc(d.name) + '</b> <span class=\"dim\">' + d.events.toLocaleString() + \" events, \" + fmtBytes(d.bytes) + \"</span></span><span>\" + ib(\"copy\", \"Download\", \"downloaddump\", d.name) + ib(\"trash\", \"Delete\", \"deletedump\", d.name, \"danger\") + \"</span></li>\").join(\"\") : '<li class=\"empty\">no dumps yet</li>';\n }\n // A dump is fetched with a signed request and handed to the browser as a file.\n async function downloadDump(name) {\n if (!signer.ready()) throw new Error(NO_SIGNER);\n const url = location.origin + \"/dumps/\" + name;\n const ev = await signer.signEvent({ kind: 27235, created_at: Math.floor(Date.now() / 1000), content: \"\", tags: [[\"u\", url], [\"method\", \"GET\"]] });\n const resp = await fetch(url, { headers: { authorization: \"Nostr \" + btoa(JSON.stringify(ev)) } });\n if (!resp.ok) throw new Error((await resp.json()).error || \"download failed\");\n const a = document.createElement(\"a\");\n a.href = URL.createObjectURL(await resp.blob()); a.download = host.split(\".\")[0] + \"-\" + name; a.click(); URL.revokeObjectURL(a.href);\n }\n // A plain member's own invites, when the owner lets members invite.\n async function loadMine() {\n const sec = $(\"#myinvites\");\n if (!me || !owner || myRole || !signer.ready()) { sec.classList.add(\"hidden\"); return; }\n let mine;\n try { mine = await rpc(\"listinvites\"); } catch { sec.classList.add(\"hidden\"); return; }\n sec.classList.remove(\"hidden\");\n const link = (code) => location.origin + \"/invite/\" + code;\n $(\"#mine\").innerHTML = mine.length ? mine.map((i) => '<li><i class=\"ev\">inv</i><span><a class=\"mono\" href=\"/invite/' + i.code + '\">…' + i.code.slice(-8) + '</a> <span class=\"dim\">' + i.uses + (i.max_uses ? \"/\" + i.max_uses : \"\") + \" used, until \" + esc(fmtDay(i.expires_at)) + \"</span></span><span>\" + ib(\"copy\", \"Copy link\", \"copy\", link(i.code)) + ib(\"x\", \"Revoke\", \"revokeinvite\", i.code) + \"</span></li>\").join(\"\") : '<li class=\"empty\">no invites yet</li>';\n }\n async function loadStorage() {\n storage = await rpc(\"storagestats\");\n pollJobs();\n loadViews();\n loadDumps().catch(() => {});\n const st = storage;\n const top = st.kinds.slice(0, 4), rest = st.kinds.slice(4).reduce((a, k) => a + k.bytes, 0);\n const parts = [...top.map((k, i) => [k.kind + \" \" + kindName(k.kind), \"k\" + (i + 1), k.bytes]), ...(rest ? [[\"other\", \"k5\", rest]] : [])];\n const total = st.eventBytes || 1;\n $(\"#s-bar\").innerHTML = parts.length ? parts.map(([n, c, b]) => '<i class=\"' + c + '\" style=\"flex:' + b + '\" title=\"' + esc(n) + \" \" + fmtBytes(b) + '\"></i>').join(\"\") : '<i class=\"k5\" style=\"flex:1\"></i>';\n $(\"#s-legend\").innerHTML = parts.length ? parts.map(([n, c, b]) => '<span><i class=\"' + c + '\"></i>' + esc(n) + \" \" + Math.round(b * 100 / total) + \"%</span>\").join(\"\") : \"<span>no events yet</span>\";\n const overhead = Math.max(0, st.databaseBytes - st.eventBytes);\n $(\"#s-totals\").innerHTML = [[\"events\", st.events.toLocaleString() + \" <small>\" + fmtBytes(st.eventBytes) + \"</small>\"], [\"index and overhead\", fmtBytes(overhead)], [\"files\", st.blobs.toLocaleString() + \" <small>\" + fmtBytes(st.mediaBytes) + \"</small>\"]]\n .map(([k, v]) => \"<div><small>\" + k + \"</small><b>\" + v + \"</b></div>\").join(\"\");\n const any = st.retention.find((r) => r.kind === null);\n const own = (kind) => { const r = st.retention.find((x) => x.kind === kind); return r ? r.days : \"\"; };\n const row = (k) => {\n const key = k.kind === null ? \"\" : String(k.kind);\n const placeholder = k.kind === null ? \"forever\" : k.replaceable || !any ? \"forever\" : any.days + \" (everything else)\";\n const pill = k.kind === null ? \"everything else\" : '<span class=\"kind' + (k.protected ? ' sys\" title=\"required: the relay depends on this kind' : \"\") + '\">' + k.kind + \"</span> \" + esc(kindName(k.kind));\n const cells = k.protected\n ? '<td class=\"keep sys\" colspan=\"2\" title=\"The relay depends on this kind, so it is never expired or purged.\">required</td>'\n : '<td class=\"keep\"><input class=\"txt num days\" type=\"number\" min=\"0\" max=\"36500\" placeholder=\"' + placeholder + '\" value=\"' + own(k.kind) + '\"> days</td><td class=\"r\">' + ib(\"check\", \"Save keep-for rule\", \"saveretention\", key) + ib(\"trash\", \"Purge\", \"purgekind\", key, \"danger\") + \"</td>\";\n return '<tr class=\"' + (k.kind === null ? \"any\" : \"\") + '\" data-kind=\"' + key + '\"><td>' + pill + '</td><td class=\"r mono\">' + (k.n === undefined ? \"\" : k.n.toLocaleString()) + '</td><td class=\"r mono\">' + (k.bytes === undefined ? \"\" : fmtBytes(k.bytes)) + '</td><td class=\"dim\">' + (k.oldest ? esc(fmtDay(k.oldest)) : \"\") + \"</td>\" + cells + \"</tr>\";\n };\n $(\"#kinds tbody\").innerHTML = [...st.kinds, { kind: null, days: any ? any.days : 0 }].map(row).join(\"\");\n }\n\n let searchQuery = \"\";\n async function loadEvents(reset) {\n const list = searchQuery ? await rpc(\"searchevents\", searchQuery, 200) : await rpc(\"listrecentevents\", reset ? 50 : 200);\n $(\"#events tbody\").innerHTML = list.length ? list.map((e) => '<tr><td class=\"dim\">' + esc(fmtTime(e.created_at)) + '</td><td><span class=\"kind' + (SYS_KINDS.has(e.kind) ? ' sys\" title=\"required: the relay depends on this kind' : \"\") + '\">' + e.kind + '</span></td><td class=\"mono\" title=\"' + e.pubkey + '\">' + av(e.pubkey) + key(e.pubkey) + '</td><td class=\"c\">' + esc(e.content) + '</td><td class=\"r\">' + ib(\"pin\", \"Pin\", \"pinevent\", e.id) + ib(\"trash\", \"Delete event\", \"deleteevent\", e.id) + ib(\"ban\", \"Ban event\", \"banevent\", e.id, \"danger\") + ib(\"banuser\", \"Ban author\", \"banpubkey\", e.pubkey, \"danger\") + \"</td></tr>\").join(\"\") : '<tr><td colspan=\"5\" class=\"muted\">no events yet</td></tr>';\n $(\"#more\").classList.toggle(\"hidden\", searchQuery || list.length < 50);\n }\n $(\"#searchform\").onsubmit = guard(async (ev) => {\n searchQuery = ev.target.q.value.trim();\n $(\"#searchclear\").classList.toggle(\"hidden\", !searchQuery);\n await loadEvents(true);\n });\n $(\"#searchclear\").onclick = guard(async () => {\n searchQuery = \"\"; $(\"#searchform\").reset(); $(\"#searchclear\").classList.add(\"hidden\");\n await loadEvents(true);\n });\n async function loadPins() {\n const pins = await rpc(\"listpins\");\n $(\"#pins-count\").textContent = pins.length || \"\";\n $(\"#pins\").innerHTML = pins.length ? pins.map((t) => '<li><i class=\"ev\">' + (t[0] === \"e\" ? \"event\" : \"address\") + '</i><span class=\"mono\">' + esc(t[1].length > 40 ? t[1].slice(0, 16) + \"\\u2026\" + t[1].slice(-8) : t[1]) + \"</span><span>\" + ib(\"copy\", \"Copy\", \"copy\", t[1]) + ib(\"x\", \"Unpin\", \"unpinevent\", t[1]) + \"</span></li>\").join(\"\") : '<li class=\"muted\">nothing pinned</li>';\n }\n $(\"#pinform\").onsubmit = guard(async (ev) => {\n await rpc(\"pinevent\", ev.target.id.value.trim()); ev.target.reset(); toast(\"Pinned\"); await loadPins();\n });\n\n // ---- actions ----\n const refresh = () => Promise.all([loadLists(), loadEvents(true), loadStorage(), loadPins()]);\n\n $(\"#copy\").onclick = async () => { await navigator.clipboard.writeText(wsURL); toast(\"copied \" + wsURL); };\n $(\"#signin\").onclick = guard(async () => {\n if (!window.nostr) throw new Error(\"No nostr extension found. Install one (Alby, nos2x, …) and reload, or use a remote signer.\");\n me = await window.nostr.getPublicKey();\n localStorage.setItem(\"me\", me);\n renderHeader(); await loadAdmin(); await loadPeople();\n });\n $(\"#signin46\").onclick = () => showRemote(null);\n $$(\".remote\").forEach((b) => { b.onclick = () => showRemote($(\"#\" + b.dataset.note)); });\n $(\"#nccopy\").onclick = async () => { await navigator.clipboard.writeText($(\"#nclink\").href); toast(\"Link copied; paste it into your signer app\"); };\n $(\"#bunkerform\").onsubmit = guard(async (ev) => {\n $(\"#remotenote\").textContent = \"Connecting…\";\n try { await connectBunker(ev.target.url.value); } catch (e) { $(\"#remotenote\").textContent = e.message; throw e; }\n $(\"#remotenote\").textContent = \"\"; ev.target.reset();\n await remoteDone();\n });\n $(\"#signout\").onclick = async () => {\n if (remote) { const r = remote; remote = null; try { await withTimeout(r.logout(), 5000, \"The signer\"); } catch { /* the session is gone either way */ } }\n localStorage.removeItem(\"nip46\");\n me = null; localStorage.removeItem(\"me\"); renderHeader(); await loadAdmin(); await loadPeople();\n };\n const claimNow = async (note) => {\n if (!signer.ready()) { note.textContent = \"Sign the claim with a nostr extension or a remote signer.\"; showRemote(note); return; }\n me = await signer.getPublicKey();\n const r = await rpc(\"claim\");\n if (!r.claimed) throw new Error(\"Somebody else claimed it first.\");\n localStorage.setItem(\"me\", me);\n toast(\"It's yours.\");\n await loadInfo(); await loadFuel(); await loadAdmin(); await loadPeople();\n if (r.converted && confirm(\"This relay began as a temporary one: anyone can write, and everything is deleted after 14 days. Switch to the default rules and keep everything from now on? Each rule can be changed later on the Rules and Storage tabs.\")) {\n await rpc(\"resetrules\"); toast(\"Rules reset\"); await loadInfo(); await loadAdmin();\n }\n };\n $(\"#claim\").onclick = guard(() => claimNow($(\"#claimnote\")));\n $(\"#claimlease\").onclick = guard(() => claimNow($(\"#leasenote\")));\n // Jobs run in the background; the table follows them while the tab is open.\n let jobsTimer = 0;\n const fmtJob = (j) => {\n const what = j.kind === \"import\" ? \"import\" : j.label === \"backfill\" ? \"fetch my history\" : j.kind === \"pull\" ? \"pull\" : \"rebroadcast\";\n const f = [];\n if (j.filter.authors) f.push(j.filter.authors.length === 1 && j.filter.authors[0] === me ? \"my events\" : j.filter.authors.length + \" authors\");\n if (j.filter.kinds) f.push(\"kinds \" + j.filter.kinds.join(\", \"));\n if (j.filter.since) f.push(\"since \" + fmtDay(j.filter.since));\n const when = j.every ? \"every \" + (j.every === 24 ? \"day\" : j.every + \" h\") + (j.nextRun ? \", next \" + fmtTime(j.nextRun) : \"\") : \"once\";\n const l = j.last;\n const count = (stored, blobs, sent, refused) => (j.kind === \"import\" ? stored.toLocaleString() + \" events\" + ((j.last ? j.last.duplicates : j.duplicates) ? \", \" + (j.last ? j.last.duplicates : j.duplicates) + \" already here\" : \"\") : j.kind === \"pull\" ? stored.toLocaleString() + \" events\" + (blobs ? \", \" + blobs + \" files\" : \"\") : sent.toLocaleString() + \" sent\" + (refused ? \", \" + refused + \" refused\" : \"\"));\n const res = j.running ? \"running: \" + count(j.stored, j.blobs, j.sent, j.refused) + \"...\" : !l ? \"waiting\" : l.error ? \"failed: \" + l.error : count(l.stored, l.blobs, l.sent, l.refused) + (l.skipped ? \", \" + l.skipped + \" skipped\" : \"\") + \", \" + fmtTime(l.finishedAt);\n return \"<tr><td>\" + what + \"</td><td class=\\\"mono\\\">\" + j.relays.map(esc).join(\"<br>\") + \"</td><td>\" + (f.join(\", \") || \"everything\") + \"</td><td>\" + when + \"</td><td>\" + esc(res) + \"</td><td>\" + (j.running ? \"\" : ib(\"undo\", \"Run now\", \"runjob\", j.id)) + ib(\"x\", \"Remove\", \"removejob\", j.id) + \"</td></tr>\";\n };\n async function pollJobs() {\n clearTimeout(jobsTimer);\n let jobs;\n try { jobs = await rpc(\"listjobs\"); } catch { return; }\n $(\"#jobs tbody\").innerHTML = jobs.length ? jobs.map(fmtJob).join(\"\") : '<tr><td colspan=\"6\" class=\"muted\">no jobs yet</td></tr>';\n if (jobs.some((j) => j.running || (j.nextRun && j.nextRun <= Math.floor(Date.now() / 1000) + 1))) jobsTimer = setTimeout(pollJobs, 3000);\n }\n const urls = (s) => s.split(/[\\s,]+/).map((u) => u.trim()).filter(Boolean);\n const kindsOf = (s) => s.split(/[\\s,]+/).map((k) => parseInt(k, 10)).filter((k) => Number.isInteger(k) && k >= 0);\n $(\"#pullform\").onsubmit = guard(async (ev) => {\n const f = ev.target;\n await rpc(\"addjob\", { kind: \"pull\", relays: [f.url.value.trim()], every: +f.every.value });\n toast(+f.every.value ? \"Mirror scheduled\" : \"Pull started\"); f.reset(); await pollJobs();\n });\n $(\"#backfillform\").onsubmit = guard(async (ev) => {\n const f = ev.target;\n await rpc(\"backfill\", urls(f.relays.value));\n toast(\"Fetching your history\"); f.reset(); await pollJobs();\n });\n $(\"#pushform\").onsubmit = guard(async (ev) => {\n const f = ev.target;\n const filter = {};\n const kinds = kindsOf(f.kinds.value); if (kinds.length) filter.kinds = kinds;\n const days = +f.days.value; if (days > 0) filter.since = Math.floor(Date.now() / 1000) - days * 86400;\n await rpc(\"addjob\", { kind: \"push\", relays: urls(f.targets.value), filter, every: +f.every.value });\n toast(\"Rebroadcast started\"); f.reset(); await pollJobs();\n });\n // ---- presets: writes, reads, kinds and keep-for rules in one click ----\n let presets = null;\n async function renderPresets() {\n if (!presets) { try { presets = await rpc(\"listpresets\"); } catch { presets = []; } }\n $(\"#presets\").innerHTML = presets.map((p) => '<button class=\"btn\" data-preset=\"' + esc(p.name) + '\" title=\"' + esc(p.about) + '\">' + esc(p.title) + \"</button>\").join(\"\");\n $(\"#presetsourcerow\").classList.toggle(\"hidden\", !presets.some((p) => p.source));\n }\n $(\"#presets\").addEventListener(\"click\", async (ev) => {\n const b = ev.target.closest(\"button[data-preset]\"); if (!b) return;\n const p = (presets || []).find((x) => x.name === b.dataset.preset); if (!p) return;\n if (!confirm(p.title + \": \" + p.about + \"\\n\\nThis replaces the writes and reads rules, the directory setting, the kind rules and the keep-for rules. Limits, identity, people and bans stay.\")) return;\n const source = $(\"#presetsource\").value.trim();\n if (p.source === \"required\" && !source) { $(\"#presetnote\").textContent = p.title + \" needs a source relay to mirror; enter its wss:// URL first.\"; return; }\n b.disabled = true;\n try { policy = await rpc(\"applypreset\", p.name, p.source && source ? { source } : undefined); $(\"#presetnote\").textContent = \"Now: \" + p.about + (policy.job ? \" Mirroring \" + source + \" every \" + policy.job.every + \" h.\" : \"\"); toast(p.title + \" applied\"); await loadInfo(); await loadAdmin(); }\n catch (e) { toast(e.message); } finally { b.disabled = false; }\n });\n\n // ---- wire me in: this relay in the owner's own lists ----\n // The lists are replaceable, so a fresh list with only this relay would\n // clobber the real one once it spread. Every publish starts from the\n // newest copy found here or on the indexers, verified when the signer\n // library is around, and adds or removes this relay in it.\n const INDEXERS = [\"wss://purplepag.es\", \"wss://relay.nostr.band\", \"wss://relay.damus.io\", \"wss://nos.lol\"];\n const LISTS = [\n { kind: 10002, tag: \"r\", title: \"Relay list\", nip: \"NIP-65\", about: \"where clients read your notes and send you mentions\" },\n { kind: 10050, tag: \"relay\", title: \"DM inbox\", nip: \"NIP-17\", about: \"where people send you private messages\" },\n { kind: 10007, tag: \"relay\", title: \"Search relays\", nip: \"NIP-51\", about: \"where clients run your searches\" },\n { kind: 10063, tag: \"server\", title: \"Blossom servers\", nip: \"BUD-03\", about: \"where clients upload and look for your files\" },\n ];\n const mineFor = (l) => (l.tag === \"server\" ? location.origin : wsURL);\n const normURL = (u) => { try { const x = new URL(String(u).trim()); return (x.host + x.pathname).replace(/\\/+$/, \"\").toLowerCase(); } catch { return String(u).trim().toLowerCase(); } };\n const isMine = (l, t) => t[0] === l.tag && normURL(t[1] || \"\") === normURL(mineFor(l));\n const listHas = (list, l) => !!list && list.tags.some((t) => isMine(l, t));\n // mergeList is pure: the newest list with this relay put first or taken out, every other tag kept.\n function mergeList(list, l, include) {\n const tags = (list ? list.tags : []).filter((t) => !isMine(l, t));\n if (include) tags.unshift([l.tag, mineFor(l)]);\n return { kind: l.kind, created_at: Math.floor(Date.now() / 1000), content: list ? list.content : \"\", tags };\n }\n const relaysIn = (list) => list.tags.filter((t) => (t[0] === \"r\" || t[0] === \"relay\") && /^wss?:\\/\\//i.test(t[1] || \"\")).map((t) => t[1].trim());\n // overWS opens one socket, sends one message, feeds answers to onMessage until it says done or time runs out.\n function overWS(url, ms, first, onMessage) {\n return new Promise((res) => {\n let ws = null, done = false, out = null;\n const finish = () => { if (done) return; done = true; clearTimeout(timer); try { if (ws) ws.close(); } catch { /* closed */ } res(out); };\n const timer = setTimeout(finish, ms);\n try { ws = new WebSocket(url); } catch { return finish(); }\n ws.onopen = () => ws.send(JSON.stringify(first));\n ws.onmessage = (m) => { let d; try { d = JSON.parse(m.data); } catch { return; } if (!Array.isArray(d)) return; if (onMessage(d, (v) => { out = v; })) finish(); };\n ws.onerror = finish; ws.onclose = finish;\n });\n }\n function fetchNewest(url, filter, ms) {\n let best = null;\n return overWS(url, ms, [\"REQ\", \"w\", filter], (d, set) => {\n if (d[0] === \"EVENT\" && d[1] === \"w\" && d[2] && (!best || d[2].created_at > best.created_at)) { best = d[2]; set(best); }\n return (d[0] === \"EOSE\" || d[0] === \"CLOSED\") && d[1] === \"w\";\n });\n }\n function publishTo(url, event, ms) {\n return overWS(url, ms, [\"EVENT\", event], (d, set) => { if (d[0] === \"OK\" && d[1] === event.id) { set({ url, ok: !!d[2], msg: d[3] || \"\" }); return true; } return false; })\n .then((r) => r || { url, ok: false, msg: \"no answer\" });\n }\n // bridge signs a NIP-98 request to this relay's HTTP door, so the owner's own reads and writes pass any read rule.\n async function bridge(path, body) {\n const url = location.origin + path, raw = JSON.stringify(body);\n const ev = await signer.signEvent({ kind: 27235, created_at: Math.floor(Date.now() / 1000), content: \"\", tags: [[\"u\", url], [\"method\", \"POST\"], [\"payload\", await sha256hex(raw)]] });\n const resp = await fetch(url, { method: \"POST\", headers: { \"content-type\": \"application/json\", authorization: \"Nostr \" + btoa(JSON.stringify(ev)) }, body: raw });\n const json = await resp.json();\n if (!resp.ok) throw new Error(json.error || \"HTTP \" + resp.status);\n return json;\n }\n const wire = {};\n function renderWire() {\n const pill = (state) => {\n const cls = state === \"listed\" ? \"on\" : state === \"failed\" ? \"bad\" : state === \"not listed\" ? \"off\" : \"\";\n const label = state === \"checking\" || state === \"publishing\" ? state + \"\\u2026\" : state;\n return '<span class=\"pill ' + cls + '\">' + esc(label) + \"</span>\";\n };\n $(\"#wire\").innerHTML = LISTS.map((l) => {\n const w = wire[l.kind] || { state: \"\", results: [], list: null };\n const busy = w.state === \"checking\" || w.state === \"publishing\";\n const btn = (act, cls, label) => '<button class=\"btn ' + cls + '\" data-wire=\"' + l.kind + '\" data-do=\"' + act + '\"' + (busy ? \" disabled\" : \"\") + \">\" + label + \"</button>\";\n const buttons = w.state === \"listed\" ? btn(\"remove\", \"\", \"Remove this relay\") : btn(\"add\", \"pri\", \"Add this relay\") + btn(\"check\", \"\", w.state ? \"Check again\" : \"Check\");\n const n = w.list ? w.list.tags.filter((t) => t[0] === l.tag).length : 0;\n const from = w.list ? \"<span>\" + (n === 1 ? \"1 entry\" : n + \" entries\") + \", \" + fmtTime(w.list.created_at) + \"</span>\" : w.state && !busy ? \"<span>no list found</span>\" : \"\";\n const sent = (w.results || []).map((r) => '<span class=\"chip' + (r.ok ? \"\" : \" bad\") + '\" title=\"' + esc(r.msg) + '\">' + esc(r.url.replace(/^wss?:\\/\\//, \"\")) + (r.ok ? \"\" : \" failed\") + \"</span>\").join(\"\");\n const meta = (w.state ? pill(w.state) : \"\") + from + sent;\n return '<div class=\"wire-row\"><div class=\"wire-main\"><b>' + l.title + '</b> <small class=\"muted\">' + l.nip + \"</small><br><small>\" + l.about + '</small></div><div class=\"wire-side\"><div class=\"wire-acts\">' + buttons + '</div><div class=\"wire-meta\">' + meta + \"</div></div></div>\";\n }).join(\"\");\n }\n async function wireCheck(l) {\n const w = (wire[l.kind] = { state: \"checking\", list: null, results: [] }); renderWire();\n const filter = { kinds: [l.kind], authors: [me], limit: 1 };\n const found = [];\n try { found.push(...(await bridge(\"/query\", [filter]))); } catch { /* the relay may hold none */ }\n const remote = await Promise.all(INDEXERS.map((u) => fetchNewest(u, filter, 4000)));\n let lib = null; try { lib = await signerLib(); } catch { /* unverified lists are still the owner's own, by pubkey */ }\n for (const e of remote) if (e && e.pubkey === me && e.kind === l.kind && Array.isArray(e.tags) && (!lib || lib.verifyEvent(e))) found.push(e);\n w.list = found.sort((a, b) => b.created_at - a.created_at)[0] || null;\n w.state = listHas(w.list, l) ? \"listed\" : \"not listed\"; renderWire();\n }\n async function wirePublish(l, include) {\n if (!wire[l.kind] || !wire[l.kind].state || wire[l.kind].state === \"failed\") await wireCheck(l);\n const w = wire[l.kind]; w.state = \"publishing\"; w.results = []; renderWire();\n try {\n const signed = await signer.signEvent(mergeList(w.list, l, include));\n const here = await bridge(\"/events\", signed).then((r) => ({ url: wsURL, ok: !!r.accepted, msg: r.message || \"\" })).catch((e) => ({ url: wsURL, ok: false, msg: e.message }));\n const targets = [...new Set([...relaysIn(signed), ...INDEXERS])].filter((u) => normURL(u) !== normURL(wsURL));\n const rest = await Promise.all(targets.map((u) => publishTo(u, signed, 6000)));\n w.results = [here, ...rest]; w.list = signed; w.state = include ? \"listed\" : \"not listed\";\n toast((include ? \"Added to your \" : \"Removed from your \") + l.title.toLowerCase());\n } catch (e) { w.state = \"failed\"; w.results = [{ url: wsURL, ok: false, msg: e.message }]; toast(e.message); }\n renderWire();\n }\n $(\"#wire\").addEventListener(\"click\", (ev) => {\n const b = ev.target.closest(\"button[data-wire]\"); if (!b) return;\n const l = LISTS.find((x) => String(x.kind) === b.dataset.wire); if (!l) return;\n if (b.dataset.do === \"check\") wireCheck(l); else wirePublish(l, b.dataset.do === \"add\");\n });\n\n // ---- custom domains: this relay under a hostname the owner controls ----\n let domains = null; // null: not enabled on this host\n function renderDomains() {\n $(\"#domains\").innerHTML = (domains || []).map((d) => {\n const state = d.ready ? \"active\" : \"hostname \" + d.status.replace(/_/g, \" \") + \", certificate \" + d.sslStatus.replace(/_/g, \" \");\n const btn = (act, cls, label) => '<button class=\"btn ' + cls + '\" data-domain=\"' + esc(d.host) + '\" data-do=\"' + act + '\">' + label + \"</button>\";\n const rows = d.ready ? \"\" : '<div class=\"scroll\"><table class=\"events\"><thead><tr><th>Type</th><th>Name</th><th>Value</th><th></th></tr></thead><tbody>' + d.records.map((r) => \"<tr><td>\" + esc(r.type) + '</td><td class=\"mono\">' + esc(r.name) + '</td><td class=\"mono\">' + esc(r.value) + \"</td><td>\" + esc(r.note) + \"</td></tr>\").join(\"\") + \"</tbody></table></div>\";\n return '<div class=\"row\"><b class=\"mono\">' + esc(d.host) + '</b><span class=\"note\">' + esc(state) + \"</span>\" + btn(\"check\", \"\", \"Check\") + btn(\"remove\", \"danger\", \"Remove\") + \"</div>\" + rows;\n }).join(\"\");\n }\n async function loadDomains() {\n try { domains = await rpc(\"listdomains\"); $(\"#domainnote\").textContent = domains.length ? \"\" : \"No custom domain yet.\"; $(\"#adddomain\").classList.remove(\"hidden\"); }\n catch (e) { domains = null; $(\"#domainnote\").textContent = /^unsupported/.test(e.message) ? \"Custom domains are not enabled on this host.\" : e.message; $(\"#adddomain\").classList.add(\"hidden\"); }\n renderDomains();\n }\n $(\"#adddomain\").onsubmit = guard(async (ev) => {\n const f = ev.target;\n await rpc(\"adddomain\", f.host.value.trim()); f.reset(); toast(\"Domain added; now create the CNAME\"); await loadDomains();\n });\n $(\"#domains\").addEventListener(\"click\", async (ev) => {\n const b = ev.target.closest(\"button[data-domain]\"); if (!b) return;\n const host = b.dataset.domain;\n if (b.dataset.do === \"remove\" && !confirm(\"Remove \" + host + \"? Its certificate goes with it and the name stops answering.\")) return;\n b.disabled = true;\n try {\n if (b.dataset.do === \"remove\") { await rpc(\"removedomain\", host); toast(\"Removed\"); }\n else { const d = await rpc(\"checkdomain\", host); toast(d.ready ? host + \" is live\" : \"Not yet: \" + (d.status === \"active\" ? \"certificate pending\" : \"waiting for the CNAME\")); }\n await loadDomains();\n } catch (e) { toast(e.message); } finally { b.disabled = false; }\n });\n\n $(\"#access\").onsubmit = guard(async (ev) => {\n const f = ev.target;\n const openKinds = f.openKinds.value.split(/[\\s,]+/).filter(Boolean).map(Number);\n if (openKinds.some((k) => !Number.isInteger(k) || k < 0 || k > 65535)) throw new Error(\"Open kinds must be whole numbers.\");\n policy = await rpc(\"setpolicy\", { writes: f.writes.value, reads: f.reads.value, openKinds, guestReplies: f.guestReplies.checked, minPow: +f.minPow.value, maxFuture: +f.maxFuture.value, maxLimit: +f.maxLimit.value, maxSubs: +f.maxSubs.value, maxMessageKB: +f.maxMessageKB.value, eventsPerMinute: +f.eventsPerMinute.value, reqsPerMinute: +f.reqsPerMinute.value, maxBlobMB: +f.maxBlobMB.value });\n toast(\"Rules saved\"); await loadInfo();\n });\n $(\"#notify\").onsubmit = guard(async (ev) => {\n const f = ev.target;\n policy = await rpc(\"setpolicy\", { notify: { reports: f.reports.checked, fuel: f.fuel.checked, jobs: f.jobs.checked, succession: f.succession.checked, digest: f.digest.checked } });\n toast(\"Notifications saved\");\n });\n $(\"#notifytest\").onclick = guard(async () => {\n const r = await rpc(\"notifytest\");\n toast(r.sent ? \"Sent. Look for a message from the relay in your DMs.\" : \"Could not send.\");\n });\n $(\"#identity\").onsubmit = guard(async (ev) => {\n const f = ev.target;\n const list = (v) => v.split(\",\").map((s) => s.trim()).filter(Boolean);\n policy = await rpc(\"setpolicy\", { name: f.name.value, contact: f.contact.value, description: f.description.value, icon: f.icon.value,\n banner: f.banner.value, postingPolicy: f.postingPolicy.value, privacyPolicy: f.privacyPolicy.value, tags: list(f.tags.value), languageTags: list(f.languageTags.value), relayCountries: list(f.relayCountries.value) });\n toast(\"Identity saved\"); await loadInfo(); await loadPeople(); loadCard();\n });\n $(\"#addmember\").onsubmit = guard(async (ev) => {\n const f = ev.target; const pk = npubToHex(f.pubkey.value); if (!pk) throw new Error(\"That is not a pubkey.\");\n await rpc(\"setmember\", pk, { name: f.name.value.trim() || null, note: f.note.value }); f.reset(); toast(\"Member added\"); await loadLists();\n });\n $(\"#addblock\").onsubmit = guard(async (ev) => {\n const f = ev.target;\n await rpc(\"blockip\", f.ip.value.trim(), f.reason.value); f.reset(); toast(\"Blocked\"); await loadLists();\n });\n $(\"#wordsform\").onsubmit = guard(async (ev) => {\n const words = ev.target.words.value.split(\"\\n\").map((s) => s.trim()).filter(Boolean);\n const kept = await rpc(\"setblockedwords\", words);\n policy = await rpc(\"setpolicy\", { blockedWordsInTags: ev.target.inTags.checked });\n ev.target.words.value = kept.join(\"\\n\"); toast(kept.length ? kept.length + \" words blocked\" : \"No words blocked\");\n });\n $(\"#thresholdform\").onsubmit = guard(async (ev) => {\n policy = await rpc(\"setpolicy\", { reportThreshold: +ev.target.reportThreshold.value });\n toast(policy.reportThreshold ? \"Hidden after \" + policy.reportThreshold + \" reports\" : \"Never hidden by reports\"); await loadLists();\n });\n $(\"#addban\").onsubmit = guard(async (ev) => {\n const pk = npubToHex(ev.target.pubkey.value); if (!pk) throw new Error(\"That is not a pubkey.\");\n await rpc(\"banpubkey\", pk, ev.target.reason.value, ev.target.erase.checked); ev.target.reset(); toast(ev.target.erase.checked ? \"Banned and erased\" : \"Banned\"); await loadLists();\n });\n $(\"#mintinvite\").onsubmit = guard(async (ev) => {\n const f = ev.target;\n const inv = await rpc(\"createinvite\", +f.ttl.value, +f.max.value, f.note.value);\n await navigator.clipboard.writeText(location.origin + \"/invite/\" + inv.code).catch(() => {});\n f.note.value = \"\"; toast(\"Invite created and copied\"); await loadLists();\n });\n $(\"#kindform\").onsubmit = guard(async (ev) => {\n const rule = ev.submitter.value; const k = +ev.target.kind.value;\n await rpc(rule === \"allow\" ? \"allowkind\" : \"disallowkind\", k); ev.target.reset(); toast((rule === \"allow\" ? \"Allowed kind \" : \"Blocked kind \") + k); await loadLists();\n });\n $(\"#more\").onclick = guard(() => loadEvents(false));\n $(\"#dumpform\").onsubmit = guard(async (ev) => {\n const f = ev.target;\n policy = await rpc(\"setpolicy\", { dumps: f.dumps.value, dumpsKeep: Math.max(1, Math.min(60, Math.floor(+f.keep.value || 7))) });\n toast(policy.dumps === \"off\" ? \"Dumps off\" : \"Dumping \" + policy.dumps); await loadDumps();\n });\n $(\"#importform\").onsubmit = guard(async (ev) => {\n const file = ev.target.file.files[0]; if (!file) return;\n if (!signer.ready()) throw new Error(NO_SIGNER);\n if (file.size > 64 * 1024 * 1024) throw new Error(\"At most 64 MB per import.\");\n const body = await file.text();\n const url = location.origin + \"/import?name=\" + encodeURIComponent(file.name);\n const token = await signer.signEvent({ kind: 27235, created_at: Math.floor(Date.now() / 1000), content: \"\", tags: [[\"u\", url], [\"method\", \"PUT\"], [\"payload\", await sha256hex(body)]] });\n const resp = await fetch(url, { method: \"PUT\", headers: { authorization: \"Nostr \" + btoa(JSON.stringify(token)), \"content-type\": \"application/x-ndjson\" }, body });\n const r = await resp.json();\n if (!resp.ok) throw new Error(r.error || \"import failed\");\n ev.target.reset(); toast(\"Importing \" + fmtBytes(r.bytes)); await pollJobs();\n });\n $(\"#dumpnow\").onclick = guard(async () => { const d = await rpc(\"dumpnow\"); toast(\"Dumped \" + d.events.toLocaleString() + \" events\"); await loadStorage(); });\n $(\"#treeform\").onsubmit = guard(async (ev) => {\n const f = ev.target;\n policy = await rpc(\"setpolicy\", { memberInvites: { depth: Math.max(0, Math.floor(+f.depth.value || 0)), quota: Math.max(0, Math.floor(+f.quota.value || 0)) } });\n toast(policy.memberInvites.depth ? \"Members may invite\" : \"Only you and moderators invite\");\n });\n $(\"#mintmine\").onclick = guard(async () => {\n const inv = await rpc(\"createinvite\", 259200, 1, \"\");\n await navigator.clipboard.writeText(location.origin + \"/invite/\" + inv.code).catch(() => {});\n toast(\"Invite created and copied\"); await loadMine();\n });\n $(\"#exportcfg\").onclick = guard(async () => {\n const cfg = await rpc(\"exportconfig\");\n const a = document.createElement(\"a\");\n a.href = URL.createObjectURL(new Blob([JSON.stringify(cfg, null, 2)], { type: \"application/json\" }));\n a.download = host.split(\".\")[0] + \".bind.ws.config.json\";\n a.click(); URL.revokeObjectURL(a.href);\n });\n $(\"#importcfg\").onclick = () => $(\"#cfgfile\").click();\n $(\"#cfgfile\").onchange = guard(async (ev) => {\n const file = ev.target.files[0]; if (!file) return;\n const cfg = JSON.parse(await file.text());\n if (!confirm(\"Replace this relay's rules, identity, members, bans and kind rules with \" + file.name + \"?\")) { ev.target.value = \"\"; return; }\n await rpc(\"importconfig\", cfg); ev.target.value = \"\"; toast(\"Configuration imported\"); await loadInfo(); await loadAdmin(); await loadPeople();\n });\n // Succession: the status line under the heir form, from successionstatus.\n async function renderSuccession() {\n const el = $(\"#successionnote\"), f = $(\"#succession\");\n let st;\n try { st = await rpc(\"successionstatus\"); } catch { el.textContent = \"\"; return; }\n const sc = st.succession;\n $(\"#clearsuccession\").classList.toggle(\"hidden\", !sc);\n f.querySelector(\"button.btn:not(#clearsuccession)\").textContent = sc ? \"Change heir\" : \"Set heir\";\n if (!sc) { el.textContent = \"No heir named. Last signed in \" + fmtTime(st.ownerSeenAt) + \".\"; return; }\n if (f.heir.querySelector('[value=\"' + sc.heir + '\"]')) f.heir.value = sc.heir;\n f.afterDays.value = String(sc.afterDays);\n const who = (window.__members || []).find((m) => m.pubkey === sc.heir);\n const heir = who && who.name ? who.name + \"@\" + host : short(sc.heir);\n el.textContent = \"Heir: \" + heir + \". Last signed in \" + fmtTime(st.ownerSeenAt) + (st.silentDays ? \" (\" + st.silentDays + \" days ago)\" : \"\") + \". \" +\n (st.warning ? \"The warning month is running: the relay goes to \" + heir + \" on \" + fmtDay(st.handoverAt) + \" unless you sign in.\" : \"If you stay away, it goes to \" + heir + \" around \" + fmtDay(st.handoverAt) + \".\") +\n (st.log && st.log.length ? \" Handed over before: \" + st.log.map((l) => fmtDay(l.at) + \" to \" + short(l.to)).join(\", \") + \".\" : \"\");\n }\n $(\"#succession\").onsubmit = guard(async (ev) => {\n const f = ev.target;\n await rpc(\"setsuccession\", { heir: f.heir.value, afterDays: +f.afterDays.value });\n toast(\"Heir set\"); await loadAdmin();\n });\n $(\"#clearsuccession\").onclick = guard(async () => {\n await rpc(\"clearsuccession\");\n toast(\"Heir cleared\"); await loadAdmin();\n });\n $(\"#transfer\").onsubmit = guard(async (ev) => {\n const pk = ev.target.pubkey.value; if (!pk) return;\n const name = host.split(\".\")[0];\n const typed = prompt(\"This hands \" + host + \" to \" + short(pk) + \" for good. You stay on as a moderator. Type the relay name (\" + name + \") to confirm.\");\n if (typed === null) return;\n if (typed.trim() !== name) { toast(\"That didn't match; nothing changed.\"); return; }\n await rpc(\"transferowner\", pk);\n toast(\"Transferred\");\n await loadInfo(); await loadAdmin(); await loadPeople();\n });\n // ---- fork: a new name with this relay's events, claim reserved for a key ----\n $(\"#forkform\").scope.onchange = (ev) => $(\"#forkkinds\").classList.toggle(\"hidden\", ev.target.value !== \"kinds\");\n $(\"#joinform\").onsubmit = guard(async (ev) => {\n const f = ev.target;\n policy = await rpc(\"setpolicy\", { joinTerms: f.joinTerms.value, directoryPublic: f.directoryPublic.checked });\n toast(\"Saved\"); await loadInfo(); await loadPeople();\n });\n $(\"#forkform\").onsubmit = guard(async (ev) => {\n const f = ev.target;\n const opts = { people: f.people.checked };\n if (f.name.value.trim()) opts.name = f.name.value.trim().toLowerCase();\n if (f.holder.value.trim()) { const pk = npubToHex(f.holder.value); if (!pk) throw new Error(\"That is not a pubkey.\"); opts.holder = pk; }\n if (f.scope.value === \"mine\") opts.filter = { authors: [me] };\n if (f.scope.value === \"kinds\") { const kinds = f.kinds.value.split(/[\\s,]+/).filter(Boolean).map(Number); if (!kinds.length || kinds.some((k) => !Number.isInteger(k) || k < 0)) throw new Error(\"Give kinds as numbers.\"); opts.filter = { kinds }; }\n if (!confirm(\"Fork this relay into a new name\" + (opts.name ? \" (\" + opts.name + \")\" : \"\") + \"? It pulls \" + (f.scope.value === \"all\" ? \"everything\" : f.scope.value === \"mine\" ? \"your events\" : \"the chosen kinds\") + (opts.people ? \" and the people\" : \"\") + \", and only \" + (opts.holder ? \"that key\" : \"you\") + \" can claim it.\")) return;\n const r = await rpc(\"forkrelay\", opts);\n $(\"#forknote\").textContent = r.handover + \" Expires \" + fmtTime(r.expires_at) + \".\";\n $(\"#forkurl\").textContent = r.console;\n $(\"#forkresult\").classList.remove(\"hidden\");\n toast(\"Forked to \" + r.name);\n });\n $(\"#forkcopy\").onclick = async () => { await navigator.clipboard.writeText($(\"#forkurl\").textContent); toast(\"copied\"); };\n $(\"#deleterelay\").onclick = guard(async () => {\n const name = host.split(\".\")[0];\n const typed = prompt(\"This deletes everything on \" + host + \" and gives the name up. Type the relay name (\" + name + \") to confirm.\");\n if (typed === null) return;\n if (typed.trim() !== name) { toast(\"That didn't match; nothing was deleted.\"); return; }\n await rpc(\"deleterelay\", name);\n localStorage.removeItem(\"me\");\n location.href = \"/\";\n });\n $(\"#topup\").onsubmit = guard((ev) => topUp(+ev.target.sats.value));\n $(\"#inv-copy\").onclick = async () => { await navigator.clipboard.writeText($(\"#inv-text\").value); toast(\"invoice copied\"); };\n $$(\"button[data-copy]\").forEach((b) => { b.onclick = async () => { await navigator.clipboard.writeText(b.dataset.copy === \"ws\" ? wsURL : location.origin); toast(\"copied\"); }; });\n document.addEventListener(\"click\", async (ev) => {\n const b = ev.target.closest(\"button[data-act]\"); if (!b) return;\n const act = b.dataset.act, id = b.dataset.id;\n if (act === \"copy\") { await navigator.clipboard.writeText(id); toast(\"copied\"); return; }\n if ((act === \"banpubkey\" || act === \"banevent\" || act === \"resolve:ban\") && !confirm(act === \"banevent\" ? \"Delete this event and refuse it forever?\" : \"Ban this author and refuse everything they post?\")) return;\n const erase = (act === \"banpubkey\" || act === \"resolve:ban\") && confirm(\"Also erase everything they wrote and uploaded here?\");\n if (act === \"deleteblob\" && !confirm(\"Delete this file for good?\")) return;\n if (act === \"deletedump\" && !confirm(\"Delete this dump?\")) return;\n if (act === \"downloaddump\") { b.disabled = true; try { await downloadDump(id); } catch (e) { toast(e.message); } finally { b.disabled = false; } return; }\n if ((act === \"removemember\" || act === \"banpubkey\") && (window.__members || []).some((m) => m.invited_by === id) && confirm(\"Also remove everyone this member invited, and everyone they invited in turn?\")) {\n b.disabled = true;\n try { const r = await rpc(\"removesubtree\", id); if (act === \"banpubkey\") await rpc(\"banpubkey\", id, \"\", erase); toast(\"Removed \" + r.removed.length); await refresh(); } catch (e) { toast(e.message); } finally { b.disabled = false; }\n return;\n }\n if (act === \"removejob\" && !confirm(\"Remove this job?\")) return;\n if (act === \"purgekind\") {\n const kind = id === \"\" ? null : +id;\n const label = kind === null ? \"everything without its own rule\" : kindName(kind) + \" (kind \" + kind + \")\";\n const typed = prompt(\"Purge \" + label + \" older than how many days? 0 purges all of them. This cannot be undone.\", \"30\");\n if (typed === null) return;\n const days = Math.max(0, Math.floor(+typed || 0));\n b.disabled = true;\n try { const r = await rpc(\"purgekind\", kind, days); toast(\"Purged \" + r.deleted.toLocaleString() + \" events\"); await refresh(); } catch (e) { toast(e.message); } finally { b.disabled = false; }\n return;\n }\n if (act === \"saveretention\") {\n const kind = id === \"\" ? null : +id;\n const days = Math.max(0, Math.floor(+b.closest(\"tr\").querySelector(\".days\").value || 0));\n b.disabled = true;\n try { await rpc(\"setretention\", kind, days); toast(days ? \"Kept for \" + days + \" days\" : \"Kept forever\"); await loadStorage(); await loadInfo(); } catch (e) { toast(e.message); } finally { b.disabled = false; }\n return;\n }\n b.disabled = true;\n try {\n if (act === \"savemember\") { const tr = b.closest(\"tr\"), role = tr.querySelector(\"select.role\"), keep = tr.querySelector(\".keep\"), cap = tr.querySelector(\".cap\"); await rpc(\"setmember\", id, { name: tr.querySelector(\".name\").value.trim() || null, note: tr.querySelector(\".note\").value, ...(role ? { role: role.value } : {}), ...(keep ? { keepDays: Math.max(0, Math.floor(+keep.value || 0)), maxBytes: Math.max(0, Math.floor(+cap.value || 0)) * 1024 } : {}) }); }\n else if (act.startsWith(\"resolve:\")) await rpc(\"resolvereport\", id, act.slice(8), erase);\n else if (act === \"banpubkey\") await rpc(\"banpubkey\", id, \"\", erase);\n else await rpc(act, act === \"unrulekind\" ? +id : id, \"\");\n toast(\"Done\"); await (myRole ? refresh() : loadMine());\n if (act === \"pinevent\" || act === \"unpinevent\") await loadPins();\n } catch (e) { toast(e.message); } finally { b.disabled = false; }\n });\n\n // The share block: the card picture, the group naddr and its QR.\n let card = null;\n async function loadCard() {\n try {\n card = await (await fetch(\"/card.json\", { cache: \"no-store\" })).json();\n $(\"#cardimg\").src = \"/card.svg?t=\" + Date.now();\n $(\"#naddr\").textContent = card.naddr || \"\";\n const q = $(\"#naddrqr\");\n if (card.naddr) { q.src = \"/qr.svg?text=\" + encodeURIComponent(card.naddr); q.classList.remove(\"hidden\"); } else q.classList.add(\"hidden\");\n } catch { /* the card is decoration */ }\n renderApps();\n }\n // The app rows on the Connect section: one per client people actually use,\n // with the link that lands on this relay in it. Relay apps take the relay\n // or the group address; feed apps take the owner's profile with this relay\n // as the hint, since they have no notion of opening a relay.\n function renderApps() {\n const el = $(\"#apps\"); if (!el) return;\n const enc = encodeURIComponent;\n const nprofile = card && card.nprofile ? card.nprofile : \"\";\n const naddr = card && card.naddr ? card.naddr : \"\";\n const whose = me && owner && me === owner ? \"your\" : \"the owner's\";\n const link = (label, href) => '<a class=\"btn\" href=\"' + esc(href) + '\" target=\"_blank\" rel=\"noopener\">' + label + \"</a>\";\n const app = (label, uri) => '<a class=\"btn\" href=\"' + esc(uri) + '\">' + label + \"</a>\";\n const copy = (label, text) => '<button class=\"btn\" data-copytext=\"' + esc(text) + '\">' + label + \"</button>\";\n const row = (name, where, note, acts) => '<div class=\"app\"><div class=\"app-head\"><b>' + name + \"</b><small>\" + where + \"</small></div><p>\" + note + '</p><div class=\"app-acts\">' + acts.filter(Boolean).join(\"\") + \"</div></div>\";\n const profileNote = \"Opens \" + whose + \" profile with this relay attached.\";\n const groups = [\n [\"As a place\", \"These open the relay itself: its feed, its people, its group.\", [\n row(\"Jumble\", \"web\", \"A feed of everything on this relay.\", [link(\"Open\", \"https://jumble.social/?r=\" + enc(wsURL))]),\n row(\"Coracle\", \"web\", \"The relay's page: its feed and its people.\", [link(\"Open\", \"https://coracle.social/relays/\" + enc(host))]),\n row(\"Flotilla\", \"web, phone\", \"The relay as a space, with the group as a room.\", [link(\"Open\", \"https://app.flotilla.social/spaces/\" + enc(host)), naddr && app(\"Open group\", \"nostr:\" + naddr)]),\n row(\"0xchat\", \"phone\", \"The group, in a chat app.\", [naddr && app(\"Open group\", \"nostr:\" + naddr), naddr && copy(\"Copy naddr\", naddr)]),\n row(\"noStrudel\", \"web\", \"Relays, add this one, then open its page.\", [copy(\"Copy relay URL\", wsURL)]),\n ]],\n [\"Find me here\", \"Feed apps have no relay pages. They meet this relay through a profile link that names it, then keep it once it is in the relay settings.\", [\n row(\"Primal\", \"web, phone\", profileNote, [nprofile && link(\"Open\", \"https://primal.net/p/\" + nprofile), nprofile && app(\"Open in app\", \"nostr:\" + nprofile)]),\n row(\"YakiHonne\", \"web, phone\", profileNote, [nprofile && link(\"Open\", \"https://yakihonne.com/profile/\" + nprofile), nprofile && app(\"Open in app\", \"nostr:\" + nprofile)]),\n row(\"Damus\", \"iPhone\", profileNote + \" Then Settings, Relays.\", [nprofile && app(\"Open in app\", \"nostr:\" + nprofile), copy(\"Copy relay URL\", wsURL)]),\n row(\"Amethyst\", \"Android\", profileNote + \" Then Relays in the drawer.\", [nprofile && app(\"Open in app\", \"nostr:\" + nprofile), copy(\"Copy relay URL\", wsURL)]),\n row(\"Nostur\", \"iPhone, Mac\", profileNote + \" Then Settings, Relays.\", [nprofile && app(\"Open in app\", \"nostr:\" + nprofile), copy(\"Copy relay URL\", wsURL)]),\n ]],\n ];\n el.innerHTML = groups.map(([h, note, rows]) => '<div class=\"appgroup\"><h4>' + h + '</h4><p class=\"note\">' + note + '</p><div class=\"appgrid\">' + rows.join(\"\") + \"</div></div>\").join(\"\");\n $(\"#apps-ws\").textContent = wsURL;\n const tile = (label, text) => '<div class=\"door\"><small>' + label + '</small><img src=\"/qr.svg?text=' + enc(text) + '\" alt=\"QR code: ' + esc(label) + '\" width=\"150\" height=\"150\"></div>';\n $(\"#phones\").innerHTML = [nprofile && tile(\"Find me here, for a phone\", \"nostr:\" + nprofile), naddr && tile(\"The group, for a phone\", \"nostr:\" + naddr)].filter(Boolean).join(\"\");\n }\n // The folds are a group: opening one closes the rest. Browsers with the\n // details name attribute do this themselves; this covers the others.\n $$(\".folds details\").forEach((d) => d.addEventListener(\"toggle\", () => { if (d.open) $$(\".folds details\").forEach((o) => { if (o !== d && o.open) o.open = false; }); }));\n document.addEventListener(\"click\", async (ev) => {\n const b = ev.target.closest(\"button[data-copytext]\"); if (!b) return;\n try { await navigator.clipboard.writeText(b.dataset.copytext); toast(\"copied\"); } catch { /* no clipboard */ }\n });\n $(\"#copynaddr\").onclick = async () => { if (!card || !card.naddr) return; await navigator.clipboard.writeText(card.naddr); toast(\"copied naddr\"); };\n $(\"#copyembed\").onclick = async () => { await navigator.clipboard.writeText('<a href=\"' + location.origin + '/\"><img src=\"' + location.origin + '/card.svg\" alt=\"' + host + '\" width=\"600\" height=\"315\"></a>'); toast(\"copied embed\"); };\n\n // A click on a key copies it; a double-click selects the whole key so the usual copy shortcut takes it too.\n document.addEventListener(\"dblclick\", (ev) => {\n const k = ev.target.closest(\".key\"); if (!k) return;\n const s = window.getSelection(), r = document.createRange(); r.selectNodeContents(k.querySelector(\".full\")); s.removeAllRanges(); s.addRange(r);\n });\n document.addEventListener(\"click\", async (ev) => {\n const k = ev.target.closest(\".key\"); if (!k || ev.detail > 1 || String(window.getSelection())) return;\n try { await navigator.clipboard.writeText(k.title); toast(\"copied \" + k.dataset.short); } catch { /* no clipboard */ }\n });\n // ---- boot ----\n try { await loadInfo(); } catch { renderHeader(); }\n await loadFuel();\n await loadCard();\n me = localStorage.getItem(\"me\");\n if (me && await resumeRemote()) { /* a remote session answers for itself when first used */ }\n else if (me && window.nostr) { try { const pk = await window.nostr.getPublicKey(); if (pk !== me) me = null; } catch { me = null; } }\n else if (me) me = null;\n renderHeader();\n renderApps();\n await loadAdmin();\n await loadPeople();\n})();\n";
M src/gen/signer.ts +1 -1
@@ -1,3 +1,3 @@
1-// Generated by scripts/build-signer.mjs from scripts/signer-entry.js. Do not edit; run npm run build:signer.1+// Generated by scripts/build/build-signer.mjs from scripts/build/signer-entry.js. Do not edit; run npm run build:signer.22 export const SIGNER_HASH = "91778b87037e";33 export const SIGNER_JS = "\"use strict\";(()=>{var Bo=Object.defineProperty;var Ao=(t,e,n)=>e in t?Bo(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var f=(t,e,n)=>Ao(t,typeof e!=\"symbol\"?e+\"\":e,n);function ze(t){return t instanceof Uint8Array||ArrayBuffer.isView(t)&&t.constructor.name===\"Uint8Array\"}function fe(t,e=\"\"){if(!Number.isSafeInteger(t)||t<0){let n=e&&`\"${e}\" `;throw new Error(`${n}expected integer >= 0, got ${t}`)}}function P(t,e,n=\"\"){let r=ze(t),o=t?.length,s=e!==void 0;if(!r||s&&o!==e){let i=n&&`\"${n}\" `,c=s?` of length ${e}`:\"\",a=r?`length=${o}`:`type=${typeof t}`;throw new Error(i+\"expected Uint8Array\"+c+\", got \"+a)}return t}function nt(t){if(typeof t!=\"function\"||typeof t.create!=\"function\")throw new Error(\"Hash must wrapped by utils.createHasher\");fe(t.outputLen),fe(t.blockLen)}function Ce(t,e=!0){if(t.destroyed)throw new Error(\"Hash instance has been destroyed\");if(e&&t.finished)throw new Error(\"Hash#digest() has already been called\")}function Un(t,e){P(t,void 0,\"digestInto() output\");let n=e.outputLen;if(t.length<n)throw new Error('\"digestInto() output\" expected to be of length >='+n)}function Te(...t){for(let e=0;e<t.length;e++)t[e].fill(0)}function rt(t){return new DataView(t.buffer,t.byteOffset,t.byteLength)}function ie(t,e){return t<<32-e|t>>>e}var Pn=typeof Uint8Array.from([]).toHex==\"function\"&&typeof Uint8Array.fromHex==\"function\",To=Array.from({length:256},(t,e)=>e.toString(16).padStart(2,\"0\"));function He(t){if(P(t),Pn)return t.toHex();let e=\"\";for(let n=0;n<t.length;n++)e+=To[t[n]];return e}var he={_0:48,_9:57,A:65,F:70,a:97,f:102};function Cn(t){if(t>=he._0&&t<=he._9)return t-he._0;if(t>=he.A&&t<=he.F)return t-(he.A-10);if(t>=he.a&&t<=he.f)return t-(he.a-10)}function Re(t){if(typeof t!=\"string\")throw new Error(\"hex string expected, got \"+typeof t);if(Pn)return Uint8Array.fromHex(t);let e=t.length,n=e/2;if(e%2)throw new Error(\"hex string expected, got unpadded hex of length \"+e);let r=new Uint8Array(n);for(let o=0,s=0;o<n;o++,s+=2){let i=Cn(t.charCodeAt(s)),c=Cn(t.charCodeAt(s+1));if(i===void 0||c===void 0){let a=t[s]+t[s+1];throw new Error('hex string expected, got non-hex character \"'+a+'\" at index '+s)}r[o]=i*16+c}return r}function oe(...t){let e=0;for(let r=0;r<t.length;r++){let o=t[r];P(o),e+=o.length}let n=new Uint8Array(e);for(let r=0,o=0;r<t.length;r++){let s=t[r];n.set(s,o),o+=s.length}return n}function Nn(t,e={}){let n=(o,s)=>t(s).update(o).digest(),r=t(void 0);return n.outputLen=r.outputLen,n.blockLen=r.blockLen,n.create=o=>t(o),Object.assign(n,e),Object.freeze(n)}function Le(t=32){let e=typeof globalThis==\"object\"?globalThis.crypto:null;if(typeof e?.getRandomValues!=\"function\")throw new Error(\"crypto.getRandomValues must be defined\");return e.getRandomValues(new Uint8Array(t))}var qn=t=>({oid:Uint8Array.from([6,9,96,134,72,1,101,3,4,2,t])});function Dn(t,e,n){return t&e^~t&n}function $n(t,e,n){return t&e^t&n^e&n}var ot=class{constructor(e,n,r,o){f(this,\"blockLen\");f(this,\"outputLen\");f(this,\"padOffset\");f(this,\"isLE\");f(this,\"buffer\");f(this,\"view\");f(this,\"finished\",!1);f(this,\"length\",0);f(this,\"pos\",0);f(this,\"destroyed\",!1);this.blockLen=e,this.outputLen=n,this.padOffset=r,this.isLE=o,this.buffer=new Uint8Array(e),this.view=rt(this.buffer)}update(e){Ce(this),P(e);let{view:n,buffer:r,blockLen:o}=this,s=e.length;for(let i=0;i<s;){let c=Math.min(o-this.pos,s-i);if(c===o){let a=rt(e);for(;o<=s-i;i+=o)this.process(a,i);continue}r.set(e.subarray(i,i+c),this.pos),this.pos+=c,i+=c,this.pos===o&&(this.process(n,0),this.pos=0)}return this.length+=e.length,this.roundClean(),this}digestInto(e){Ce(this),Un(e,this),this.finished=!0;let{buffer:n,view:r,blockLen:o,isLE:s}=this,{pos:i}=this;n[i++]=128,Te(this.buffer.subarray(i)),this.padOffset>o-i&&(this.process(r,0),i=0);for(let d=i;d<o;d++)n[d]=0;r.setBigUint64(o-8,BigInt(this.length*8),s),this.process(r,0);let c=rt(e),a=this.outputLen;if(a%4)throw new Error(\"_sha2: outputLen must be aligned to 32bit\");let l=a/4,u=this.get();if(l>u.length)throw new Error(\"_sha2: outputLen bigger than state\");for(let d=0;d<l;d++)c.setUint32(4*d,u[d],s)}digest(){let{buffer:e,outputLen:n}=this;this.digestInto(e);let r=e.slice(0,n);return this.destroy(),r}_cloneInto(e){e||(e=new this.constructor),e.set(...this.get());let{blockLen:n,buffer:r,length:o,finished:s,destroyed:i,pos:c}=this;return e.destroyed=i,e.finished=s,e.length=o,e.pos=c,o%n&&e.buffer.set(r),e}clone(){return this._cloneInto()}},de=Uint32Array.from([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]);var Ho=Uint32Array.from([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),ve=new Uint32Array(64),It=class extends ot{constructor(e){super(64,e,8,!1)}get(){let{A:e,B:n,C:r,D:o,E:s,F:i,G:c,H:a}=this;return[e,n,r,o,s,i,c,a]}set(e,n,r,o,s,i,c,a){this.A=e|0,this.B=n|0,this.C=r|0,this.D=o|0,this.E=s|0,this.F=i|0,this.G=c|0,this.H=a|0}process(e,n){for(let d=0;d<16;d++,n+=4)ve[d]=e.getUint32(n,!1);for(let d=16;d<64;d++){let h=ve[d-15],b=ve[d-2],x=ie(h,7)^ie(h,18)^h>>>3,w=ie(b,17)^ie(b,19)^b>>>10;ve[d]=w+ve[d-7]+x+ve[d-16]|0}let{A:r,B:o,C:s,D:i,E:c,F:a,G:l,H:u}=this;for(let d=0;d<64;d++){let h=ie(c,6)^ie(c,11)^ie(c,25),b=u+h+Dn(c,a,l)+Ho[d]+ve[d]|0,w=(ie(r,2)^ie(r,13)^ie(r,22))+$n(r,o,s)|0;u=l,l=a,a=c,c=i+b|0,i=s,s=o,o=r,r=b+w|0}r=r+this.A|0,o=o+this.B|0,s=s+this.C|0,i=i+this.D|0,c=c+this.E|0,a=a+this.F|0,l=l+this.G|0,u=u+this.H|0,this.set(r,o,s,i,c,a,l,u)}roundClean(){Te(ve)}destroy(){this.set(0,0,0,0,0,0,0,0),Te(this.buffer)}},Ct=class extends It{constructor(){super(32);f(this,\"A\",de[0]|0);f(this,\"B\",de[1]|0);f(this,\"C\",de[2]|0);f(this,\"D\",de[3]|0);f(this,\"E\",de[4]|0);f(this,\"F\",de[5]|0);f(this,\"G\",de[6]|0);f(this,\"H\",de[7]|0)}};var st=Nn(()=>new Ct,qn(1));var Pt=BigInt(0),Ut=BigInt(1);function Ze(t,e=\"\"){if(typeof t!=\"boolean\"){let n=e&&`\"${e}\" `;throw new Error(n+\"expected boolean, got type=\"+typeof t)}return t}function Mn(t){if(typeof t==\"bigint\"){if(!it(t))throw new Error(\"positive bigint expected, got \"+t)}else fe(t);return t}function Fe(t){let e=Mn(t).toString(16);return e.length&1?\"0\"+e:e}function Vn(t){if(typeof t!=\"string\")throw new Error(\"hex string expected, got \"+typeof t);return t===\"\"?Pt:BigInt(\"0x\"+t)}function Se(t){return Vn(He(t))}function Nt(t){return Vn(He(Ro(P(t)).reverse()))}function ct(t,e){fe(e),t=Mn(t);let n=Re(t.toString(16).padStart(e*2,\"0\"));if(n.length!==e)throw new Error(\"number too large\");return n}function qt(t,e){return ct(t,e).reverse()}function Ro(t){return Uint8Array.from(t)}function Wn(t){return Uint8Array.from(t,(e,n)=>{let r=e.charCodeAt(0);if(e.length!==1||r>127)throw new Error(`string contains non-ASCII character \"${t[n]}\" with code ${r} at position ${n}`);return r})}var it=t=>typeof t==\"bigint\"&&Pt<=t;function Lo(t,e,n){return it(t)&&it(e)&&it(n)&&e<=t&&t<n}function Kn(t,e,n,r){if(!Lo(e,n,r))throw new Error(\"expected valid \"+t+\": \"+n+\" <= n < \"+r+\", got \"+e)}function Dt(t){let e;for(e=0;t>Pt;t>>=Ut,e+=1);return e}var Ye=t=>(Ut<<BigInt(t))-Ut;function jn(t,e,n){if(fe(t,\"hashLen\"),fe(e,\"qByteLen\"),typeof n!=\"function\")throw new Error(\"hmacFn must be a function\");let r=E=>new Uint8Array(E),o=Uint8Array.of(),s=Uint8Array.of(0),i=Uint8Array.of(1),c=1e3,a=r(t),l=r(t),u=0,d=()=>{a.fill(1),l.fill(0),u=0},h=(...E)=>n(l,oe(a,...E)),b=(E=o)=>{l=h(s,E),a=h(),E.length!==0&&(l=h(i,E),a=h())},x=()=>{if(u++>=c)throw new Error(\"drbg: tried max amount of iterations\");let E=0,B=[];for(;E<e;){a=h();let k=a.slice();B.push(k),E+=a.length}return oe(...B)};return(E,B)=>{d(),b(E);let k;for(;!(k=B(x()));)b();return d(),k}}function Je(t,e={},n={}){if(!t||typeof t!=\"object\")throw new Error(\"expected valid options object\");function r(s,i,c){let a=t[s];if(c&&a===void 0)return;let l=typeof a;if(l!==i||a===null)throw new Error(`param \"${s}\" is invalid: expected ${i}, got ${l}`)}let o=(s,i)=>Object.entries(s).forEach(([c,a])=>r(c,a,i));o(e,!1),o(n,!0)}function $t(t){let e=new WeakMap;return(n,...r)=>{let o=e.get(n);if(o!==void 0)return o;let s=t(n,...r);return e.set(n,s),s}}var re=BigInt(0),ee=BigInt(1),Oe=BigInt(2),Zn=BigInt(3),Fn=BigInt(4),Yn=BigInt(5),Oo=BigInt(7),Jn=BigInt(8),ko=BigInt(9),Xn=BigInt(16);function ce(t,e){let n=t%e;return n>=re?n:e+n}function se(t,e,n){let r=t;for(;e-- >re;)r*=r,r%=n;return r}function Gn(t,e){if(t===re)throw new Error(\"invert: expected non-zero number\");if(e<=re)throw new Error(\"invert: expected positive modulus, got \"+e);let n=ce(t,e),r=e,o=re,s=ee,i=ee,c=re;for(;n!==re;){let l=r/n,u=r%n,d=o-i*l,h=s-c*l;r=n,n=u,o=i,s=c,i=d,c=h}if(r!==ee)throw new Error(\"invert: does not exist\");return ce(o,e)}function Vt(t,e,n){if(!t.eql(t.sqr(e),n))throw new Error(\"Cannot find square root\")}function Qn(t,e){let n=(t.ORDER+ee)/Fn,r=t.pow(e,n);return Vt(t,r,e),r}function _o(t,e){let n=(t.ORDER-Yn)/Jn,r=t.mul(e,Oe),o=t.pow(r,n),s=t.mul(e,o),i=t.mul(t.mul(s,Oe),o),c=t.mul(s,t.sub(i,t.ONE));return Vt(t,c,e),c}function Io(t){let e=Ue(t),n=er(t),r=n(e,e.neg(e.ONE)),o=n(e,r),s=n(e,e.neg(r)),i=(t+Oo)/Xn;return(c,a)=>{let l=c.pow(a,i),u=c.mul(l,r),d=c.mul(l,o),h=c.mul(l,s),b=c.eql(c.sqr(u),a),x=c.eql(c.sqr(d),a);l=c.cmov(l,u,b),u=c.cmov(h,d,x);let w=c.eql(c.sqr(u),a),E=c.cmov(l,u,w);return Vt(c,E,a),E}}function er(t){if(t<Zn)throw new Error(\"sqrt is not defined for small field\");let e=t-ee,n=0;for(;e%Oe===re;)e/=Oe,n++;let r=Oe,o=Ue(t);for(;zn(o,r)===1;)if(r++>1e3)throw new Error(\"Cannot find square root: probably non-prime P\");if(n===1)return Qn;let s=o.pow(r,e),i=(e+ee)/Oe;return function(a,l){if(a.is0(l))return l;if(zn(a,l)!==1)throw new Error(\"Cannot find square root\");let u=n,d=a.mul(a.ONE,s),h=a.pow(l,e),b=a.pow(l,i);for(;!a.eql(h,a.ONE);){if(a.is0(h))return a.ZERO;let x=1,w=a.sqr(h);for(;!a.eql(w,a.ONE);)if(x++,w=a.sqr(w),x===u)throw new Error(\"Cannot find square root\");let E=ee<<BigInt(u-x-1),B=a.pow(d,E);u=x,d=a.sqr(B),h=a.mul(h,d),b=a.mul(b,B)}return b}}function Co(t){return t%Fn===Zn?Qn:t%Jn===Yn?_o:t%Xn===ko?Io(t):er(t)}var Uo=[\"create\",\"isValid\",\"is0\",\"neg\",\"inv\",\"sqrt\",\"sqr\",\"eql\",\"add\",\"sub\",\"mul\",\"pow\",\"div\",\"addN\",\"subN\",\"mulN\",\"sqrN\"];function Wt(t){let e={ORDER:\"bigint\",BYTES:\"number\",BITS:\"number\"},n=Uo.reduce((r,o)=>(r[o]=\"function\",r),e);return Je(t,n),t}function Po(t,e,n){if(n<re)throw new Error(\"invalid exponent, negatives unsupported\");if(n===re)return t.ONE;if(n===ee)return e;let r=t.ONE,o=e;for(;n>re;)n&ee&&(r=t.mul(r,o)),o=t.sqr(o),n>>=ee;return r}function at(t,e,n=!1){let r=new Array(e.length).fill(n?t.ZERO:void 0),o=e.reduce((i,c,a)=>t.is0(c)?i:(r[a]=i,t.mul(i,c)),t.ONE),s=t.inv(o);return e.reduceRight((i,c,a)=>t.is0(c)?i:(r[a]=t.mul(i,r[a]),t.mul(i,c)),s),r}function zn(t,e){let n=(t.ORDER-ee)/Oe,r=t.pow(e,n),o=t.eql(r,t.ONE),s=t.eql(r,t.ZERO),i=t.eql(r,t.neg(t.ONE));if(!o&&!s&&!i)throw new Error(\"invalid Legendre symbol result\");return o?1:s?0:-1}function No(t,e){e!==void 0&&fe(e);let n=e!==void 0?e:t.toString(2).length,r=Math.ceil(n/8);return{nBitLength:n,nByteLength:r}}var Mt=class{constructor(e,n={}){f(this,\"ORDER\");f(this,\"BITS\");f(this,\"BYTES\");f(this,\"isLE\");f(this,\"ZERO\",re);f(this,\"ONE\",ee);f(this,\"_lengths\");f(this,\"_sqrt\");f(this,\"_mod\");if(e<=re)throw new Error(\"invalid field: expected ORDER > 0, got \"+e);let r;this.isLE=!1,n!=null&&typeof n==\"object\"&&(typeof n.BITS==\"number\"&&(r=n.BITS),typeof n.sqrt==\"function\"&&(this.sqrt=n.sqrt),typeof n.isLE==\"boolean\"&&(this.isLE=n.isLE),n.allowedLengths&&(this._lengths=n.allowedLengths?.slice()),typeof n.modFromBytes==\"boolean\"&&(this._mod=n.modFromBytes));let{nBitLength:o,nByteLength:s}=No(e,r);if(s>2048)throw new Error(\"invalid field: expected ORDER of <= 2048 bytes\");this.ORDER=e,this.BITS=o,this.BYTES=s,this._sqrt=void 0,Object.preventExtensions(this)}create(e){return ce(e,this.ORDER)}isValid(e){if(typeof e!=\"bigint\")throw new Error(\"invalid field element: expected bigint, got \"+typeof e);return re<=e&&e<this.ORDER}is0(e){return e===re}isValidNot0(e){return!this.is0(e)&&this.isValid(e)}isOdd(e){return(e&ee)===ee}neg(e){return ce(-e,this.ORDER)}eql(e,n){return e===n}sqr(e){return ce(e*e,this.ORDER)}add(e,n){return ce(e+n,this.ORDER)}sub(e,n){return ce(e-n,this.ORDER)}mul(e,n){return ce(e*n,this.ORDER)}pow(e,n){return Po(this,e,n)}div(e,n){return ce(e*Gn(n,this.ORDER),this.ORDER)}sqrN(e){return e*e}addN(e,n){return e+n}subN(e,n){return e-n}mulN(e,n){return e*n}inv(e){return Gn(e,this.ORDER)}sqrt(e){return this._sqrt||(this._sqrt=Co(this.ORDER)),this._sqrt(this,e)}toBytes(e){return this.isLE?qt(e,this.BYTES):ct(e,this.BYTES)}fromBytes(e,n=!1){P(e);let{_lengths:r,BYTES:o,isLE:s,ORDER:i,_mod:c}=this;if(r){if(!r.includes(e.length)||e.length>o)throw new Error(\"Field.fromBytes: expected \"+r+\" bytes, got \"+e.length);let l=new Uint8Array(o);l.set(e,s?0:l.length-e.length),e=l}if(e.length!==o)throw new Error(\"Field.fromBytes: expected \"+o+\" bytes, got \"+e.length);let a=s?Nt(e):Se(e);if(c&&(a=ce(a,i)),!n&&!this.isValid(a))throw new Error(\"invalid field element: outside of range 0..ORDER\");return a}invertBatch(e){return at(this,e)}cmov(e,n,r){return r?n:e}};function Ue(t,e={}){return new Mt(t,e)}function tr(t){if(typeof t!=\"bigint\")throw new Error(\"field order must be bigint\");let e=t.toString(2).length;return Math.ceil(e/8)}function Kt(t){let e=tr(t);return e+Math.ceil(e/2)}function ft(t,e,n=!1){P(t);let r=t.length,o=tr(e),s=Kt(e);if(r<16||r<s||r>1024)throw new Error(\"expected \"+s+\"-1024 bytes of input, got \"+r);let i=n?Nt(t):Se(t),c=ce(i,e-ee)+ee;return n?qt(c,o):ct(c,o)}var Pe=BigInt(0),ke=BigInt(1);function Xe(t,e){let n=e.negate();return t?n:e}function Zt(t,e){let n=at(t.Fp,e.map(r=>r.Z));return e.map((r,o)=>t.fromAffine(r.toAffine(n[o])))}function sr(t,e){if(!Number.isSafeInteger(t)||t<=0||t>e)throw new Error(\"invalid window size, expected [1..\"+e+\"], got W=\"+t)}function jt(t,e){sr(t,e);let n=Math.ceil(e/t)+1,r=2**(t-1),o=2**t,s=Ye(t),i=BigInt(t);return{windows:n,windowSize:r,mask:s,maxNumber:o,shiftBy:i}}function nr(t,e,n){let{windowSize:r,mask:o,maxNumber:s,shiftBy:i}=n,c=Number(t&o),a=t>>i;c>r&&(c-=s,a+=ke);let l=e*r,u=l+Math.abs(c)-1,d=c===0,h=c<0,b=e%2!==0;return{nextN:a,offset:u,isZero:d,isNeg:h,isNegF:b,offsetF:l}}var Gt=new WeakMap,ir=new WeakMap;function zt(t){return ir.get(t)||1}function rr(t){if(t!==Pe)throw new Error(\"invalid wNAF\")}var lt=class{constructor(e,n){f(this,\"BASE\");f(this,\"ZERO\");f(this,\"Fn\");f(this,\"bits\");this.BASE=e.BASE,this.ZERO=e.ZERO,this.Fn=e.Fn,this.bits=n}_unsafeLadder(e,n,r=this.ZERO){let o=e;for(;n>Pe;)n&ke&&(r=r.add(o)),o=o.double(),n>>=ke;return r}precomputeWindow(e,n){let{windows:r,windowSize:o}=jt(n,this.bits),s=[],i=e,c=i;for(let a=0;a<r;a++){c=i,s.push(c);for(let l=1;l<o;l++)c=c.add(i),s.push(c);i=c.double()}return s}wNAF(e,n,r){if(!this.Fn.isValid(r))throw new Error(\"invalid scalar\");let o=this.ZERO,s=this.BASE,i=jt(e,this.bits);for(let c=0;c<i.windows;c++){let{nextN:a,offset:l,isZero:u,isNeg:d,isNegF:h,offsetF:b}=nr(r,c,i);r=a,u?s=s.add(Xe(h,n[b])):o=o.add(Xe(d,n[l]))}return rr(r),{p:o,f:s}}wNAFUnsafe(e,n,r,o=this.ZERO){let s=jt(e,this.bits);for(let i=0;i<s.windows&&r!==Pe;i++){let{nextN:c,offset:a,isZero:l,isNeg:u}=nr(r,i,s);if(r=c,!l){let d=n[a];o=o.add(u?d.negate():d)}}return rr(r),o}getPrecomputes(e,n,r){let o=Gt.get(n);return o||(o=this.precomputeWindow(n,e),e!==1&&(typeof r==\"function\"&&(o=r(o)),Gt.set(n,o))),o}cached(e,n,r){let o=zt(e);return this.wNAF(o,this.getPrecomputes(o,e,r),n)}unsafe(e,n,r,o){let s=zt(e);return s===1?this._unsafeLadder(e,n,o):this.wNAFUnsafe(s,this.getPrecomputes(s,e,r),n,o)}createCache(e,n){sr(n,this.bits),ir.set(e,n),Gt.delete(e)}hasCache(e){return zt(e)!==1}};function cr(t,e,n,r){let o=e,s=t.ZERO,i=t.ZERO;for(;n>Pe||r>Pe;)n&ke&&(s=s.add(o)),r&ke&&(i=i.add(o)),o=o.double(),n>>=ke,r>>=ke;return{p1:s,p2:i}}function or(t,e,n){if(e){if(e.ORDER!==t)throw new Error(\"Field.ORDER must match order: Fp == p, Fn == n\");return Wt(e),e}else return Ue(t,{isLE:n})}function ar(t,e,n={},r){if(r===void 0&&(r=t===\"edwards\"),!e||typeof e!=\"object\")throw new Error(`expected valid ${t} CURVE object`);for(let a of[\"p\",\"n\",\"h\"]){let l=e[a];if(!(typeof l==\"bigint\"&&l>Pe))throw new Error(`CURVE.${a} must be positive bigint`)}let o=or(e.p,n.Fp,r),s=or(e.n,n.Fn,r),c=[\"Gx\",\"Gy\",\"a\",t===\"weierstrass\"?\"b\":\"d\"];for(let a of c)if(!o.isValid(e[a]))throw new Error(`CURVE.${a} must be valid field element of CURVE.Fp`);return e=Object.freeze(Object.assign({},e)),{CURVE:e,Fp:o,Fn:s}}function ut(t,e){return function(r){let o=t(r);return{secretKey:o,publicKey:e(o)}}}var ht=class{constructor(e,n){f(this,\"oHash\");f(this,\"iHash\");f(this,\"blockLen\");f(this,\"outputLen\");f(this,\"finished\",!1);f(this,\"destroyed\",!1);if(nt(e),P(n,void 0,\"key\"),this.iHash=e.create(),typeof this.iHash.update!=\"function\")throw new Error(\"Expected instance of class which extends utils.Hash\");this.blockLen=this.iHash.blockLen,this.outputLen=this.iHash.outputLen;let r=this.blockLen,o=new Uint8Array(r);o.set(n.length>r?e.create().update(n).digest():n);for(let s=0;s<o.length;s++)o[s]^=54;this.iHash.update(o),this.oHash=e.create();for(let s=0;s<o.length;s++)o[s]^=106;this.oHash.update(o),Te(o)}update(e){return Ce(this),this.iHash.update(e),this}digestInto(e){Ce(this),P(e,this.outputLen,\"output\"),this.finished=!0,this.iHash.digestInto(e),this.oHash.update(e),this.oHash.digestInto(e),this.destroy()}digest(){let e=new Uint8Array(this.oHash.outputLen);return this.digestInto(e),e}_cloneInto(e){e||(e=Object.create(Object.getPrototypeOf(this),{}));let{oHash:n,iHash:r,finished:o,destroyed:s,blockLen:i,outputLen:c}=this;return e=e,e.finished=o,e.destroyed=s,e.blockLen=i,e.outputLen=c,e.oHash=n._cloneInto(e.oHash),e.iHash=r._cloneInto(e.iHash),e}clone(){return this._cloneInto()}destroy(){this.destroyed=!0,this.oHash.destroy(),this.iHash.destroy()}},Ft=(t,e,n)=>new ht(t,e).update(n).digest();Ft.create=(t,e)=>new ht(t,e);var fr=(t,e)=>(t+(t>=0?e:-e)/lr)/e;function qo(t,e,n){let[[r,o],[s,i]]=e,c=fr(i*t,n),a=fr(-o*t,n),l=t-c*r-a*s,u=-c*o-a*i,d=l<be,h=u<be;d&&(l=-l),h&&(u=-u);let b=Ye(Math.ceil(Dt(n)/2))+Ne;if(l<be||l>=b||u<be||u>=b)throw new Error(\"splitScalar (endomorphism): failed, k=\"+t);return{k1neg:d,k1:l,k2neg:h,k2:u}}function Jt(t){if(![\"compact\",\"recovered\",\"der\"].includes(t))throw new Error('Signature format must be \"compact\", \"recovered\", or \"der\"');return t}function Yt(t,e){let n={};for(let r of Object.keys(e))n[r]=t[r]===void 0?e[r]:t[r];return Ze(n.lowS,\"lowS\"),Ze(n.prehash,\"prehash\"),n.format!==void 0&&Jt(n.format),n}var Xt=class extends Error{constructor(e=\"\"){super(e)}},Be={Err:Xt,_tlv:{encode:(t,e)=>{let{Err:n}=Be;if(t<0||t>256)throw new n(\"tlv.encode: wrong tag\");if(e.length&1)throw new n(\"tlv.encode: unpadded data\");let r=e.length/2,o=Fe(r);if(o.length/2&128)throw new n(\"tlv.encode: long form length too big\");let s=r>127?Fe(o.length/2|128):\"\";return Fe(t)+s+o+e},decode(t,e){let{Err:n}=Be,r=0;if(t<0||t>256)throw new n(\"tlv.encode: wrong tag\");if(e.length<2||e[r++]!==t)throw new n(\"tlv.decode: wrong tlv\");let o=e[r++],s=!!(o&128),i=0;if(!s)i=o;else{let a=o&127;if(!a)throw new n(\"tlv.decode(long): indefinite length not supported\");if(a>4)throw new n(\"tlv.decode(long): byte length is too big\");let l=e.subarray(r,r+a);if(l.length!==a)throw new n(\"tlv.decode: length bytes not complete\");if(l[0]===0)throw new n(\"tlv.decode(long): zero leftmost byte\");for(let u of l)i=i<<8|u;if(r+=a,i<128)throw new n(\"tlv.decode(long): not minimal encoding\")}let c=e.subarray(r,r+i);if(c.length!==i)throw new n(\"tlv.decode: wrong value length\");return{v:c,l:e.subarray(r+i)}}},_int:{encode(t){let{Err:e}=Be;if(t<be)throw new e(\"integer: negative integers are not allowed\");let n=Fe(t);if(Number.parseInt(n[0],16)&8&&(n=\"00\"+n),n.length&1)throw new e(\"unexpected DER parsing assertion: unpadded hex\");return n},decode(t){let{Err:e}=Be;if(t[0]&128)throw new e(\"invalid signature integer: negative\");if(t[0]===0&&!(t[1]&128))throw new e(\"invalid signature integer: unnecessary leading zero\");return Se(t)}},toSig(t){let{Err:e,_int:n,_tlv:r}=Be,o=P(t,void 0,\"signature\"),{v:s,l:i}=r.decode(48,o);if(i.length)throw new e(\"invalid signature: left bytes after parsing\");let{v:c,l:a}=r.decode(2,s),{v:l,l:u}=r.decode(2,a);if(u.length)throw new e(\"invalid signature: left bytes after parsing\");return{r:n.decode(c),s:n.decode(l)}},hexFromSig(t){let{_tlv:e,_int:n}=Be,r=e.encode(2,n.encode(t.r)),o=e.encode(2,n.encode(t.s)),s=r+o;return e.encode(48,s)}},be=BigInt(0),Ne=BigInt(1),lr=BigInt(2),dt=BigInt(3),Do=BigInt(4);function ur(t,e={}){let n=ar(\"weierstrass\",t,e),{Fp:r,Fn:o}=n,s=n.CURVE,{h:i,n:c}=s;Je(e,{},{allowInfinityPoint:\"boolean\",clearCofactor:\"function\",isTorsionFree:\"function\",fromBytes:\"function\",toBytes:\"function\",endo:\"object\"});let{endo:a}=e;if(a&&(!r.is0(s.a)||typeof a.beta!=\"bigint\"||!Array.isArray(a.basises)))throw new Error('invalid endo: expected \"beta\": bigint and \"basises\": array');let l=dr(r,o);function u(){if(!r.isOdd)throw new Error(\"compression is not supported: Field does not have .isOdd()\")}function d(L,y,p){let{x:m,y:v}=y.toAffine(),A=r.toBytes(m);if(Ze(p,\"isCompressed\"),p){u();let g=!r.isOdd(v);return oe(hr(g),A)}else return oe(Uint8Array.of(4),A,r.toBytes(v))}function h(L){P(L,void 0,\"Point\");let{publicKey:y,publicKeyUncompressed:p}=l,m=L.length,v=L[0],A=L.subarray(1);if(m===y&&(v===2||v===3)){let g=r.fromBytes(A);if(!r.isValid(g))throw new Error(\"bad point: is not on curve, wrong x\");let T=w(g),S;try{S=r.sqrt(T)}catch(V){let D=V instanceof Error?\": \"+V.message:\"\";throw new Error(\"bad point: is not on curve, sqrt error\"+D)}u();let H=r.isOdd(S);return(v&1)===1!==H&&(S=r.neg(S)),{x:g,y:S}}else if(m===p&&v===4){let g=r.BYTES,T=r.fromBytes(A.subarray(0,g)),S=r.fromBytes(A.subarray(g,g*2));if(!E(T,S))throw new Error(\"bad point: is not on curve\");return{x:T,y:S}}else throw new Error(`bad point: got length ${m}, expected compressed=${y} or uncompressed=${p}`)}let b=e.toBytes||d,x=e.fromBytes||h;function w(L){let y=r.sqr(L),p=r.mul(y,L);return r.add(r.add(p,r.mul(L,s.a)),s.b)}function E(L,y){let p=r.sqr(y),m=w(L);return r.eql(p,m)}if(!E(s.Gx,s.Gy))throw new Error(\"bad curve params: generator point\");let B=r.mul(r.pow(s.a,dt),Do),k=r.mul(r.sqr(s.b),BigInt(27));if(r.is0(r.add(B,k)))throw new Error(\"bad curve params: a or b\");function U(L,y,p=!1){if(!r.isValid(y)||p&&r.is0(y))throw new Error(`bad point coordinate ${L}`);return y}function N(L){if(!(L instanceof C))throw new Error(\"Weierstrass Point expected\")}function X(L){if(!a||!a.basises)throw new Error(\"no endo\");return qo(L,a.basises,o.ORDER)}let M=$t((L,y)=>{let{X:p,Y:m,Z:v}=L;if(r.eql(v,r.ONE))return{x:p,y:m};let A=L.is0();y==null&&(y=A?r.ONE:r.inv(v));let g=r.mul(p,y),T=r.mul(m,y),S=r.mul(v,y);if(A)return{x:r.ZERO,y:r.ZERO};if(!r.eql(S,r.ONE))throw new Error(\"invZ was invalid\");return{x:g,y:T}}),z=$t(L=>{if(L.is0()){if(e.allowInfinityPoint&&!r.is0(L.Y))return;throw new Error(\"bad point: ZERO\")}let{x:y,y:p}=L.toAffine();if(!r.isValid(y)||!r.isValid(p))throw new Error(\"bad point: x or y not field elements\");if(!E(y,p))throw new Error(\"bad point: equation left != right\");if(!L.isTorsionFree())throw new Error(\"bad point: not in prime-order subgroup\");return!0});function K(L,y,p,m,v){return p=new C(r.mul(p.X,L),p.Y,p.Z),y=Xe(m,y),p=Xe(v,p),y.add(p)}let O=class O{constructor(y,p,m){f(this,\"X\");f(this,\"Y\");f(this,\"Z\");this.X=U(\"x\",y),this.Y=U(\"y\",p,!0),this.Z=U(\"z\",m),Object.freeze(this)}static CURVE(){return s}static fromAffine(y){let{x:p,y:m}=y||{};if(!y||!r.isValid(p)||!r.isValid(m))throw new Error(\"invalid affine point\");if(y instanceof O)throw new Error(\"projective point not allowed\");return r.is0(p)&&r.is0(m)?O.ZERO:new O(p,m,r.ONE)}static fromBytes(y){let p=O.fromAffine(x(P(y,void 0,\"point\")));return p.assertValidity(),p}static fromHex(y){return O.fromBytes(Re(y))}get x(){return this.toAffine().x}get y(){return this.toAffine().y}precompute(y=8,p=!0){return q.createCache(this,y),p||this.multiply(dt),this}assertValidity(){z(this)}hasEvenY(){let{y}=this.toAffine();if(!r.isOdd)throw new Error(\"Field doesn't support isOdd\");return!r.isOdd(y)}equals(y){N(y);let{X:p,Y:m,Z:v}=this,{X:A,Y:g,Z:T}=y,S=r.eql(r.mul(p,T),r.mul(A,v)),H=r.eql(r.mul(m,T),r.mul(g,v));return S&&H}negate(){return new O(this.X,r.neg(this.Y),this.Z)}double(){let{a:y,b:p}=s,m=r.mul(p,dt),{X:v,Y:A,Z:g}=this,T=r.ZERO,S=r.ZERO,H=r.ZERO,_=r.mul(v,v),V=r.mul(A,A),D=r.mul(g,g),I=r.mul(v,A);return I=r.add(I,I),H=r.mul(v,g),H=r.add(H,H),T=r.mul(y,H),S=r.mul(m,D),S=r.add(T,S),T=r.sub(V,S),S=r.add(V,S),S=r.mul(T,S),T=r.mul(I,T),H=r.mul(m,H),D=r.mul(y,D),I=r.sub(_,D),I=r.mul(y,I),I=r.add(I,H),H=r.add(_,_),_=r.add(H,_),_=r.add(_,D),_=r.mul(_,I),S=r.add(S,_),D=r.mul(A,g),D=r.add(D,D),_=r.mul(D,I),T=r.sub(T,_),H=r.mul(D,V),H=r.add(H,H),H=r.add(H,H),new O(T,S,H)}add(y){N(y);let{X:p,Y:m,Z:v}=this,{X:A,Y:g,Z:T}=y,S=r.ZERO,H=r.ZERO,_=r.ZERO,V=s.a,D=r.mul(s.b,dt),I=r.mul(p,A),W=r.mul(m,g),j=r.mul(v,T),Z=r.add(p,m),G=r.add(A,g);Z=r.mul(Z,G),G=r.add(I,W),Z=r.sub(Z,G),G=r.add(p,v);let ne=r.add(A,T);return G=r.mul(G,ne),ne=r.add(I,j),G=r.sub(G,ne),ne=r.add(m,v),S=r.add(g,T),ne=r.mul(ne,S),S=r.add(W,j),ne=r.sub(ne,S),_=r.mul(V,G),S=r.mul(D,j),_=r.add(S,_),S=r.sub(W,_),_=r.add(W,_),H=r.mul(S,_),W=r.add(I,I),W=r.add(W,I),j=r.mul(V,j),G=r.mul(D,G),W=r.add(W,j),j=r.sub(I,j),j=r.mul(V,j),G=r.add(G,j),I=r.mul(W,G),H=r.add(H,I),I=r.mul(ne,G),S=r.mul(Z,S),S=r.sub(S,I),I=r.mul(Z,W),_=r.mul(ne,_),_=r.add(_,I),new O(S,H,_)}subtract(y){return this.add(y.negate())}is0(){return this.equals(O.ZERO)}multiply(y){let{endo:p}=e;if(!o.isValidNot0(y))throw new Error(\"invalid scalar: out of range\");let m,v,A=g=>q.cached(this,g,T=>Zt(O,T));if(p){let{k1neg:g,k1:T,k2neg:S,k2:H}=X(y),{p:_,f:V}=A(T),{p:D,f:I}=A(H);v=V.add(I),m=K(p.beta,_,D,g,S)}else{let{p:g,f:T}=A(y);m=g,v=T}return Zt(O,[m,v])[0]}multiplyUnsafe(y){let{endo:p}=e,m=this;if(!o.isValid(y))throw new Error(\"invalid scalar: out of range\");if(y===be||m.is0())return O.ZERO;if(y===Ne)return m;if(q.hasCache(this))return this.multiply(y);if(p){let{k1neg:v,k1:A,k2neg:g,k2:T}=X(y),{p1:S,p2:H}=cr(O,m,A,T);return K(p.beta,S,H,v,g)}else return q.unsafe(m,y)}toAffine(y){return M(this,y)}isTorsionFree(){let{isTorsionFree:y}=e;return i===Ne?!0:y?y(O,this):q.unsafe(this,c).is0()}clearCofactor(){let{clearCofactor:y}=e;return i===Ne?this:y?y(O,this):this.multiplyUnsafe(i)}isSmallOrder(){return this.multiplyUnsafe(i).is0()}toBytes(y=!0){return Ze(y,\"isCompressed\"),this.assertValidity(),b(O,this,y)}toHex(y=!0){return He(this.toBytes(y))}toString(){return`<Point ${this.is0()?\"ZERO\":this.toHex()}>`}};f(O,\"BASE\",new O(s.Gx,s.Gy,r.ONE)),f(O,\"ZERO\",new O(r.ZERO,r.ONE,r.ZERO)),f(O,\"Fp\",r),f(O,\"Fn\",o);let C=O,$=o.BITS,q=new lt(C,e.endo?Math.ceil($/2):$);return C.BASE.precompute(8),C}function hr(t){return Uint8Array.of(t?2:3)}function dr(t,e){return{secretKey:e.BYTES,publicKey:1+t.BYTES,publicKeyUncompressed:1+2*t.BYTES,publicKeyHasPrefix:!0,signature:2*e.BYTES}}function $o(t,e={}){let{Fn:n}=t,r=e.randomBytes||Le,o=Object.assign(dr(t.Fp,n),{seed:Kt(n.ORDER)});function s(b){try{let x=n.fromBytes(b);return n.isValidNot0(x)}catch{return!1}}function i(b,x){let{publicKey:w,publicKeyUncompressed:E}=o;try{let B=b.length;return x===!0&&B!==w||x===!1&&B!==E?!1:!!t.fromBytes(b)}catch{return!1}}function c(b=r(o.seed)){return ft(P(b,o.seed,\"seed\"),n.ORDER)}function a(b,x=!0){return t.BASE.multiply(n.fromBytes(b)).toBytes(x)}function l(b){let{secretKey:x,publicKey:w,publicKeyUncompressed:E}=o;if(!ze(b)||\"_lengths\"in n&&n._lengths||x===w)return;let B=P(b,void 0,\"key\").length;return B===w||B===E}function u(b,x,w=!0){if(l(b)===!0)throw new Error(\"first arg must be private key\");if(l(x)===!1)throw new Error(\"second arg must be public key\");let E=n.fromBytes(b);return t.fromBytes(x).multiply(E).toBytes(w)}let d={isValidSecretKey:s,isValidPublicKey:i,randomSecretKey:c},h=ut(c,a);return Object.freeze({getPublicKey:a,getSharedSecret:u,keygen:h,Point:t,utils:d,lengths:o})}function br(t,e,n={}){nt(e),Je(n,{},{hmac:\"function\",lowS:\"boolean\",randomBytes:\"function\",bits2int:\"function\",bits2int_modN:\"function\"}),n=Object.assign({},n);let r=n.randomBytes||Le,o=n.hmac||((y,p)=>Ft(e,y,p)),{Fp:s,Fn:i}=t,{ORDER:c,BITS:a}=i,{keygen:l,getPublicKey:u,getSharedSecret:d,utils:h,lengths:b}=$o(t,n),x={prehash:!0,lowS:typeof n.lowS==\"boolean\"?n.lowS:!0,format:\"compact\",extraEntropy:!1},w=c*lr<s.ORDER;function E(y){let p=c>>Ne;return y>p}function B(y,p){if(!i.isValidNot0(p))throw new Error(`invalid signature ${y}: out of range 1..Point.Fn.ORDER`);return p}function k(){if(w)throw new Error('\"recovered\" sig type is not supported for cofactor >2 curves')}function U(y,p){Jt(p);let m=b.signature,v=p===\"compact\"?m:p===\"recovered\"?m+1:void 0;return P(y,v)}class N{constructor(p,m,v){f(this,\"r\");f(this,\"s\");f(this,\"recovery\");if(this.r=B(\"r\",p),this.s=B(\"s\",m),v!=null){if(k(),![0,1,2,3].includes(v))throw new Error(\"invalid recovery id\");this.recovery=v}Object.freeze(this)}static fromBytes(p,m=x.format){U(p,m);let v;if(m===\"der\"){let{r:S,s:H}=Be.toSig(P(p));return new N(S,H)}m===\"recovered\"&&(v=p[0],m=\"compact\",p=p.subarray(1));let A=b.signature/2,g=p.subarray(0,A),T=p.subarray(A,A*2);return new N(i.fromBytes(g),i.fromBytes(T),v)}static fromHex(p,m){return this.fromBytes(Re(p),m)}assertRecovery(){let{recovery:p}=this;if(p==null)throw new Error(\"invalid recovery id: must be present\");return p}addRecoveryBit(p){return new N(this.r,this.s,p)}recoverPublicKey(p){let{r:m,s:v}=this,A=this.assertRecovery(),g=A===2||A===3?m+c:m;if(!s.isValid(g))throw new Error(\"invalid recovery id: sig.r+curve.n != R.x\");let T=s.toBytes(g),S=t.fromBytes(oe(hr((A&1)===0),T)),H=i.inv(g),_=M(P(p,void 0,\"msgHash\")),V=i.create(-_*H),D=i.create(v*H),I=t.BASE.multiplyUnsafe(V).add(S.multiplyUnsafe(D));if(I.is0())throw new Error(\"invalid recovery: point at infinify\");return I.assertValidity(),I}hasHighS(){return E(this.s)}toBytes(p=x.format){if(Jt(p),p===\"der\")return Re(Be.hexFromSig(this));let{r:m,s:v}=this,A=i.toBytes(m),g=i.toBytes(v);return p===\"recovered\"?(k(),oe(Uint8Array.of(this.assertRecovery()),A,g)):oe(A,g)}toHex(p){return He(this.toBytes(p))}}let X=n.bits2int||function(p){if(p.length>8192)throw new Error(\"input is too large\");let m=Se(p),v=p.length*8-a;return v>0?m>>BigInt(v):m},M=n.bits2int_modN||function(p){return i.create(X(p))},z=Ye(a);function K(y){return Kn(\"num < 2^\"+a,y,be,z),i.toBytes(y)}function C(y,p){return P(y,void 0,\"message\"),p?P(e(y),void 0,\"prehashed message\"):y}function $(y,p,m){let{lowS:v,prehash:A,extraEntropy:g}=Yt(m,x);y=C(y,A);let T=M(y),S=i.fromBytes(p);if(!i.isValidNot0(S))throw new Error(\"invalid private key\");let H=[K(S),K(T)];if(g!=null&&g!==!1){let I=g===!0?r(b.secretKey):g;H.push(P(I,void 0,\"extraEntropy\"))}let _=oe(...H),V=T;function D(I){let W=X(I);if(!i.isValidNot0(W))return;let j=i.inv(W),Z=t.BASE.multiply(W).toAffine(),G=i.create(Z.x);if(G===be)return;let ne=i.create(j*i.create(V+G*S));if(ne===be)return;let _n=(Z.x===G?0:2)|Number(Z.y&Ne),In=ne;return v&&E(ne)&&(In=i.neg(ne),_n^=1),new N(G,In,w?void 0:_n)}return{seed:_,k2sig:D}}function q(y,p,m={}){let{seed:v,k2sig:A}=$(y,p,m);return jn(e.outputLen,i.BYTES,o)(v,A).toBytes(m.format)}function O(y,p,m,v={}){let{lowS:A,prehash:g,format:T}=Yt(v,x);if(m=P(m,void 0,\"publicKey\"),p=C(p,g),!ze(y)){let S=y instanceof N?\", use sig.toBytes()\":\"\";throw new Error(\"verify expects Uint8Array signature\"+S)}U(y,T);try{let S=N.fromBytes(y,T),H=t.fromBytes(m);if(A&&S.hasHighS())return!1;let{r:_,s:V}=S,D=M(p),I=i.inv(V),W=i.create(D*I),j=i.create(_*I),Z=t.BASE.multiplyUnsafe(W).add(H.multiplyUnsafe(j));return Z.is0()?!1:i.create(Z.x)===_}catch{return!1}}function L(y,p,m={}){let{prehash:v}=Yt(m,x);return p=C(p,v),N.fromBytes(y,\"recovered\").recoverPublicKey(p).toBytes()}return Object.freeze({keygen:l,getPublicKey:u,getSharedSecret:d,utils:h,lengths:b,Point:t,sign:q,verify:O,recoverPublicKey:L,Signature:N,hash:e})}var yt={p:BigInt(\"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f\"),n:BigInt(\"0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141\"),h:BigInt(1),a:BigInt(0),b:BigInt(7),Gx:BigInt(\"0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"),Gy:BigInt(\"0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8\")},Mo={beta:BigInt(\"0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee\"),basises:[[BigInt(\"0x3086d221a7d46bcde86c90e49284eb15\"),-BigInt(\"0xe4437ed6010e88286f547fa90abfe4c3\")],[BigInt(\"0x114ca50f7a8e2f3f657c1108d9d44cfd8\"),BigInt(\"0x3086d221a7d46bcde86c90e49284eb15\")]]},Vo=BigInt(0),Qt=BigInt(2);function Wo(t){let e=yt.p,n=BigInt(3),r=BigInt(6),o=BigInt(11),s=BigInt(22),i=BigInt(23),c=BigInt(44),a=BigInt(88),l=t*t*t%e,u=l*l*t%e,d=se(u,n,e)*u%e,h=se(d,n,e)*u%e,b=se(h,Qt,e)*l%e,x=se(b,o,e)*b%e,w=se(x,s,e)*x%e,E=se(w,c,e)*w%e,B=se(E,a,e)*E%e,k=se(B,c,e)*w%e,U=se(k,n,e)*u%e,N=se(U,i,e)*x%e,X=se(N,r,e)*l%e,M=se(X,Qt,e);if(!bt.eql(bt.sqr(M),t))throw new Error(\"Cannot find square root\");return M}var bt=Ue(yt.p,{sqrt:Wo}),_e=ur(yt,{Fp:bt,endo:Mo}),xr=br(_e,st),pr={};function pt(t,...e){let n=pr[t];if(n===void 0){let r=st(Wn(t));n=oe(r,r),pr[t]=n}return st(oe(n,...e))}var tn=t=>t.toBytes(!0).slice(1),nn=t=>t%Qt===Vo;function en(t){let{Fn:e,BASE:n}=_e,r=e.fromBytes(t),o=n.multiply(r);return{scalar:nn(o.y)?r:e.neg(r),bytes:tn(o)}}function gr(t){let e=bt;if(!e.isValidNot0(t))throw new Error(\"invalid x: Fail if x \\u2265 p\");let n=e.create(t*t),r=e.create(n*t+BigInt(7)),o=e.sqrt(r);nn(o)||(o=e.neg(o));let s=_e.fromAffine({x:t,y:o});return s.assertValidity(),s}var Qe=Se;function wr(...t){return _e.Fn.create(Qe(pt(\"BIP0340/challenge\",...t)))}function yr(t){return en(t).bytes}function Ko(t,e,n=Le(32)){let{Fn:r}=_e,o=P(t,void 0,\"message\"),{bytes:s,scalar:i}=en(e),c=P(n,32,\"auxRand\"),a=r.toBytes(i^Qe(pt(\"BIP0340/aux\",c))),l=pt(\"BIP0340/nonce\",a,s,o),{bytes:u,scalar:d}=en(l),h=wr(u,s,o),b=new Uint8Array(64);if(b.set(u,0),b.set(r.toBytes(r.create(d+h*i)),32),!mr(b,o,s))throw new Error(\"sign: Invalid signature produced\");return b}function mr(t,e,n){let{Fp:r,Fn:o,BASE:s}=_e,i=P(t,64,\"signature\"),c=P(e,void 0,\"message\"),a=P(n,32,\"publicKey\");try{let l=gr(Qe(a)),u=Qe(i.subarray(0,32));if(!r.isValidNot0(u))return!1;let d=Qe(i.subarray(32,64));if(!o.isValidNot0(d))return!1;let h=wr(o.toBytes(u),tn(l),c),b=s.multiplyUnsafe(d).add(l.multiplyUnsafe(o.neg(h))),{x,y:w}=b.toAffine();return!(b.is0()||!nn(w)||x!==u)}catch{return!1}}var Y=(()=>{let n=(r=Le(48))=>ft(r,yt.n);return{keygen:ut(n,yr),getPublicKey:yr,sign:Ko,verify:mr,Point:_e,utils:{randomSecretKey:n,taggedHash:pt,lift_x:gr,pointToBytes:tn},lengths:{secretKey:32,publicKey:32,publicKeyHasPrefix:!1,signature:64,seed:48}}})();function jo(t){return t instanceof Uint8Array||ArrayBuffer.isView(t)&&t.constructor.name===\"Uint8Array\"}function xt(t,e=\"\"){if(!Number.isSafeInteger(t)||t<0){let n=e&&`\"${e}\" `;throw new Error(`${n}expected integer >= 0, got ${t}`)}}function le(t,e,n=\"\"){let r=jo(t),o=t?.length,s=e!==void 0;if(!r||s&&o!==e){let i=n&&`\"${n}\" `,c=s?` of length ${e}`:\"\",a=r?`length=${o}`:`type=${typeof t}`;throw new Error(i+\"expected Uint8Array\"+c+\", got \"+a)}return t}function et(t){if(typeof t!=\"function\"||typeof t.create!=\"function\")throw new Error(\"Hash must wrapped by utils.createHasher\");xt(t.outputLen),xt(t.blockLen)}function qe(t,e=!0){if(t.destroyed)throw new Error(\"Hash instance has been destroyed\");if(e&&t.finished)throw new Error(\"Hash#digest() has already been called\")}function vr(t,e){le(t,void 0,\"digestInto() output\");let n=e.outputLen;if(t.length<n)throw new Error('\"digestInto() output\" expected to be of length >='+n)}function ye(...t){for(let e=0;e<t.length;e++)t[e].fill(0)}function gt(t){return new DataView(t.buffer,t.byteOffset,t.byteLength)}function ae(t,e){return t<<32-e|t>>>e}var Sr=typeof Uint8Array.from([]).toHex==\"function\"&&typeof Uint8Array.fromHex==\"function\",Go=Array.from({length:256},(t,e)=>e.toString(16).padStart(2,\"0\"));function te(t){if(le(t),Sr)return t.toHex();let e=\"\";for(let n=0;n<t.length;n++)e+=Go[t[n]];return e}var pe={_0:48,_9:57,A:65,F:70,a:97,f:102};function Er(t){if(t>=pe._0&&t<=pe._9)return t-pe._0;if(t>=pe.A&&t<=pe.F)return t-(pe.A-10);if(t>=pe.a&&t<=pe.f)return t-(pe.a-10)}function F(t){if(typeof t!=\"string\")throw new Error(\"hex string expected, got \"+typeof t);if(Sr)return Uint8Array.fromHex(t);let e=t.length,n=e/2;if(e%2)throw new Error(\"hex string expected, got unpadded hex of length \"+e);let r=new Uint8Array(n);for(let o=0,s=0;o<n;o++,s+=2){let i=Er(t.charCodeAt(s)),c=Er(t.charCodeAt(s+1));if(i===void 0||c===void 0){let a=t[s]+t[s+1];throw new Error('hex string expected, got non-hex character \"'+a+'\" at index '+s)}r[o]=i*16+c}return r}function De(...t){let e=0;for(let r=0;r<t.length;r++){let o=t[r];le(o),e+=o.length}let n=new Uint8Array(e);for(let r=0,o=0;r<t.length;r++){let s=t[r];n.set(s,o),o+=s.length}return n}function Br(t,e={}){let n=(o,s)=>t(s).update(o).digest(),r=t(void 0);return n.outputLen=r.outputLen,n.blockLen=r.blockLen,n.create=o=>t(o),Object.assign(n,e),Object.freeze(n)}function Ar(t=32){let e=typeof globalThis==\"object\"?globalThis.crypto:null;if(typeof e?.getRandomValues!=\"function\")throw new Error(\"crypto.getRandomValues must be defined\");return e.getRandomValues(new Uint8Array(t))}var Tr=t=>({oid:Uint8Array.from([6,9,96,134,72,1,101,3,4,2,t])});function Hr(t,e,n){return t&e^~t&n}function Rr(t,e,n){return t&e^t&n^e&n}var wt=class{constructor(e,n,r,o){f(this,\"blockLen\");f(this,\"outputLen\");f(this,\"padOffset\");f(this,\"isLE\");f(this,\"buffer\");f(this,\"view\");f(this,\"finished\",!1);f(this,\"length\",0);f(this,\"pos\",0);f(this,\"destroyed\",!1);this.blockLen=e,this.outputLen=n,this.padOffset=r,this.isLE=o,this.buffer=new Uint8Array(e),this.view=gt(this.buffer)}update(e){qe(this),le(e);let{view:n,buffer:r,blockLen:o}=this,s=e.length;for(let i=0;i<s;){let c=Math.min(o-this.pos,s-i);if(c===o){let a=gt(e);for(;o<=s-i;i+=o)this.process(a,i);continue}r.set(e.subarray(i,i+c),this.pos),this.pos+=c,i+=c,this.pos===o&&(this.process(n,0),this.pos=0)}return this.length+=e.length,this.roundClean(),this}digestInto(e){qe(this),vr(e,this),this.finished=!0;let{buffer:n,view:r,blockLen:o,isLE:s}=this,{pos:i}=this;n[i++]=128,ye(this.buffer.subarray(i)),this.padOffset>o-i&&(this.process(r,0),i=0);for(let d=i;d<o;d++)n[d]=0;r.setBigUint64(o-8,BigInt(this.length*8),s),this.process(r,0);let c=gt(e),a=this.outputLen;if(a%4)throw new Error(\"_sha2: outputLen must be aligned to 32bit\");let l=a/4,u=this.get();if(l>u.length)throw new Error(\"_sha2: outputLen bigger than state\");for(let d=0;d<l;d++)c.setUint32(4*d,u[d],s)}digest(){let{buffer:e,outputLen:n}=this;this.digestInto(e);let r=e.slice(0,n);return this.destroy(),r}_cloneInto(e){e||(e=new this.constructor),e.set(...this.get());let{blockLen:n,buffer:r,length:o,finished:s,destroyed:i,pos:c}=this;return e.destroyed=i,e.finished=s,e.length=o,e.pos=c,o%n&&e.buffer.set(r),e}clone(){return this._cloneInto()}},xe=Uint32Array.from([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]);var zo=Uint32Array.from([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),Ae=new Uint32Array(64),rn=class extends wt{constructor(e){super(64,e,8,!1)}get(){let{A:e,B:n,C:r,D:o,E:s,F:i,G:c,H:a}=this;return[e,n,r,o,s,i,c,a]}set(e,n,r,o,s,i,c,a){this.A=e|0,this.B=n|0,this.C=r|0,this.D=o|0,this.E=s|0,this.F=i|0,this.G=c|0,this.H=a|0}process(e,n){for(let d=0;d<16;d++,n+=4)Ae[d]=e.getUint32(n,!1);for(let d=16;d<64;d++){let h=Ae[d-15],b=Ae[d-2],x=ae(h,7)^ae(h,18)^h>>>3,w=ae(b,17)^ae(b,19)^b>>>10;Ae[d]=w+Ae[d-7]+x+Ae[d-16]|0}let{A:r,B:o,C:s,D:i,E:c,F:a,G:l,H:u}=this;for(let d=0;d<64;d++){let h=ae(c,6)^ae(c,11)^ae(c,25),b=u+h+Hr(c,a,l)+zo[d]+Ae[d]|0,w=(ae(r,2)^ae(r,13)^ae(r,22))+Rr(r,o,s)|0;u=l,l=a,a=c,c=i+b|0,i=s,s=o,o=r,r=b+w|0}r=r+this.A|0,o=o+this.B|0,s=s+this.C|0,i=i+this.D|0,c=c+this.E|0,a=a+this.F|0,l=l+this.G|0,u=u+this.H|0,this.set(r,o,s,i,c,a,l,u)}roundClean(){ye(Ae)}destroy(){this.set(0,0,0,0,0,0,0,0),ye(this.buffer)}},on=class extends rn{constructor(){super(32);f(this,\"A\",xe[0]|0);f(this,\"B\",xe[1]|0);f(this,\"C\",xe[2]|0);f(this,\"D\",xe[3]|0);f(this,\"E\",xe[4]|0);f(this,\"F\",xe[5]|0);f(this,\"G\",xe[6]|0);f(this,\"H\",xe[7]|0)}};var ue=Br(()=>new on,Tr(1));var Ac=new TextDecoder(\"utf-8\"),Zo=new TextEncoder;function Fo(t){if(t.length!==64)return!1;for(let e=0;e<64;e++){let n=t.charCodeAt(e);if(isNaN(n)||n<48||n>102||n>57&&n<97)return!1}return!0}var $e=Symbol(\"verified\"),Yo=t=>t instanceof Object;function Jo(t){if(!Yo(t)||typeof t.kind!=\"number\"||typeof t.content!=\"string\"||typeof t.created_at!=\"number\"||typeof t.pubkey!=\"string\"||!Fo(t.pubkey)||!Array.isArray(t.tags))return!1;for(let e=0;e<t.tags.length;e++){let n=t.tags[e];if(!Array.isArray(n))return!1;for(let r=0;r<n.length;r++)if(typeof n[r]!=\"string\")return!1}return!0}var Xo=class{generateSecretKey(){return Y.utils.randomSecretKey()}getPublicKey(t){return te(Y.getPublicKey(t))}finalizeEvent(t,e){let n=t;return n.pubkey=te(Y.getPublicKey(e)),n.id=sn(n),n.sig=te(Y.sign(F(sn(n)),e)),n[$e]=!0,n}verifyEvent(t){if(typeof t[$e]==\"boolean\")return t[$e];try{let e=sn(t);if(e!==t.id)return t[$e]=!1,!1;let n=Y.verify(F(t.sig),F(e),F(t.pubkey));return t[$e]=n,n}catch{return t[$e]=!1,!1}}};function Qo(t){if(!Jo(t))throw new Error(\"can't serialize event with wrong or missing properties\");return JSON.stringify([0,t.pubkey,t.created_at,t.kind,t.tags,t.content])}function sn(t){let e=ue(Zo.encode(Qo(t)));return te(e)}var mt=new Xo,Lr=mt.generateSecretKey,Or=mt.getPublicKey,kr=mt.finalizeEvent,_r=mt.verifyEvent;function es(t){return t instanceof Uint8Array||ArrayBuffer.isView(t)&&t.constructor.name===\"Uint8Array\"}function Et(t){if(typeof t!=\"boolean\")throw new Error(`boolean expected, not ${t}`)}function vt(t){if(!Number.isSafeInteger(t)||t<0)throw new Error(\"positive integer expected, got \"+t)}function J(t,e,n=\"\"){let r=es(t),o=t?.length,s=e!==void 0;if(!r||s&&o!==e){let i=n&&`\"${n}\" `,c=s?` of length ${e}`:\"\",a=r?`length=${o}`:`type=${typeof t}`;throw new Error(i+\"expected Uint8Array\"+c+\", got \"+a)}return t}function cn(t,e=!0){if(t.destroyed)throw new Error(\"Hash instance has been destroyed\");if(e&&t.finished)throw new Error(\"Hash#digest() has already been called\")}function Ir(t,e){J(t,void 0,\"output\");let n=e.outputLen;if(t.length<n)throw new Error(\"digestInto() expects output buffer of length at least \"+n)}function ge(t){return new Uint32Array(t.buffer,t.byteOffset,Math.floor(t.byteLength/4))}function we(...t){for(let e=0;e<t.length;e++)t[e].fill(0)}function ts(t){return new DataView(t.buffer,t.byteOffset,t.byteLength)}var ns=new Uint8Array(new Uint32Array([287454020]).buffer)[0]===68;function Cr(t,e){if(e==null||typeof e!=\"object\")throw new Error(\"options must be defined\");return Object.assign(t,e)}function St(t,e){if(t.length!==e.length)return!1;let n=0;for(let r=0;r<t.length;r++)n|=t[r]^e[r];return n===0}var an=(t,e)=>{function n(r,...o){if(J(r,void 0,\"key\"),!ns)throw new Error(\"Non little-endian hardware is not yet supported\");if(t.nonceLength!==void 0){let u=o[0];J(u,t.varSizeNonce?void 0:t.nonceLength,\"nonce\")}let s=t.tagLength;s&&o[1]!==void 0&&J(o[1],void 0,\"AAD\");let i=e(r,...o),c=(u,d)=>{if(d!==void 0){if(u!==2)throw new Error(\"cipher output not supported\");J(d,void 0,\"output\")}},a=!1;return{encrypt(u,d){if(a)throw new Error(\"cannot encrypt() twice with same key + nonce\");return a=!0,J(u),c(i.encrypt.length,d),i.encrypt(u,d)},decrypt(u,d){if(J(u),s&&u.length<s)throw new Error('\"ciphertext\" expected length bigger than tagLength='+s);return c(i.decrypt.length,d),i.decrypt(u,d)}}}return Object.assign(n,t),n};function fn(t,e,n=!0){if(e===void 0)return new Uint8Array(t);if(e.length!==t)throw new Error('\"output\" expected Uint8Array of length '+t+\", got: \"+e.length);if(n&&!rs(e))throw new Error(\"invalid output, must be aligned\");return e}function Ur(t,e,n){Et(n);let r=new Uint8Array(16),o=ts(r);return o.setBigUint64(0,BigInt(e),n),o.setBigUint64(8,BigInt(t),n),r}function rs(t){return t.byteOffset%4===0}function Me(t){return Uint8Array.from(t)}var Nr=t=>Uint8Array.from(t.split(\"\"),e=>e.charCodeAt(0)),os=Nr(\"expand 16-byte k\"),ss=Nr(\"expand 32-byte k\"),is=ge(os),cs=ge(ss);function R(t,e){return t<<e|t>>>32-e}function ln(t){return t.byteOffset%4===0}var Bt=64,as=16,qr=2**32-1,Pr=Uint32Array.of();function fs(t,e,n,r,o,s,i,c){let a=o.length,l=new Uint8Array(Bt),u=ge(l),d=ln(o)&&ln(s),h=d?ge(o):Pr,b=d?ge(s):Pr;for(let x=0;x<a;i++){if(t(e,n,r,u,i,c),i>=qr)throw new Error(\"arx: counter overflow\");let w=Math.min(Bt,a-x);if(d&&w===Bt){let E=x/4;if(x%4!==0)throw new Error(\"arx: invalid block position\");for(let B=0,k;B<as;B++)k=E+B,b[k]=h[k]^u[B];x+=Bt;continue}for(let E=0,B;E<w;E++)B=x+E,s[B]=o[B]^l[E];x+=w}}function un(t,e){let{allowShortKeys:n,extendNonceFn:r,counterLength:o,counterRight:s,rounds:i}=Cr({allowShortKeys:!1,counterLength:8,counterRight:!1,rounds:20},e);if(typeof t!=\"function\")throw new Error(\"core must be a function\");return vt(o),vt(i),Et(s),Et(n),(c,a,l,u,d=0)=>{J(c,void 0,\"key\"),J(a,void 0,\"nonce\"),J(l,void 0,\"data\");let h=l.length;if(u===void 0&&(u=new Uint8Array(h)),J(u,void 0,\"output\"),vt(d),d<0||d>=qr)throw new Error(\"arx: counter overflow\");if(u.length<h)throw new Error(`arx: output (${u.length}) is shorter than data (${h})`);let b=[],x=c.length,w,E;if(x===32)b.push(w=Me(c)),E=cs;else if(x===16&&n)w=new Uint8Array(32),w.set(c),w.set(c,16),E=is,b.push(w);else throw J(c,32,\"arx key\"),new Error(\"invalid key size\");ln(a)||b.push(a=Me(a));let B=ge(w);if(r){if(a.length!==24)throw new Error(\"arx: extended nonce must be 24 bytes\");r(E,B,ge(a.subarray(0,16)),B),a=a.subarray(16)}let k=16-o;if(k!==a.length)throw new Error(`arx: nonce must be ${k} or 16 bytes`);if(k!==12){let N=new Uint8Array(12);N.set(a,s?0:12-a.length),a=N,b.push(a)}let U=ge(a);return fs(t,E,B,U,l,u,d,i),we(...b),u}}function Q(t,e){return t[e++]&255|(t[e++]&255)<<8}var hn=class{constructor(e){f(this,\"blockLen\",16);f(this,\"outputLen\",16);f(this,\"buffer\",new Uint8Array(16));f(this,\"r\",new Uint16Array(10));f(this,\"h\",new Uint16Array(10));f(this,\"pad\",new Uint16Array(8));f(this,\"pos\",0);f(this,\"finished\",!1);e=Me(J(e,32,\"key\"));let n=Q(e,0),r=Q(e,2),o=Q(e,4),s=Q(e,6),i=Q(e,8),c=Q(e,10),a=Q(e,12),l=Q(e,14);this.r[0]=n&8191,this.r[1]=(n>>>13|r<<3)&8191,this.r[2]=(r>>>10|o<<6)&7939,this.r[3]=(o>>>7|s<<9)&8191,this.r[4]=(s>>>4|i<<12)&255,this.r[5]=i>>>1&8190,this.r[6]=(i>>>14|c<<2)&8191,this.r[7]=(c>>>11|a<<5)&8065,this.r[8]=(a>>>8|l<<8)&8191,this.r[9]=l>>>5&127;for(let u=0;u<8;u++)this.pad[u]=Q(e,16+2*u)}process(e,n,r=!1){let o=r?0:2048,{h:s,r:i}=this,c=i[0],a=i[1],l=i[2],u=i[3],d=i[4],h=i[5],b=i[6],x=i[7],w=i[8],E=i[9],B=Q(e,n+0),k=Q(e,n+2),U=Q(e,n+4),N=Q(e,n+6),X=Q(e,n+8),M=Q(e,n+10),z=Q(e,n+12),K=Q(e,n+14),C=s[0]+(B&8191),$=s[1]+((B>>>13|k<<3)&8191),q=s[2]+((k>>>10|U<<6)&8191),O=s[3]+((U>>>7|N<<9)&8191),L=s[4]+((N>>>4|X<<12)&8191),y=s[5]+(X>>>1&8191),p=s[6]+((X>>>14|M<<2)&8191),m=s[7]+((M>>>11|z<<5)&8191),v=s[8]+((z>>>8|K<<8)&8191),A=s[9]+(K>>>5|o),g=0,T=g+C*c+$*(5*E)+q*(5*w)+O*(5*x)+L*(5*b);g=T>>>13,T&=8191,T+=y*(5*h)+p*(5*d)+m*(5*u)+v*(5*l)+A*(5*a),g+=T>>>13,T&=8191;let S=g+C*a+$*c+q*(5*E)+O*(5*w)+L*(5*x);g=S>>>13,S&=8191,S+=y*(5*b)+p*(5*h)+m*(5*d)+v*(5*u)+A*(5*l),g+=S>>>13,S&=8191;let H=g+C*l+$*a+q*c+O*(5*E)+L*(5*w);g=H>>>13,H&=8191,H+=y*(5*x)+p*(5*b)+m*(5*h)+v*(5*d)+A*(5*u),g+=H>>>13,H&=8191;let _=g+C*u+$*l+q*a+O*c+L*(5*E);g=_>>>13,_&=8191,_+=y*(5*w)+p*(5*x)+m*(5*b)+v*(5*h)+A*(5*d),g+=_>>>13,_&=8191;let V=g+C*d+$*u+q*l+O*a+L*c;g=V>>>13,V&=8191,V+=y*(5*E)+p*(5*w)+m*(5*x)+v*(5*b)+A*(5*h),g+=V>>>13,V&=8191;let D=g+C*h+$*d+q*u+O*l+L*a;g=D>>>13,D&=8191,D+=y*c+p*(5*E)+m*(5*w)+v*(5*x)+A*(5*b),g+=D>>>13,D&=8191;let I=g+C*b+$*h+q*d+O*u+L*l;g=I>>>13,I&=8191,I+=y*a+p*c+m*(5*E)+v*(5*w)+A*(5*x),g+=I>>>13,I&=8191;let W=g+C*x+$*b+q*h+O*d+L*u;g=W>>>13,W&=8191,W+=y*l+p*a+m*c+v*(5*E)+A*(5*w),g+=W>>>13,W&=8191;let j=g+C*w+$*x+q*b+O*h+L*d;g=j>>>13,j&=8191,j+=y*u+p*l+m*a+v*c+A*(5*E),g+=j>>>13,j&=8191;let Z=g+C*E+$*w+q*x+O*b+L*h;g=Z>>>13,Z&=8191,Z+=y*d+p*u+m*l+v*a+A*c,g+=Z>>>13,Z&=8191,g=(g<<2)+g|0,g=g+T|0,T=g&8191,g=g>>>13,S+=g,s[0]=T,s[1]=S,s[2]=H,s[3]=_,s[4]=V,s[5]=D,s[6]=I,s[7]=W,s[8]=j,s[9]=Z}finalize(){let{h:e,pad:n}=this,r=new Uint16Array(10),o=e[1]>>>13;e[1]&=8191;for(let c=2;c<10;c++)e[c]+=o,o=e[c]>>>13,e[c]&=8191;e[0]+=o*5,o=e[0]>>>13,e[0]&=8191,e[1]+=o,o=e[1]>>>13,e[1]&=8191,e[2]+=o,r[0]=e[0]+5,o=r[0]>>>13,r[0]&=8191;for(let c=1;c<10;c++)r[c]=e[c]+o,o=r[c]>>>13,r[c]&=8191;r[9]-=8192;let s=(o^1)-1;for(let c=0;c<10;c++)r[c]&=s;s=~s;for(let c=0;c<10;c++)e[c]=e[c]&s|r[c];e[0]=(e[0]|e[1]<<13)&65535,e[1]=(e[1]>>>3|e[2]<<10)&65535,e[2]=(e[2]>>>6|e[3]<<7)&65535,e[3]=(e[3]>>>9|e[4]<<4)&65535,e[4]=(e[4]>>>12|e[5]<<1|e[6]<<14)&65535,e[5]=(e[6]>>>2|e[7]<<11)&65535,e[6]=(e[7]>>>5|e[8]<<8)&65535,e[7]=(e[8]>>>8|e[9]<<5)&65535;let i=e[0]+n[0];e[0]=i&65535;for(let c=1;c<8;c++)i=(e[c]+n[c]|0)+(i>>>16)|0,e[c]=i&65535;we(r)}update(e){cn(this),J(e),e=Me(e);let{buffer:n,blockLen:r}=this,o=e.length;for(let s=0;s<o;){let i=Math.min(r-this.pos,o-s);if(i===r){for(;r<=o-s;s+=r)this.process(e,s);continue}n.set(e.subarray(s,s+i),this.pos),this.pos+=i,s+=i,this.pos===r&&(this.process(n,0,!1),this.pos=0)}return this}destroy(){we(this.h,this.r,this.buffer,this.pad)}digestInto(e){cn(this),Ir(e,this),this.finished=!0;let{buffer:n,h:r}=this,{pos:o}=this;if(o){for(n[o++]=1;o<16;o++)n[o]=0;this.process(n,0,!0)}this.finalize();let s=0;for(let i=0;i<8;i++)e[s++]=r[i]>>>0,e[s++]=r[i]>>>8;return e}digest(){let{buffer:e,outputLen:n}=this;this.digestInto(e);let r=e.slice(0,n);return this.destroy(),r}};function ls(t){let e=(r,o)=>t(o).update(r).digest(),n=t(new Uint8Array(32));return e.outputLen=n.outputLen,e.blockLen=n.blockLen,e.create=r=>t(r),e}var Dr=ls(t=>new hn(t));function Vr(t,e,n,r,o,s=20){let i=t[0],c=t[1],a=t[2],l=t[3],u=e[0],d=e[1],h=e[2],b=e[3],x=e[4],w=e[5],E=e[6],B=e[7],k=o,U=n[0],N=n[1],X=n[2],M=i,z=c,K=a,C=l,$=u,q=d,O=h,L=b,y=x,p=w,m=E,v=B,A=k,g=U,T=N,S=X;for(let _=0;_<s;_+=2)M=M+$|0,A=R(A^M,16),y=y+A|0,$=R($^y,12),M=M+$|0,A=R(A^M,8),y=y+A|0,$=R($^y,7),z=z+q|0,g=R(g^z,16),p=p+g|0,q=R(q^p,12),z=z+q|0,g=R(g^z,8),p=p+g|0,q=R(q^p,7),K=K+O|0,T=R(T^K,16),m=m+T|0,O=R(O^m,12),K=K+O|0,T=R(T^K,8),m=m+T|0,O=R(O^m,7),C=C+L|0,S=R(S^C,16),v=v+S|0,L=R(L^v,12),C=C+L|0,S=R(S^C,8),v=v+S|0,L=R(L^v,7),M=M+q|0,S=R(S^M,16),m=m+S|0,q=R(q^m,12),M=M+q|0,S=R(S^M,8),m=m+S|0,q=R(q^m,7),z=z+O|0,A=R(A^z,16),v=v+A|0,O=R(O^v,12),z=z+O|0,A=R(A^z,8),v=v+A|0,O=R(O^v,7),K=K+L|0,g=R(g^K,16),y=y+g|0,L=R(L^y,12),K=K+L|0,g=R(g^K,8),y=y+g|0,L=R(L^y,7),C=C+$|0,T=R(T^C,16),p=p+T|0,$=R($^p,12),C=C+$|0,T=R(T^C,8),p=p+T|0,$=R($^p,7);let H=0;r[H++]=i+M|0,r[H++]=c+z|0,r[H++]=a+K|0,r[H++]=l+C|0,r[H++]=u+$|0,r[H++]=d+q|0,r[H++]=h+O|0,r[H++]=b+L|0,r[H++]=x+y|0,r[H++]=w+p|0,r[H++]=E+m|0,r[H++]=B+v|0,r[H++]=k+A|0,r[H++]=U+g|0,r[H++]=N+T|0,r[H++]=X+S|0}function us(t,e,n,r){let o=t[0],s=t[1],i=t[2],c=t[3],a=e[0],l=e[1],u=e[2],d=e[3],h=e[4],b=e[5],x=e[6],w=e[7],E=n[0],B=n[1],k=n[2],U=n[3];for(let X=0;X<20;X+=2)o=o+a|0,E=R(E^o,16),h=h+E|0,a=R(a^h,12),o=o+a|0,E=R(E^o,8),h=h+E|0,a=R(a^h,7),s=s+l|0,B=R(B^s,16),b=b+B|0,l=R(l^b,12),s=s+l|0,B=R(B^s,8),b=b+B|0,l=R(l^b,7),i=i+u|0,k=R(k^i,16),x=x+k|0,u=R(u^x,12),i=i+u|0,k=R(k^i,8),x=x+k|0,u=R(u^x,7),c=c+d|0,U=R(U^c,16),w=w+U|0,d=R(d^w,12),c=c+d|0,U=R(U^c,8),w=w+U|0,d=R(d^w,7),o=o+l|0,U=R(U^o,16),x=x+U|0,l=R(l^x,12),o=o+l|0,U=R(U^o,8),x=x+U|0,l=R(l^x,7),s=s+u|0,E=R(E^s,16),w=w+E|0,u=R(u^w,12),s=s+u|0,E=R(E^s,8),w=w+E|0,u=R(u^w,7),i=i+d|0,B=R(B^i,16),h=h+B|0,d=R(d^h,12),i=i+d|0,B=R(B^i,8),h=h+B|0,d=R(d^h,7),c=c+a|0,k=R(k^c,16),b=b+k|0,a=R(a^b,12),c=c+a|0,k=R(k^c,8),b=b+k|0,a=R(a^b,7);let N=0;r[N++]=o,r[N++]=s,r[N++]=i,r[N++]=c,r[N++]=E,r[N++]=B,r[N++]=k,r[N++]=U}var At=un(Vr,{counterRight:!1,counterLength:4,allowShortKeys:!1}),hs=un(Vr,{counterRight:!1,counterLength:8,extendNonceFn:us,allowShortKeys:!1});var ds=new Uint8Array(16),$r=(t,e)=>{t.update(e);let n=e.length%16;n&&t.update(ds.subarray(n))},bs=new Uint8Array(32);function Mr(t,e,n,r,o){o!==void 0&&J(o,void 0,\"AAD\");let s=t(e,n,bs),i=Ur(r.length,o?o.length:0,!0),c=Dr.create(s);o&&$r(c,o),$r(c,r),c.update(i);let a=c.digest();return we(s,i),a}var Wr=t=>(e,n,r)=>({encrypt(s,i){let c=s.length;i=fn(c+16,i,!1),i.set(s);let a=i.subarray(0,-16);t(e,n,a,a,1);let l=Mr(t,e,n,a,r);return i.set(l,c),we(l),i},decrypt(s,i){i=fn(s.length-16,i,!1);let c=s.subarray(0,-16),a=s.subarray(-16),l=Mr(t,e,n,c,r);if(!St(a,l))throw new Error(\"invalid tag\");return i.set(s.subarray(0,-16)),t(e,n,i,i,1),we(l),i}}),jc=an({blockSize:64,nonceLength:12,tagLength:16},Wr(At)),Gc=an({blockSize:64,nonceLength:24,tagLength:16},Wr(hs));var Tt=class{constructor(e,n){f(this,\"oHash\");f(this,\"iHash\");f(this,\"blockLen\");f(this,\"outputLen\");f(this,\"finished\",!1);f(this,\"destroyed\",!1);if(et(e),le(n,void 0,\"key\"),this.iHash=e.create(),typeof this.iHash.update!=\"function\")throw new Error(\"Expected instance of class which extends utils.Hash\");this.blockLen=this.iHash.blockLen,this.outputLen=this.iHash.outputLen;let r=this.blockLen,o=new Uint8Array(r);o.set(n.length>r?e.create().update(n).digest():n);for(let s=0;s<o.length;s++)o[s]^=54;this.iHash.update(o),this.oHash=e.create();for(let s=0;s<o.length;s++)o[s]^=106;this.oHash.update(o),ye(o)}update(e){return qe(this),this.iHash.update(e),this}digestInto(e){qe(this),le(e,this.outputLen,\"output\"),this.finished=!0,this.iHash.digestInto(e),this.oHash.update(e),this.oHash.digestInto(e),this.destroy()}digest(){let e=new Uint8Array(this.oHash.outputLen);return this.digestInto(e),e}_cloneInto(e){e||(e=Object.create(Object.getPrototypeOf(this),{}));let{oHash:n,iHash:r,finished:o,destroyed:s,blockLen:i,outputLen:c}=this;return e=e,e.finished=o,e.destroyed=s,e.blockLen=i,e.outputLen=c,e.oHash=n._cloneInto(e.oHash),e.iHash=r._cloneInto(e.iHash),e}clone(){return this._cloneInto()}destroy(){this.destroyed=!0,this.oHash.destroy(),this.iHash.destroy()}},Ve=(t,e,n)=>new Tt(t,e).update(n).digest();Ve.create=(t,e)=>new Tt(t,e);function jr(t,e,n){return et(t),n===void 0&&(n=new Uint8Array(t.outputLen)),Ve(t,n,e)}var dn=Uint8Array.of(0),Kr=Uint8Array.of();function Gr(t,e,n,r=32){et(t),xt(r,\"length\");let o=t.outputLen;if(r>255*o)throw new Error(\"Length must be <= 255*HashLen\");let s=Math.ceil(r/o);n===void 0?n=Kr:le(n,void 0,\"info\");let i=new Uint8Array(s*o),c=Ve.create(t,e),a=c._cloneInto(),l=new Uint8Array(c.outputLen);for(let u=0;u<s;u++)dn[0]=u+1,a.update(u===0?Kr:l).update(n).update(dn).digestInto(l),i.set(l,o*u),c._cloneInto(a);return c.destroy(),a.destroy(),ye(l,dn),i.slice(0,r)}function xn(t){return t instanceof Uint8Array||ArrayBuffer.isView(t)&&t.constructor.name===\"Uint8Array\"}function ps(t){if(!xn(t))throw new Error(\"Uint8Array expected\")}function Yr(t,e){return Array.isArray(e)?e.length===0?!0:t?e.every(n=>typeof n==\"string\"):e.every(n=>Number.isSafeInteger(n)):!1}function ys(t){if(typeof t!=\"function\")throw new Error(\"function expected\");return!0}function Ie(t,e){if(typeof e!=\"string\")throw new Error(`${t}: string expected`);return!0}function gn(t){if(!Number.isSafeInteger(t))throw new Error(`invalid integer: ${t}`)}function bn(t){if(!Array.isArray(t))throw new Error(\"array expected\")}function Rt(t,e){if(!Yr(!0,e))throw new Error(`${t}: array of strings expected`)}function Jr(t,e){if(!Yr(!1,e))throw new Error(`${t}: array of numbers expected`)}function Xr(...t){let e=s=>s,n=(s,i)=>c=>s(i(c)),r=t.map(s=>s.encode).reduceRight(n,e),o=t.map(s=>s.decode).reduce(n,e);return{encode:r,decode:o}}function Qr(t){let e=typeof t==\"string\"?t.split(\"\"):t,n=e.length;Rt(\"alphabet\",e);let r=new Map(e.map((o,s)=>[o,s]));return{encode:o=>(bn(o),o.map(s=>{if(!Number.isSafeInteger(s)||s<0||s>=n)throw new Error(`alphabet.encode: digit index outside alphabet \"${s}\". Allowed: ${t}`);return e[s]})),decode:o=>(bn(o),o.map(s=>{Ie(\"alphabet.decode\",s);let i=r.get(s);if(i===void 0)throw new Error(`Unknown letter: \"${s}\". Allowed: ${t}`);return i}))}}function eo(t=\"\"){return Ie(\"join\",t),{encode:e=>(Rt(\"join.decode\",e),e.join(t)),decode:e=>(Ie(\"join.decode\",e),e.split(t))}}function xs(t,e=\"=\"){return gn(t),Ie(\"padding\",e),{encode(n){for(Rt(\"padding.encode\",n);n.length*t%8;)n.push(e);return n},decode(n){Rt(\"padding.decode\",n);let r=n.length;if(r*t%8)throw new Error(\"padding: invalid, string should have whole number of bytes\");for(;r>0&&n[r-1]===e;r--)if((r-1)*t%8===0)throw new Error(\"padding: invalid, string has too much padding\");return n.slice(0,r)}}}var to=(t,e)=>e===0?t:to(e,t%e),Lt=(t,e)=>t+(e-to(t,e)),Ht=(()=>{let t=[];for(let e=0;e<40;e++)t.push(2**e);return t})();function pn(t,e,n,r){if(bn(t),e<=0||e>32)throw new Error(`convertRadix2: wrong from=${e}`);if(n<=0||n>32)throw new Error(`convertRadix2: wrong to=${n}`);if(Lt(e,n)>32)throw new Error(`convertRadix2: carry overflow from=${e} to=${n} carryBits=${Lt(e,n)}`);let o=0,s=0,i=Ht[e],c=Ht[n]-1,a=[];for(let l of t){if(gn(l),l>=i)throw new Error(`convertRadix2: invalid data word=${l} from=${e}`);if(o=o<<e|l,s+e>32)throw new Error(`convertRadix2: carry overflow pos=${s} from=${e}`);for(s+=e;s>=n;s-=n)a.push((o>>s-n&c)>>>0);let u=Ht[s];if(u===void 0)throw new Error(\"invalid carry\");o&=u-1}if(o=o<<n-s&c,!r&&s>=e)throw new Error(\"Excess padding\");if(!r&&o>0)throw new Error(`Non-zero padding: ${o}`);return r&&s>0&&a.push(o>>>0),a}function no(t,e=!1){if(gn(t),t<=0||t>32)throw new Error(\"radix2: bits should be in (0..32]\");if(Lt(8,t)>32||Lt(t,8)>32)throw new Error(\"radix2: carry overflow\");return{encode:n=>{if(!xn(n))throw new Error(\"radix2.encode input should be Uint8Array\");return pn(Array.from(n),8,t,!e)},decode:n=>(Jr(\"radix2.decode\",n),Uint8Array.from(pn(n,t,8,e)))}}function zr(t){return ys(t),function(...e){try{return t.apply(null,e)}catch{}}}var gs=typeof Uint8Array.from([]).toBase64==\"function\"&&typeof Uint8Array.fromBase64==\"function\",ws=(t,e)=>{Ie(\"base64\",t);let n=e?/^[A-Za-z0-9=_-]+$/:/^[A-Za-z0-9=+/]+$/,r=e?\"base64url\":\"base64\";if(t.length>0&&!n.test(t))throw new Error(\"invalid base64\");return Uint8Array.fromBase64(t,{alphabet:r,lastChunkHandling:\"strict\"})},wn=gs?{encode(t){return ps(t),t.toBase64()},decode(t){return ws(t,!1)}}:Xr(no(6),Qr(\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"),xs(6),eo(\"\"));var yn=Xr(Qr(\"qpzry9x8gf2tvdw0s3jn54khce6mua7l\"),eo(\"\")),Zr=[996825010,642813549,513874426,1027748829,705979059];function tt(t){let e=t>>25,n=(t&33554431)<<5;for(let r=0;r<Zr.length;r++)(e>>r&1)===1&&(n^=Zr[r]);return n}function Fr(t,e,n=1){let r=t.length,o=1;for(let s=0;s<r;s++){let i=t.charCodeAt(s);if(i<33||i>126)throw new Error(`Invalid prefix (${t})`);o=tt(o)^i>>5}o=tt(o);for(let s=0;s<r;s++)o=tt(o)^t.charCodeAt(s)&31;for(let s of e)o=tt(o)^s;for(let s=0;s<6;s++)o=tt(o);return o^=n,yn.encode(pn([o%Ht[30]],30,5,!1))}function ms(t){let e=t===\"bech32\"?1:734539939,n=no(5),r=n.decode,o=n.encode,s=zr(r);function i(d,h,b=90){Ie(\"bech32.encode prefix\",d),xn(h)&&(h=Array.from(h)),Jr(\"bech32.encode\",h);let x=d.length;if(x===0)throw new TypeError(`Invalid prefix length ${x}`);let w=x+7+h.length;if(b!==!1&&w>b)throw new TypeError(`Length ${w} exceeds limit ${b}`);let E=d.toLowerCase(),B=Fr(E,h,e);return`${E}1${yn.encode(h)}${B}`}function c(d,h=90){Ie(\"bech32.decode input\",d);let b=d.length;if(b<8||h!==!1&&b>h)throw new TypeError(`invalid string length: ${b} (${d}). Expected (8..${h})`);let x=d.toLowerCase();if(d!==x&&d!==d.toUpperCase())throw new Error(\"String must be lowercase or uppercase\");let w=x.lastIndexOf(\"1\");if(w===0||w===-1)throw new Error('Letter \"1\" must be present between prefix and data only');let E=x.slice(0,w),B=x.slice(w+1);if(B.length<6)throw new Error(\"Data must be at least 6 characters long\");let k=yn.decode(B).slice(0,-6),U=Fr(E,k,e);if(!B.endsWith(U))throw new Error(`Invalid checksum in ${d}: expected \"${U}\"`);return{prefix:E,words:k}}let a=zr(c);function l(d){let{prefix:h,words:b}=c(d,!1);return{prefix:h,words:b,bytes:r(b)}}function u(d,h){return i(d,o(h))}return{encode:i,decode:c,encodeFromBytes:u,decodeToBytes:l,decodeUnsafe:a,fromWords:r,fromWordsUnsafe:s,toWords:o}}var mn=ms(\"bech32\");var Es=new TextDecoder(\"utf-8\"),An=new TextEncoder;function Ke(t){try{t.indexOf(\"://\")===-1&&(t=\"wss://\"+t);let e=new URL(t);return e.protocol===\"http:\"?e.protocol=\"ws:\":e.protocol===\"https:\"&&(e.protocol=\"wss:\"),e.pathname=e.pathname.replace(/\\/+/g,\"/\"),e.pathname.endsWith(\"/\")&&(e.pathname=e.pathname.slice(0,-1)),(e.port===\"80\"&&e.protocol===\"ws:\"||e.port===\"443\"&&e.protocol===\"wss:\")&&(e.port=\"\"),e.searchParams.sort(),e.hash=\"\",e.toString()}catch{throw new Error(`Invalid URL: ${t}`)}}function vs(t){if(t.length!==64)return!1;for(let e=0;e<64;e++){let n=t.charCodeAt(e);if(isNaN(n)||n<48||n>102||n>57&&n<97)return!1}return!0}var We=Symbol(\"verified\"),Ss=t=>t instanceof Object;function Bs(t){if(!Ss(t)||typeof t.kind!=\"number\"||typeof t.content!=\"string\"||typeof t.created_at!=\"number\"||typeof t.pubkey!=\"string\"||!vs(t.pubkey)||!Array.isArray(t.tags))return!1;for(let e=0;e<t.tags.length;e++){let n=t.tags[e];if(!Array.isArray(n))return!1;for(let r=0;r<n.length;r++)if(typeof n[r]!=\"string\")return!1}return!0}var As=class{generateSecretKey(){return Y.utils.randomSecretKey()}getPublicKey(t){return te(Y.getPublicKey(t))}finalizeEvent(t,e){let n=t;return n.pubkey=te(Y.getPublicKey(e)),n.id=En(n),n.sig=te(Y.sign(F(En(n)),e)),n[We]=!0,n}verifyEvent(t){if(typeof t[We]==\"boolean\")return t[We];try{let e=En(t);if(e!==t.id)return t[We]=!1,!1;let n=Y.verify(F(t.sig),F(e),F(t.pubkey));return t[We]=n,n}catch{return t[We]=!1,!1}}};function Ts(t){if(!Bs(t))throw new Error(\"can't serialize event with wrong or missing properties\");return JSON.stringify([0,t.pubkey,t.created_at,t.kind,t.tags,t.content])}function En(t){let e=ue(An.encode(Ts(t)));return te(e)}var kt=new As,oa=kt.generateSecretKey,ro=kt.getPublicKey,Hs=kt.finalizeEvent,so=kt.verifyEvent,Tn=1,Hn=4294967295,Rn=65536;function vn(t,e){let n=xr.getSharedSecret(t,F(\"02\"+e)).subarray(1,33);return jr(ue,n,An.encode(\"nip44-v2\"))}function io(t,e){let n=Gr(ue,t,e,76);return{chacha_key:n.subarray(0,32),chacha_nonce:n.subarray(32,44),hmac_key:n.subarray(44,76)}}function co(t){if(!Number.isSafeInteger(t)||t<1)throw new Error(\"expected positive integer\");if(t<=32)return 32;let e=2**(Math.floor(Math.log2(t-1))+1),n=e<=256?32:e/8;return n*(Math.floor((t-1)/n)+1)}function Rs(t){if(!Number.isSafeInteger(t)||t<Tn||t>65535)throw new Error(\"invalid plaintext size: must be between 1 and 65535 bytes\");let e=new Uint8Array(2);return new DataView(e.buffer).setUint16(0,t,!1),e}function Ls(t){if(!Number.isSafeInteger(t)||t<Rn||t>Hn)throw new Error(\"invalid plaintext size: must be between 65536 and 4294967295 bytes\");let e=new Uint8Array(4);return new DataView(e.buffer).setUint32(0,t,!1),e}function Os(t){let e=An.encode(t),n=e.length;if(n<Tn||n>Hn)throw new Error(\"invalid plaintext size: must be between 1 and 4294967295 bytes\");let r=n>=Rn?De(new Uint8Array([0,0]),Ls(n)):Rs(n),o=new Uint8Array(co(n)-n);return De(r,e,o)}function ks(t){let e=new DataView(t.buffer,t.byteOffset,t.byteLength),n=e.getUint16(0),r,o;if(n===0){if(r=e.getUint32(2),r<Rn)throw new Error(\"invalid padding\");o=6}else r=n,o=2;let s=t.subarray(o,o+r);if(r<Tn||r>Hn||s.length!==r||t.length!==o+co(r))throw new Error(\"invalid padding\");return Es.decode(s)}function ao(t,e,n){if(n.length!==32)throw new Error(\"AAD associated data must be 32 bytes\");let r=De(n,e);return Ve(ue,t,r)}function _s(t){if(typeof t!=\"string\")throw new Error(\"payload must be a valid string\");let e=t.length;if(e<132)throw new Error(\"invalid payload length: \"+e);if(t[0]===\"#\")throw new Error(\"unknown encryption version\");let n;try{n=wn.decode(t)}catch(s){throw new Error(\"invalid base64: \"+s.message)}let r=n.length;if(r<99)throw new Error(\"invalid data length: \"+r);let o=n[0];if(o!==2)throw new Error(\"unknown encryption version \"+o);return{nonce:n.subarray(1,33),ciphertext:n.subarray(33,-32),mac:n.subarray(-32)}}function Is(t,e,n=Ar(32)){let{chacha_key:r,chacha_nonce:o,hmac_key:s}=io(e,n),i=Os(t),c=At(r,o,i),a=ao(s,c,n);return wn.encode(De(new Uint8Array([2]),n,c,a))}function oo(t,e){let{nonce:n,ciphertext:r,mac:o}=_s(t),{chacha_key:s,chacha_nonce:i,hmac_key:c}=io(e,n),a=ao(c,r,n);if(!St(a,o))throw new Error(\"invalid MAC\");let l=At(s,i,r);return ks(l)}var Cs=/^(?:([\\w.+-]+)@)?([\\w_-]+(\\.[\\w_-]+)+)$/,Us;try{Us=fetch}catch{}var Ps=22242,Sn=24133;function Ns(t,e){if(t.ids&&t.ids.indexOf(e.id)===-1||t.kinds&&t.kinds.indexOf(e.kind)===-1||t.authors&&t.authors.indexOf(e.pubkey)===-1)return!1;for(let n in t)if(n[0]===\"#\"){let r=n.slice(1),o=t[`#${r}`];if(o&&!e.tags.find(([s,i])=>s===n.slice(1)&&o.indexOf(i)!==-1))return!1}return!(t.since&&e.created_at<t.since||t.until&&e.created_at>t.until)}function qs(t,e){for(let n=0;n<t.length;n++)if(Ns(t[n],e))return!0;return!1}function Ds(t,e){let n=e.length+3,r=t.indexOf(`\"${e}\":`)+n,o=t.slice(r).indexOf('\"')+r+1;return t.slice(o,o+64)}function $s(t){let e=t.slice(0,22).indexOf('\"EVENT\"');if(e===-1)return null;let n=t.slice(e+7+1).indexOf('\"');if(n===-1)return null;let r=e+7+1+n,o=t.slice(r+1,80).indexOf('\"');if(o===-1)return null;let s=r+1+o;return t.slice(r+1,s)}function Ms(t,e){return{kind:Ps,created_at:Math.floor(Date.now()/1e3),tags:[[\"relay\",t],[\"challenge\",e]],content:\"\"}}var Bn=class extends Error{constructor(t,e){super(`Tried to send message '${t} on a closed connection to ${e}.`),this.name=\"SendingOnClosedConnection\"}},fo=class{constructor(t,e){f(this,\"url\");f(this,\"_connected\",!1);f(this,\"onclose\",null);f(this,\"onnotice\",t=>console.debug(`NOTICE from ${this.url}: ${t}`));f(this,\"onauth\");f(this,\"baseEoseTimeout\",4400);f(this,\"publishTimeout\",4400);f(this,\"pingFrequency\",29e3);f(this,\"pingTimeout\",2e4);f(this,\"resubscribeBackoff\",[1e4,1e4,1e4,2e4,2e4,3e4,6e4]);f(this,\"openSubs\",new Map);f(this,\"enablePing\");f(this,\"enableReconnect\");f(this,\"idleTimeout\",0);f(this,\"idleSince\",Date.now());f(this,\"ongoingOperations\",0);f(this,\"reconnectTimeoutHandle\");f(this,\"pingIntervalHandle\");f(this,\"reconnectAttempts\",0);f(this,\"skipReconnection\",!1);f(this,\"idleTimeoutHandle\");f(this,\"connectionPromise\");f(this,\"openCountRequests\",new Map);f(this,\"openEventPublishes\",new Map);f(this,\"ws\");f(this,\"challenge\");f(this,\"authPromise\");f(this,\"serial\",0);f(this,\"verifyEvent\");f(this,\"_WebSocket\");this.url=Ke(t),this.verifyEvent=e.verifyEvent,this._WebSocket=e.websocketImplementation||WebSocket,this.enablePing=e.enablePing,this.enableReconnect=e.enableReconnect||!1,e.idleTimeout&&(this.idleTimeout=e.idleTimeout)}static async connect(t,e){let n=new fo(t,e);return await n.connect(e),n}closeAllSubscriptions(t){for(let[e,n]of this.openSubs)n.close(t);this.openSubs.clear();for(let[e,n]of this.openEventPublishes)n.reject(new Error(t));this.openEventPublishes.clear();for(let[e,n]of this.openCountRequests)n.reject(new Error(t));this.openCountRequests.clear()}get connected(){return this._connected}clearIdleTimeout(){this.idleTimeoutHandle&&(clearTimeout(this.idleTimeoutHandle),this.idleTimeoutHandle=void 0)}scheduleIdleClose(){this.clearIdleTimeout(),this.idleTimeout>0&&(this.idleTimeoutHandle=setTimeout(()=>{this.ongoingOperations===0&&this.idleSince&&this.close()},this.idleTimeout))}async reconnect(){let t=this.resubscribeBackoff[Math.min(this.reconnectAttempts,this.resubscribeBackoff.length-1)];this.reconnectAttempts++,this.reconnectTimeoutHandle=setTimeout(async()=>{try{await this.connect()}catch{}},t)}handleHardClose(t){this.ws&&(this.ws.onopen=null,this.ws.onerror=null,this.ws.onclose=null),this.pingIntervalHandle&&(clearInterval(this.pingIntervalHandle),this.pingIntervalHandle=void 0),this._connected=!1,this.connectionPromise=void 0,this.idleSince=void 0,this.clearIdleTimeout(),this.enableReconnect&&!this.skipReconnection?this.reconnect():(this.onclose?.(),this.closeAllSubscriptions(t))}async connect(t){let e;return this.connectionPromise?this.connectionPromise:(this.challenge=void 0,this.authPromise=void 0,this.skipReconnection=!1,this.connectionPromise=new Promise((n,r)=>{t?.timeout&&(e=setTimeout(()=>{r(\"connection timed out\"),this.connectionPromise=void 0,this.reconnectAttempts===0&&(this.skipReconnection=!0),this.handleHardClose(\"relay connection timed out\")},t.timeout)),t?.abort&&(t.abort.onabort=r);try{this.ws=new this._WebSocket(this.url)}catch(o){clearTimeout(e),r(o);return}this.ws.onopen=()=>{this.reconnectTimeoutHandle&&(clearTimeout(this.reconnectTimeoutHandle),this.reconnectTimeoutHandle=void 0),clearTimeout(e),this._connected=!0;let o=this.reconnectAttempts>0;this.reconnectAttempts=0;for(let s of this.openSubs.values()){if(s.eosed=!1,o)for(let i=0;i<s.filters.length;i++)s.lastEmitted&&(s.filters[i].since=s.lastEmitted+1);s.fire()}this.enablePing&&(this.pingIntervalHandle=setInterval(()=>this.pingpong(),this.pingFrequency)),n()},this.ws.onerror=()=>{clearTimeout(e),r(\"connection failed\"),this.connectionPromise=void 0,this.reconnectAttempts===0&&(this.skipReconnection=!0),this.handleHardClose(\"relay connection failed\")},this.ws.onclose=o=>{clearTimeout(e),r(o.message||\"websocket closed\"),this.handleHardClose(\"relay connection closed\")},this.ws.onmessage=this._onmessage.bind(this)}),this.connectionPromise)}waitForPingPong(){return new Promise(t=>{this.ws.once(\"pong\",()=>t(!0)),this.ws.ping()})}waitForDummyReq(){return new Promise((t,e)=>{if(!this.connectionPromise)return e(new Error(`no connection to ${this.url}, can't ping`));try{let n=this.subscribe([{ids:[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"],limit:0}],{label:\"<forced-ping>\",oneose:()=>{t(!0),n.close()},onclose(){t(!0)},eoseTimeout:this.pingTimeout+1e3})}catch(n){e(n)}})}async pingpong(){this.ws?.readyState===1&&(await Promise.any([this.ws&&this.ws.ping&&this.ws.once?this.waitForPingPong():this.waitForDummyReq(),new Promise(e=>setTimeout(()=>e(!1),this.pingTimeout))])||this.ws?.readyState===this._WebSocket.OPEN&&this.ws?.close())}async send(t){if(!this.connectionPromise)throw new Bn(t,this.url);this.connectionPromise.then(()=>{this.ws?.send(t)})}async auth(t){let e=this.challenge;if(!e)throw new Error(\"can't perform auth, no challenge was received\");return this.authPromise?this.authPromise:(this.authPromise=new Promise(async(n,r)=>{try{let o=await t(Ms(this.url,e)),s=setTimeout(()=>{let i=this.openEventPublishes.get(o.id);i&&(i.reject(new Error(\"auth timed out\")),this.openEventPublishes.delete(o.id))},this.publishTimeout);this.openEventPublishes.set(o.id,{resolve:n,reject:r,timeout:s}),this.send('[\"AUTH\",'+JSON.stringify(o)+\"]\")}catch(o){console.warn(\"subscribe auth function failed:\",o)}}),this.authPromise)}async publish(t){this.idleSince=void 0,this.clearIdleTimeout(),this.ongoingOperations++;let e=new Promise((n,r)=>{let o=setTimeout(()=>{let s=this.openEventPublishes.get(t.id);s&&(s.reject(new Error(\"publish timed out\")),this.openEventPublishes.delete(t.id))},this.publishTimeout);this.openEventPublishes.set(t.id,{resolve:n,reject:r,timeout:o})});try{await this.send('[\"EVENT\",'+JSON.stringify(t)+\"]\")}catch(n){let r=this.openEventPublishes.get(t.id);r&&(r.reject(n),this.openEventPublishes.delete(t.id))}return this.ongoingOperations--,this.ongoingOperations===0&&(this.idleSince=Date.now(),this.scheduleIdleClose()),e}async count(t,e){return(await this.countWithHLL(t,e)).count}async countWithHLL(t,e){this.serial++;let n=e?.id||\"count:\"+this.serial,r=new Promise((o,s)=>{this.openCountRequests.set(n,{resolve:o,reject:s})});try{await this.send('[\"COUNT\",\"'+n+'\",'+JSON.stringify(t).substring(1))}catch(o){let s=this.openCountRequests.get(n);s&&(s.reject(o),this.openCountRequests.delete(n))}return r}subscribe(t,e){e.label!==\"<forced-ping>\"&&(this.idleSince=void 0,this.clearIdleTimeout(),this.ongoingOperations++);let n=this.prepareSubscription(t,e);return n.fire(),e.abort&&(e.abort.onabort=()=>n.close(String(e.abort.reason||\"<aborted>\"))),n}prepareSubscription(t,e){this.serial++;let n=e.id||(e.label?e.label+\":\":\"sub:\")+this.serial,r=new Vs(this,n,t,e);return this.openSubs.set(n,r),r}close(){this.skipReconnection=!0,this.reconnectTimeoutHandle&&(clearTimeout(this.reconnectTimeoutHandle),this.reconnectTimeoutHandle=void 0),this.pingIntervalHandle&&(clearInterval(this.pingIntervalHandle),this.pingIntervalHandle=void 0),this.closeAllSubscriptions(\"relay connection closed by us\"),this._connected=!1,this.connectionPromise=void 0,this.idleSince=void 0,this.clearIdleTimeout(),this.onclose?.(),this.ws&&(this.ws.onopen=null,this.ws.onerror=null,this.ws.onclose=null,this.ws.readyState!==this._WebSocket.CLOSING&&this.ws.readyState!==this._WebSocket.CLOSED&&this.ws.close())}_onmessage(t){let e=t.data;if(!e)return;let n=$s(e);if(n){let r=this.openSubs.get(n);if(!r)return;let o=Ds(e,\"id\"),s=r.alreadyHaveEvent?.(o);if(r.receivedEvent?.(this,o),s)return}try{let r=JSON.parse(e);switch(r[0]){case\"EVENT\":{let o=this.openSubs.get(r[1]),s=r[2];qs(o.filters,s)&&this.verifyEvent(s,this.url)?o.onevent(s):o.oninvalidevent?.(s),(!o.lastEmitted||o.lastEmitted<s.created_at)&&(o.lastEmitted=s.created_at);return}case\"COUNT\":{let o=r[1],s=r[2],i=this.openCountRequests.get(o);i&&(i.resolve(s),this.openCountRequests.delete(o));return}case\"EOSE\":{let o=this.openSubs.get(r[1]);if(!o)return;o.receivedEose();return}case\"OK\":{let o=r[1],s=r[2],i=r[3],c=this.openEventPublishes.get(o);c&&(clearTimeout(c.timeout),s?c.resolve(i):c.reject(new Error(i)),this.openEventPublishes.delete(o));return}case\"CLOSED\":{let o=r[1],s=this.openSubs.get(o);if(!s){let i=this.openCountRequests.get(o);i&&(i.reject(new Error(r[2])),this.openCountRequests.delete(o));return}s.closed=!0,s.close(r[2]);return}case\"NOTICE\":{this.onnotice(r[1]);return}case\"AUTH\":{this.challenge=r[1],this.onauth&&this.auth(this.onauth).catch(o=>{if(!(o instanceof Bn))throw o});return}default:{this.openSubs.get(r[1])?.oncustom?.(r);return}}}catch(r){try{let[o,s,i]=JSON.parse(e);console.warn(`[nostr] relay ${this.url} error processing message:`,r,i)}catch{console.warn(`[nostr] relay ${this.url} error processing message:`,r)}return}}},Vs=class{constructor(t,e,n,r){f(this,\"relay\");f(this,\"id\");f(this,\"lastEmitted\");f(this,\"closed\",!1);f(this,\"eosed\",!1);f(this,\"filters\");f(this,\"alreadyHaveEvent\");f(this,\"receivedEvent\");f(this,\"onevent\");f(this,\"oninvalidevent\");f(this,\"oneose\");f(this,\"onclose\");f(this,\"oncustom\");f(this,\"eoseTimeout\");f(this,\"eoseTimeoutHandle\");if(n.length===0)throw new Error(\"subscription can't be created with zero filters\");this.relay=t,this.filters=n,this.id=e,this.alreadyHaveEvent=r.alreadyHaveEvent,this.receivedEvent=r.receivedEvent,this.eoseTimeout=r.eoseTimeout||t.baseEoseTimeout,this.oneose=r.oneose,this.onclose=r.onclose,this.oninvalidevent=r.oninvalidevent,this.onevent=r.onevent||(o=>{console.warn(`onevent() callback not defined for subscription '${this.id}' in relay ${this.relay.url}. event received:`,o)})}fire(){this.relay.send('[\"REQ\",\"'+this.id+'\",'+JSON.stringify(this.filters).substring(1)),this.eoseTimeoutHandle=setTimeout(this.receivedEose.bind(this),this.eoseTimeout)}receivedEose(){this.eosed||(clearTimeout(this.eoseTimeoutHandle),this.eosed=!0,this.oneose?.())}close(t=\"closed by caller\"){if(!this.closed&&this.relay.connected){try{this.relay.send('[\"CLOSE\",'+JSON.stringify(this.id)+\"]\")}catch(e){if(!(e instanceof Bn))throw e}this.closed=!0}this.relay.openSubs.delete(this.id),this.id.startsWith(\"<forced-ping>\")||(this.relay.ongoingOperations--,this.relay.ongoingOperations===0&&(this.relay.idleSince=Date.now(),this.relay.scheduleIdleClose())),this.onclose?.(t)}},me=256,Ws=me*2,da=new TextEncoder;function Ks(t,e){switch(e){case\"reactions\":return{\"#e\":[t],kinds:[7]};case\"reposts\":return{\"#e\":[t],kinds:[6]};case\"quotes\":return{\"#q\":[t],kinds:[1,1111]};case\"replies\":return{\"#e\":[t],kinds:[1]};case\"comments\":return{\"#E\":[t],kinds:[1111]};case\"followers\":return{\"#p\":[t],kinds:[3]}}}function js(){return new Uint8Array(me)}function Gs(t){if(t.length!==Ws||!/^[0-9a-f]+$/.test(t))return;let e=new Uint8Array(me);for(let n=0;n<me;n++)e[n]=parseInt(t.slice(n*2,n*2+2),16);return e}function zs(t){if(t.length!==me)throw new Error(`invalid number of registers ${t.length}`);let e=\"\";for(let n=0;n<me;n++)e+=t[n].toString(16).padStart(2,\"0\");return e}function Zs(t,e){if(t.length===0&&(t=js()),t.length!==me)throw new Error(`invalid number of registers ${t.length}`);if(e.length!==me)throw new Error(`invalid number of registers ${e.length}`);for(let n=0;n<me;n++)e[n]>t[n]&&(t[n]=e[n]);return t}var Fs=class{constructor(t){f(this,\"relays\",new Map);f(this,\"seenOn\",new Map);f(this,\"trackRelays\",!1);f(this,\"verifyEvent\");f(this,\"enablePing\");f(this,\"enableReconnect\");f(this,\"idleTimeout\",2e4);f(this,\"automaticallyAuth\");f(this,\"onRelayConnectionFailure\");f(this,\"onRelayConnectionSuccess\");f(this,\"allowConnectingToRelay\");f(this,\"maxWaitForConnection\");f(this,\"_WebSocket\");this.verifyEvent=t.verifyEvent,this._WebSocket=t.websocketImplementation,this.enablePing=t.enablePing,this.enableReconnect=t.enableReconnect||!1,t.idleTimeout&&(this.idleTimeout=t.idleTimeout),this.automaticallyAuth=t.automaticallyAuth,this.onRelayConnectionFailure=t.onRelayConnectionFailure,this.onRelayConnectionSuccess=t.onRelayConnectionSuccess,this.allowConnectingToRelay=t.allowConnectingToRelay,this.maxWaitForConnection=t.maxWaitForConnection||3e3}async ensureRelay(t,e){t=Ke(t);let n=this.relays.get(t);if(n||(n=new fo(t,{verifyEvent:this.verifyEvent,websocketImplementation:this._WebSocket,enablePing:this.enablePing,enableReconnect:this.enableReconnect,idleTimeout:this.idleTimeout}),n.onclose=()=>{this.relays.delete(t)},this.relays.set(t,n)),this.automaticallyAuth){let r=this.automaticallyAuth(t);r&&(n.onauth=r)}try{await n.connect({timeout:e?.connectionTimeout,abort:e?.abort})}catch(r){throw this.relays.delete(t),r}return n}close(t){t.map(Ke).forEach(e=>{this.relays.get(e)?.close(),this.relays.delete(e)})}subscribe(t,e,n){let r=[],o=[];for(let s=0;s<t.length;s++){let i=Ke(t[s]);r.find(c=>c.url===i)||o.indexOf(i)===-1&&(o.push(i),r.push({url:i,filter:e}))}return this.subscribeMap(r,n)}subscribeMany(t,e,n){return this.subscribe(t,e,n)}subscribeMap(t,e){let n=new Map;for(let h of t){let{url:b,filter:x}=h;n.has(b)||n.set(b,[]),n.get(b).push(x)}let r=Array.from(n.entries()).map(([h,b])=>({url:h,filters:b}));this.trackRelays&&(e.receivedEvent=(h,b)=>{let x=this.seenOn.get(b);x||(x=new Set,this.seenOn.set(b,x)),x.add(h)});let o=new Set,s=[],i=[],c=h=>{i[h]||(i[h]=!0,i.filter(b=>b).length===r.length&&(e.oneose?.(),c=()=>{}))},a=[],l=(h,b,x)=>{a[h]||(c(h),a[h]={url:b,reason:x},a.filter(w=>w).length===r.length&&(e.onclose?.(a),l=()=>{}))},u=h=>{if(e.alreadyHaveEvent?.(h))return!0;let b=o.has(h);return o.add(h),b},d=Promise.all(r.map(async({url:h,filters:b},x)=>{if(this.allowConnectingToRelay?.(h,[\"read\",b])===!1){l(x,h,\"connection skipped by allowConnectingToRelay\");return}let w;try{w=await this.ensureRelay(h,{connectionTimeout:this.maxWaitForConnection<(e.maxWait||0)?Math.max(e.maxWait*.8,e.maxWait-1e3):this.maxWaitForConnection,abort:e.abort})}catch(B){this.onRelayConnectionFailure?.(h),l(x,h,B?.message||String(B));return}this.onRelayConnectionSuccess?.(h);let E=w.subscribe(b,{...e,oneose:()=>c(x),onclose:B=>{B.startsWith(\"auth-required: \")&&e.onauth?w.auth(e.onauth).then(()=>{w.subscribe(b,{...e,oneose:()=>c(x),onclose:k=>{l(x,h,k)},alreadyHaveEvent:u,eoseTimeout:e.maxWait,abort:e.abort})}).catch(k=>{l(x,h,`auth was required and attempted, but failed with: ${k}`)}):l(x,h,B)},alreadyHaveEvent:u,eoseTimeout:e.maxWait,abort:e.abort});s.push(E)}));return{async close(h){await d,s.forEach(b=>{b.close(h)})}}}subscribeEose(t,e,n){let r;return r=this.subscribe(t,e,{...n,oneose(){let o=\"closed automatically on eose\";r?r.close(o):n.onclose?.(t.map(s=>({url:s,reason:o})))}}),r}subscribeManyEose(t,e,n){return this.subscribeEose(t,e,n)}async querySync(t,e,n){return new Promise(async r=>{let o=[];this.subscribeEose(t,e,{...n,onevent(s){o.push(s)},onclose(s){r(o)}})})}async get(t,e,n){e.limit=1;let r=await this.querySync(t,e,n);return r.sort((o,s)=>s.created_at-o.created_at),r[0]||null}async countMany(t,e,n,r){let o=Ks(e,n),s=[];for(let l=0;l<t.length;l++){let u=Ke(t[l]);s.indexOf(u)===-1&&s.push(u)}let i=await Promise.all(s.map(async l=>{if(this.allowConnectingToRelay?.(l,[\"read\",[o]])===!1)return null;let u;try{u=await this.ensureRelay(l,{connectionTimeout:this.maxWaitForConnection<(r?.maxWait||0)?Math.max(r.maxWait*.8,r.maxWait-1e3):this.maxWaitForConnection,abort:r?.abort})}catch{return this.onRelayConnectionFailure?.(l),null}return this.onRelayConnectionSuccess?.(l),u.countWithHLL([o],{id:r?.id}).catch(()=>null)})),c=0,a;for(let l of i){if(!l||(l.count>c&&(c=l.count),!l.hll||l.hll.length!==512))continue;let u=Gs(l.hll);u&&(a=Zs(a||new Uint8Array(0),u))}return a?{count:c,hll:zs(a)}:{count:c}}publish(t,e,n){return t.map(Ke).map(async(r,o,s)=>{if(s.indexOf(r)!==o)return Promise.reject(\"duplicate url\");if(this.allowConnectingToRelay?.(r,[\"write\",e])===!1)return Promise.reject(\"connection skipped by allowConnectingToRelay\");let i;try{i=await this.ensureRelay(r,{connectionTimeout:this.maxWaitForConnection<(n?.maxWait||0)?Math.max(n.maxWait*.8,n.maxWait-1e3):this.maxWaitForConnection,abort:n?.abort})}catch(c){return this.onRelayConnectionFailure?.(r),Promise.reject(\"connection failure: \"+String(c))}return i.publish(e).catch(async c=>{if(c instanceof Error&&c.message.startsWith(\"auth-required: \")&&n?.onauth)return await i.auth(n.onauth),i.publish(e);throw c}).then(c=>{if(this.trackRelays){let a=this.seenOn.get(e.id);a||(a=new Set,this.seenOn.set(e.id,a)),a.add(i)}return c})})}listConnectionStatus(){let t=new Map;return this.relays.forEach((e,n)=>t.set(n,e.connected)),t}destroy(){this.relays.forEach(t=>t.close()),this.relays=new Map}pruneIdleRelays(t=1e4){let e=[];for(let[n,r]of this.relays)r.idleSince&&Date.now()-r.idleSince>=t&&(this.relays.delete(n),e.push(n),r.close());return e}},lo;try{lo=WebSocket}catch{}var Ys=class extends Fs{constructor(t){super({verifyEvent:so,websocketImplementation:lo,maxWaitForConnection:3e3,...t})}},uo;try{uo=fetch}catch{}var Js=/^bunker:\\/\\/([0-9a-f]{64})\\??([?\\/\\w:.=&%-]*)$/;async function ho(t){let e=t.match(Js);if(e)try{let n=e[1],r=new URLSearchParams(e[2]);return{pubkey:n,relays:r.getAll(\"relay\"),secret:r.get(\"secret\")}}catch{}return Xs(t)}async function Xs(t){let e=t.match(Cs);if(!e)return null;let[n,r=\"_\",o]=e;try{let s=`https://${o}/.well-known/nostr.json?name=${r}`,i=await(await uo(s,{redirect:\"error\"})).json(),c=i.names[r],a=i.nip46[c]||[];return{pubkey:c,relays:a,secret:null}}catch{return null}}function bo(t){let e=new URLSearchParams;return t.relays.forEach(n=>{e.append(\"relay\",n)}),e.append(\"secret\",t.secret),t.perms&&t.perms.length>0&&e.append(\"perms\",t.perms.join(\",\")),t.name&&e.append(\"name\",t.name),t.url&&e.append(\"url\",t.url),t.image&&e.append(\"image\",t.image),`nostrconnect://${t.clientPubkey}?${e.toString()}`}var Ot=class{constructor(t,e){f(this,\"params\");f(this,\"pool\");f(this,\"subCloser\");f(this,\"isOpen\");f(this,\"serial\");f(this,\"idPrefix\");f(this,\"listeners\");f(this,\"waitingForAuth\");f(this,\"secretKey\");f(this,\"conversationKey\");f(this,\"bp\");f(this,\"cachedPubKey\");this.params=e,this.pool=e.pool||new Ys,this.secretKey=t,this.isOpen=!1,this.idPrefix=Math.random().toString(36).substring(7),this.serial=0,this.listeners={},this.waitingForAuth={}}static fromBunker(t,e,n={}){if(e.relays.length===0)throw new Error(\"no relays specified for this bunker\");let r=new Ot(t,n);return r.conversationKey=vn(t,e.pubkey),r.bp=e,r.setupSubscription(),r}static async fromURI(t,e,n={},r=3e5){let o=new URL(e),s=o.searchParams.get(\"secret\");if(!s)throw new Error(\"nostrconnect:// URI has no secret\");let i=new Ot(t,n),c=ro(t);return new Promise((a,l)=>{let u=!1,d=i.pool.subscribe(o.searchParams.getAll(\"relay\"),{kinds:[Sn],\"#p\":[c],limit:0},{onevent:async h=>{try{let b=vn(t,h.pubkey),x=oo(h.content,b);JSON.parse(x).result===s&&(d.close(),i.bp={pubkey:h.pubkey,relays:o.searchParams.getAll(\"relay\"),secret:s},i.conversationKey=vn(t,h.pubkey),i.setupSubscription(),u=!0,n.skipSwitchRelays||await Promise.race([new Promise(E=>setTimeout(E,1e3)),i.switchRelays()]),a(i))}catch(b){console.warn(\"failed to process potential connection event\",b)}},onclose:()=>{u||l(new Error(\"subscription closed before connection was established.\"))},maxWait:typeof r==\"number\"?r:void 0,abort:typeof r!=\"number\"?r:void 0})})}setupSubscription(){let t=this.listeners,e=this.waitingForAuth,n=this.conversationKey;this.subCloser=this.pool.subscribe(this.bp.relays,{kinds:[Sn],authors:[this.bp.pubkey],\"#p\":[ro(this.secretKey)],limit:0},{onevent:async r=>{let o=JSON.parse(oo(r.content,n)),{id:s,result:i,error:c}=o;if(i===\"auth_url\"&&e[s]){delete e[s],this.params.onauth?this.params.onauth(c):console.warn(`nostr-tools/nip46: remote signer ${this.bp.pubkey} tried to send an \"auth_url\"='${c}' but there was no onauth() callback configured.`);return}let a=t[s];a&&(c?a.reject(c):i&&a.resolve(i),delete t[s])},onclose:()=>{this.subCloser=void 0}}),this.isOpen=!0}async switchRelays(){try{let t=await this.sendRequest(\"switch_relays\",[]),e=JSON.parse(t);if(!e||JSON.stringify(e.sort())===JSON.stringify(this.bp.relays))return!1;this.bp.relays=e;let n=this.subCloser;return setTimeout(()=>{n.close()},5e3),this.subCloser=void 0,this.setupSubscription(),!0}catch{return!1}}async close(){this.isOpen=!1,this.subCloser?.close(),this.subCloser=void 0}async sendRequest(t,e){return new Promise(async(n,r)=>{try{if(!this.isOpen)throw new Error(\"this signer is not open anymore, create a new one\");this.subCloser||this.setupSubscription(),this.serial++;let o=`${this.idPrefix}-${this.serial}`,s=Is(JSON.stringify({id:o,method:t,params:e}),this.conversationKey),i=Hs({kind:Sn,tags:[[\"p\",this.bp.pubkey]],content:s,created_at:Math.floor(Date.now()/1e3)},this.secretKey);this.listeners[o]={resolve:n,reject:r},this.waitingForAuth[o]=!0,await Promise.any(this.pool.publish(this.bp.relays,i))}catch(o){r(o)}})}async ping(){let t=await this.sendRequest(\"ping\",[]);if(t!==\"pong\")throw new Error(`result is not pong: ${t}`)}async connect(t){let e=[this.bp.pubkey,this.bp.secret||\"\"];t&&(e.push(\"\"),e.push(JSON.stringify(t))),await this.sendRequest(\"connect\",e)}async logout(){let t=await this.sendRequest(\"logout\",[]);if(t!==\"ack\")throw new Error(`result is not ack: ${t}`);await this.close()}async getPublicKey(){return this.cachedPubKey||(this.cachedPubKey=await this.sendRequest(\"get_public_key\",[])),this.cachedPubKey}async signEvent(t){let e=await this.sendRequest(\"sign_event\",[JSON.stringify(t)]),n=JSON.parse(e);if(so(n))return n;throw new Error(`event returned from bunker is improperly signed: ${JSON.stringify(n)}`)}async nip04Encrypt(t,e){return await this.sendRequest(\"nip04_encrypt\",[t,e])}async nip04Decrypt(t,e){return await this.sendRequest(\"nip04_decrypt\",[t,e])}async nip44Encrypt(t,e){return await this.sendRequest(\"nip44_encrypt\",[t,e])}async nip44Decrypt(t,e){return await this.sendRequest(\"nip44_decrypt\",[t,e])}};var Aa=new TextDecoder(\"utf-8\"),Qs=new TextEncoder;function Ge(t){try{t.indexOf(\"://\")===-1&&(t=\"wss://\"+t);let e=new URL(t);return e.protocol===\"http:\"?e.protocol=\"ws:\":e.protocol===\"https:\"&&(e.protocol=\"wss:\"),e.pathname=e.pathname.replace(/\\/+/g,\"/\"),e.pathname.endsWith(\"/\")&&(e.pathname=e.pathname.slice(0,-1)),(e.port===\"80\"&&e.protocol===\"ws:\"||e.port===\"443\"&&e.protocol===\"wss:\")&&(e.port=\"\"),e.searchParams.sort(),e.hash=\"\",e.toString()}catch{throw new Error(`Invalid URL: ${t}`)}}function ei(t){if(t.length!==64)return!1;for(let e=0;e<64;e++){let n=t.charCodeAt(e);if(isNaN(n)||n<48||n>102||n>57&&n<97)return!1}return!0}var je=Symbol(\"verified\"),ti=t=>t instanceof Object;function ni(t){if(!ti(t)||typeof t.kind!=\"number\"||typeof t.content!=\"string\"||typeof t.created_at!=\"number\"||typeof t.pubkey!=\"string\"||!ei(t.pubkey)||!Array.isArray(t.tags))return!1;for(let e=0;e<t.tags.length;e++){let n=t.tags[e];if(!Array.isArray(n))return!1;for(let r=0;r<n.length;r++)if(typeof n[r]!=\"string\")return!1}return!0}var ri=class{generateSecretKey(){return Y.utils.randomSecretKey()}getPublicKey(t){return te(Y.getPublicKey(t))}finalizeEvent(t,e){let n=t;return n.pubkey=te(Y.getPublicKey(e)),n.id=Ln(n),n.sig=te(Y.sign(F(Ln(n)),e)),n[je]=!0,n}verifyEvent(t){if(typeof t[je]==\"boolean\")return t[je];try{let e=Ln(t);if(e!==t.id)return t[je]=!1,!1;let n=Y.verify(F(t.sig),F(e),F(t.pubkey));return t[je]=n,n}catch{return t[je]=!1,!1}}};function oi(t){if(!ni(t))throw new Error(\"can't serialize event with wrong or missing properties\");return JSON.stringify([0,t.pubkey,t.created_at,t.kind,t.tags,t.content])}function Ln(t){let e=ue(Qs.encode(oi(t)));return te(e)}var _t=new ri,Ha=_t.generateSecretKey,Ra=_t.getPublicKey,La=_t.finalizeEvent,si=_t.verifyEvent,ii=22242;function ci(t,e){if(t.ids&&t.ids.indexOf(e.id)===-1||t.kinds&&t.kinds.indexOf(e.kind)===-1||t.authors&&t.authors.indexOf(e.pubkey)===-1)return!1;for(let n in t)if(n[0]===\"#\"){let r=n.slice(1),o=t[`#${r}`];if(o&&!e.tags.find(([s,i])=>s===n.slice(1)&&o.indexOf(i)!==-1))return!1}return!(t.since&&e.created_at<t.since||t.until&&e.created_at>t.until)}function ai(t,e){for(let n=0;n<t.length;n++)if(ci(t[n],e))return!0;return!1}function fi(t,e){let n=e.length+3,r=t.indexOf(`\"${e}\":`)+n,o=t.slice(r).indexOf('\"')+r+1;return t.slice(o,o+64)}function li(t){let e=t.slice(0,22).indexOf('\"EVENT\"');if(e===-1)return null;let n=t.slice(e+7+1).indexOf('\"');if(n===-1)return null;let r=e+7+1+n,o=t.slice(r+1,80).indexOf('\"');if(o===-1)return null;let s=r+1+o;return t.slice(r+1,s)}function ui(t,e){return{kind:ii,created_at:Math.floor(Date.now()/1e3),tags:[[\"relay\",t],[\"challenge\",e]],content:\"\"}}var On=class extends Error{constructor(t,e){super(`Tried to send message '${t} on a closed connection to ${e}.`),this.name=\"SendingOnClosedConnection\"}},po=class{constructor(t,e){f(this,\"url\");f(this,\"_connected\",!1);f(this,\"onclose\",null);f(this,\"onnotice\",t=>console.debug(`NOTICE from ${this.url}: ${t}`));f(this,\"onauth\");f(this,\"baseEoseTimeout\",4400);f(this,\"publishTimeout\",4400);f(this,\"pingFrequency\",29e3);f(this,\"pingTimeout\",2e4);f(this,\"resubscribeBackoff\",[1e4,1e4,1e4,2e4,2e4,3e4,6e4]);f(this,\"openSubs\",new Map);f(this,\"enablePing\");f(this,\"enableReconnect\");f(this,\"idleTimeout\",0);f(this,\"idleSince\",Date.now());f(this,\"ongoingOperations\",0);f(this,\"reconnectTimeoutHandle\");f(this,\"pingIntervalHandle\");f(this,\"reconnectAttempts\",0);f(this,\"skipReconnection\",!1);f(this,\"idleTimeoutHandle\");f(this,\"connectionPromise\");f(this,\"openCountRequests\",new Map);f(this,\"openEventPublishes\",new Map);f(this,\"ws\");f(this,\"challenge\");f(this,\"authPromise\");f(this,\"serial\",0);f(this,\"verifyEvent\");f(this,\"_WebSocket\");this.url=Ge(t),this.verifyEvent=e.verifyEvent,this._WebSocket=e.websocketImplementation||WebSocket,this.enablePing=e.enablePing,this.enableReconnect=e.enableReconnect||!1,e.idleTimeout&&(this.idleTimeout=e.idleTimeout)}static async connect(t,e){let n=new po(t,e);return await n.connect(e),n}closeAllSubscriptions(t){for(let[e,n]of this.openSubs)n.close(t);this.openSubs.clear();for(let[e,n]of this.openEventPublishes)n.reject(new Error(t));this.openEventPublishes.clear();for(let[e,n]of this.openCountRequests)n.reject(new Error(t));this.openCountRequests.clear()}get connected(){return this._connected}clearIdleTimeout(){this.idleTimeoutHandle&&(clearTimeout(this.idleTimeoutHandle),this.idleTimeoutHandle=void 0)}scheduleIdleClose(){this.clearIdleTimeout(),this.idleTimeout>0&&(this.idleTimeoutHandle=setTimeout(()=>{this.ongoingOperations===0&&this.idleSince&&this.close()},this.idleTimeout))}async reconnect(){let t=this.resubscribeBackoff[Math.min(this.reconnectAttempts,this.resubscribeBackoff.length-1)];this.reconnectAttempts++,this.reconnectTimeoutHandle=setTimeout(async()=>{try{await this.connect()}catch{}},t)}handleHardClose(t){this.ws&&(this.ws.onopen=null,this.ws.onerror=null,this.ws.onclose=null),this.pingIntervalHandle&&(clearInterval(this.pingIntervalHandle),this.pingIntervalHandle=void 0),this._connected=!1,this.connectionPromise=void 0,this.idleSince=void 0,this.clearIdleTimeout(),this.enableReconnect&&!this.skipReconnection?this.reconnect():(this.onclose?.(),this.closeAllSubscriptions(t))}async connect(t){let e;return this.connectionPromise?this.connectionPromise:(this.challenge=void 0,this.authPromise=void 0,this.skipReconnection=!1,this.connectionPromise=new Promise((n,r)=>{t?.timeout&&(e=setTimeout(()=>{r(\"connection timed out\"),this.connectionPromise=void 0,this.reconnectAttempts===0&&(this.skipReconnection=!0),this.handleHardClose(\"relay connection timed out\")},t.timeout)),t?.abort&&(t.abort.onabort=r);try{this.ws=new this._WebSocket(this.url)}catch(o){clearTimeout(e),r(o);return}this.ws.onopen=()=>{this.reconnectTimeoutHandle&&(clearTimeout(this.reconnectTimeoutHandle),this.reconnectTimeoutHandle=void 0),clearTimeout(e),this._connected=!0;let o=this.reconnectAttempts>0;this.reconnectAttempts=0;for(let s of this.openSubs.values()){if(s.eosed=!1,o)for(let i=0;i<s.filters.length;i++)s.lastEmitted&&(s.filters[i].since=s.lastEmitted+1);s.fire()}this.enablePing&&(this.pingIntervalHandle=setInterval(()=>this.pingpong(),this.pingFrequency)),n()},this.ws.onerror=()=>{clearTimeout(e),r(\"connection failed\"),this.connectionPromise=void 0,this.reconnectAttempts===0&&(this.skipReconnection=!0),this.handleHardClose(\"relay connection failed\")},this.ws.onclose=o=>{clearTimeout(e),r(o.message||\"websocket closed\"),this.handleHardClose(\"relay connection closed\")},this.ws.onmessage=this._onmessage.bind(this)}),this.connectionPromise)}waitForPingPong(){return new Promise(t=>{this.ws.once(\"pong\",()=>t(!0)),this.ws.ping()})}waitForDummyReq(){return new Promise((t,e)=>{if(!this.connectionPromise)return e(new Error(`no connection to ${this.url}, can't ping`));try{let n=this.subscribe([{ids:[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"],limit:0}],{label:\"<forced-ping>\",oneose:()=>{t(!0),n.close()},onclose(){t(!0)},eoseTimeout:this.pingTimeout+1e3})}catch(n){e(n)}})}async pingpong(){this.ws?.readyState===1&&(await Promise.any([this.ws&&this.ws.ping&&this.ws.once?this.waitForPingPong():this.waitForDummyReq(),new Promise(e=>setTimeout(()=>e(!1),this.pingTimeout))])||this.ws?.readyState===this._WebSocket.OPEN&&this.ws?.close())}async send(t){if(!this.connectionPromise)throw new On(t,this.url);this.connectionPromise.then(()=>{this.ws?.send(t)})}async auth(t){let e=this.challenge;if(!e)throw new Error(\"can't perform auth, no challenge was received\");return this.authPromise?this.authPromise:(this.authPromise=new Promise(async(n,r)=>{try{let o=await t(ui(this.url,e)),s=setTimeout(()=>{let i=this.openEventPublishes.get(o.id);i&&(i.reject(new Error(\"auth timed out\")),this.openEventPublishes.delete(o.id))},this.publishTimeout);this.openEventPublishes.set(o.id,{resolve:n,reject:r,timeout:s}),this.send('[\"AUTH\",'+JSON.stringify(o)+\"]\")}catch(o){console.warn(\"subscribe auth function failed:\",o)}}),this.authPromise)}async publish(t){this.idleSince=void 0,this.clearIdleTimeout(),this.ongoingOperations++;let e=new Promise((n,r)=>{let o=setTimeout(()=>{let s=this.openEventPublishes.get(t.id);s&&(s.reject(new Error(\"publish timed out\")),this.openEventPublishes.delete(t.id))},this.publishTimeout);this.openEventPublishes.set(t.id,{resolve:n,reject:r,timeout:o})});try{await this.send('[\"EVENT\",'+JSON.stringify(t)+\"]\")}catch(n){let r=this.openEventPublishes.get(t.id);r&&(r.reject(n),this.openEventPublishes.delete(t.id))}return this.ongoingOperations--,this.ongoingOperations===0&&(this.idleSince=Date.now(),this.scheduleIdleClose()),e}async count(t,e){return(await this.countWithHLL(t,e)).count}async countWithHLL(t,e){this.serial++;let n=e?.id||\"count:\"+this.serial,r=new Promise((o,s)=>{this.openCountRequests.set(n,{resolve:o,reject:s})});try{await this.send('[\"COUNT\",\"'+n+'\",'+JSON.stringify(t).substring(1))}catch(o){let s=this.openCountRequests.get(n);s&&(s.reject(o),this.openCountRequests.delete(n))}return r}subscribe(t,e){e.label!==\"<forced-ping>\"&&(this.idleSince=void 0,this.clearIdleTimeout(),this.ongoingOperations++);let n=this.prepareSubscription(t,e);return n.fire(),e.abort&&(e.abort.onabort=()=>n.close(String(e.abort.reason||\"<aborted>\"))),n}prepareSubscription(t,e){this.serial++;let n=e.id||(e.label?e.label+\":\":\"sub:\")+this.serial,r=new hi(this,n,t,e);return this.openSubs.set(n,r),r}close(){this.skipReconnection=!0,this.reconnectTimeoutHandle&&(clearTimeout(this.reconnectTimeoutHandle),this.reconnectTimeoutHandle=void 0),this.pingIntervalHandle&&(clearInterval(this.pingIntervalHandle),this.pingIntervalHandle=void 0),this.closeAllSubscriptions(\"relay connection closed by us\"),this._connected=!1,this.connectionPromise=void 0,this.idleSince=void 0,this.clearIdleTimeout(),this.onclose?.(),this.ws&&(this.ws.onopen=null,this.ws.onerror=null,this.ws.onclose=null,this.ws.readyState!==this._WebSocket.CLOSING&&this.ws.readyState!==this._WebSocket.CLOSED&&this.ws.close())}_onmessage(t){let e=t.data;if(!e)return;let n=li(e);if(n){let r=this.openSubs.get(n);if(!r)return;let o=fi(e,\"id\"),s=r.alreadyHaveEvent?.(o);if(r.receivedEvent?.(this,o),s)return}try{let r=JSON.parse(e);switch(r[0]){case\"EVENT\":{let o=this.openSubs.get(r[1]),s=r[2];ai(o.filters,s)&&this.verifyEvent(s,this.url)?o.onevent(s):o.oninvalidevent?.(s),(!o.lastEmitted||o.lastEmitted<s.created_at)&&(o.lastEmitted=s.created_at);return}case\"COUNT\":{let o=r[1],s=r[2],i=this.openCountRequests.get(o);i&&(i.resolve(s),this.openCountRequests.delete(o));return}case\"EOSE\":{let o=this.openSubs.get(r[1]);if(!o)return;o.receivedEose();return}case\"OK\":{let o=r[1],s=r[2],i=r[3],c=this.openEventPublishes.get(o);c&&(clearTimeout(c.timeout),s?c.resolve(i):c.reject(new Error(i)),this.openEventPublishes.delete(o));return}case\"CLOSED\":{let o=r[1],s=this.openSubs.get(o);if(!s){let i=this.openCountRequests.get(o);i&&(i.reject(new Error(r[2])),this.openCountRequests.delete(o));return}s.closed=!0,s.close(r[2]);return}case\"NOTICE\":{this.onnotice(r[1]);return}case\"AUTH\":{this.challenge=r[1],this.onauth&&this.auth(this.onauth).catch(o=>{if(!(o instanceof On))throw o});return}default:{this.openSubs.get(r[1])?.oncustom?.(r);return}}}catch(r){try{let[o,s,i]=JSON.parse(e);console.warn(`[nostr] relay ${this.url} error processing message:`,r,i)}catch{console.warn(`[nostr] relay ${this.url} error processing message:`,r)}return}}},hi=class{constructor(t,e,n,r){f(this,\"relay\");f(this,\"id\");f(this,\"lastEmitted\");f(this,\"closed\",!1);f(this,\"eosed\",!1);f(this,\"filters\");f(this,\"alreadyHaveEvent\");f(this,\"receivedEvent\");f(this,\"onevent\");f(this,\"oninvalidevent\");f(this,\"oneose\");f(this,\"onclose\");f(this,\"oncustom\");f(this,\"eoseTimeout\");f(this,\"eoseTimeoutHandle\");if(n.length===0)throw new Error(\"subscription can't be created with zero filters\");this.relay=t,this.filters=n,this.id=e,this.alreadyHaveEvent=r.alreadyHaveEvent,this.receivedEvent=r.receivedEvent,this.eoseTimeout=r.eoseTimeout||t.baseEoseTimeout,this.oneose=r.oneose,this.onclose=r.onclose,this.oninvalidevent=r.oninvalidevent,this.onevent=r.onevent||(o=>{console.warn(`onevent() callback not defined for subscription '${this.id}' in relay ${this.relay.url}. event received:`,o)})}fire(){this.relay.send('[\"REQ\",\"'+this.id+'\",'+JSON.stringify(this.filters).substring(1)),this.eoseTimeoutHandle=setTimeout(this.receivedEose.bind(this),this.eoseTimeout)}receivedEose(){this.eosed||(clearTimeout(this.eoseTimeoutHandle),this.eosed=!0,this.oneose?.())}close(t=\"closed by caller\"){if(!this.closed&&this.relay.connected){try{this.relay.send('[\"CLOSE\",'+JSON.stringify(this.id)+\"]\")}catch(e){if(!(e instanceof On))throw e}this.closed=!0}this.relay.openSubs.delete(this.id),this.id.startsWith(\"<forced-ping>\")||(this.relay.ongoingOperations--,this.relay.ongoingOperations===0&&(this.relay.idleSince=Date.now(),this.relay.scheduleIdleClose())),this.onclose?.(t)}},Ee=256,di=Ee*2,Oa=new TextEncoder;function bi(t,e){switch(e){case\"reactions\":return{\"#e\":[t],kinds:[7]};case\"reposts\":return{\"#e\":[t],kinds:[6]};case\"quotes\":return{\"#q\":[t],kinds:[1,1111]};case\"replies\":return{\"#e\":[t],kinds:[1]};case\"comments\":return{\"#E\":[t],kinds:[1111]};case\"followers\":return{\"#p\":[t],kinds:[3]}}}function pi(){return new Uint8Array(Ee)}function yi(t){if(t.length!==di||!/^[0-9a-f]+$/.test(t))return;let e=new Uint8Array(Ee);for(let n=0;n<Ee;n++)e[n]=parseInt(t.slice(n*2,n*2+2),16);return e}function xi(t){if(t.length!==Ee)throw new Error(`invalid number of registers ${t.length}`);let e=\"\";for(let n=0;n<Ee;n++)e+=t[n].toString(16).padStart(2,\"0\");return e}function gi(t,e){if(t.length===0&&(t=pi()),t.length!==Ee)throw new Error(`invalid number of registers ${t.length}`);if(e.length!==Ee)throw new Error(`invalid number of registers ${e.length}`);for(let n=0;n<Ee;n++)e[n]>t[n]&&(t[n]=e[n]);return t}var wi=class{constructor(t){f(this,\"relays\",new Map);f(this,\"seenOn\",new Map);f(this,\"trackRelays\",!1);f(this,\"verifyEvent\");f(this,\"enablePing\");f(this,\"enableReconnect\");f(this,\"idleTimeout\",2e4);f(this,\"automaticallyAuth\");f(this,\"onRelayConnectionFailure\");f(this,\"onRelayConnectionSuccess\");f(this,\"allowConnectingToRelay\");f(this,\"maxWaitForConnection\");f(this,\"_WebSocket\");this.verifyEvent=t.verifyEvent,this._WebSocket=t.websocketImplementation,this.enablePing=t.enablePing,this.enableReconnect=t.enableReconnect||!1,t.idleTimeout&&(this.idleTimeout=t.idleTimeout),this.automaticallyAuth=t.automaticallyAuth,this.onRelayConnectionFailure=t.onRelayConnectionFailure,this.onRelayConnectionSuccess=t.onRelayConnectionSuccess,this.allowConnectingToRelay=t.allowConnectingToRelay,this.maxWaitForConnection=t.maxWaitForConnection||3e3}async ensureRelay(t,e){t=Ge(t);let n=this.relays.get(t);if(n||(n=new po(t,{verifyEvent:this.verifyEvent,websocketImplementation:this._WebSocket,enablePing:this.enablePing,enableReconnect:this.enableReconnect,idleTimeout:this.idleTimeout}),n.onclose=()=>{this.relays.delete(t)},this.relays.set(t,n)),this.automaticallyAuth){let r=this.automaticallyAuth(t);r&&(n.onauth=r)}try{await n.connect({timeout:e?.connectionTimeout,abort:e?.abort})}catch(r){throw this.relays.delete(t),r}return n}close(t){t.map(Ge).forEach(e=>{this.relays.get(e)?.close(),this.relays.delete(e)})}subscribe(t,e,n){let r=[],o=[];for(let s=0;s<t.length;s++){let i=Ge(t[s]);r.find(c=>c.url===i)||o.indexOf(i)===-1&&(o.push(i),r.push({url:i,filter:e}))}return this.subscribeMap(r,n)}subscribeMany(t,e,n){return this.subscribe(t,e,n)}subscribeMap(t,e){let n=new Map;for(let h of t){let{url:b,filter:x}=h;n.has(b)||n.set(b,[]),n.get(b).push(x)}let r=Array.from(n.entries()).map(([h,b])=>({url:h,filters:b}));this.trackRelays&&(e.receivedEvent=(h,b)=>{let x=this.seenOn.get(b);x||(x=new Set,this.seenOn.set(b,x)),x.add(h)});let o=new Set,s=[],i=[],c=h=>{i[h]||(i[h]=!0,i.filter(b=>b).length===r.length&&(e.oneose?.(),c=()=>{}))},a=[],l=(h,b,x)=>{a[h]||(c(h),a[h]={url:b,reason:x},a.filter(w=>w).length===r.length&&(e.onclose?.(a),l=()=>{}))},u=h=>{if(e.alreadyHaveEvent?.(h))return!0;let b=o.has(h);return o.add(h),b},d=Promise.all(r.map(async({url:h,filters:b},x)=>{if(this.allowConnectingToRelay?.(h,[\"read\",b])===!1){l(x,h,\"connection skipped by allowConnectingToRelay\");return}let w;try{w=await this.ensureRelay(h,{connectionTimeout:this.maxWaitForConnection<(e.maxWait||0)?Math.max(e.maxWait*.8,e.maxWait-1e3):this.maxWaitForConnection,abort:e.abort})}catch(B){this.onRelayConnectionFailure?.(h),l(x,h,B?.message||String(B));return}this.onRelayConnectionSuccess?.(h);let E=w.subscribe(b,{...e,oneose:()=>c(x),onclose:B=>{B.startsWith(\"auth-required: \")&&e.onauth?w.auth(e.onauth).then(()=>{w.subscribe(b,{...e,oneose:()=>c(x),onclose:k=>{l(x,h,k)},alreadyHaveEvent:u,eoseTimeout:e.maxWait,abort:e.abort})}).catch(k=>{l(x,h,`auth was required and attempted, but failed with: ${k}`)}):l(x,h,B)},alreadyHaveEvent:u,eoseTimeout:e.maxWait,abort:e.abort});s.push(E)}));return{async close(h){await d,s.forEach(b=>{b.close(h)})}}}subscribeEose(t,e,n){let r;return r=this.subscribe(t,e,{...n,oneose(){let o=\"closed automatically on eose\";r?r.close(o):n.onclose?.(t.map(s=>({url:s,reason:o})))}}),r}subscribeManyEose(t,e,n){return this.subscribeEose(t,e,n)}async querySync(t,e,n){return new Promise(async r=>{let o=[];this.subscribeEose(t,e,{...n,onevent(s){o.push(s)},onclose(s){r(o)}})})}async get(t,e,n){e.limit=1;let r=await this.querySync(t,e,n);return r.sort((o,s)=>s.created_at-o.created_at),r[0]||null}async countMany(t,e,n,r){let o=bi(e,n),s=[];for(let l=0;l<t.length;l++){let u=Ge(t[l]);s.indexOf(u)===-1&&s.push(u)}let i=await Promise.all(s.map(async l=>{if(this.allowConnectingToRelay?.(l,[\"read\",[o]])===!1)return null;let u;try{u=await this.ensureRelay(l,{connectionTimeout:this.maxWaitForConnection<(r?.maxWait||0)?Math.max(r.maxWait*.8,r.maxWait-1e3):this.maxWaitForConnection,abort:r?.abort})}catch{return this.onRelayConnectionFailure?.(l),null}return this.onRelayConnectionSuccess?.(l),u.countWithHLL([o],{id:r?.id}).catch(()=>null)})),c=0,a;for(let l of i){if(!l||(l.count>c&&(c=l.count),!l.hll||l.hll.length!==512))continue;let u=yi(l.hll);u&&(a=gi(a||new Uint8Array(0),u))}return a?{count:c,hll:xi(a)}:{count:c}}publish(t,e,n){return t.map(Ge).map(async(r,o,s)=>{if(s.indexOf(r)!==o)return Promise.reject(\"duplicate url\");if(this.allowConnectingToRelay?.(r,[\"write\",e])===!1)return Promise.reject(\"connection skipped by allowConnectingToRelay\");let i;try{i=await this.ensureRelay(r,{connectionTimeout:this.maxWaitForConnection<(n?.maxWait||0)?Math.max(n.maxWait*.8,n.maxWait-1e3):this.maxWaitForConnection,abort:n?.abort})}catch(c){return this.onRelayConnectionFailure?.(r),Promise.reject(\"connection failure: \"+String(c))}return i.publish(e).catch(async c=>{if(c instanceof Error&&c.message.startsWith(\"auth-required: \")&&n?.onauth)return await i.auth(n.onauth),i.publish(e);throw c}).then(c=>{if(this.trackRelays){let a=this.seenOn.get(e.id);a||(a=new Set,this.seenOn.set(e.id,a)),a.add(i)}return c})})}listConnectionStatus(){let t=new Map;return this.relays.forEach((e,n)=>t.set(n,e.connected)),t}destroy(){this.relays.forEach(t=>t.close()),this.relays=new Map}pruneIdleRelays(t=1e4){let e=[];for(let[n,r]of this.relays)r.idleSince&&Date.now()-r.idleSince>=t&&(this.relays.delete(n),e.push(n),r.close());return e}},yo;try{yo=WebSocket}catch{}var xo=class extends wi{constructor(t){super({verifyEvent:si,websocketImplementation:yo,maxWaitForConnection:3e3,...t})}};var Ma=new TextDecoder(\"utf-8\"),Va=new TextEncoder;var mi=5e3;function go(t){return vi(\"npub\",F(t))}function Ei(t,e){let n=mn.toWords(e);return mn.encode(t,n,mi)}function vi(t,e){return Ei(t,e)}function wo(t){return t instanceof Uint8Array||ArrayBuffer.isView(t)&&t.constructor.name===\"Uint8Array\"&&\"BYTES_PER_ELEMENT\"in t&&t.BYTES_PER_ELEMENT===1}var kn=t=>t?`\"${t}\" `:\"\";function Si(t,e=\"\"){if(typeof t!=\"number\")throw new TypeError(kn(e)+\"expected number, got \"+typeof t);if(!Number.isSafeInteger(t)||t<0)throw new RangeError(kn(e)+\"expected integer >= 0, got \"+t);return t}function Bi(t,e,n=\"\"){if(wo(t)&&(e===void 0||t.length===e))return t;e!==void 0&&Si(e,\"length\");let r=wo(t),o=e!==void 0?` of length ${e}`:\"\",s=r?`length=${t.length}`:`type=${typeof t}`,i=kn(n)+\"expected Uint8Array\"+o+\", got \"+s;throw r?new RangeError(i):new TypeError(i)}var Eo=typeof Uint8Array.from([]).toHex==\"function\"&&typeof Uint8Array.fromHex==\"function\",Ai=Array.from({length:256},(t,e)=>e.toString(16).padStart(2,\"0\"));function vo(t){if(Bi(t),Eo)return t.toHex();let e=\"\";for(let n=0;n<t.length;n++)e+=Ai[t[n]];return e}function mo(t){return t>=48&&t<=57?t-48:t>=65&&t<=70?t-55:t>=97&&t<=102?t-87:void 0}function So(t){if(typeof t!=\"string\")throw new TypeError(\"hex string expected, got \"+typeof t);if(Eo)try{return Uint8Array.fromHex(t)}catch(o){throw o instanceof SyntaxError?new RangeError(o.message):o}let e=t.length,n=e/2;if(e%2)throw new RangeError(\"hex string expected, got unpadded hex of length \"+e);let r=new Uint8Array(n);for(let o=0,s=0;o<n;o++,s+=2){let i=mo(t.charCodeAt(s)),c=mo(t.charCodeAt(s+1));if(i===void 0||c===void 0){let a=t[s]+t[s+1];throw new RangeError('hex string expected, got non-hex character \"'+a+'\" at index '+s)}r[o]=i*16+c}return r}window.NostrSigner={generateSecretKey:Lr,getPublicKey:Or,finalizeEvent:kr,verifyEvent:_r,SimplePool:xo,BunkerSigner:Ot,parseBunkerInput:ho,createNostrConnectURI:bo,npubEncode:go,bytesToHex:vo,hexToBytes:So};})();\n";