|
| 1 | diff --git a/.npmrc b/.npmrc | | 2 | new file mode 100644 | | 3 | index 0000000..41583e3 | | 4 | --- /dev/null | | 5 | +++ b/.npmrc | | 6 | @@ -0,0 +1 @@ | | 7 | +@jsr:registry=https://npm.jsr.io | | 8 | diff --git a/docs/app.md b/docs/app.md | | 9 | index e698359..290202d 100644 | | 10 | --- a/docs/app.md | | 11 | +++ b/docs/app.md | | 12 | @@ -21,3 +21,10 @@ Notifications are off until you turn them on, and each device is enabled separat | | 13 | Each device chooses what wakes it: private messages, replies to your posts, issues and pull requests, mentions, requests for a decision, and relay notices such as a report awaiting moderation or a finished job. A burst of activity produces one notification per category. The text is encrypted to the device, so the push service never reads it. The app icon shows how many conversation events arrived since you last opened Inbox, plus the requests that still wait for your decision. Opening Inbox clears the conversation count, and answering a request clears that request. Devices that stop accepting messages are removed. | | 14 | | | 15 | A request for a decision arrives with **Approve**, **Deny** and **Reply** buttons on the notification; a question offers only **Reply**. Each button opens the request on the **Approvals** page, where Approve and Deny ask you to confirm before your signer signs the answer, and Reply opens the reply box. Nothing is signed from the notification alone. See [Asking a person](agents.md#asking-a-person) for how requests are made and answered. | | 16 | + | | 17 | +## Names | | 18 | + | | 19 | +People show by name wherever the relay knows one: room authors and members, mentions, approvals, agent owners, wiki authors and the signed-in line. The name is the profile (kind 0) published on this relay, resolved in the browser by the `nostr-name` element from [nostr-web-components](https://web.nostr.technology/), which tiny vendors as one bundled script. The bundle replaces the library's profile loader with one that asks this relay only, so no public indexer learns who you look at; results are cached in the browser for six hours. Without JavaScript, or before a profile arrives, the short key shows instead, and the key stays in the element's title. | | 20 | + | | 21 | +Rebuild the bundle after changing `scripts/nostr-name-setup.mjs` or updating the package with `npm run build:nostr-name`. The package pulls its dependencies from the JSR registry, which `.npmrc` maps for the `@jsr` scope. | | 22 | + | | 23 | diff --git a/internal/webui/agents_test.go b/internal/webui/agents_test.go | | 24 | index 79d97a3..47e5ad8 100644 | | 25 | --- a/internal/webui/agents_test.go | | 26 | +++ b/internal/webui/agents_test.go | | 27 | @@ -97,7 +97,7 @@ func TestAgentsPageRendersTableCardsActivityAndGrantForm(t *testing.T) { | | 28 | `<a href="#agent-` + agentRevoked + `">` + shortID(agentRevoked) + `</a>`, | | 29 | `<agent-card id="agent-` + agentActive + `">`, | | 30 | `<h3>hermes <small><span data-state="active">active</span> | signed by you | expires `, | | 31 | - `<code>tinyrelay</code> maintain, owner <nostr-key hex="` + backend.policy.Owner + `"`, | | 32 | + `<code>tinyrelay</code> maintain, owner <nostr-name pubkey="` + backend.policy.Owner + `"`, | | 33 | `<th>rate</th><td>60 events per minute</td>`, | | 34 | `<rpc-form method="pauseagent" refresh><input type="hidden" name="param" value=""` + agentActive + `""><button>Pause</button></rpc-form>`, | | 35 | `<rpc-form method="revokeagent" refresh><input type="hidden" name="param" value=""` + agentActive + `""><button>Revoke</button></rpc-form>`, | | 36 | diff --git a/internal/webui/approvals_test.go b/internal/webui/approvals_test.go | | 37 | index f6b87fd..cab81cc 100644 | | 38 | --- a/internal/webui/approvals_test.go | | 39 | +++ b/internal/webui/approvals_test.go | | 40 | @@ -62,7 +62,7 @@ func TestApprovalsPageRendersRequestsForTheOwner(t *testing.T) { | | 41 | asker := strings.Repeat("c", 64) | | 42 | for _, marker := range []string{ | | 43 | `<approval-item id="approval-` + strings.Repeat("1", 64) + `" data-type="approve" data-state="open">`, | | 44 | - `<nostr-key hex="` + asker + `"`, "in repository tinyrelay", "expires <time", | | 45 | + `<nostr-name pubkey="` + asker + `"`, "in repository tinyrelay", "expires <time", | | 46 | "<p>Publish release notes 1.4</p>", "<p>Publish release notes 1.4 to the articles feed as drafted?</p>", | | 47 | "<pre>about 30617:" + backend.policy.Owner + ":tinyrelay | kind 1621 dddddddddddd</pre>", | | 48 | `<nostr-react event="` + strings.Repeat("1", 64) + `" pubkey="` + asker + `" kind="1111"><button name="reaction" value="+">Approve</button> <button name="reaction" value="-">Deny</button></nostr-react>`, | | 49 | diff --git a/internal/webui/browse.html b/internal/webui/browse.html | | 50 | index 6219251..cbb89cc 100644 | | 51 | --- a/internal/webui/browse.html | | 52 | +++ b/internal/webui/browse.html | | 53 | @@ -65,7 +65,7 @@ | | 54 | <table id="rows"> | | 55 | <thead><tr><th>asker</th><th>request</th><th>answer</th></tr></thead> | | 56 | <tbody> | | 57 | -{{range $item := $done}}<tr id="approval-{{$item.ID}}" data-state="{{$item.State}}"><td>{{template "nostrKey" $item.Asker}}</td><td><a href="{{$item.Open}}">{{$item.Title}}</a>{{if eq $item.Decision "replied"}}{{with $item.AnswerContent}} <small>{{.}}</small>{{end}}{{end}}</td><td>{{if eq $item.State "expired"}}expired{{else}}{{$item.Decision}} | <time datetime="{{datetime $item.AnsweredAt}}">{{when $item.AnsweredAt}}</time>{{end}}</td></tr> | | 58 | +{{range $item := $done}}<tr id="approval-{{$item.ID}}" data-state="{{$item.State}}"><td>{{template "nostrName" $item.Asker}}</td><td><a href="{{$item.Open}}">{{$item.Title}}</a>{{if eq $item.Decision "replied"}}{{with $item.AnswerContent}} <small>{{.}}</small>{{end}}{{end}}</td><td>{{if eq $item.State "expired"}}expired{{else}}{{$item.Decision}} | <time datetime="{{datetime $item.AnsweredAt}}">{{when $item.AnsweredAt}}</time>{{end}}</td></tr> | | 59 | {{end}}</tbody> | | 60 | </table> | | 61 | {{end}}{{with (asMap .Event).next_cursor}}<p id="pages"><a href="{{browsePageURL "/approvals" $.Query "cursor" .}}">next page</a></p> | | 62 | diff --git a/internal/webui/browse_test.go b/internal/webui/browse_test.go | | 63 | index f45446e..69eb199 100644 | | 64 | --- a/internal/webui/browse_test.go | | 65 | +++ b/internal/webui/browse_test.go | | 66 | @@ -158,3 +158,25 @@ func servedScript(t *testing.T, app *App, path string) string { | | 67 | } | | 68 | return recorder.Body.String() | | 69 | } | | 70 | + | | 71 | +func TestNostrNameBundleIsServedAndLoadedOnEveryPage(t *testing.T) { | | 72 | + b := &browseBackend{fakeBackend: fakeBackend{policy: policy.Defaults(strings.Repeat("a", 64))}} | | 73 | + app, err := New(b, Options{}) | | 74 | + if err != nil { | | 75 | + t.Fatal(err) | | 76 | + } | | 77 | + bundle := servedScript(t, app, "/scripts/nostr-name.js") | | 78 | + for _, marker := range []string{`customElements.define("nostr-name"`, "nostrSharedMetadataLoader", "tiny:names:v1"} { | | 79 | + if !strings.Contains(bundle, marker) { | | 80 | + t.Fatalf("nostr-name bundle missing %q", marker) | | 81 | + } | | 82 | + } | | 83 | + if strings.Contains(bundle, "cdn.jsdelivr.net") { | | 84 | + t.Fatal("nostr-name bundle reaches for a CDN") | | 85 | + } | | 86 | + recorder := httptest.NewRecorder() | | 87 | + app.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/repos", nil)) | | 88 | + if body := recorder.Body.String(); !strings.Contains(body, `src="/scripts/nostr-name.js?v=`) { | | 89 | + t.Fatal("page does not load the nostr-name bundle") | | 90 | + } | | 91 | +} | | 92 | diff --git a/internal/webui/collaboration.html b/internal/webui/collaboration.html | | 93 | index 3530c47..34fcdc2 100644 | | 94 | --- a/internal/webui/collaboration.html | | 95 | +++ b/internal/webui/collaboration.html | | 96 | @@ -8,7 +8,7 @@ | | 97 | </form> | | 98 | {{$items := collabItems .Event}}{{if $items}}<table id="rows"> | | 99 | <thead><tr><th>status</th><th>title</th><th>author</th><th>opened</th></tr></thead> | | 100 | -<tbody>{{range $item := $items}}{{$item := asMap $item}}<tr><td>{{index $item "status"}}</td><td><a href="{{if $issues}}{{repoURL $.Query "issue" (str (index $item "id"))}}{{else}}{{repoURL $.Query "pr" (str (index $item "id"))}}{{end}}">{{index $item "title"}}</a>{{with collabLabels $item}} <small>{{join . ", "}}</small>{{end}}</td><td>{{template "nostrKey" (str (index $item "author"))}}</td><td><time datetime="{{datetime (index $item "created_at")}}">{{when (index $item "created_at")}}</time></td></tr> | | 101 | +<tbody>{{range $item := $items}}{{$item := asMap $item}}<tr><td>{{index $item "status"}}</td><td><a href="{{if $issues}}{{repoURL $.Query "issue" (str (index $item "id"))}}{{else}}{{repoURL $.Query "pr" (str (index $item "id"))}}{{end}}">{{index $item "title"}}</a>{{with collabLabels $item}} <small>{{join . ", "}}</small>{{end}}</td><td>{{template "nostrName" (str (index $item "author"))}}</td><td><time datetime="{{datetime (index $item "created_at")}}">{{when (index $item "created_at")}}</time></td></tr> | | 102 | {{end}}</tbody></table>{{else}}<p>No matching {{if $issues}}issues{{else}}pull requests{{end}}.</p>{{end}} | | 103 | {{with index $data "next_cursor"}}<p id="pages"><a href="{{browsePageURL "/repo" $.Query "cursor" .}}">next page</a></p>{{end}} | | 104 | {{if .Actor}}<details id="collaboration-new"><summary>{{if $issues}}New issue{{else}}New pull request{{end}}</summary> | | 105 | @@ -21,7 +21,7 @@ | | 106 | {{else}}{{$item := asMap (index $data "item")}}{{$diff := reviewDiff .Event .Query .Actor}}{{$anchor := reviewAnchor .Query}}<section id="collaboration-detail"> | | 107 | <p><a href="{{if $issues}}{{repoURL .Query "issues" ""}}{{else}}{{repoURL .Query "prs" ""}}{{end}}">{{if $issues}}All issues{{else}}All pull requests{{end}}</a></p> | | 108 | <h1>{{index $item "title"}}</h1> | | 109 | -<p>{{index $item "status"}} by {{template "nostrKey" (str (index $item "author"))}} <time datetime="{{datetime (index $item "created_at")}}">{{when (index $item "created_at")}}</time></p> | | 110 | +<p>{{index $item "status"}} by {{template "nostrName" (str (index $item "author"))}} <time datetime="{{datetime (index $item "created_at")}}">{{when (index $item "created_at")}}</time></p> | | 111 | {{with collabLabels $item}}<p>Labels: {{join . ", "}}</p>{{end}} | | 112 | <article id="collaboration-body">{{markdown (str (index $item "content"))}}</article> | | 113 | {{if not $issues}}<h2>Changes</h2>{{if index $data "diff_unavailable"}}<p>{{index $data "diff_unavailable"}}. The diff will be available after the required commits have been synchronized.</p>{{else}}<diff-view><table><tbody> | | 114 | diff --git a/internal/webui/collaboration_test.go b/internal/webui/collaboration_test.go | | 115 | index 0a76f9d..90762b2 100644 | | 116 | --- a/internal/webui/collaboration_test.go | | 117 | +++ b/internal/webui/collaboration_test.go | | 118 | @@ -74,7 +74,7 @@ func TestCollaborationPagesUseTypedResultsAndWorkingDetailLinks(t *testing.T) { | | 119 | if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), "Issue body") || !strings.Contains(w.Body.String(), `kind="status"`) || !strings.Contains(w.Body.String(), "https://git.example/test.git") || !strings.Contains(w.Body.String(), "<td>private</td>") { | | 120 | t.Fatalf("detail failed: %d %s", w.Code, w.Body.String()) | | 121 | } | | 122 | - if body := w.Body.String(); !strings.Contains(body, `<ul id="maintainers">`) || !strings.Contains(body, `hex="`+strings.Repeat("d", 64)+`"`) || !strings.Contains(body, "<small>agent release-notes</small>") || strings.Count(body, "<small>owner</small>") != 1 || strings.Contains(body, "<small>maintainer</small>") { | | 123 | + if body := w.Body.String(); !strings.Contains(body, `<ul id="maintainers">`) || !strings.Contains(body, `pubkey="`+strings.Repeat("d", 64)+`"`) || !strings.Contains(body, "<small>agent release-notes</small>") || strings.Count(body, "<small>owner</small>") != 1 || strings.Contains(body, "<small>maintainer</small>") { | | 124 | t.Fatalf("maintainer panel: %s", body) | | 125 | } | | 126 | var request map[string]any | | 127 | diff --git a/internal/webui/components.js b/internal/webui/components.js | | 128 | index 2a6fc53..55c806c 100644 | | 129 | --- a/internal/webui/components.js | | 130 | +++ b/internal/webui/components.js | | 131 | @@ -617,11 +617,14 @@ | | 132 | return node; | | 133 | }; | | 134 | const keyNode = hex => { const node = el("nostr-key", hex.slice(0, 12)); node.setAttribute("hex", hex); node.title = hex; return node; }; | | 135 | + // nameNode shows a person: the vendored nostr-name element replaces the short | | 136 | + // id with the profile name published on this relay. | | 137 | + const nameNode = hex => { const node = el("nostr-name", hex.slice(0, 12)); node.setAttribute("pubkey", hex); node.title = hex; return node; }; | | 138 | // panelMembers reads the members list in the panel: role and agent marker by key. | | 139 | const panelMembers = () => { | | 140 | const members = {}; | | 141 | document.querySelectorAll("#members li").forEach(item => { | | 142 | - const hex = item.querySelector("nostr-key")?.getAttribute("hex"); | | 143 | + const hex = item.querySelector("nostr-name")?.getAttribute("pubkey") || item.querySelector("nostr-key")?.getAttribute("hex"); | | 144 | if (hex) members[hex] = {role: (item.querySelector("small")?.textContent || "").split("|")[0].trim(), agent: item.hasAttribute("data-agent")}; | | 145 | }); | | 146 | return members; | | 147 | @@ -638,7 +641,7 @@ | | 148 | if (member?.agent) node.dataset.agent = ""; | | 149 | if (notice) node.dataset.notice = ""; | | 150 | const header = el("header"), name = el("b"), time = el("time", clock(event.created_at)), small = el("small"); | | 151 | - name.append(keyNode(pubkey)); | | 152 | + name.append(nameNode(pubkey)); | | 153 | time.dateTime = new Date(event.created_at * 1000).toISOString().replace(/\.\d+Z$/, "Z"); | | 154 | time.title = time.dateTime.slice(0, 16).replace("T", " ") + " UTC"; | | 155 | small.append(time); | | 156 | @@ -647,7 +650,7 @@ | | 157 | node.append(header, body); | | 158 | const footer = el("footer"); | | 159 | const mentions = notice ? [] : (event.tags || []).filter(tag => tag[0] === "p" && isHex64(tag[1]) && tag[1] !== pubkey).map(tag => tag[1]); | | 160 | - if (mentions.length) { const span = el("span", "to "); mentions.forEach(key => span.append(keyNode(key), " ")); footer.append(span); } | | 161 | + if (mentions.length) { const span = el("span", "to "); mentions.forEach(key => span.append(nameNode(key), " ")); footer.append(span); } | | 162 | if (!inThread && room && event.kind === 11) { const link = el("a", "thread"); link.href = roomPath(room, "/thread/" + event.id); footer.append(link); } | | 163 | const root = tagValue(event, "e"); | | 164 | if (!inThread && room && event.kind === 12 && isHex64(root)) { const link = el("a", "in thread"); link.href = roomPath(room, "/thread/" + root); footer.append(link); } | | 165 | diff --git a/internal/webui/manage.html b/internal/webui/manage.html | | 166 | index 165bf72..90e2390 100644 | | 167 | --- a/internal/webui/manage.html | | 168 | +++ b/internal/webui/manage.html | | 169 | @@ -34,21 +34,21 @@ | | 170 | <p>Agents are members with their own keys. Each one acts under a grant you sign, and stays the author of everything it does.</p> | | 171 | {{template "alert" .Error}} | | 172 | <table id="rows"><thead><tr><th>agent</th><th>scope</th><th>owner</th><th>state</th></tr></thead><tbody> | | 173 | -{{range .Feed}}{{$a := asMap .}}{{$pk := str (index $a "pubkey")}}<tr><td><a href="#agent-{{$pk}}">{{agentLabel $a}}</a></td><td title="{{agentScope $a}}">{{agentScope $a}}</td><td>{{template "nostrKey" (str (index $a "owner"))}}</td><td data-state="{{agentState $a}}">{{agentState $a}}{{with agentSince $a}} | {{.}}{{end}}</td></tr> | | 174 | +{{range .Feed}}{{$a := asMap .}}{{$pk := str (index $a "pubkey")}}<tr><td><a href="#agent-{{$pk}}">{{agentLabel $a}}</a></td><td title="{{agentScope $a}}">{{agentScope $a}}</td><td>{{template "nostrName" (str (index $a "owner"))}}</td><td data-state="{{agentState $a}}">{{agentState $a}}{{with agentSince $a}} | {{.}}{{end}}</td></tr> | | 175 | {{else}}<tr><td colspan="4">No agents yet. Sign a grant below to add one.</td></tr> | | 176 | {{end}}</tbody></table> | | 177 | {{range .Feed}}{{$a := asMap .}}{{$pk := str (index $a "pubkey")}}{{$state := agentState $a}}{{$scope := asMap (index $a "scope")}} | | 178 | <agent-card id="agent-{{$pk}}"> | | 179 | -<h3>{{agentLabel $a}} <small><span data-state="{{$state}}">{{$state}}</span> | signed by {{if eq (str (index $a "owner")) $.Actor}}you{{else}}{{template "nostrKey" (str (index $a "owner"))}}{{end}} | expires {{short (index $a "expires")}}</small></h3> | | 180 | +<h3>{{agentLabel $a}} <small><span data-state="{{$state}}">{{$state}}</span> | signed by {{if eq (str (index $a "owner")) $.Actor}}you{{else}}{{template "nostrName" (str (index $a "owner"))}}{{end}} | expires {{short (index $a "expires")}}</small></h3> | | 181 | <table> | | 182 | <tr><th>key</th><td><nostr-key hex="{{$pk}}" title="{{$pk}}">{{$pk}}</nostr-key> <small>click to copy</small></td></tr> | | 183 | <tr><th>rooms</th><td>{{or (scopeList $a "rooms") "none"}}</td></tr> | | 184 | -<tr><th>repositories</th><td>{{range $i, $r := index $scope "repos"}}{{$repo := asMap $r}}{{if $i}}<br>{{end}}<code>{{str (index $repo "identifier")}}</code> {{str (index $repo "level")}}, owner {{template "nostrKey" (str (index $repo "owner"))}}{{else}}none{{end}}</td></tr> | | 185 | +<tr><th>repositories</th><td>{{range $i, $r := index $scope "repos"}}{{$repo := asMap $r}}{{if $i}}<br>{{end}}<code>{{str (index $repo "identifier")}}</code> {{str (index $repo "level")}}, owner {{template "nostrName" (str (index $repo "owner"))}}{{else}}none{{end}}</td></tr> | | 186 | <tr><th>wiki</th><td>{{or (str (index $scope "wiki")) "none"}}</td></tr> | | 187 | <tr><th>kinds</th><td>{{or (scopeList $a "kinds") "profile and relay list only"}}</td></tr> | | 188 | <tr><th>rate</th><td>{{str (index $scope "rate")}} events per minute</td></tr> | | 189 | <tr><th>expires</th><td><time datetime="{{datetime (index $a "expires")}}">{{when (index $a "expires")}}</time></td></tr> | | 190 | -<tr><th>owner</th><td>{{template "nostrKey" (str (index $a "owner"))}}</td></tr> | | 191 | +<tr><th>owner</th><td>{{template "nostrName" (str (index $a "owner"))}}</td></tr> | | 192 | {{with index $a "lastEvent"}}{{if when .}}<tr><th>last event</th><td><time datetime="{{datetime .}}">{{when .}}</time></td></tr>{{end}}{{end}} | | 193 | <tr><th>callbacks</th><td>{{range $c := callbacksFor $.Callbacks $pk}}{{$cm := asMap $c}}{{$cs := callbackState $c}}{{$cid := str (index $cm "id")}}<agent-callback data-state="{{$cs}}"><code>{{str (index $cm "host")}}</code> kinds {{callbackKinds $c}} | <span data-state="{{$cs}}" title="{{str (index $cm "lastStatus")}}">{{$cs}}</span>{{with index $cm "lastDelivery"}}{{if when .}} | delivered <time datetime="{{datetime .}}">{{when .}}</time>{{end}}{{end}} {{if eq $cs "paused"}}<rpc-form method="resumecallback" refresh><input type="hidden" name="param" value=""{{$cid}}""><button>Resume</button></rpc-form>{{else}}<rpc-form method="pausecallback" refresh><input type="hidden" name="param" value=""{{$cid}}""><button>Pause</button></rpc-form>{{end}}<rpc-form method="removecallback" refresh><input type="hidden" name="param" value=""{{$cid}}""><button>Remove</button></rpc-form></agent-callback>{{else}}none{{end}}</td></tr> | | 194 | </table> | | 195 | diff --git a/internal/webui/nostr-name.js b/internal/webui/nostr-name.js | | 196 | new file mode 100644 | | 197 | index 0000000..0aaa437 | | 198 | --- /dev/null | | 199 | +++ b/internal/webui/nostr-name.js | | 200 | @@ -0,0 +1,25 @@ | | 201 | +"use strict"; | | 202 | +(()=>{var Mo=Object.defineProperty;var Vo=(t,e,n)=>e in t?Mo(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var l=(t,e,n)=>Vo(t,typeof e!="symbol"?e+"":e,n);function Ut(t){return t instanceof Uint8Array||ArrayBuffer.isView(t)&&t.constructor.name==="Uint8Array"}function Te(t,e=""){if(!Number.isSafeInteger(t)||t<0){let n=e&&`"${e}" `;throw new Error(`${n}expected integer >= 0, got ${t}`)}}function I(t,e,n=""){let r=Ut(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 Pt(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 Nn(t,e){I(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 Pe(...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 z(t,e){return t<<32-e|t>>>e}var Mn=typeof Uint8Array.from([]).toHex=="function"&&typeof Uint8Array.fromHex=="function",Wo=Array.from({length:256},(t,e)=>e.toString(16).padStart(2,"0"));function He(t){if(I(t),Mn)return t.toHex();let e="";for(let n=0;n<t.length;n++)e+=Wo[t[n]];return e}var se={_0:48,_9:57,A:65,F:70,a:97,f:102};function Dn(t){if(t>=se._0&&t<=se._9)return t-se._0;if(t>=se.A&&t<=se.F)return t-(se.A-10);if(t>=se.a&&t<=se.f)return t-(se.a-10)}function De(t){if(typeof t!="string")throw new Error("hex string expected, got "+typeof t);if(Mn)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=Dn(t.charCodeAt(s)),c=Dn(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 pe(...t){let e=0;for(let r=0;r<t.length;r++){let o=t[r];I(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 Vn(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 Ne(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 Wn=t=>({oid:Uint8Array.from([6,9,96,134,72,1,101,3,4,2,t])});function qn(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){l(this,"blockLen");l(this,"outputLen");l(this,"padOffset");l(this,"isLE");l(this,"buffer");l(this,"view");l(this,"finished",!1);l(this,"length",0);l(this,"pos",0);l(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){Pt(this),I(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){Pt(this),Nn(e,this),this.finished=!0;let{buffer:n,view:r,blockLen:o,isLE:s}=this,{pos:i}=this;n[i++]=128,Pe(this.buffer.subarray(i)),this.padOffset>o-i&&(this.process(r,0),i=0);for(let f=i;f<o;f++)n[f]=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 u=a/4,h=this.get();if(u>h.length)throw new Error("_sha2: outputLen bigger than state");for(let f=0;f<u;f++)c.setUint32(4*f,h[f],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()}},ie=Uint32Array.from([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]);var qo=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]),xe=new Uint32Array(64),Dt=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 f=0;f<16;f++,n+=4)xe[f]=e.getUint32(n,!1);for(let f=16;f<64;f++){let p=xe[f-15],d=xe[f-2],b=z(p,7)^z(p,18)^p>>>3,x=z(d,17)^z(d,19)^d>>>10;xe[f]=x+xe[f-7]+b+xe[f-16]|0}let{A:r,B:o,C:s,D:i,E:c,F:a,G:u,H:h}=this;for(let f=0;f<64;f++){let p=z(c,6)^z(c,11)^z(c,25),d=h+p+qn(c,a,u)+qo[f]+xe[f]|0,x=(z(r,2)^z(r,13)^z(r,22))+$n(r,o,s)|0;h=u,u=a,a=c,c=i+d|0,i=s,s=o,o=r,r=d+x|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,u=u+this.G|0,h=h+this.H|0,this.set(r,o,s,i,c,a,u,h)}roundClean(){Pe(xe)}destroy(){this.set(0,0,0,0,0,0,0,0),Pe(this.buffer)}},Nt=class extends Dt{constructor(){super(32);l(this,"A",ie[0]|0);l(this,"B",ie[1]|0);l(this,"C",ie[2]|0);l(this,"D",ie[3]|0);l(this,"E",ie[4]|0);l(this,"F",ie[5]|0);l(this,"G",ie[6]|0);l(this,"H",ie[7]|0)}};var Mt=Vn(()=>new Nt,Wn(1));var Wt=BigInt(0),Vt=BigInt(1);function qt(t,e=""){if(typeof t!="boolean"){let n=e&&`"${e}" `;throw new Error(n+"expected boolean, got type="+typeof t)}return t}function $o(t){if(typeof t=="bigint"){if(!Fo(t))throw new Error("positive bigint expected, got "+t)}else Te(t);return t}function Gn(t){if(typeof t!="string")throw new Error("hex string expected, got "+typeof t);return t===""?Wt:BigInt("0x"+t)}function Ce(t){return Gn(He(t))}function $t(t){return Gn(He(Go(I(t)).reverse()))}function st(t,e){Te(e),t=$o(t);let n=De(t.toString(16).padStart(e*2,"0"));if(n.length!==e)throw new Error("number too large");return n}function Gt(t,e){return st(t,e).reverse()}function Go(t){return Uint8Array.from(t)}function Fn(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 Fo=t=>typeof t=="bigint"&&Wt<=t;function Ft(t){let e;for(e=0;t>Wt;t>>=Vt,e+=1);return e}var it=t=>(Vt<<BigInt(t))-Vt;function ct(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 u=typeof a;if(u!==i||a===null)throw new Error(`param "${s}" is invalid: expected ${i}, got ${u}`)}let o=(s,i)=>Object.entries(s).forEach(([c,a])=>r(c,a,i));o(e,!1),o(n,!0)}function jt(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 N=BigInt(0),P=BigInt(1),we=BigInt(2),zn=BigInt(3),Zn=BigInt(4),Yn=BigInt(5),jo=BigInt(7),Jn=BigInt(8),Ko=BigInt(9),Xn=BigInt(16);function Z(t,e){let n=t%e;return n>=N?n:e+n}function V(t,e,n){let r=t;for(;e-- >N;)r*=r,r%=n;return r}function jn(t,e){if(t===N)throw new Error("invert: expected non-zero number");if(e<=N)throw new Error("invert: expected positive modulus, got "+e);let n=Z(t,e),r=e,o=N,s=P,i=P,c=N;for(;n!==N;){let u=r/n,h=r%n,f=o-i*u,p=s-c*u;r=n,n=h,o=i,s=c,i=f,c=p}if(r!==P)throw new Error("invert: does not exist");return Z(o,e)}function zt(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+P)/Zn,r=t.pow(e,n);return zt(t,r,e),r}function zo(t,e){let n=(t.ORDER-Yn)/Jn,r=t.mul(e,we),o=t.pow(r,n),s=t.mul(e,o),i=t.mul(t.mul(s,we),o),c=t.mul(s,t.sub(i,t.ONE));return zt(t,c,e),c}function Zo(t){let e=Le(t),n=er(t),r=n(e,e.neg(e.ONE)),o=n(e,r),s=n(e,e.neg(r)),i=(t+jo)/Xn;return(c,a)=>{let u=c.pow(a,i),h=c.mul(u,r),f=c.mul(u,o),p=c.mul(u,s),d=c.eql(c.sqr(h),a),b=c.eql(c.sqr(f),a);u=c.cmov(u,h,d),h=c.cmov(p,f,b);let x=c.eql(c.sqr(h),a),w=c.cmov(u,h,x);return zt(c,w,a),w}}function er(t){if(t<zn)throw new Error("sqrt is not defined for small field");let e=t-P,n=0;for(;e%we===N;)e/=we,n++;let r=we,o=Le(t);for(;Kn(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+P)/we;return function(a,u){if(a.is0(u))return u;if(Kn(a,u)!==1)throw new Error("Cannot find square root");let h=n,f=a.mul(a.ONE,s),p=a.pow(u,e),d=a.pow(u,i);for(;!a.eql(p,a.ONE);){if(a.is0(p))return a.ZERO;let b=1,x=a.sqr(p);for(;!a.eql(x,a.ONE);)if(b++,x=a.sqr(x),b===h)throw new Error("Cannot find square root");let w=P<<BigInt(h-b-1),L=a.pow(f,w);h=b,f=a.sqr(L),p=a.mul(p,f),d=a.mul(d,L)}return d}}function Yo(t){return t%Zn===zn?Qn:t%Jn===Yn?zo:t%Xn===Ko?Zo(t):er(t)}var Jo=["create","isValid","is0","neg","inv","sqrt","sqr","eql","add","sub","mul","pow","div","addN","subN","mulN","sqrN"];function tr(t){let e={ORDER:"bigint",BYTES:"number",BITS:"number"},n=Jo.reduce((r,o)=>(r[o]="function",r),e);return ct(t,n),t}function Xo(t,e,n){if(n<N)throw new Error("invalid exponent, negatives unsupported");if(n===N)return t.ONE;if(n===P)return e;let r=t.ONE,o=e;for(;n>N;)n&P&&(r=t.mul(r,o)),o=t.sqr(o),n>>=P;return r}function Zt(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 Kn(t,e){let n=(t.ORDER-P)/we,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 Qo(t,e){e!==void 0&&Te(e);let n=e!==void 0?e:t.toString(2).length,r=Math.ceil(n/8);return{nBitLength:n,nByteLength:r}}var Kt=class{constructor(e,n={}){l(this,"ORDER");l(this,"BITS");l(this,"BYTES");l(this,"isLE");l(this,"ZERO",N);l(this,"ONE",P);l(this,"_lengths");l(this,"_sqrt");l(this,"_mod");if(e<=N)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}=Qo(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 Z(e,this.ORDER)}isValid(e){if(typeof e!="bigint")throw new Error("invalid field element: expected bigint, got "+typeof e);return N<=e&&e<this.ORDER}is0(e){return e===N}isValidNot0(e){return!this.is0(e)&&this.isValid(e)}isOdd(e){return(e&P)===P}neg(e){return Z(-e,this.ORDER)}eql(e,n){return e===n}sqr(e){return Z(e*e,this.ORDER)}add(e,n){return Z(e+n,this.ORDER)}sub(e,n){return Z(e-n,this.ORDER)}mul(e,n){return Z(e*n,this.ORDER)}pow(e,n){return Xo(this,e,n)}div(e,n){return Z(e*jn(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 jn(e,this.ORDER)}sqrt(e){return this._sqrt||(this._sqrt=Yo(this.ORDER)),this._sqrt(this,e)}toBytes(e){return this.isLE?Gt(e,this.BYTES):st(e,this.BYTES)}fromBytes(e,n=!1){I(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 u=new Uint8Array(o);u.set(e,s?0:u.length-e.length),e=u}if(e.length!==o)throw new Error("Field.fromBytes: expected "+o+" bytes, got "+e.length);let a=s?$t(e):Ce(e);if(c&&(a=Z(a,i)),!n&&!this.isValid(a))throw new Error("invalid field element: outside of range 0..ORDER");return a}invertBatch(e){return Zt(this,e)}cmov(e,n,r){return r?n:e}};function Le(t,e={}){return new Kt(t,e)}function nr(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 es(t){let e=nr(t);return e+Math.ceil(e/2)}function rr(t,e,n=!1){I(t);let r=t.length,o=nr(e),s=es(e);if(r<16||r<s||r>1024)throw new Error("expected "+s+"-1024 bytes of input, got "+r);let i=n?$t(t):Ce(t),c=Z(i,e-P)+P;return n?Gt(c,o):st(c,o)}var ke=BigInt(0),Ee=BigInt(1);function Me(t,e){let n=e.negate();return t?n:e}function Qt(t,e){let n=Zt(t.Fp,e.map(r=>r.Z));return e.map((r,o)=>t.fromAffine(r.toAffine(n[o])))}function cr(t,e){if(!Number.isSafeInteger(t)||t<=0||t>e)throw new Error("invalid window size, expected [1.."+e+"], got W="+t)}function Yt(t,e){cr(t,e);let n=Math.ceil(e/t)+1,r=2**(t-1),o=2**t,s=it(t),i=BigInt(t);return{windows:n,windowSize:r,mask:s,maxNumber:o,shiftBy:i}}function or(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+=Ee);let u=e*r,h=u+Math.abs(c)-1,f=c===0,p=c<0,d=e%2!==0;return{nextN:a,offset:h,isZero:f,isNeg:p,isNegF:d,offsetF:u}}var Jt=new WeakMap,ar=new WeakMap;function Xt(t){return ar.get(t)||1}function sr(t){if(t!==ke)throw new Error("invalid wNAF")}var at=class{constructor(e,n){l(this,"BASE");l(this,"ZERO");l(this,"Fn");l(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>ke;)n&Ee&&(r=r.add(o)),o=o.double(),n>>=Ee;return r}precomputeWindow(e,n){let{windows:r,windowSize:o}=Yt(n,this.bits),s=[],i=e,c=i;for(let a=0;a<r;a++){c=i,s.push(c);for(let u=1;u<o;u++)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=Yt(e,this.bits);for(let c=0;c<i.windows;c++){let{nextN:a,offset:u,isZero:h,isNeg:f,isNegF:p,offsetF:d}=or(r,c,i);r=a,h?s=s.add(Me(p,n[d])):o=o.add(Me(f,n[u]))}return sr(r),{p:o,f:s}}wNAFUnsafe(e,n,r,o=this.ZERO){let s=Yt(e,this.bits);for(let i=0;i<s.windows&&r!==ke;i++){let{nextN:c,offset:a,isZero:u,isNeg:h}=or(r,i,s);if(r=c,!u){let f=n[a];o=o.add(h?f.negate():f)}}return sr(r),o}getPrecomputes(e,n,r){let o=Jt.get(n);return o||(o=this.precomputeWindow(n,e),e!==1&&(typeof r=="function"&&(o=r(o)),Jt.set(n,o))),o}cached(e,n,r){let o=Xt(e);return this.wNAF(o,this.getPrecomputes(o,e,r),n)}unsafe(e,n,r,o){let s=Xt(e);return s===1?this._unsafeLadder(e,n,o):this.wNAFUnsafe(s,this.getPrecomputes(s,e,r),n,o)}createCache(e,n){cr(n,this.bits),ar.set(e,n),Jt.delete(e)}hasCache(e){return Xt(e)!==1}};function lr(t,e,n,r){let o=e,s=t.ZERO,i=t.ZERO;for(;n>ke||r>ke;)n&Ee&&(s=s.add(o)),r&Ee&&(i=i.add(o)),o=o.double(),n>>=Ee,r>>=Ee;return{p1:s,p2:i}}function ir(t,e,n){if(e){if(e.ORDER!==t)throw new Error("Field.ORDER must match order: Fp == p, Fn == n");return tr(e),e}else return Le(t,{isLE:n})}function fr(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 u=e[a];if(!(typeof u=="bigint"&&u>ke))throw new Error(`CURVE.${a} must be positive bigint`)}let o=ir(e.p,n.Fp,r),s=ir(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 en(t,e){return function(r){let o=t(r);return{secretKey:o,publicKey:e(o)}}}var ur=(t,e)=>(t+(t>=0?e:-e)/ns)/e;function ts(t,e,n){let[[r,o],[s,i]]=e,c=ur(i*t,n),a=ur(-o*t,n),u=t-c*r-a*s,h=-c*o-a*i,f=u<Ve,p=h<Ve;f&&(u=-u),p&&(h=-h);let d=it(Math.ceil(Ft(n)/2))+ft;if(u<Ve||u>=d||h<Ve||h>=d)throw new Error("splitScalar (endomorphism): failed, k="+t);return{k1neg:f,k1:u,k2neg:p,k2:h}}var Ve=BigInt(0),ft=BigInt(1),ns=BigInt(2),lt=BigInt(3),rs=BigInt(4);function dr(t,e={}){let n=fr("weierstrass",t,e),{Fp:r,Fn:o}=n,s=n.CURVE,{h:i,n:c}=s;ct(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 u=ss(r,o);function h(){if(!r.isOdd)throw new Error("compression is not supported: Field does not have .isOdd()")}function f(T,m,y){let{x:E,y:v}=m.toAffine(),H=r.toBytes(E);if(qt(y,"isCompressed"),y){h();let R=!r.isOdd(v);return pe(os(R),H)}else return pe(Uint8Array.of(4),H,r.toBytes(v))}function p(T){I(T,void 0,"Point");let{publicKey:m,publicKeyUncompressed:y}=u,E=T.length,v=T[0],H=T.subarray(1);if(E===m&&(v===2||v===3)){let R=r.fromBytes(H);if(!r.isValid(R))throw new Error("bad point: is not on curve, wrong x");let A=x(R),g;try{g=r.sqrt(A)}catch($){let O=$ instanceof Error?": "+$.message:"";throw new Error("bad point: is not on curve, sqrt error"+O)}h();let S=r.isOdd(g);return(v&1)===1!==S&&(g=r.neg(g)),{x:R,y:g}}else if(E===y&&v===4){let R=r.BYTES,A=r.fromBytes(H.subarray(0,R)),g=r.fromBytes(H.subarray(R,R*2));if(!w(A,g))throw new Error("bad point: is not on curve");return{x:A,y:g}}else throw new Error(`bad point: got length ${E}, expected compressed=${m} or uncompressed=${y}`)}let d=e.toBytes||f,b=e.fromBytes||p;function x(T){let m=r.sqr(T),y=r.mul(m,T);return r.add(r.add(y,r.mul(T,s.a)),s.b)}function w(T,m){let y=r.sqr(m),E=x(T);return r.eql(y,E)}if(!w(s.Gx,s.Gy))throw new Error("bad curve params: generator point");let L=r.mul(r.pow(s.a,lt),rs),_=r.mul(r.sqr(s.b),BigInt(27));if(r.is0(r.add(L,_)))throw new Error("bad curve params: a or b");function U(T,m,y=!1){if(!r.isValid(m)||y&&r.is0(m))throw new Error(`bad point coordinate ${T}`);return m}function et(T){if(!(T instanceof ge))throw new Error("Weierstrass Point expected")}function tt(T){if(!a||!a.basises)throw new Error("no endo");return ts(T,a.basises,o.ORDER)}let nt=jt((T,m)=>{let{X:y,Y:E,Z:v}=T;if(r.eql(v,r.ONE))return{x:y,y:E};let H=T.is0();m==null&&(m=H?r.ONE:r.inv(v));let R=r.mul(y,m),A=r.mul(E,m),g=r.mul(v,m);if(H)return{x:r.ZERO,y:r.ZERO};if(!r.eql(g,r.ONE))throw new Error("invZ was invalid");return{x:R,y:A}}),No=jt(T=>{if(T.is0()){if(e.allowInfinityPoint&&!r.is0(T.Y))return;throw new Error("bad point: ZERO")}let{x:m,y}=T.toAffine();if(!r.isValid(m)||!r.isValid(y))throw new Error("bad point: x or y not field elements");if(!w(m,y))throw new Error("bad point: equation left != right");if(!T.isTorsionFree())throw new Error("bad point: not in prime-order subgroup");return!0});function Un(T,m,y,E,v){return y=new ge(r.mul(y.X,T),y.Y,y.Z),m=Me(E,m),y=Me(v,y),m.add(y)}let k=class k{constructor(m,y,E){l(this,"X");l(this,"Y");l(this,"Z");this.X=U("x",m),this.Y=U("y",y,!0),this.Z=U("z",E),Object.freeze(this)}static CURVE(){return s}static fromAffine(m){let{x:y,y:E}=m||{};if(!m||!r.isValid(y)||!r.isValid(E))throw new Error("invalid affine point");if(m instanceof k)throw new Error("projective point not allowed");return r.is0(y)&&r.is0(E)?k.ZERO:new k(y,E,r.ONE)}static fromBytes(m){let y=k.fromAffine(b(I(m,void 0,"point")));return y.assertValidity(),y}static fromHex(m){return k.fromBytes(De(m))}get x(){return this.toAffine().x}get y(){return this.toAffine().y}precompute(m=8,y=!0){return Ue.createCache(this,m),y||this.multiply(lt),this}assertValidity(){No(this)}hasEvenY(){let{y:m}=this.toAffine();if(!r.isOdd)throw new Error("Field doesn't support isOdd");return!r.isOdd(m)}equals(m){et(m);let{X:y,Y:E,Z:v}=this,{X:H,Y:R,Z:A}=m,g=r.eql(r.mul(y,A),r.mul(H,v)),S=r.eql(r.mul(E,A),r.mul(R,v));return g&&S}negate(){return new k(this.X,r.neg(this.Y),this.Z)}double(){let{a:m,b:y}=s,E=r.mul(y,lt),{X:v,Y:H,Z:R}=this,A=r.ZERO,g=r.ZERO,S=r.ZERO,B=r.mul(v,v),$=r.mul(H,H),O=r.mul(R,R),C=r.mul(v,H);return C=r.add(C,C),S=r.mul(v,R),S=r.add(S,S),A=r.mul(m,S),g=r.mul(E,O),g=r.add(A,g),A=r.sub($,g),g=r.add($,g),g=r.mul(A,g),A=r.mul(C,A),S=r.mul(E,S),O=r.mul(m,O),C=r.sub(B,O),C=r.mul(m,C),C=r.add(C,S),S=r.add(B,B),B=r.add(S,B),B=r.add(B,O),B=r.mul(B,C),g=r.add(g,B),O=r.mul(H,R),O=r.add(O,O),B=r.mul(O,C),A=r.sub(A,B),S=r.mul(O,$),S=r.add(S,S),S=r.add(S,S),new k(A,g,S)}add(m){et(m);let{X:y,Y:E,Z:v}=this,{X:H,Y:R,Z:A}=m,g=r.ZERO,S=r.ZERO,B=r.ZERO,$=s.a,O=r.mul(s.b,lt),C=r.mul(y,H),j=r.mul(E,R),K=r.mul(v,A),Be=r.add(y,E),D=r.add(H,R);Be=r.mul(Be,D),D=r.add(C,j),Be=r.sub(Be,D),D=r.add(y,v);let Q=r.add(H,A);return D=r.mul(D,Q),Q=r.add(C,K),D=r.sub(D,Q),Q=r.add(E,v),g=r.add(R,A),Q=r.mul(Q,g),g=r.add(j,K),Q=r.sub(Q,g),B=r.mul($,D),g=r.mul(O,K),B=r.add(g,B),g=r.sub(j,B),B=r.add(j,B),S=r.mul(g,B),j=r.add(C,C),j=r.add(j,C),K=r.mul($,K),D=r.mul(O,D),j=r.add(j,K),K=r.sub(C,K),K=r.mul($,K),D=r.add(D,K),C=r.mul(j,D),S=r.add(S,C),C=r.mul(Q,D),g=r.mul(Be,g),g=r.sub(g,C),C=r.mul(Be,j),B=r.mul(Q,B),B=r.add(B,C),new k(g,S,B)}subtract(m){return this.add(m.negate())}is0(){return this.equals(k.ZERO)}multiply(m){let{endo:y}=e;if(!o.isValidNot0(m))throw new Error("invalid scalar: out of range");let E,v,H=R=>Ue.cached(this,R,A=>Qt(k,A));if(y){let{k1neg:R,k1:A,k2neg:g,k2:S}=tt(m),{p:B,f:$}=H(A),{p:O,f:C}=H(S);v=$.add(C),E=Un(y.beta,B,O,R,g)}else{let{p:R,f:A}=H(m);E=R,v=A}return Qt(k,[E,v])[0]}multiplyUnsafe(m){let{endo:y}=e,E=this;if(!o.isValid(m))throw new Error("invalid scalar: out of range");if(m===Ve||E.is0())return k.ZERO;if(m===ft)return E;if(Ue.hasCache(this))return this.multiply(m);if(y){let{k1neg:v,k1:H,k2neg:R,k2:A}=tt(m),{p1:g,p2:S}=lr(k,E,H,A);return Un(y.beta,g,S,v,R)}else return Ue.unsafe(E,m)}toAffine(m){return nt(this,m)}isTorsionFree(){let{isTorsionFree:m}=e;return i===ft?!0:m?m(k,this):Ue.unsafe(this,c).is0()}clearCofactor(){let{clearCofactor:m}=e;return i===ft?this:m?m(k,this):this.multiplyUnsafe(i)}isSmallOrder(){return this.multiplyUnsafe(i).is0()}toBytes(m=!0){return qt(m,"isCompressed"),this.assertValidity(),d(k,this,m)}toHex(m=!0){return He(this.toBytes(m))}toString(){return`<Point ${this.is0()?"ZERO":this.toHex()}>`}};l(k,"BASE",new k(s.Gx,s.Gy,r.ONE)),l(k,"ZERO",new k(r.ZERO,r.ONE,r.ZERO)),l(k,"Fp",r),l(k,"Fn",o);let ge=k,Pn=o.BITS,Ue=new at(ge,e.endo?Math.ceil(Pn/2):Pn);return ge.BASE.precompute(8),ge}function os(t){return Uint8Array.of(t?2:3)}function ss(t,e){return{secretKey:e.BYTES,publicKey:1+t.BYTES,publicKeyUncompressed:1+2*t.BYTES,publicKeyHasPrefix:!0,signature:2*e.BYTES}}var ht={p:BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"),n:BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"),h:BigInt(1),a:BigInt(0),b:BigInt(7),Gx:BigInt("0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"),Gy:BigInt("0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8")},is={beta:BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"),basises:[[BigInt("0x3086d221a7d46bcde86c90e49284eb15"),-BigInt("0xe4437ed6010e88286f547fa90abfe4c3")],[BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8"),BigInt("0x3086d221a7d46bcde86c90e49284eb15")]]},cs=BigInt(0),tn=BigInt(2);function as(t){let e=ht.p,n=BigInt(3),r=BigInt(6),o=BigInt(11),s=BigInt(22),i=BigInt(23),c=BigInt(44),a=BigInt(88),u=t*t*t%e,h=u*u*t%e,f=V(h,n,e)*h%e,p=V(f,n,e)*h%e,d=V(p,tn,e)*u%e,b=V(d,o,e)*d%e,x=V(b,s,e)*b%e,w=V(x,c,e)*x%e,L=V(w,a,e)*w%e,_=V(L,c,e)*x%e,U=V(_,n,e)*h%e,et=V(U,i,e)*b%e,tt=V(et,r,e)*u%e,nt=V(tt,tn,e);if(!ut.eql(ut.sqr(nt),t))throw new Error("Cannot find square root");return nt}var ut=Le(ht.p,{sqrt:as}),Ie=dr(ht,{Fp:ut,endo:is});var hr={};function dt(t,...e){let n=hr[t];if(n===void 0){let r=Mt(Fn(t));n=pe(r,r),hr[t]=n}return Mt(pe(n,...e))}var rn=t=>t.toBytes(!0).slice(1),on=t=>t%tn===cs;function nn(t){let{Fn:e,BASE:n}=Ie,r=e.fromBytes(t),o=n.multiply(r);return{scalar:on(o.y)?r:e.neg(r),bytes:rn(o)}}function xr(t){let e=ut;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);on(o)||(o=e.neg(o));let s=Ie.fromAffine({x:t,y:o});return s.assertValidity(),s}var We=Ce;function br(...t){return Ie.Fn.create(We(dt("BIP0340/challenge",...t)))}function pr(t){return nn(t).bytes}function ls(t,e,n=Ne(32)){let{Fn:r}=Ie,o=I(t,void 0,"message"),{bytes:s,scalar:i}=nn(e),c=I(n,32,"auxRand"),a=r.toBytes(i^We(dt("BIP0340/aux",c))),u=dt("BIP0340/nonce",a,s,o),{bytes:h,scalar:f}=nn(u),p=br(h,s,o),d=new Uint8Array(64);if(d.set(h,0),d.set(r.toBytes(r.create(f+p*i)),32),!mr(d,o,s))throw new Error("sign: Invalid signature produced");return d}function mr(t,e,n){let{Fp:r,Fn:o,BASE:s}=Ie,i=I(t,64,"signature"),c=I(e,void 0,"message"),a=I(n,32,"publicKey");try{let u=xr(We(a)),h=We(i.subarray(0,32));if(!r.isValidNot0(h))return!1;let f=We(i.subarray(32,64));if(!o.isValidNot0(f))return!1;let p=br(o.toBytes(h),rn(u),c),d=s.multiplyUnsafe(f).add(u.multiplyUnsafe(o.neg(p))),{x:b,y:x}=d.toAffine();return!(d.is0()||!on(x)||b!==h)}catch{return!1}}var G=(()=>{let n=(r=Ne(48))=>rr(r,ht.n);return{keygen:en(n,pr),getPublicKey:pr,sign:ls,verify:mr,Point:Ie,utils:{randomSecretKey:n,taggedHash:dt,lift_x:xr,pointToBytes:rn},lengths:{secretKey:32,publicKey:32,publicKeyHasPrefix:!1,signature:64,seed:48}}})();function fs(t){return t instanceof Uint8Array||ArrayBuffer.isView(t)&&t.constructor.name==="Uint8Array"}function pt(t,e,n=""){let r=fs(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 sn(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 gr(t,e){pt(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 qe(...t){for(let e=0;e<t.length;e++)t[e].fill(0)}function xt(t){return new DataView(t.buffer,t.byteOffset,t.byteLength)}function Y(t,e){return t<<32-e|t>>>e}var wr=typeof Uint8Array.from([]).toHex=="function"&&typeof Uint8Array.fromHex=="function",us=Array.from({length:256},(t,e)=>e.toString(16).padStart(2,"0"));function W(t){if(pt(t),wr)return t.toHex();let e="";for(let n=0;n<t.length;n++)e+=us[t[n]];return e}var ce={_0:48,_9:57,A:65,F:70,a:97,f:102};function yr(t){if(t>=ce._0&&t<=ce._9)return t-ce._0;if(t>=ce.A&&t<=ce.F)return t-(ce.A-10);if(t>=ce.a&&t<=ce.f)return t-(ce.a-10)}function ve(t){if(typeof t!="string")throw new Error("hex string expected, got "+typeof t);if(wr)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=yr(t.charCodeAt(s)),c=yr(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 Er(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)}var vr=t=>({oid:Uint8Array.from([6,9,96,134,72,1,101,3,4,2,t])});var ae=Symbol("verified"),ds=t=>t instanceof Object;function cn(t){if(!ds(t)||typeof t.kind!="number"||typeof t.content!="string"||typeof t.created_at!="number"||typeof t.pubkey!="string"||!t.pubkey.match(/^[a-f0-9]{64}$/)||!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}function Ar(t,e,n){return t&e^~t&n}function Sr(t,e,n){return t&e^t&n^e&n}var bt=class{constructor(e,n,r,o){l(this,"blockLen");l(this,"outputLen");l(this,"padOffset");l(this,"isLE");l(this,"buffer");l(this,"view");l(this,"finished",!1);l(this,"length",0);l(this,"pos",0);l(this,"destroyed",!1);this.blockLen=e,this.outputLen=n,this.padOffset=r,this.isLE=o,this.buffer=new Uint8Array(e),this.view=xt(this.buffer)}update(e){sn(this),pt(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=xt(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){sn(this),gr(e,this),this.finished=!0;let{buffer:n,view:r,blockLen:o,isLE:s}=this,{pos:i}=this;n[i++]=128,qe(this.buffer.subarray(i)),this.padOffset>o-i&&(this.process(r,0),i=0);for(let f=i;f<o;f++)n[f]=0;r.setBigUint64(o-8,BigInt(this.length*8),s),this.process(r,0);let c=xt(e),a=this.outputLen;if(a%4)throw new Error("_sha2: outputLen must be aligned to 32bit");let u=a/4,h=this.get();if(u>h.length)throw new Error("_sha2: outputLen bigger than state");for(let f=0;f<u;f++)c.setUint32(4*f,h[f],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()}},le=Uint32Array.from([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]);var hs=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]),be=new Uint32Array(64),an=class extends bt{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 f=0;f<16;f++,n+=4)be[f]=e.getUint32(n,!1);for(let f=16;f<64;f++){let p=be[f-15],d=be[f-2],b=Y(p,7)^Y(p,18)^p>>>3,x=Y(d,17)^Y(d,19)^d>>>10;be[f]=x+be[f-7]+b+be[f-16]|0}let{A:r,B:o,C:s,D:i,E:c,F:a,G:u,H:h}=this;for(let f=0;f<64;f++){let p=Y(c,6)^Y(c,11)^Y(c,25),d=h+p+Ar(c,a,u)+hs[f]+be[f]|0,x=(Y(r,2)^Y(r,13)^Y(r,22))+Sr(r,o,s)|0;h=u,u=a,a=c,c=i+d|0,i=s,s=o,o=r,r=d+x|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,u=u+this.G|0,h=h+this.H|0,this.set(r,o,s,i,c,a,u,h)}roundClean(){qe(be)}destroy(){this.set(0,0,0,0,0,0,0,0),qe(this.buffer)}},ln=class extends an{constructor(){super(32);l(this,"A",le[0]|0);l(this,"B",le[1]|0);l(this,"C",le[2]|0);l(this,"D",le[3]|0);l(this,"E",le[4]|0);l(this,"F",le[5]|0);l(this,"G",le[6]|0);l(this,"H",le[7]|0)}};var Rr=Er(()=>new ln,vr(1));var $e=new TextDecoder("utf-8"),fn=new TextEncoder;function Ae(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}`)}}var dn=class{generateSecretKey(){return G.utils.randomSecretKey()}getPublicKey(e){return W(G.getPublicKey(e))}finalizeEvent(e,n){let r=e;return r.pubkey=W(G.getPublicKey(n)),r.id=un(r),r.sig=W(G.sign(ve(un(r)),n)),r[ae]=!0,r}verifyEvent(e){if(typeof e[ae]=="boolean")return e[ae];let n=un(e);if(n!==e.id)return e[ae]=!1,!1;try{let r=G.verify(ve(e.sig),ve(n),ve(e.pubkey));return e[ae]=r,r}catch{return e[ae]=!1,!1}}};function ps(t){if(!cn(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 un(t){let e=Rr(fn.encode(ps(t)));return W(e)}var mt=new dn,Xi=mt.generateSecretKey,Qi=mt.getPublicKey,ec=mt.finalizeEvent,Br=mt.verifyEvent;var Tr=22242;function xs(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 Hr(t,e){for(let n=0;n<t.length;n++)if(xs(t[n],e))return!0;return!1}function Cr(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 Lr(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 kr(t,e){return{kind:Tr,created_at:Math.floor(Date.now()/1e3),tags:[["relay",t],["challenge",e]],content:""}}var yt=class extends Error{constructor(e,n){super(`Tried to send message '${e} on a closed connection to ${n}.`),this.name="SendingOnClosedConnection"}},gt=class t{constructor(e,n){l(this,"url");l(this,"_connected",!1);l(this,"onclose",null);l(this,"onnotice",e=>console.debug(`NOTICE from ${this.url}: ${e}`));l(this,"onauth");l(this,"baseEoseTimeout",4400);l(this,"publishTimeout",4400);l(this,"pingFrequency",29e3);l(this,"pingTimeout",2e4);l(this,"resubscribeBackoff",[1e4,1e4,1e4,2e4,2e4,3e4,6e4]);l(this,"openSubs",new Map);l(this,"enablePing");l(this,"enableReconnect");l(this,"idleSince",Date.now());l(this,"ongoingOperations",0);l(this,"reconnectTimeoutHandle");l(this,"pingIntervalHandle");l(this,"reconnectAttempts",0);l(this,"skipReconnection",!1);l(this,"connectionPromise");l(this,"openCountRequests",new Map);l(this,"openEventPublishes",new Map);l(this,"ws");l(this,"challenge");l(this,"authPromise");l(this,"serial",0);l(this,"verifyEvent");l(this,"_WebSocket");this.url=Ae(e),this.verifyEvent=n.verifyEvent,this._WebSocket=n.websocketImplementation||WebSocket,this.enablePing=n.enablePing,this.enableReconnect=n.enableReconnect||!1}static async connect(e,n){let r=new t(e,n);return await r.connect(n),r}closeAllSubscriptions(e){for(let[n,r]of this.openSubs)r.close(e);this.openSubs.clear();for(let[n,r]of this.openEventPublishes)r.reject(new Error(e));this.openEventPublishes.clear();for(let[n,r]of this.openCountRequests)r.reject(new Error(e));this.openCountRequests.clear()}get connected(){return this._connected}async reconnect(){let e=this.resubscribeBackoff[Math.min(this.reconnectAttempts,this.resubscribeBackoff.length-1)];this.reconnectAttempts++,this.reconnectTimeoutHandle=setTimeout(async()=>{try{await this.connect()}catch{}},e)}handleHardClose(e){this.pingIntervalHandle&&(clearInterval(this.pingIntervalHandle),this.pingIntervalHandle=void 0),this._connected=!1,this.connectionPromise=void 0,this.idleSince=void 0,this.enableReconnect&&!this.skipReconnection?this.reconnect():(this.onclose?.(),this.closeAllSubscriptions(e))}async connect(e){let n;return this.connectionPromise?this.connectionPromise:(this.challenge=void 0,this.authPromise=void 0,this.skipReconnection=!1,this.connectionPromise=new Promise((r,o)=>{e?.timeout&&(n=setTimeout(()=>{o("connection timed out"),this.connectionPromise=void 0,this.skipReconnection=!0,this.onclose?.(),this.handleHardClose("relay connection timed out")},e.timeout)),e?.abort&&(e.abort.onabort=o);try{this.ws=new this._WebSocket(this.url)}catch(s){clearTimeout(n),o(s);return}this.ws.onopen=()=>{this.reconnectTimeoutHandle&&(clearTimeout(this.reconnectTimeoutHandle),this.reconnectTimeoutHandle=void 0),clearTimeout(n),this._connected=!0;let s=this.reconnectAttempts>0;this.reconnectAttempts=0;for(let i of this.openSubs.values()){if(i.eosed=!1,s)for(let c=0;c<i.filters.length;c++)i.lastEmitted&&(i.filters[c].since=i.lastEmitted+1);i.fire()}this.enablePing&&(this.pingIntervalHandle=setInterval(()=>this.pingpong(),this.pingFrequency)),r()},this.ws.onerror=()=>{clearTimeout(n),o("connection failed"),this.connectionPromise=void 0,this.skipReconnection=!0,this.onclose?.(),this.handleHardClose("relay connection failed")},this.ws.onclose=s=>{clearTimeout(n),o(s.message||"websocket closed"),this.handleHardClose("relay connection closed")},this.ws.onmessage=this._onmessage.bind(this)}),this.connectionPromise)}waitForPingPong(){return new Promise(e=>{this.ws.once("pong",()=>e(!0)),this.ws.ping()})}waitForDummyReq(){return new Promise((e,n)=>{if(!this.connectionPromise)return n(new Error(`no connection to ${this.url}, can't ping`));try{let r=this.subscribe([{ids:["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],limit:0}],{label:"<forced-ping>",oneose:()=>{e(!0),r.close()},onclose(){e(!0)},eoseTimeout:this.pingTimeout+1e3})}catch(r){n(r)}})}async pingpong(){this.ws?.readyState===1&&(await Promise.any([this.ws&&this.ws.ping&&this.ws.once?this.waitForPingPong():this.waitForDummyReq(),new Promise(n=>setTimeout(()=>n(!1),this.pingTimeout))])||this.ws?.readyState===this._WebSocket.OPEN&&this.ws?.close())}async send(e){if(!this.connectionPromise)throw new yt(e,this.url);this.connectionPromise.then(()=>{this.ws?.send(e)})}async auth(e){let n=this.challenge;if(!n)throw new Error("can't perform auth, no challenge was received");return this.authPromise?this.authPromise:(this.authPromise=new Promise(async(r,o)=>{try{let s=await e(kr(this.url,n)),i=setTimeout(()=>{let c=this.openEventPublishes.get(s.id);c&&(c.reject(new Error("auth timed out")),this.openEventPublishes.delete(s.id))},this.publishTimeout);this.openEventPublishes.set(s.id,{resolve:r,reject:o,timeout:i}),this.send('["AUTH",'+JSON.stringify(s)+"]")}catch(s){console.warn("subscribe auth function failed:",s)}}),this.authPromise)}async publish(e){this.idleSince=void 0,this.ongoingOperations++;let n=new Promise((r,o)=>{let s=setTimeout(()=>{let i=this.openEventPublishes.get(e.id);i&&(i.reject(new Error("publish timed out")),this.openEventPublishes.delete(e.id))},this.publishTimeout);this.openEventPublishes.set(e.id,{resolve:r,reject:o,timeout:s})});return this.send('["EVENT",'+JSON.stringify(e)+"]"),this.ongoingOperations--,this.ongoingOperations===0&&(this.idleSince=Date.now()),n}async count(e,n){this.serial++;let r=n?.id||"count:"+this.serial,o=new Promise((s,i)=>{this.openCountRequests.set(r,{resolve:s,reject:i})});return this.send('["COUNT","'+r+'",'+JSON.stringify(e).substring(1)),o}subscribe(e,n){n.label!=="<forced-ping>"&&(this.idleSince=void 0,this.ongoingOperations++);let r=this.prepareSubscription(e,n);return r.fire(),n.abort&&(n.abort.onabort=()=>r.close(String(n.abort.reason||"<aborted>"))),r}prepareSubscription(e,n){this.serial++;let r=n.id||(n.label?n.label+":":"sub:")+this.serial,o=new hn(this,r,e,n);return this.openSubs.set(r,o),o}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.idleSince=void 0,this.onclose?.(),this.ws?.readyState===this._WebSocket.OPEN&&this.ws?.close()}_onmessage(e){let n=e.data;if(!n)return;let r=Lr(n);if(r){let o=this.openSubs.get(r);if(!o)return;let s=Cr(n,"id"),i=o.alreadyHaveEvent?.(s);if(o.receivedEvent?.(this,s),i)return}try{let o=JSON.parse(n);switch(o[0]){case"EVENT":{let s=this.openSubs.get(o[1]),i=o[2];this.verifyEvent(i)&&Hr(s.filters,i)&&s.onevent(i),(!s.lastEmitted||s.lastEmitted<i.created_at)&&(s.lastEmitted=i.created_at);return}case"COUNT":{let s=o[1],i=o[2],c=this.openCountRequests.get(s);c&&(c.resolve(i.count),this.openCountRequests.delete(s));return}case"EOSE":{let s=this.openSubs.get(o[1]);if(!s)return;s.receivedEose();return}case"OK":{let s=o[1],i=o[2],c=o[3],a=this.openEventPublishes.get(s);a&&(clearTimeout(a.timeout),i?a.resolve(c):a.reject(new Error(c)),this.openEventPublishes.delete(s));return}case"CLOSED":{let s=o[1],i=this.openSubs.get(s);if(!i)return;i.closed=!0,i.close(o[2]);return}case"NOTICE":{this.onnotice(o[1]);return}case"AUTH":{this.challenge=o[1],this.onauth&&this.auth(this.onauth);return}default:{this.openSubs.get(o[1])?.oncustom?.(o);return}}}catch(o){let[s,i,c]=JSON.parse(n);window.printer.maybe(c.pubkey,":: caught err",c,this.url,o);return}}},hn=class{constructor(e,n,r,o){l(this,"relay");l(this,"id");l(this,"lastEmitted");l(this,"closed",!1);l(this,"eosed",!1);l(this,"filters");l(this,"alreadyHaveEvent");l(this,"receivedEvent");l(this,"onevent");l(this,"oneose");l(this,"onclose");l(this,"oncustom");l(this,"eoseTimeout");l(this,"eoseTimeoutHandle");if(r.length===0)throw new Error("subscription can't be created with zero filters");this.relay=e,this.filters=r,this.id=n,this.alreadyHaveEvent=o.alreadyHaveEvent,this.receivedEvent=o.receivedEvent,this.eoseTimeout=o.eoseTimeout||e.baseEoseTimeout,this.oneose=o.oneose,this.onclose=o.onclose,this.onevent=o.onevent||(s=>{console.warn(`onevent() callback not defined for subscription '${this.id}' in relay ${this.relay.url}. event received:`,s)})}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(e="closed by caller"){if(!this.closed&&this.relay.connected){try{this.relay.send('["CLOSE",'+JSON.stringify(this.id)+"]")}catch(n){if(!(n instanceof yt))throw n}this.closed=!0}this.relay.openSubs.delete(this.id),this.relay.ongoingOperations--,this.relay.ongoingOperations===0&&(this.relay.idleSince=Date.now()),this.onclose?.(e)}};var Ir=t=>(t[ae]=!0,!0);var wt=class{constructor(e){l(this,"relays",new Map);l(this,"seenOn",new Map);l(this,"trackRelays",!1);l(this,"verifyEvent");l(this,"enablePing");l(this,"enableReconnect");l(this,"automaticallyAuth");l(this,"trustedRelayURLs",new Set);l(this,"onRelayConnectionFailure");l(this,"onRelayConnectionSuccess");l(this,"allowConnectingToRelay");l(this,"maxWaitForConnection");l(this,"_WebSocket");this.verifyEvent=e.verifyEvent,this._WebSocket=e.websocketImplementation,this.enablePing=e.enablePing,this.enableReconnect=e.enableReconnect||!1,this.automaticallyAuth=e.automaticallyAuth,this.onRelayConnectionFailure=e.onRelayConnectionFailure,this.onRelayConnectionSuccess=e.onRelayConnectionSuccess,this.allowConnectingToRelay=e.allowConnectingToRelay,this.maxWaitForConnection=e.maxWaitForConnection||3e3}async ensureRelay(e,n){e=Ae(e);let r=this.relays.get(e);if(r||(r=new gt(e,{verifyEvent:this.trustedRelayURLs.has(e)?Ir:this.verifyEvent,websocketImplementation:this._WebSocket,enablePing:this.enablePing,enableReconnect:this.enableReconnect}),r.onclose=()=>{this.relays.delete(e)},this.relays.set(e,r)),this.automaticallyAuth){let o=this.automaticallyAuth(e);o&&(r.onauth=o)}try{await r.connect({timeout:n?.connectionTimeout,abort:n?.abort})}catch(o){throw this.relays.delete(e),o}return r}close(e){e.map(Ae).forEach(n=>{this.relays.get(n)?.close(),this.relays.delete(n)})}subscribe(e,n,r){let o=[],s=[];for(let i=0;i<e.length;i++){let c=Ae(e[i]);o.find(a=>a.url===c)||s.indexOf(c)===-1&&(s.push(c),o.push({url:c,filter:n}))}return this.subscribeMap(o,r)}subscribeMany(e,n,r){return this.subscribe(e,n,r)}subscribeMap(e,n){let r=new Map;for(let d of e){let{url:b,filter:x}=d;r.has(b)||r.set(b,[]),r.get(b).push(x)}let o=Array.from(r.entries()).map(([d,b])=>({url:d,filters:b}));this.trackRelays&&(n.receivedEvent=(d,b)=>{let x=this.seenOn.get(b);x||(x=new Set,this.seenOn.set(b,x)),x.add(d)});let s=new Set,i=[],c=[],a=d=>{c[d]||(c[d]=!0,c.filter(b=>b).length===o.length&&(n.oneose?.(),a=()=>{}))},u=[],h=(d,b)=>{u[d]||(a(d),u[d]=b,u.filter(x=>x).length===o.length&&(n.onclose?.(u),h=()=>{}))},f=d=>{if(n.alreadyHaveEvent?.(d))return!0;let b=s.has(d);return s.add(d),b},p=Promise.all(o.map(async({url:d,filters:b},x)=>{if(this.allowConnectingToRelay?.(d,["read",b])===!1){h(x,"connection skipped by allowConnectingToRelay");return}let w;try{w=await this.ensureRelay(d,{connectionTimeout:this.maxWaitForConnection<(n.maxWait||0)?Math.max(n.maxWait*.8,n.maxWait-1e3):this.maxWaitForConnection,abort:n.abort})}catch(_){this.onRelayConnectionFailure?.(d),h(x,_?.message||String(_));return}this.onRelayConnectionSuccess?.(d);let L=w.subscribe(b,{...n,oneose:()=>a(x),onclose:_=>{_.startsWith("auth-required: ")&&n.onauth?w.auth(n.onauth).then(()=>{w.subscribe(b,{...n,oneose:()=>a(x),onclose:U=>{h(x,U)},alreadyHaveEvent:f,eoseTimeout:n.maxWait,abort:n.abort})}).catch(U=>{h(x,`auth was required and attempted, but failed with: ${U}`)}):h(x,_)},alreadyHaveEvent:f,eoseTimeout:n.maxWait,abort:n.abort});i.push(L)}));return{async close(d){await p,i.forEach(b=>{b.close(d)})}}}subscribeEose(e,n,r){let o=this.subscribe(e,n,{...r,oneose(){o.close("closed automatically on eose")}});return o}subscribeManyEose(e,n,r){return this.subscribeEose(e,n,r)}async querySync(e,n,r){return new Promise(async o=>{let s=[];this.subscribeEose(e,n,{...r,onevent(i){s.push(i)},onclose(i){o(s)}})})}async get(e,n,r){n.limit=1;let o=await this.querySync(e,n,r);return o.sort((s,i)=>i.created_at-s.created_at),o[0]||null}publish(e,n,r){return e.map(Ae).map(async(o,s,i)=>{if(i.indexOf(o)!==s)return Promise.reject("duplicate url");if(this.allowConnectingToRelay?.(o,["write",n])===!1)return Promise.reject("connection skipped by allowConnectingToRelay");let c;try{c=await this.ensureRelay(o,{connectionTimeout:this.maxWaitForConnection<(r?.maxWait||0)?Math.max(r.maxWait*.8,r.maxWait-1e3):this.maxWaitForConnection,abort:r?.abort})}catch(a){return this.onRelayConnectionFailure?.(o),"connection failure: "+String(a)}return c.publish(n).catch(async a=>{if(a instanceof Error&&a.message.startsWith("auth-required: ")&&r?.onauth)return await c.auth(r.onauth),c.publish(n);throw a}).then(a=>{if(this.trackRelays){let u=this.seenOn.get(n.id);u||(u=new Set,this.seenOn.set(n.id,u)),u.add(c)}return a})})}listConnectionStatus(){let e=new Map;return this.relays.forEach((n,r)=>e.set(r,n.connected)),e}destroy(){this.relays.forEach(e=>e.close()),this.relays=new Map}pruneIdleRelays(e=1e4){let n=[];for(let[r,o]of this.relays)o.idleSince&&Date.now()-o.idleSince>=e&&(this.relays.delete(r),n.push(r),o.close());return n}};var _r;try{_r=WebSocket}catch{}var _e=class extends wt{constructor(e){super({verifyEvent:Br,websocketImplementation:_r,maxWaitForConnection:3e3,...e})}};var bs=Math.pow(2,8)-1,ms=Math.pow(2,16)-1,ys=Math.pow(2,32)-1,Cc=Math.pow(2,7)-1,Lc=Math.pow(2,15)-1,kc=Math.pow(2,31)-1;var Or=function(t){var e=t-1;if(e<=bs)return Uint8Array;if(e<=ms)return Uint16Array;if(e<=ys)return Uint32Array;throw new Error("mnemonist: Pointer Array of size > 4294967295 is not supported.")};function ee(t){if(this.capacity=t,typeof this.capacity!="number"||this.capacity<=0)throw new Error("mnemonist/lru-cache: capacity should be positive number.");if(!isFinite(this.capacity)||Math.floor(this.capacity)!==this.capacity)throw new Error("mnemonist/lru-cache: capacity should be a finite positive integer.");var e=Or(t);this.forward=new e(t),this.backward=new e(t),this.K=new Array(t),this.V=new Array(t),this.size=0,this.head=0,this.tail=0,this.items={}}ee.prototype.clear=function(){this.size=0,this.head=0,this.tail=0,this.items={}};ee.prototype.splayOnTop=function(t){var e=this.head;if(this.head===t)return this;var n=this.backward[t],r=this.forward[t];return this.tail===t?this.tail=n:this.backward[r]=n,this.forward[n]=r,this.backward[e]=t,this.head=t,this.forward[t]=e,this};ee.prototype.set=function(t,e){var n=this.items[t];if(typeof n<"u"){this.splayOnTop(n),this.V[n]=e;return}this.size<this.capacity?n=this.size++:(n=this.tail,this.tail=this.backward[n],delete this.items[this.K[n]]),this.items[t]=n,this.K[n]=t,this.V[n]=e,this.forward[n]=this.head,this.backward[this.head]=n,this.head=n};ee.prototype.setpop=function(t,e){var n=null,r=null,o=this.items[t];return typeof o<"u"?(this.splayOnTop(o),n=this.V[o],this.V[o]=e,{evicted:!1,key:t,value:n}):(this.size<this.capacity?o=this.size++:(o=this.tail,this.tail=this.backward[o],n=this.V[o],r=this.K[o],delete this.items[r]),this.items[t]=o,this.K[o]=t,this.V[o]=e,this.forward[o]=this.head,this.backward[this.head]=o,this.head=o,r?{evicted:!0,key:r,value:n}:null)};ee.prototype.has=function(t){return t in this.items};ee.prototype.get=function(t){var e=this.items[t];if(!(typeof e>"u"))return this.splayOnTop(e),this.V[e]};ee.prototype.peek=function(t){var e=this.items[t];if(!(typeof e>"u"))return this.V[e]};var xn=class{constructor(e,n){l(this,"_batchLoadFn");l(this,"_maxBatchSize");l(this,"_cacheKeyFn");l(this,"_cacheMap");l(this,"_batch");if(typeof e!="function")throw new TypeError(`DataLoader must be constructed with a function which accepts Array<key> and returns Promise<Array<value>>, but got: ${e}.`);this._batchLoadFn=e,this._maxBatchSize=n?.maxBatchSize||1/0,this._cacheKeyFn=n?.cacheKeyFn;let r=new ee(2e3);this._cacheMap={get:n.transformCacheHit?o=>r.get(o)?.then(n.transformCacheHit):o=>r.get(o),set(o,s){return r.set(o,s)},delete(o){r.set(o,void 0)},clear(){r.clear()}},this._batch=null}load(e){if(e==null)throw new TypeError(`The loader.load() function must be called with a value, but got: ${String(e)}.`);let n=ws(this),r=this._cacheKeyFn(e),o=this._cacheMap.get(r);if(o){let i=n.cacheHits||(n.cacheHits=[]);return new Promise(c=>{i.push(()=>{c(o)})})}n.keys.push(e);let s=new Promise((i,c)=>{n.callbacks.push({resolve:i,reject:c})});return this._cacheMap.set(r,s),s}clear(e){let n=this._cacheMap;if(n){let r=this._cacheKeyFn(e);n.delete(r)}return this}};function ws(t){let e=t._batch;if(e!==null&&!e.hasDispatched&&e.keys.length<t._maxBatchSize)return e;let n={hasDispatched:!1,keys:[],callbacks:[]};return t._batch=n,setTimeout(()=>{Es(t,n)}),n}function Es(t,e){if(e.hasDispatched=!0,e.keys.length===0){bn(e);return}let n;try{n=t._batchLoadFn(e.keys)}catch(r){return pn(t,e,new TypeError(`DataLoader must be constructed with a function which accepts Array<key> and returns Promise<Array<value>>, but the function errored synchronously: ${String(r)}.`))}if(!n||typeof n.then!="function")return pn(t,e,new TypeError(`DataLoader must be constructed with a function which accepts Array<key> and returns Promise<Array<value>>, but the function did not return a Promise: ${String(n)}.`));n.then(r=>{if(r.length!==e.keys.length)throw new TypeError(`DataLoader must be constructed with a function which accepts Array<key> and returns Promise<Array<value>>, but the function did not return a Promise of an Array of the same length as the Array of keys. | | 203 | + | | 204 | +Keys: | | 205 | +${String(e.keys)} | | 206 | + | | 207 | +Values: | | 208 | +${String(r)}`);bn(e);for(let o=0;o<e.callbacks.length;o++){let s=r[o];s instanceof Error?e.callbacks[o].reject(s):e.callbacks[o].resolve(s)}}).catch(r=>{pn(t,e,r)})}function pn(t,e,n){bn(e);for(let r=0;r<e.keys.length;r++)t.clear(e.keys[r]),e.callbacks[r].reject(n)}function bn(t){if(t.cacheHits)for(let e=0;e<t.cacheHits.length;e++)t.cacheHits[e]()}var Et=xn;function vs(t){return t instanceof Uint8Array||ArrayBuffer.isView(t)&&t.constructor.name==="Uint8Array"}function vt(t,e,n=""){let r=vs(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 mn(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 Pr(t,e){vt(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 Ge(...t){for(let e=0;e<t.length;e++)t[e].fill(0)}function At(t){return new DataView(t.buffer,t.byteOffset,t.byteLength)}function J(t,e){return t<<32-e|t>>>e}var Dr=typeof Uint8Array.from([]).toHex=="function"&&typeof Uint8Array.fromHex=="function",As=Array.from({length:256},(t,e)=>e.toString(16).padStart(2,"0"));function q(t){if(vt(t),Dr)return t.toHex();let e="";for(let n=0;n<t.length;n++)e+=As[t[n]];return e}var fe={_0:48,_9:57,A:65,F:70,a:97,f:102};function Ur(t){if(t>=fe._0&&t<=fe._9)return t-fe._0;if(t>=fe.A&&t<=fe.F)return t-(fe.A-10);if(t>=fe.a&&t<=fe.f)return t-(fe.a-10)}function Se(t){if(typeof t!="string")throw new Error("hex string expected, got "+typeof t);if(Dr)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=Ur(t.charCodeAt(s)),c=Ur(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 Nr(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)}var Mr=t=>({oid:Uint8Array.from([6,9,96,134,72,1,101,3,4,2,t])});function $r(t){return t instanceof Uint8Array||ArrayBuffer.isView(t)&&t.constructor.name==="Uint8Array"}function Gr(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 Ss(t){if(typeof t!="function")throw new Error("function expected");return!0}function je(t,e){if(typeof e!="string")throw new Error(`${t}: string expected`);return!0}function Fr(t){if(!Number.isSafeInteger(t))throw new Error(`invalid integer: ${t}`)}function yn(t){if(!Array.isArray(t))throw new Error("array expected")}function jr(t,e){if(!Gr(!0,e))throw new Error(`${t}: array of strings expected`)}function Kr(t,e){if(!Gr(!1,e))throw new Error(`${t}: array of numbers expected`)}function Rs(...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 Bs(t){let e=typeof t=="string"?t.split(""):t,n=e.length;jr("alphabet",e);let r=new Map(e.map((o,s)=>[o,s]));return{encode:o=>(yn(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=>(yn(o),o.map(s=>{je("alphabet.decode",s);let i=r.get(s);if(i===void 0)throw new Error(`Unknown letter: "${s}". Allowed: ${t}`);return i}))}}function Ts(t=""){return je("join",t),{encode:e=>(jr("join.decode",e),e.join(t)),decode:e=>(je("join.decode",e),e.split(t))}}var zr=(t,e)=>e===0?t:zr(e,t%e),Rt=(t,e)=>t+(e-zr(t,e)),St=(()=>{let t=[];for(let e=0;e<40;e++)t.push(2**e);return t})();function gn(t,e,n,r){if(yn(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(Rt(e,n)>32)throw new Error(`convertRadix2: carry overflow from=${e} to=${n} carryBits=${Rt(e,n)}`);let o=0,s=0,i=St[e],c=St[n]-1,a=[];for(let u of t){if(Fr(u),u>=i)throw new Error(`convertRadix2: invalid data word=${u} from=${e}`);if(o=o<<e|u,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 h=St[s];if(h===void 0)throw new Error("invalid carry");o&=h-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 Hs(t,e=!1){if(Fr(t),t<=0||t>32)throw new Error("radix2: bits should be in (0..32]");if(Rt(8,t)>32||Rt(t,8)>32)throw new Error("radix2: carry overflow");return{encode:n=>{if(!$r(n))throw new Error("radix2.encode input should be Uint8Array");return gn(Array.from(n),8,t,!e)},decode:n=>(Kr("radix2.decode",n),Uint8Array.from(gn(n,t,8,e)))}}function Vr(t){return Ss(t),function(...e){try{return t.apply(null,e)}catch{}}}var wn=Rs(Bs("qpzry9x8gf2tvdw0s3jn54khce6mua7l"),Ts("")),Wr=[996825010,642813549,513874426,1027748829,705979059];function Fe(t){let e=t>>25,n=(t&33554431)<<5;for(let r=0;r<Wr.length;r++)(e>>r&1)===1&&(n^=Wr[r]);return n}function qr(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=Fe(o)^i>>5}o=Fe(o);for(let s=0;s<r;s++)o=Fe(o)^t.charCodeAt(s)&31;for(let s of e)o=Fe(o)^s;for(let s=0;s<6;s++)o=Fe(o);return o^=n,wn.encode(gn([o%St[30]],30,5,!1))}function Cs(t){let e=t==="bech32"?1:734539939,n=Hs(5),r=n.decode,o=n.encode,s=Vr(r);function i(f,p,d=90){je("bech32.encode prefix",f),$r(p)&&(p=Array.from(p)),Kr("bech32.encode",p);let b=f.length;if(b===0)throw new TypeError(`Invalid prefix length ${b}`);let x=b+7+p.length;if(d!==!1&&x>d)throw new TypeError(`Length ${x} exceeds limit ${d}`);let w=f.toLowerCase(),L=qr(w,p,e);return`${w}1${wn.encode(p)}${L}`}function c(f,p=90){je("bech32.decode input",f);let d=f.length;if(d<8||p!==!1&&d>p)throw new TypeError(`invalid string length: ${d} (${f}). Expected (8..${p})`);let b=f.toLowerCase();if(f!==b&&f!==f.toUpperCase())throw new Error("String must be lowercase or uppercase");let x=b.lastIndexOf("1");if(x===0||x===-1)throw new Error('Letter "1" must be present between prefix and data only');let w=b.slice(0,x),L=b.slice(x+1);if(L.length<6)throw new Error("Data must be at least 6 characters long");let _=wn.decode(L).slice(0,-6),U=qr(w,_,e);if(!L.endsWith(U))throw new Error(`Invalid checksum in ${f}: expected "${U}"`);return{prefix:w,words:_}}let a=Vr(c);function u(f){let{prefix:p,words:d}=c(f,!1);return{prefix:p,words:d,bytes:r(d)}}function h(f,p){return i(f,o(p))}return{encode:i,decode:c,encodeFromBytes:h,decodeToBytes:u,decodeUnsafe:a,fromWords:r,fromWordsUnsafe:s,toWords:o}}var te=Cs("bech32");var Ke=new TextDecoder("utf-8"),En=new TextEncoder;function M(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 vn(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 Zr=5e3;function Sn(t){let{prefix:e,words:n}=te.decode(t,Zr),r=new Uint8Array(te.fromWords(n));switch(e){case"nprofile":{let o=An(r);if(!o[0]?.[0])throw new Error("missing TLV 0 for nprofile");if(o[0][0].length!==32)throw new Error("TLV 0 should be 32 bytes");return{type:"nprofile",data:{pubkey:q(o[0][0]),relays:o[1]?o[1].map(s=>Ke.decode(s)):[]}}}case"nevent":{let o=An(r);if(!o[0]?.[0])throw new Error("missing TLV 0 for nevent");if(o[0][0].length!==32)throw new Error("TLV 0 should be 32 bytes");if(o[2]&&o[2][0].length!==32)throw new Error("TLV 2 should be 32 bytes");if(o[3]&&o[3][0].length!==4)throw new Error("TLV 3 should be 4 bytes");return{type:"nevent",data:{id:q(o[0][0]),relays:o[1]?o[1].map(s=>Ke.decode(s)):[],author:o[2]?.[0]?q(o[2][0]):void 0,kind:o[3]?.[0]?parseInt(q(o[3][0]),16):void 0}}}case"naddr":{let o=An(r);if(!o[0]?.[0])throw new Error("missing TLV 0 for naddr");if(!o[2]?.[0])throw new Error("missing TLV 2 for naddr");if(o[2][0].length!==32)throw new Error("TLV 2 should be 32 bytes");if(!o[3]?.[0])throw new Error("missing TLV 3 for naddr");if(o[3][0].length!==4)throw new Error("TLV 3 should be 4 bytes");return{type:"naddr",data:{identifier:Ke.decode(o[0][0]),pubkey:q(o[2][0]),kind:parseInt(q(o[3][0]),16),relays:o[1]?o[1].map(s=>Ke.decode(s)):[]}}}case"nsec":return{type:e,data:r};case"npub":case"note":return{type:e,data:q(r)};default:throw new Error(`unknown prefix ${e}`)}}function An(t){let e={},n=t;for(;n.length>0;){if(n.length<2)throw new Error("not enough data to read TLV");let r=n[0],o=n[1],s=n.slice(2,2+o);if(n=n.slice(2+o),s.length<o)throw new Error(`not enough data to read on TLV ${r}`);e[r]=e[r]||[],e[r].push(s)}return e}function Rn(t){return ks("npub",Se(t))}function Ls(t,e){let n=te.toWords(e);return te.encode(t,n,Zr)}function ks(t,e){return Ls(t,e)}var Re=Symbol("verified"),Is=t=>t instanceof Object;function Bn(t){if(!Is(t)||typeof t.kind!="number"||typeof t.content!="string"||typeof t.created_at!="number"||typeof t.pubkey!="string"||!vn(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}function Yr(t,e,n){return t&e^~t&n}function Jr(t,e,n){return t&e^t&n^e&n}var Bt=class{constructor(e,n,r,o){l(this,"blockLen");l(this,"outputLen");l(this,"padOffset");l(this,"isLE");l(this,"buffer");l(this,"view");l(this,"finished",!1);l(this,"length",0);l(this,"pos",0);l(this,"destroyed",!1);this.blockLen=e,this.outputLen=n,this.padOffset=r,this.isLE=o,this.buffer=new Uint8Array(e),this.view=At(this.buffer)}update(e){mn(this),vt(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=At(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){mn(this),Pr(e,this),this.finished=!0;let{buffer:n,view:r,blockLen:o,isLE:s}=this,{pos:i}=this;n[i++]=128,Ge(this.buffer.subarray(i)),this.padOffset>o-i&&(this.process(r,0),i=0);for(let f=i;f<o;f++)n[f]=0;r.setBigUint64(o-8,BigInt(this.length*8),s),this.process(r,0);let c=At(e),a=this.outputLen;if(a%4)throw new Error("_sha2: outputLen must be aligned to 32bit");let u=a/4,h=this.get();if(u>h.length)throw new Error("_sha2: outputLen bigger than state");for(let f=0;f<u;f++)c.setUint32(4*f,h[f],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()}},ue=Uint32Array.from([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]);var _s=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]),me=new Uint32Array(64),Tn=class extends Bt{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 f=0;f<16;f++,n+=4)me[f]=e.getUint32(n,!1);for(let f=16;f<64;f++){let p=me[f-15],d=me[f-2],b=J(p,7)^J(p,18)^p>>>3,x=J(d,17)^J(d,19)^d>>>10;me[f]=x+me[f-7]+b+me[f-16]|0}let{A:r,B:o,C:s,D:i,E:c,F:a,G:u,H:h}=this;for(let f=0;f<64;f++){let p=J(c,6)^J(c,11)^J(c,25),d=h+p+Yr(c,a,u)+_s[f]+me[f]|0,x=(J(r,2)^J(r,13)^J(r,22))+Jr(r,o,s)|0;h=u,u=a,a=c,c=i+d|0,i=s,s=o,o=r,r=d+x|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,u=u+this.G|0,h=h+this.H|0,this.set(r,o,s,i,c,a,u,h)}roundClean(){Ge(me)}destroy(){this.set(0,0,0,0,0,0,0,0),Ge(this.buffer)}},Hn=class extends Tn{constructor(){super(32);l(this,"A",ue[0]|0);l(this,"B",ue[1]|0);l(this,"C",ue[2]|0);l(this,"D",ue[3]|0);l(this,"E",ue[4]|0);l(this,"F",ue[5]|0);l(this,"G",ue[6]|0);l(this,"H",ue[7]|0)}};var Xr=Nr(()=>new Hn,Mr(1));var Ln=class{generateSecretKey(){return G.utils.randomSecretKey()}getPublicKey(e){return q(G.getPublicKey(e))}finalizeEvent(e,n){let r=e;return r.pubkey=q(G.getPublicKey(n)),r.id=Cn(r),r.sig=q(G.sign(Se(Cn(r)),n)),r[Re]=!0,r}verifyEvent(e){if(typeof e[Re]=="boolean")return e[Re];try{let n=Cn(e);if(n!==e.id)return e[Re]=!1,!1;let r=G.verify(Se(e.sig),Se(n),Se(e.pubkey));return e[Re]=r,r}catch{return e[Re]=!1,!1}}};function Os(t){if(!Bn(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 Cn(t){let e=Xr(En.encode(Os(t)));return q(e)}var Tt=new Ln,ca=Tt.generateSecretKey,aa=Tt.getPublicKey,la=Tt.finalizeEvent,Qr=Tt.verifyEvent;var eo=22242;function Us(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 to(t,e){for(let n=0;n<t.length;n++)if(Us(t[n],e))return!0;return!1}function no(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 ro(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 oo(t,e){return{kind:eo,created_at:Math.floor(Date.now()/1e3),tags:[["relay",t],["challenge",e]],content:""}}var ze=class extends Error{constructor(e,n){super(`Tried to send message '${e} on a closed connection to ${n}.`),this.name="SendingOnClosedConnection"}},Ht=class t{constructor(e,n){l(this,"url");l(this,"_connected",!1);l(this,"onclose",null);l(this,"onnotice",e=>console.debug(`NOTICE from ${this.url}: ${e}`));l(this,"onauth");l(this,"baseEoseTimeout",4400);l(this,"publishTimeout",4400);l(this,"pingFrequency",29e3);l(this,"pingTimeout",2e4);l(this,"resubscribeBackoff",[1e4,1e4,1e4,2e4,2e4,3e4,6e4]);l(this,"openSubs",new Map);l(this,"enablePing");l(this,"enableReconnect");l(this,"idleTimeout",0);l(this,"idleSince",Date.now());l(this,"ongoingOperations",0);l(this,"reconnectTimeoutHandle");l(this,"pingIntervalHandle");l(this,"reconnectAttempts",0);l(this,"skipReconnection",!1);l(this,"idleTimeoutHandle");l(this,"connectionPromise");l(this,"openCountRequests",new Map);l(this,"openEventPublishes",new Map);l(this,"ws");l(this,"challenge");l(this,"authPromise");l(this,"serial",0);l(this,"verifyEvent");l(this,"_WebSocket");this.url=M(e),this.verifyEvent=n.verifyEvent,this._WebSocket=n.websocketImplementation||WebSocket,this.enablePing=n.enablePing,this.enableReconnect=n.enableReconnect||!1,n.idleTimeout&&(this.idleTimeout=n.idleTimeout)}static async connect(e,n){let r=new t(e,n);return await r.connect(n),r}closeAllSubscriptions(e){for(let[n,r]of this.openSubs)r.close(e);this.openSubs.clear();for(let[n,r]of this.openEventPublishes)r.reject(new Error(e));this.openEventPublishes.clear();for(let[n,r]of this.openCountRequests)r.reject(new Error(e));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 e=this.resubscribeBackoff[Math.min(this.reconnectAttempts,this.resubscribeBackoff.length-1)];this.reconnectAttempts++,this.reconnectTimeoutHandle=setTimeout(async()=>{try{await this.connect()}catch{}},e)}handleHardClose(e){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(e))}async connect(e){let n;return this.connectionPromise?this.connectionPromise:(this.challenge=void 0,this.authPromise=void 0,this.skipReconnection=!1,this.connectionPromise=new Promise((r,o)=>{e?.timeout&&(n=setTimeout(()=>{o("connection timed out"),this.connectionPromise=void 0,this.reconnectAttempts===0&&(this.skipReconnection=!0),this.ws?.close?.(),this.handleHardClose("relay connection timed out")},e.timeout)),e?.abort&&(e.abort.onabort=o);try{this.ws=new this._WebSocket(this.url)}catch(s){clearTimeout(n),o(s);return}this.ws.onopen=()=>{this.reconnectTimeoutHandle&&(clearTimeout(this.reconnectTimeoutHandle),this.reconnectTimeoutHandle=void 0),clearTimeout(n),this._connected=!0;let s=this.reconnectAttempts>0;this.reconnectAttempts=0;for(let i of this.openSubs.values()){if(i.eosed=!1,s)for(let c=0;c<i.filters.length;c++)i.lastEmitted&&(i.filters[c].since=i.lastEmitted+1);i.fire()}this.enablePing&&(this.pingIntervalHandle=setInterval(()=>this.pingpong(),this.pingFrequency)),r()},this.ws.onerror=()=>{clearTimeout(n),o("connection failed"),this.connectionPromise=void 0,this.reconnectAttempts===0&&(this.skipReconnection=!0),this.ws?.close?.(),this.handleHardClose("relay connection failed")},this.ws.onclose=s=>{clearTimeout(n),o(s.message||"websocket closed"),this.handleHardClose("relay connection closed")},this.ws.onmessage=this._onmessage.bind(this)}),this.connectionPromise)}waitForPingPong(){return new Promise(e=>{this.ws.once("pong",()=>e(!0)),this.ws.ping()})}waitForDummyReq(){return new Promise((e,n)=>{if(!this.connectionPromise)return n(new Error(`no connection to ${this.url}, can't ping`));try{let r=this.subscribe([{ids:["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],limit:1}],{label:"<forced-ping>",oneose:()=>{e(!0),r.close()},onclose(){e(!0)},eoseTimeout:this.pingTimeout+1e3})}catch(r){n(r)}})}async pingpong(){this.ws?.readyState===1&&(await Promise.any([this.ws&&this.ws.ping&&this.ws.once?this.waitForPingPong():this.waitForDummyReq(),new Promise(n=>setTimeout(()=>n(!1),this.pingTimeout))])||this.ws?.readyState===this._WebSocket.OPEN&&this.ws?.close())}async send(e){if(!this.connectionPromise)throw new ze(e,this.url);this.connectionPromise.then(()=>{this.ws?.send(e)})}async auth(e){let n=this.challenge;if(!n)throw new Error("can't perform auth, no challenge was received");return this.authPromise?this.authPromise:(this.authPromise=new Promise(async(r,o)=>{try{let s=await e(oo(this.url,n)),i=setTimeout(()=>{let c=this.openEventPublishes.get(s.id);c&&(c.reject(new Error("auth timed out")),this.openEventPublishes.delete(s.id))},this.publishTimeout);this.openEventPublishes.set(s.id,{resolve:r,reject:o,timeout:i}),this.send('["AUTH",'+JSON.stringify(s)+"]")}catch(s){console.warn("subscribe auth function failed:",s)}}),this.authPromise)}async publish(e){this.idleSince=void 0,this.clearIdleTimeout(),this.ongoingOperations++;let n=new Promise((r,o)=>{let s=setTimeout(()=>{let i=this.openEventPublishes.get(e.id);i&&(i.reject(new Error("publish timed out")),this.openEventPublishes.delete(e.id))},this.publishTimeout);this.openEventPublishes.set(e.id,{resolve:r,reject:o,timeout:s})});try{await this.send('["EVENT",'+JSON.stringify(e)+"]")}catch(r){let o=this.openEventPublishes.get(e.id);o&&(o.reject(r),this.openEventPublishes.delete(e.id))}return this.ongoingOperations--,this.ongoingOperations===0&&(this.idleSince=Date.now(),this.scheduleIdleClose()),n}async count(e,n){return(await this.countWithHLL(e,n)).count}async countWithHLL(e,n){this.serial++;let r=n?.id||"count:"+this.serial,o=new Promise((s,i)=>{this.openCountRequests.set(r,{resolve:s,reject:i})});try{await this.send('["COUNT","'+r+'",'+JSON.stringify(e).substring(1))}catch(s){let i=this.openCountRequests.get(r);i&&(i.reject(s),this.openCountRequests.delete(r))}return o}subscribe(e,n){n.label!=="<forced-ping>"&&(this.idleSince=void 0,this.clearIdleTimeout(),this.ongoingOperations++);let r=this.prepareSubscription(e,n);return r.fire(),n.abort&&(n.abort.onabort=()=>r.close(String(n.abort.reason||"<aborted>"))),r}prepareSubscription(e,n){this.serial++;let r=n.id||(n.label?n.label+":":"sub:")+this.serial,o=new kn(this,r,e,n);return this.openSubs.set(r,o),o}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(e){let n=e.data;if(!n)return;let r=ro(n);if(r){let o=this.openSubs.get(r);if(!o)return;let s=no(n,"id"),i=o.alreadyHaveEvent?.(s);if(o.receivedEvent?.(this,s),i)return}try{let o=JSON.parse(n);switch(o[0]){case"EVENT":{let s=this.openSubs.get(o[1]),i=o[2];to(s.filters,i)&&this.verifyEvent(i,this.url)?s.onevent(i):s.oninvalidevent?.(i),(!s.lastEmitted||s.lastEmitted<i.created_at)&&(s.lastEmitted=i.created_at);return}case"COUNT":{let s=o[1],i=o[2],c=this.openCountRequests.get(s);c&&(c.resolve(i),this.openCountRequests.delete(s));return}case"EOSE":{let s=this.openSubs.get(o[1]);if(!s)return;s.receivedEose();return}case"OK":{let s=o[1],i=o[2],c=o[3],a=this.openEventPublishes.get(s);a&&(clearTimeout(a.timeout),i?a.resolve(c):a.reject(new Error(c)),this.openEventPublishes.delete(s));return}case"CLOSED":{let s=o[1],i=this.openSubs.get(s);if(!i){let c=this.openCountRequests.get(s);c&&(c.reject(new Error(o[2])),this.openCountRequests.delete(s));return}i.closed=!0,i.close(o[2]);return}case"NOTICE":{this.onnotice(o[1]);return}case"AUTH":{this.challenge=o[1],this.onauth&&this.auth(this.onauth).catch(s=>{if(!(s instanceof ze))throw s});return}default:{this.openSubs.get(o[1])?.oncustom?.(o);return}}}catch(o){try{let[s,i,c]=JSON.parse(n);console.warn(`[nostr] relay ${this.url} error processing message:`,o,c)}catch{console.warn(`[nostr] relay ${this.url} error processing message:`,o)}return}}},kn=class{constructor(e,n,r,o){l(this,"relay");l(this,"id");l(this,"lastEmitted");l(this,"closed",!1);l(this,"eosed",!1);l(this,"filters");l(this,"alreadyHaveEvent");l(this,"receivedEvent");l(this,"onevent");l(this,"oninvalidevent");l(this,"oneose");l(this,"onclose");l(this,"oncustom");l(this,"eoseTimeout");l(this,"eoseTimeoutHandle");if(r.length===0)throw new Error("subscription can't be created with zero filters");this.relay=e,this.filters=r,this.id=n,this.alreadyHaveEvent=o.alreadyHaveEvent,this.receivedEvent=o.receivedEvent,this.eoseTimeout=o.eoseTimeout||e.baseEoseTimeout,this.oneose=o.oneose,this.onclose=o.onclose,this.oninvalidevent=o.oninvalidevent,this.onevent=o.onevent||(s=>{console.warn(`onevent() callback not defined for subscription '${this.id}' in relay ${this.relay.url}. event received:`,s)})}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(e="closed by caller"){if(!this.closed&&this.relay.connected){try{this.relay.send('["CLOSE",'+JSON.stringify(this.id)+"]")}catch(n){if(!(n instanceof ze))throw n}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?.(e)}};var de=256,Ps=de*2,Ha=new TextEncoder;function so(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 Ds(){return new Uint8Array(de)}function io(t){if(t.length!==Ps||!/^[0-9a-f]+$/.test(t))return;let e=new Uint8Array(de);for(let n=0;n<de;n++)e[n]=parseInt(t.slice(n*2,n*2+2),16);return e}function co(t){if(t.length!==de)throw new Error(`invalid number of registers ${t.length}`);let e="";for(let n=0;n<de;n++)e+=t[n].toString(16).padStart(2,"0");return e}function ao(t,e){if(t.length===0&&(t=Ds()),t.length!==de)throw new Error(`invalid number of registers ${t.length}`);if(e.length!==de)throw new Error(`invalid number of registers ${e.length}`);for(let n=0;n<de;n++)e[n]>t[n]&&(t[n]=e[n]);return t}var Ct=class{constructor(e){l(this,"relays",new Map);l(this,"seenOn",new Map);l(this,"trackRelays",!1);l(this,"verifyEvent");l(this,"enablePing");l(this,"enableReconnect");l(this,"idleTimeout",2e4);l(this,"automaticallyAuth");l(this,"onRelayConnectionFailure");l(this,"onRelayConnectionSuccess");l(this,"allowConnectingToRelay");l(this,"maxWaitForConnection");l(this,"_WebSocket");this.verifyEvent=e.verifyEvent,this._WebSocket=e.websocketImplementation,this.enablePing=e.enablePing,this.enableReconnect=e.enableReconnect||!1,e.idleTimeout&&(this.idleTimeout=e.idleTimeout),this.automaticallyAuth=e.automaticallyAuth,this.onRelayConnectionFailure=e.onRelayConnectionFailure,this.onRelayConnectionSuccess=e.onRelayConnectionSuccess,this.allowConnectingToRelay=e.allowConnectingToRelay,this.maxWaitForConnection=e.maxWaitForConnection||3e3}async ensureRelay(e,n){e=M(e);let r=this.relays.get(e);if(r||(r=new Ht(e,{verifyEvent:this.verifyEvent,websocketImplementation:this._WebSocket,enablePing:this.enablePing,enableReconnect:this.enableReconnect,idleTimeout:this.idleTimeout}),r.onclose=()=>{this.relays.delete(e)},this.relays.set(e,r)),this.automaticallyAuth){let o=this.automaticallyAuth(e);o&&(r.onauth=o)}try{await r.connect({timeout:n?.connectionTimeout,abort:n?.abort})}catch(o){throw this.relays.delete(e),o}return r}close(e){e.map(M).forEach(n=>{this.relays.get(n)?.close(),this.relays.delete(n)})}subscribe(e,n,r){let o=[],s=[];for(let i=0;i<e.length;i++){let c=M(e[i]);o.find(a=>a.url===c)||s.indexOf(c)===-1&&(s.push(c),o.push({url:c,filter:n}))}return this.subscribeMap(o,r)}subscribeMany(e,n,r){return this.subscribe(e,n,r)}subscribeMap(e,n){let r=new Map;for(let d of e){let{url:b,filter:x}=d;r.has(b)||r.set(b,[]),r.get(b).push(x)}let o=Array.from(r.entries()).map(([d,b])=>({url:d,filters:b}));this.trackRelays&&(n.receivedEvent=(d,b)=>{let x=this.seenOn.get(b);x||(x=new Set,this.seenOn.set(b,x)),x.add(d)});let s=new Set,i=[],c=[],a=d=>{c[d]||(c[d]=!0,c.filter(b=>b).length===o.length&&(n.oneose?.(),a=()=>{}))},u=[],h=(d,b,x)=>{u[d]||(a(d),u[d]={url:b,reason:x},u.filter(w=>w).length===o.length&&(n.onclose?.(u),h=()=>{}))},f=d=>{if(n.alreadyHaveEvent?.(d))return!0;let b=s.has(d);return s.add(d),b},p=Promise.all(o.map(async({url:d,filters:b},x)=>{if(this.allowConnectingToRelay?.(d,["read",b])===!1){h(x,d,"connection skipped by allowConnectingToRelay");return}let w;try{w=await this.ensureRelay(d,{connectionTimeout:this.maxWaitForConnection<(n.maxWait||0)?Math.max(n.maxWait*.8,n.maxWait-1e3):this.maxWaitForConnection,abort:n.abort})}catch(_){this.onRelayConnectionFailure?.(d),h(x,d,_?.message||String(_));return}this.onRelayConnectionSuccess?.(d);let L=w.subscribe(b,{...n,oneose:()=>a(x),onclose:_=>{_.startsWith("auth-required: ")&&n.onauth?w.auth(n.onauth).then(()=>{w.subscribe(b,{...n,oneose:()=>a(x),onclose:U=>{h(x,d,U)},alreadyHaveEvent:f,eoseTimeout:n.maxWait,abort:n.abort})}).catch(U=>{h(x,d,`auth was required and attempted, but failed with: ${U}`)}):h(x,d,_)},alreadyHaveEvent:f,eoseTimeout:n.maxWait,abort:n.abort});i.push(L)}));return{async close(d){await p,i.forEach(b=>{b.close(d)})}}}subscribeEose(e,n,r){let o;return o=this.subscribe(e,n,{...r,oneose(){let s="closed automatically on eose";o?o.close(s):r.onclose?.(e.map(i=>({url:i,reason:s})))}}),o}subscribeManyEose(e,n,r){return this.subscribeEose(e,n,r)}async querySync(e,n,r){return new Promise(async o=>{let s=[];this.subscribeEose(e,n,{...r,onevent(i){s.push(i)},onclose(i){o(s)}})})}async get(e,n,r){n.limit=1;let o=await this.querySync(e,n,r);return o.sort((s,i)=>i.created_at-s.created_at),o[0]||null}async countMany(e,n,r,o){let s=so(n,r),i=[];for(let h=0;h<e.length;h++){let f=M(e[h]);i.indexOf(f)===-1&&i.push(f)}let c=await Promise.all(i.map(async h=>{if(this.allowConnectingToRelay?.(h,["read",[s]])===!1)return null;let f;try{f=await this.ensureRelay(h,{connectionTimeout:this.maxWaitForConnection<(o?.maxWait||0)?Math.max(o.maxWait*.8,o.maxWait-1e3):this.maxWaitForConnection,abort:o?.abort})}catch{return this.onRelayConnectionFailure?.(h),null}return this.onRelayConnectionSuccess?.(h),f.countWithHLL([s],{id:o?.id}).catch(()=>null)})),a=0,u;for(let h of c){if(!h||(h.count>a&&(a=h.count),!h.hll||h.hll.length!==512))continue;let f=io(h.hll);f&&(u=ao(u||new Uint8Array(0),f))}return u?{count:a,hll:co(u)}:{count:a}}publish(e,n,r){return e.map(M).map(async(o,s,i)=>{if(i.indexOf(o)!==s)return Promise.reject("duplicate url");if(this.allowConnectingToRelay?.(o,["write",n])===!1)return Promise.reject("connection skipped by allowConnectingToRelay");let c;try{c=await this.ensureRelay(o,{connectionTimeout:this.maxWaitForConnection<(r?.maxWait||0)?Math.max(r.maxWait*.8,r.maxWait-1e3):this.maxWaitForConnection,abort:r?.abort})}catch(a){return this.onRelayConnectionFailure?.(o),Promise.reject("connection failure: "+String(a))}return c.publish(n).catch(async a=>{if(a instanceof Error&&a.message.startsWith("auth-required: ")&&r?.onauth)return await c.auth(r.onauth),c.publish(n);throw a}).then(a=>{if(this.trackRelays){let u=this.seenOn.get(n.id);u||(u=new Set,this.seenOn.set(n.id,u)),u.add(c)}return a})})}listConnectionStatus(){let e=new Map;return this.relays.forEach((n,r)=>e.set(r,n.connected)),e}destroy(){this.relays.forEach(e=>e.close()),this.relays=new Map}pruneIdleRelays(e=1e4){let n=[];for(let[r,o]of this.relays)o.idleSince&&Date.now()-o.idleSince>=e&&(this.relays.delete(r),n.push(r),o.close());return n}};var lo;try{lo=WebSocket}catch{}var Lt=class extends Ct{constructor(e){super({verifyEvent:Qr,websocketImplementation:lo,maxWaitForConnection:3e3,...e})}};var kt=class{constructor(e="@nostr/gadgets/purgatory"){l(this,"label");l(this,"interval");l(this,"state",{});l(this,"startTime");this.state=JSON.parse(window.localStorage.getItem(e)||"{}"),this.startTime=Math.round(Date.now()/1e3),this.label=e,this.interval=setInterval(this.save.bind(this),6e4)}close(){this.save(),clearInterval(this.interval)}save(){this.state.lastSave=Math.round(Date.now()/1e3),window.localStorage.setItem(this.label,JSON.stringify(this.state))}onRelayConnectionFailure(e){let n=this.state[e]||{failures:0,lastAttempt:0};n.failures++,n.lastAttempt=Math.round(Date.now()/1e3),this.state[e]=n}onRelayConnectionSuccess(e){delete this.state[e]}allowConnectingToRelay(e,n){if(!e)return!1;let r=this.state[e];if(!r)return!0;let o=r.failures*15*60;return(r.lastAttempt<this.startTime?r.lastAttempt+this.startTime-this.state.lastSave:r.lastAttempt)+o<Math.round(Date.now()/1e3)}};var fo="@nostr/gadgets/replaceables";function Ze(t){return t>=3e4&&t<4e4}var In=class{loadStorage(){let e=window.localStorage.getItem(fo);if(!e)return{};try{return JSON.parse(e)}catch{return{}}}saveStorage(e){window.localStorage.setItem(fo,JSON.stringify(e))}async loadReplaceables(e){let n=this.loadStorage();return e.map(([r,o,s])=>{let i=n[o];if(!i)return Ze(r)&&s===void 0?[void 0,[]]:[void 0,void 0];let c=i[r];if(!c)return Ze(r)&&s===void 0?[void 0,[]]:[void 0,void 0];if(Ze(r)&&s===void 0){let[a,u]=c;return[a,u]}else if(Ze(r)){let[a,u]=c,h=u.find(f=>(f.tags.find(p=>p[0]==="d")?.[1]||"")===s);return[a,h]}else{let[a,u]=c;return u&&u.id==="0".repeat(64)?[a,void 0]:[a,u]}})}async saveEvent(e,n){let r=this.loadStorage(),o=n?.lastAttempt||Math.round(Date.now()/1e3),s=e.kind,i=e.pubkey;if(r[i]||(r[i]={}),Ze(s)){let c=e.tags.find(h=>h[0]==="d")?.[1]||"",a=r[i][s];if(a||(a=[o,[]]),e.id==="0".repeat(64)&&e.created_at===0)return a[0]=o,r[i][s]=a,this.saveStorage(r),!1;let u=a[1].findIndex(h=>(h.tags.find(f=>f[0]==="d")?.[1]||"")===c);if(u>=0){if(a[1][u].created_at>=e.created_at)return a[0]=o,r[i][s]=a,this.saveStorage(r),!1;a[1][u]=e}else a[1].push(e);return a[0]=o,r[i][s]=a,this.saveStorage(r),!0}else{let c=r[i][s];return e.id==="0".repeat(64)&&e.created_at===0?(c?(c[0]=o,r[i][s]=c):r[i][s]=[o,e],this.saveStorage(r),!1):c&&c[1]&&c[1].created_at>=e.created_at?(c[0]=o,r[i][s]=c,this.saveStorage(r),!1):(r[i][s]=[o,e],this.saveStorage(r),!0)}}async deleteReplaceable(e,n){let r=this.loadStorage();r[n]&&(delete r[n][e],Object.keys(r[n]).length===0&&delete r[n],this.saveStorage(r))}},uo=new In;var ne=new kt,Ye=new Lt({allowConnectingToRelay:ne.allowConnectingToRelay.bind(ne),onRelayConnectionFailure:ne.onRelayConnectionFailure.bind(ne),onRelayConnectionSuccess:ne.onRelayConnectionSuccess.bind(ne)});function ho(t){Ye=t}var F=uo;var Je=["wss://purplepag.es","wss://relay.nos.social","wss://relay.primal.net"],po=["wss://purplepag.es","wss://relay.nos.social","wss://indexer.coracle.social"];function he(t){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}function xo(t){return!!t}var Ns=0,ye=Symbol("event was just downloaded or force-updated, not loaded from cache"),Qa=X(3,Je,re(t=>{if(t.length>=2&&t[0]==="p"&&he(t[1]))return t[1]})),el=X(10101,[],re(t=>{if(t.length>=2&&t[0]==="p"&&he(t[1]))return t[1]})),tl=X(10102,[],re(t=>{if(t.length>=2&&t[0]==="relay")return t[1]})),nl=X(10012,[],re(t=>{if(t.length>=2)switch(t[0]){case"relay":return M(t[1]);case"a":let e=t[1].split(":");return!he(e[1])||e[0]!=="30002"?void 0:{identifier:e.slice(2).join(":"),pubkey:e[1],kind:parseInt(e[0]),relays:t[2]?[t[2]]:[]}}})),Xe=X(10002,po,re(t=>{if(t[0]==="r"){if(t.length===2)return{url:M(t[1]),read:!0,write:!0};if(t[2]==="read")return{url:M(t[1]),read:!0,write:!1};if(t[2]==="write")return{url:M(t[1]),read:!1,write:!0}}})),rl=X(10027,[],re(t=>{if(t.length>=2&&t[0]==="e"&&he(t[1]))return{id:t[1],kind:1227,relays:t[2]?[M(t[2])]:void 0,author:he(t[3])?t[3]:void 0}})),ol=X(1e4,[],re(t=>{if(t.length>=2){switch(t[0]){case"p":if(he(t[1]))return{label:"pubkey",value:t[1]};break;case"e":if(he(t[1]))return{label:"thread",value:t[1]};break;case"t":return{label:"hashtag",value:t[1]};case"word":return{label:"word",value:t[1]}}return}})),sl=X(10003,[],re(t=>{if(t.length>=2&&(t[0]==="e"||t[0]==="a")&&t[1])return t[1]})),il=X(10063,[],t=>t?t.tags.filter(([e,n])=>e==="server"&&n).map(([,e])=>"http"+M(e).substring(2)).filter(Boolean):[]),cl=X(10030,[],re(t=>{if(!(t.length<2)){if(t[0]==="a"){let e=t[1].split(":");return!he(e[1])||e[0]!=="30030"?void 0:{identifier:e.slice(2).join(":"),pubkey:e[1],kind:parseInt(e[0]),relays:t[2]?[t[2]]:[]}}if(!(t.length<3||t[0]!=="emoji"))return{shortcode:t[1],url:t[2]}}})),al=X(10001,[],re(t=>{if(t.length>=2&&t[0]==="e"&&t[1])return t[1]}));function re(t){return e=>e?e.tags.map(t).filter(xo):[]}function X(t,e,n){let r=new Et(o=>new Promise(async s=>{let i={},c=[],a=Math.round(Date.now()/1e3),h=(await F.loadReplaceables(o.map(p=>[t,p.target,""]))).map(([p,d],b)=>{let x=o[b];if(x.index=b,typeof x.refreshStyle=="object"){let w={event:x.refreshStyle,items:n(x.refreshStyle),[ye]:!0};return c.push(F.saveEvent(x.refreshStyle,{lastAttempt:a})),w}else if(d)if(x.refreshStyle===!0||!p||p<a-3600*24*2){let w={event:d,items:n(d),[ye]:!1};return x.refreshStyle!==!1?new Promise(L=>{L(w),i[x.target]={req:x,resolve:L,resolved:!0}}):w}else return{event:d,items:n(d),[ye]:!1};else return x.refreshStyle!==!1?new Promise(w=>{i[x.target]={req:x,resolve:w,resolved:!1}}):{items:x.defaultItems||[],event:null,[ye]:!1}});if(c.length>0&&await Promise.all(c),s(h),Object.keys(i).length===0)return;let f={};for(let{req:{target:p,relays:d}}of Object.values(i)){let b=d.slice(0,Math.min(4,d.length));do b.push(Ms(e));while(b.length<3);for(let x=0;x<b.length;x++){let w=b[x],L=f[w];L||(L={kinds:[t],authors:[]},f[w]=L),L.authors?.push(p)}}try{let p;p=Ye.subscribeMap(Object.entries(f).map(([d,b])=>({url:d,filter:b})),{label:`kind:${t}:batch(${Object.keys(i).length})`,onevent(d){let{resolve:b,resolved:x}=i[d.pubkey];x||(b({event:d,items:n(d),[ye]:!0}),i[d.pubkey].resolved=!0,F.saveEvent(d,{lastAttempt:a}))},oneose(){p?.close()},async onclose(){for(let{req:d,resolve:b,resolved:x}of Object.values(i)){if(x)continue;let w={items:d.defaultItems||[],event:null,[ye]:!1};b(w),F.saveEvent({id:"0".repeat(64),pubkey:d.target,kind:t,sig:"0".repeat(128),tags:[],created_at:0,content:""},{lastAttempt:a})}}})}catch(p){s(h.map(d=>p))}}),{cacheKeyFn:o=>o.target,transformCacheHit(o){return o[ye]=!1,o}});return async function(o,s=[],i,c){if(i===null)return await F.deleteReplaceable(t,o),r._cacheMap.delete(o),{items:c||[],event:null,[ye]:!0};let a=s;if(t===10002){let u={target:o,relays:a,refreshStyle:i,defaultItems:c};return i&&r.clear(u),await r.load(u)}else{let u=await Xe(o,s,i);a.push(...u.items.filter(({write:f,url:p})=>f&&ne.allowConnectingToRelay(p,["read",[{kinds:[t]}]])).map(({url:f})=>f).slice(0,3));let h={target:o,relays:a,refreshStyle:i,defaultItems:c};return i&&r.clear(h),await r.load(h)}}}function Ms(t){return t[Ns++%t.length]}var bo=0;function oe(t){let e,n;if(t.startsWith("npub1")){let{data:r}=Sn(t);n=r,e=t}else if(t.startsWith("nprofile")){let{data:r}=Sn(t);n=r.pubkey,e=Rn(n)}else n=t,e=Rn(t);return{pubkey:n,npub:e,shortName:e.substring(0,8)+"\u2026"+e.substring(59),metadata:{},lastUpdated:0}}async function mo(t){return typeof t=="string"?It.load({pubkey:t}):t.refreshStyle===null?(await F.deleteReplaceable(0,t.pubkey),It._cacheMap.delete(t.pubkey),oe(t.pubkey)):(t.refreshStyle&&It.clear(t),It.load(t))}var It=new Et(async t=>new Promise(async e=>{let n={},r=[],o=Math.round(Date.now()/1e3),i=(await F.loadReplaceables(t.map(a=>[0,a.pubkey,""]))).map(([a,u],h)=>{let f=t[h];if(typeof f.refreshStyle=="object"){let p=oe(f.pubkey);return yo(p,f.refreshStyle),r.push(F.saveEvent(f.refreshStyle,{lastAttempt:o})),p}else if(u)if(f.refreshStyle===!0||!a||a<o-3600*24*2){let p=Qe(u);return f.refreshStyle!==!1?new Promise(d=>{d(p),n[f.pubkey]={req:f,resolve:d,resolved:!0}}):p}else{let p=Qe(u);return a<o-3600&&!p.metadata.name&&!p.metadata.picture&&!p.metadata.about&&f.refreshStyle!==!1?new Promise(d=>{n[f.pubkey]={req:f,resolve:d,resolved:!1}}):p}else return f.refreshStyle!==!1?new Promise(p=>{n[f.pubkey]={req:f,resolve:p,resolved:!1}}):oe(f.pubkey)});r.length>0&&await Promise.all(r),e(i);let c={};await Promise.all(Object.values(n).map(async({req:{pubkey:a,relays:u=[]}})=>{let h=new Set(u.slice(0,3));try{let{items:f}=await Xe(a),p=0;for(let d=0;d<f.length&&!(f[d].write&&ne.allowConnectingToRelay(f[d].url,["read",[{kinds:[0]}]])&&(h.add(f[d].url),p++,p>=2));d++);}catch(f){console.error("Failed to load relay list for",a,f)}do h.add(Je[bo%Je.length]),bo++;while(h.size<2);for(let f of h)c[f]?c[f].push(a):c[f]=[a]}));try{let a=Object.entries(c).map(([h,f])=>({url:h,filter:{kinds:[0],authors:f}})),u=Ye.subscribeMap(a,{label:`metadata(${t.length})`,onevent(h){let f=Qe(h);n[h.pubkey].resolve(f),n[h.pubkey].resolved=!0,F.saveEvent(h,{lastAttempt:o})},oneose(){u.close();for(let{req:h,resolve:f,resolved:p}of Object.values(n)){if(p)continue;let d=oe(h.pubkey);f(d),F.saveEvent({id:"0".repeat(64),pubkey:h.pubkey,kind:0,sig:"0".repeat(128),tags:[],created_at:0,content:""},{lastAttempt:o})}}})}catch(a){for(let u=0;u<i.length;u++)i[u]=a;r.length>0?Promise.all(r).then(()=>e(i)):e(i)}}),{cacheKeyFn:t=>t.pubkey});function yo(t,e){let n={};try{n=JSON.parse(e.content)}catch{}t.metadata=n,t.shortName=n.name||n.display_name||n.nip05?.split("@")?.[0]||t.shortName,t.lastUpdated=e.created_at,n.picture&&(t.image=n.picture)}function Qe(t){let e=oe(t.pubkey);return yo(e,t),e}var go=window.nostrSharedPool||new _e;window.nostrSharedPool=go;var wo=()=>{let t=(location.pathname.match(/^\/r\/[^/]+/)||[""])[0];return(location.protocol==="https:"?"wss://":"ws://")+location.host+t},_n="tiny:names:v1",Vs=360*60,Ws=600,Eo=50,qs=100,vo=()=>{try{return JSON.parse(localStorage.getItem(_n)||"{}")||{}}catch{return{}}},$s=t=>{try{localStorage.setItem(_n,JSON.stringify(t))}catch{}},Ao=new Map,Oe=new Map,_t=null,So=(t,e)=>e?.event?Qe(e.event):oe(t),Ro=async()=>{_t=null;let t=[...Oe.keys()].slice(0,qs);if(!t.length)return;let e=vo(),n=Math.floor(Date.now()/1e3),r=[];try{r=await go.querySync([wo()],{kinds:[0],authors:t})}catch{}let o=new Map;for(let s of r)(!o.has(s.pubkey)||o.get(s.pubkey).created_at<s.created_at)&&o.set(s.pubkey,s);for(let s of t){let i=o.get(s)||e[s]?.event||null;e[s]={event:i,at:n};let c=So(s,e[s]);Ao.set(s,c);for(let a of Oe.get(s)||[])a(c);Oe.delete(s)}$s(e),Oe.size&&(_t=setTimeout(Ro,Eo))},Bo=({pubkey:t})=>{let e=String(t||"").toLowerCase();if(!/^[0-9a-f]{64}$/.test(e))return Promise.resolve(oe(e||"0".repeat(64)));let n=vo()[e],r=Math.floor(Date.now()/1e3);return n&&r-n.at<(n.event?Vs:Ws)?Promise.resolve(Ao.get(e)||So(e,n)):new Promise(o=>{Oe.set(e,[...Oe.get(e)||[],o]),_t||(_t=setTimeout(Ro,Eo))})};window.nostrSharedMetadataLoader=window.nostrSharedMetadataLoader||(t=>Bo(typeof t=="string"?{pubkey:t}:t));window.tinyNames=Object.freeze({load:Bo,relayURL:wo,cacheKey:_n});var To=5e3;function Ho(t){let{prefix:e,words:n}=te.decode(t,To),r=new Uint8Array(te.fromWords(n));switch(e){case"nprofile":{let o=On(r);if(!o[0]?.[0])throw new Error("missing TLV 0 for nprofile");if(o[0][0].length!==32)throw new Error("TLV 0 should be 32 bytes");return{type:"nprofile",data:{pubkey:W(o[0][0]),relays:o[1]?o[1].map(s=>$e.decode(s)):[]}}}case"nevent":{let o=On(r);if(!o[0]?.[0])throw new Error("missing TLV 0 for nevent");if(o[0][0].length!==32)throw new Error("TLV 0 should be 32 bytes");if(o[2]&&o[2][0].length!==32)throw new Error("TLV 2 should be 32 bytes");if(o[3]&&o[3][0].length!==4)throw new Error("TLV 3 should be 4 bytes");return{type:"nevent",data:{id:W(o[0][0]),relays:o[1]?o[1].map(s=>$e.decode(s)):[],author:o[2]?.[0]?W(o[2][0]):void 0,kind:o[3]?.[0]?parseInt(W(o[3][0]),16):void 0}}}case"naddr":{let o=On(r);if(!o[0]?.[0])throw new Error("missing TLV 0 for naddr");if(!o[2]?.[0])throw new Error("missing TLV 2 for naddr");if(o[2][0].length!==32)throw new Error("TLV 2 should be 32 bytes");if(!o[3]?.[0])throw new Error("missing TLV 3 for naddr");if(o[3][0].length!==4)throw new Error("TLV 3 should be 4 bytes");return{type:"naddr",data:{identifier:$e.decode(o[0][0]),pubkey:W(o[2][0]),kind:parseInt(W(o[3][0]),16),relays:o[1]?o[1].map(s=>$e.decode(s)):[]}}}case"nsec":return{type:e,data:r};case"npub":case"note":return{type:e,data:W(r)};default:throw new Error(`unknown prefix ${e}`)}}function On(t){let e={},n=t;for(;n.length>0;){let r=n[0],o=n[1],s=n.slice(2,2+o);if(n=n.slice(2+o),s.length<o)throw new Error(`not enough data to read on TLV ${r}`);e[r]=e[r]||[],e[r].push(s)}return e}function Co(t){return Fs("npub",ve(t))}function Gs(t,e){let n=te.toWords(e);return te.encode(t,n,To)}function Fs(t,e){return Gs(t,e)}var js=/^(?:([\w.+-]+)@)?([\w_-]+(\.[\w_-]+)+)$/;var Lo;try{Lo=fetch}catch{}async function ko(t){let e=t.match(js);if(!e)return null;let[,n="_",r]=e;try{let o=`https://${r}/.well-known/nostr.json?name=${n}`,s=await Lo(o,{redirect:"manual"});if(s.status!==200)throw Error("Wrong response code");let i=await s.json(),c=i.names[n];return c?{pubkey:c,relays:i.relays?.[c]}:null}catch{return null}}var Io=window.nostrSharedPool||new _e;window.nostrSharedPool=Io;ho(Io);var Ks=window.nostrSharedRelayListLoader||Xe;window.nostrSharedRelayListLoader=Ks;var _o=window.nostrSharedMetadataLoader||mo;window.nostrSharedMetadataLoader=_o;async function Oo(t,e){return await _o({pubkey:t,relays:e})}async function Uo(t){try{let{type:e,data:n}=Ho(t);if(e==="nprofile")return[n.pubkey,n.relays||[]];if(e==="npub")return[n,[]]}catch{if(t.match(/[0-9a-f]{64}/))return[t,[]];if(t.match(".")){let e=await ko(t);return e?[e.pubkey,e.relays||[]]:[void 0]}}return[void 0]}function Po(t,e=100){let n,r,o;function s(){let i=Date.now()-r;i<e&&i>=0?n=setTimeout(s,e-i):(n=void 0,t(...o))}return function(...i){r=Date.now(),o=i,n||(n=setTimeout(s,e))}}var zs=Object.defineProperty,Zs=(t,e,n)=>e in t?zs(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,Do=(t,e,n)=>Zs(t,typeof e!="symbol"?e+"":e,n),Ot=class extends HTMLElement{constructor(){super(),Do(this,"set",Po(async()=>{let e=this.getAttribute("pubkey");if(e){this.textContent=oe(e).shortName;let[n,r]=await Uo(e);if(n){this.title=Co(n);let o=await Oo(n,r||[]);this.textContent=o.shortName}}},200)),this.set()}connectedCallback(){this.set()}attributeChangedCallback(){this.set()}};Do(Ot,"observedAttributes",["pubkey"]);window.customElements.define("nostr-name",Ot);})(); | | 209 | +/*! Bundled license information: | | 210 | + | | 211 | +@noble/hashes/utils.js: | | 212 | +@noble/hashes/utils.js: | | 213 | +@noble/hashes/utils.js: | | 214 | + (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *) | | 215 | + | | 216 | +@noble/curves/utils.js: | | 217 | +@noble/curves/abstract/modular.js: | | 218 | +@noble/curves/abstract/curve.js: | | 219 | +@noble/curves/abstract/weierstrass.js: | | 220 | +@noble/curves/secp256k1.js: | | 221 | + (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *) | | 222 | + | | 223 | +@scure/base/index.js: | | 224 | + (*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) *) | | 225 | +*/ | | 226 | diff --git a/internal/webui/page.html b/internal/webui/page.html | | 227 | index 06ae379..55f6981 100644 | | 228 | --- a/internal/webui/page.html | | 229 | +++ b/internal/webui/page.html | | 230 | @@ -66,7 +66,7 @@ | | 231 | {{end}}</div></td> | | 232 | <td id="aside"><div id="panel">{{template "panel" .}}</div></td> | | 233 | </tr></tbody></table> | | 234 | -<footer id="footer"><table><tr><td><span id="prompt"><b>{{.Slug}}</b> » {{prompt .Path .Query}}</span></td><td><span id="who">{{if .Private}}private relay{{else if .Actor}}signed in as {{template "nostrKey" .Actor}} (<button id="session-logout" type="button">sign out</button>){{else}}browsing as guest (<a href="/signin">Sign in</a>){{end}}</span> <span id="session-status" role="status"></span><span id="navigation-status" role="status" aria-live="polite"></span></td></tr></table><button id="install" type="button" hidden>install app</button></footer> | | 235 | +<footer id="footer"><table><tr><td><span id="prompt"><b>{{.Slug}}</b> » {{prompt .Path .Query}}</span></td><td><span id="who">{{if .Private}}private relay{{else if .Actor}}signed in as {{template "nostrName" .Actor}} (<button id="session-logout" type="button">sign out</button>){{else}}browsing as guest (<a href="/signin">Sign in</a>){{end}}</span> <span id="session-status" role="status"></span><span id="navigation-status" role="status" aria-live="polite"></span></td></tr></table><button id="install" type="button" hidden>install app</button></footer> | | 236 | <script src="{{scriptURL "tiny.js"}}"></script> | | 237 | <script src="{{scriptURL "bridge.js"}}"></script> | | 238 | {{if or (eq .Tab "files") (eq .Tab "file")}}<script src="{{scriptURL "blossom-encryption.js"}}"></script> | | 239 | @@ -75,6 +75,7 @@ | | 240 | <script src="{{scriptURL "file-messages.js"}}"></script> | | 241 | <script src="{{scriptURL "file-workspace.js"}}"></script> | | 242 | {{end}}<script src="{{scriptURL "components.js"}}"></script> | | 243 | +<script src="{{scriptURL "nostr-name.js"}}"></script> | | 244 | {{if eq .Tab "connect"}}<script src="{{scriptURL "private-services.js"}}"></script> | | 245 | {{end}}</body> | | 246 | </html> | | 247 | @@ -132,7 +133,7 @@ | | 248 | {{if and .Actor (eq .Tab "inbox")}}<section><h4>Notifications</h4><push-toggle></push-toggle><p><small>Relay notices for this key, on this device only.</small></p>{{if .Owner}}<rpc-form method="notifytest"><button>Send a test</button></rpc-form>{{end}}<noscript><p>Enabling notifications needs JavaScript.</p></noscript></section>{{end}}{{end}} | | 249 | | | 250 | {{define "panel-repo"}}{{$r := asMap .Event}}{{with index $r "repository"}}{{$r = asMap .}}{{end}}<section><h4>About</h4><table><tr><th>owner</th><td>{{template "nostrKey" (str (index $r "owner"))}}</td></tr><tr><th>visibility</th><td>{{if index $r "private"}}private{{else}}public{{end}}</td></tr>{{with index $r "commit"}}<tr><th>head</th><td><code title="{{.}}">{{shortID .}}</code></td></tr>{{end}}</table></section> | | 251 | -{{with index $r "maintainers"}}<section><h4>Maintainers</h4><ul id="maintainers">{{range .}}{{$m := asMap .}}<li>{{template "nostrKey" (str (index $m "pubkey"))}}{{$role := str (index $m "role")}}{{if eq $role "agent"}} <small>agent{{with str (index $m "name")}} {{.}}{{end}}</small>{{else if eq $role "owner"}} <small>owner</small>{{end}}</li>{{end}}</ul></section> | | 252 | +{{with index $r "maintainers"}}<section><h4>Maintainers</h4><ul id="maintainers">{{range .}}{{$m := asMap .}}<li>{{template "nostrName" (str (index $m "pubkey"))}}{{$role := str (index $m "role")}}{{if eq $role "agent"}} <small>agent{{with str (index $m "name")}} {{.}}{{end}}</small>{{else if eq $role "owner"}} <small>owner</small>{{end}}</li>{{end}}</ul></section> | | 253 | {{end}}<section><h4>Clone</h4><ul>{{range index $r "clone"}}<li><code>{{.}}</code></li>{{else}}<li>no clone URL advertised</li>{{end}}</ul></section> | | 254 | {{with index $r "refs"}}<section><h4>Refs</h4><form method="get" action="/repo"><input type="hidden" name="owner" value="{{$.Query.Get "owner"}}"><input type="hidden" name="repo" value="{{$.Query.Get "repo"}}"><input type="hidden" name="path" value="{{$.Query.Get "path"}}"><input type="hidden" name="id" value="{{$.Query.Get "id"}}"><input type="hidden" name="view" value="{{repoView $.Query}}"><label for="ref-select">branch or tag</label><select id="ref-select" name="ref"><option value="">default branch</option>{{range $name, $oid := .}}<option value="{{$name}}"{{if eq ($.Query.Get "ref") $name}} selected{{end}}>{{$name}}</option>{{end}}</select><button>go</button></form></section>{{end}}{{end}} | | 255 | | | 256 | @@ -149,7 +150,7 @@ | | 257 | {{else if .Error}}<section><h4>Room</h4><table><tr><th>id</th><td><code>{{.View}}</code></td></tr></table></section> | | 258 | {{else}}{{$data := asMap .Event}}{{$room := asMap (index $data "room")}}{{$id := str (index $room "id")}}{{$role := str (index $room "role")}}<section><h4>Room</h4><table><tr><th>id</th><td><code>{{$id}}</code></td></tr><tr><th>access</th><td>{{if eq (str (index $room "access")) "members"}}members only{{else}}open{{end}}</td></tr><tr><th>created</th><td>{{short (index $room "created_at")}}</td></tr>{{if $role}}<tr><th>you</th><td>{{$role}}</td></tr>{{end}}</table></section> | | 259 | {{if eq .Tab "thread"}}<section><h4>Actions</h4><ul><li><a href="/rooms/{{$id}}">open room</a></li></ul></section> | | 260 | -{{else}}<section><h4>Members</h4><ul id="members">{{range roomMembers .Event}}{{$m := asMap .}}<li{{if index $m "agent"}} data-agent{{end}}>{{template "nostrKey" (str (index $m "pubkey"))}} <small>{{str (index $m "role")}}{{if index $m "agent"}} | agent{{end}}</small></li>{{else}}<li>{{str (index $room "members")}} members</li>{{end}}</ul></section> | | 261 | +{{else}}<section><h4>Members</h4><ul id="members">{{range roomMembers .Event}}{{$m := asMap .}}<li{{if index $m "agent"}} data-agent{{end}}>{{template "nostrName" (str (index $m "pubkey"))}} <small>{{str (index $m "role")}}{{if index $m "agent"}} | agent{{end}}</small></li>{{else}}<li>{{str (index $room "members")}} members</li>{{end}}</ul></section> | | 262 | {{if .Actor}}<section><h4>Actions</h4>{{if roomAdmin $role}}<details><summary>invite member</summary><room-action room="{{$id}}" kind="9000"><label>pubkey <input name="pubkey" placeholder="npub or 64 hex" required></label><label>role <select name="role"><option value="member">member</option><option value="admin">admin</option>{{if eq $role "owner"}}<option value="owner">owner</option>{{end}}</select></label><button>Add member</button></room-action></details> | | 263 | <details><summary>room settings</summary><room-action room="{{$id}}" kind="9002"><label>name <input name="name" value="{{str (index $room "name")}}" required maxlength="200"></label><label>about <input name="about" value="{{str (index $room "about")}}" maxlength="2000"></label><label>picture <input name="picture" value="{{str (index $room "picture")}}" placeholder="https://"></label><label>access <select name="access"><option value="open"{{if ne (str (index $room "access")) "members"}} selected{{end}}>open</option><option value="members"{{if eq (str (index $room "access")) "members"}} selected{{end}}>members only</option></select></label><button>Save</button></room-action></details> | | 264 | {{end}}{{if $role}}<room-action room="{{$id}}" kind="9022"><button>Leave room</button></room-action>{{else}}<room-action room="{{$id}}" kind="9021"><button>Join room</button></room-action>{{end}}<noscript><p>Room actions need JavaScript and a connected signer.</p></noscript></section> | | 265 | @@ -160,11 +161,13 @@ | | 266 | {{define "alert"}}{{if .}}<p role="alert">{{.}}</p>{{end}}{{end}} | | 267 | | | 268 | {{define "nostrKey"}}<nostr-key hex="{{.}}" title="{{.}}">{{shortID .}}</nostr-key>{{end}} | | 269 | +{{/* nostrName shows a person: the vendored nostr-name element swaps the short id for the profile name published on this relay. */}} | | 270 | +{{define "nostrName"}}<nostr-name pubkey="{{.}}" title="{{.}}">{{shortID .}}</nostr-name>{{end}} | | 271 | | | 272 | {{define "jsonView"}}<json-view><pre>{{json .}}</pre></json-view>{{end}} | | 273 | | | 274 | {{define "nostrEvent"}}{{$e := asMap .}}<nostr-event id="event-{{str (index $e "id")}}"> | | 275 | -<header><a href="/e/{{str (index $e "id")}}">{{shortID (index $e "id")}}</a> | kind {{str (index $e "kind")}} | {{template "nostrKey" (str (index $e "pubkey"))}}<small><time datetime="{{datetime (index $e "created_at")}}">{{when (index $e "created_at")}}</time></small></header> | | 276 | +<header><a href="/e/{{str (index $e "id")}}">{{shortID (index $e "id")}}</a> | kind {{str (index $e "kind")}} | {{template "nostrName" (str (index $e "pubkey"))}}<small><time datetime="{{datetime (index $e "created_at")}}">{{when (index $e "created_at")}}</time></small></header> | | 277 | {{if eq (str (index $e "kind")) "30023"}}<article>{{markdown (str (index $e "content"))}}</article>{{else}}<pre>{{str (index $e "content")}}</pre>{{end}} | | 278 | <details><summary>event JSON</summary>{{template "jsonView" .}}</details> | | 279 | </nostr-event>{{end}} | | 280 | diff --git a/internal/webui/rooms.html b/internal/webui/rooms.html | | 281 | index 2f3ea09..494615a 100644 | | 282 | --- a/internal/webui/rooms.html | | 283 | +++ b/internal/webui/rooms.html | | 284 | @@ -58,7 +58,7 @@ | | 285 | | | 286 | {{/* One message. room-live builds the same markup for messages that arrive on the stream. */}} | | 287 | {{define "roomMessage"}}<room-message id="msg-{{.ID}}" data-id="{{.ID}}" data-kind="{{.Kind}}" data-pubkey="{{.PubKey}}"{{if .Agent}} data-agent{{end}}{{if .Notice}} data-notice{{end}}{{if .Edited}} data-edited{{end}}> | | 288 | -<header><b>{{template "nostrKey" .PubKey}}</b>{{if .Role}} | {{.Role}}{{end}}<small><time datetime="{{datetime .CreatedAt}}" title="{{when .CreatedAt}}">{{clock .CreatedAt}}</time></small></header> | | 289 | +<header><b>{{template "nostrName" .PubKey}}</b>{{if .Role}} | {{.Role}}{{end}}<small><time datetime="{{datetime .CreatedAt}}" title="{{when .CreatedAt}}">{{clock .CreatedAt}}</time></small></header> | | 290 | <p>{{if .Notice}}{{.Notice}}{{else}}{{roomContent .Content}}{{end}}</p> | | 291 | -{{if or .Mentions (and (not .InThread) (or (eq .Kind "11") .Root)) .Reactions .Edited}}<footer>{{if .Mentions}}<span>to {{range .Mentions}}{{template "nostrKey" .}} {{end}}</span>{{end}}{{if not .InThread}}{{if eq .Kind "11"}}<a href="/rooms/{{.Room}}/thread/{{.ID}}">thread{{if .Replies}} | {{.Replies}} {{if eq .Replies 1}}reply{{else}}replies{{end}}{{end}}</a>{{else if .Root}}<a href="/rooms/{{.Room}}/thread/{{.Root}}">in thread</a>{{end}}{{end}}{{range .Reactions}}<span data-reaction="{{.Content}}">{{.Content}} {{.Count}}</span>{{end}}{{if .Edited}}<span data-edited>edited</span>{{end}}</footer>{{end}} | | 292 | +{{if or .Mentions (and (not .InThread) (or (eq .Kind "11") .Root)) .Reactions .Edited}}<footer>{{if .Mentions}}<span>to {{range .Mentions}}{{template "nostrName" .}} {{end}}</span>{{end}}{{if not .InThread}}{{if eq .Kind "11"}}<a href="/rooms/{{.Room}}/thread/{{.ID}}">thread{{if .Replies}} | {{.Replies}} {{if eq .Replies 1}}reply{{else}}replies{{end}}{{end}}</a>{{else if .Root}}<a href="/rooms/{{.Room}}/thread/{{.Root}}">in thread</a>{{end}}{{end}}{{range .Reactions}}<span data-reaction="{{.Content}}">{{.Content}} {{.Count}}</span>{{end}}{{if .Edited}}<span data-edited>edited</span>{{end}}</footer>{{end}} | | 293 | </room-message>{{end}} | | 294 | diff --git a/internal/webui/rooms_test.go b/internal/webui/rooms_test.go | | 295 | index 7eb1841..d0662f0 100644 | | 296 | --- a/internal/webui/rooms_test.go | | 297 | +++ b/internal/webui/rooms_test.go | | 298 | @@ -146,7 +146,7 @@ func TestRoomPageRendersMessagesOldestFirstWithMarkers(t *testing.T) { | | 299 | `<a href="https://example.com/docs" rel="noopener">https://example.com/docs</a>.`, | | 300 | `<a href="/open?target=nostr%3Anpub1ttrypewl3au52wqux86r22yt506c077k3maj02a0jste97wrvd5sjfutc2">nostr:npub1`, | | 301 | `<script>alert(1)</script>`, | | 302 | - `<span>to <nostr-key hex="` + roomAgent + `"`, | | 303 | + `<span>to <nostr-name pubkey="` + roomAgent + `"`, | | 304 | `<span data-reaction="+1">+1 1</span>`, | | 305 | `<p>hello again, see <a href="https://example.com/docs" rel="noopener">`, `<span data-edited>edited</span>`, | | 306 | `<a href="/rooms/general/thread/` + roomThread + `">thread | 1 reply</a>`, | | 307 | @@ -154,7 +154,7 @@ func TestRoomPageRendersMessagesOldestFirstWithMarkers(t *testing.T) { | | 308 | `<room-live room="general"></room-live>`, | | 309 | `<room-compose room="general" pubkey="` + roomOwner + `">`, | | 310 | `<noscript><p>Sending needs JavaScript and a connected signer.</p></noscript>`, | | 311 | - `<ul id="members"><li><nostr-key hex="` + roomOwner + `"`, `<li data-agent><nostr-key hex="` + roomAgent + `"`, `<small>member | agent</small>`, | | 312 | + `<ul id="members"><li><nostr-name pubkey="` + roomOwner + `"`, `<li data-agent><nostr-name pubkey="` + roomAgent + `"`, `<small>member | agent</small>`, | | 313 | `<room-action room="general" kind="9000">`, `<room-action room="general" kind="9002">`, `<room-action room="general" kind="9022">`, | | 314 | `<nav id="room-list"><a href="/rooms/general" aria-current="page">General`, | | 315 | `» rooms/general</span>`, | | 316 | diff --git a/internal/webui/style.css b/internal/webui/style.css | | 317 | index 3a17749..5e78673 100644 | | 318 | --- a/internal/webui/style.css | | 319 | +++ b/internal/webui/style.css | | 320 | @@ -34,7 +34,7 @@ img{max-width:100%} | | 321 | #footer table{width:100%;border-collapse:collapse;height:27px}#footer td{border:0;padding:0;vertical-align:middle} | | 322 | #footer td:last-child{text-align:right} | | 323 | #prompt{color:var(--muted)}#prompt b{color:var(--ink);font-weight:500} | | 324 | -#who{color:var(--muted)}#who b,#who nostr-key{color:var(--ink)} | | 325 | +#who{color:var(--muted)}#who b,#who nostr-key,#who nostr-name{color:var(--ink)} | | 326 | #who button{border:0;background:none;padding:0;color:var(--accent);font:inherit}#who button:hover{text-decoration:underline} | | 327 | #railbox{box-sizing:border-box;padding-bottom:44px} | | 328 | #rail nav+nav{margin-top:14px} | | 329 | @@ -90,8 +90,7 @@ share-link{display:inline-block}share-link:empty{display:none} | | 330 | /* Data tables inside content, including json-view results. */ | | 331 | scroll-box{display:block;overflow-x:auto;max-width:100%} | | 332 | scroll-box table,json-view table{width:100%;border-collapse:collapse;margin:.5rem 0} | | 333 | -scroll-box th,scroll-box td,json-view th,json-view td{padding:.35rem .5rem;border-bottom:1px solid var(--line);text-align:left;vertical-align:top;overflow-wrap:normal} | | 334 | -scroll-box th,json-view th,scroll-box td code,scroll-box td nostr-key,scroll-box td time,json-view td code,json-view td nostr-key,json-view td time{white-space:nowrap} | | 335 | +scroll-box th,scroll-box td,json-view th,json-view td{padding:.35rem .5rem;border-bottom:1px solid var(--line);text-align:left;vertical-align:top;overflow-wrap:normal}scroll-box th,json-view th,scroll-box td code,scroll-box td nostr-key,scroll-box td time,json-view td code,json-view td nostr-key,json-view td time,scroll-box td nostr-name,json-view td nostr-name{white-space:nowrap} | | 336 | dl{margin:.5rem 0}dt{font-weight:600}dd{margin:0 0 .35rem 1rem} | | 337 | #facts{border-collapse:collapse;font:13px var(--mono);width:auto} | | 338 | #facts th{text-align:left;font-weight:normal;color:var(--muted);width:9em;padding:3px 0;border:0}#facts td{padding:3px 0;border:0} | | 339 | @@ -169,11 +168,11 @@ nostr-event>header{font:12px var(--mono);color:var(--muted);margin-bottom:4px} | | 340 | nostr-event>header small{float:right} | | 341 | nostr-event>pre{margin:0;white-space:pre-wrap;font:14px/1.45 var(--sans)} | | 342 | nostr-event>details{margin-top:6px;font:12px var(--mono)}nostr-event>details>summary{cursor:pointer;color:var(--muted)} | | 343 | -nostr-key{font-family:var(--mono);cursor:copy} | | 344 | +nostr-key{font-family:var(--mono);cursor:copy}nostr-name{font-family:var(--mono)} | | 345 | | | 346 | /* approval-item: one request that waits for a decision, in the open rhythm of nostr-event. */ | | 347 | approval-item{display:block;border-bottom:1px solid var(--line);padding:10px 0;max-width:720px} | | 348 | -approval-item>header{font:12px var(--mono);color:var(--muted)}approval-item>header nostr-key{color:var(--ink);font-weight:500} | | 349 | +approval-item>header{font:12px var(--mono);color:var(--muted)}approval-item>header nostr-key,approval-item>header nostr-name{color:var(--ink);font-weight:500} | | 350 | approval-item>p{margin:4px 0 6px} | | 351 | approval-item>pre{background:var(--rail);border:1px solid var(--line);padding:8px 10px;font-size:12px;margin:6px 0} | | 352 | approval-item>footer{font:12px var(--mono);color:var(--muted)}approval-item>footer button{margin-right:6px;font-size:13px} | | 353 | @@ -224,11 +223,10 @@ diff-view td>nostr-event{white-space:normal;max-width:720px;border:0;padding:6px | | 354 | | | 355 | /* Wiki: the page list, one article, the merge request bar and the editor. */ | | 356 | #wiki-list #rows th{white-space:nowrap}#wiki-list #rows td:first-child{width:auto;font:14px var(--sans)}#wiki-list #rows td:nth-child(3),#wiki-list #rows td:nth-child(4){width:110px;font:12px var(--mono);color:var(--muted)} | | 357 | -wiki-article{display:block;max-width:720px;overflow-wrap:anywhere}wiki-article>header{font:12px var(--mono);color:var(--muted);margin-bottom:8px}wiki-article>header nostr-key{color:var(--ink)} | | 358 | +wiki-article{display:block;max-width:720px;overflow-wrap:anywhere}wiki-article>header{font:12px var(--mono);color:var(--muted);margin-bottom:8px}wiki-article>header nostr-key,wiki-article>header nostr-name{color:var(--ink)} | | 359 | wiki-article h1{font-size:20px;margin:18px 0 8px}wiki-article h2{font-size:17px}wiki-article h3{font-size:15px}wiki-article p{margin:0 0 10px} | | 360 | wiki-article pre{background:var(--rail);border:1px solid var(--line);padding:10px 12px;overflow:auto;font-size:12.5px;line-height:1.5;white-space:pre}wiki-article ul,wiki-article ol{padding-left:20px;margin:0 0 10px} | | 361 | -merge-request{display:block;border:1px solid var(--line);background:var(--soft);padding:8px 10px;margin:0 0 14px;max-width:720px;font:13px var(--sans)} | | 362 | -merge-request nostr-key{color:var(--ink);font-weight:500}merge-request>a,merge-request button{margin-left:6px;font-size:12px} | | 363 | +merge-request{display:block;border:1px solid var(--line);background:var(--soft);padding:8px 10px;margin:0 0 14px;max-width:720px;font:13px var(--sans)}merge-request nostr-key,merge-request nostr-name{color:var(--ink);font-weight:500}merge-request>a,merge-request button{margin-left:6px;font-size:12px} | | 364 | merge-request nostr-react,merge-request nostr-react form{display:inline}merge-request nostr-react output{display:inline-block;margin-left:6px;font:12px var(--mono);color:var(--muted)}merge-request nostr-react output[data-error]{color:var(--alert)} | | 365 | #merge-status{font:12px var(--mono);color:var(--muted)}#wiki-compare h2{margin-top:18px}#wiki-redirects{font:12px var(--mono);color:var(--muted)} | | 366 | wiki-compose{display:block;max-width:720px;border-bottom:1px solid var(--line);padding:10px 0} | | 367 | diff --git a/internal/webui/templates.go b/internal/webui/templates.go | | 368 | index 40fd257..7ed457e 100644 | | 369 | --- a/internal/webui/templates.go | | 370 | +++ b/internal/webui/templates.go | | 371 | @@ -38,6 +38,12 @@ var tinyJS string | | 372 | //go:embed components.js | | 373 | var componentsJS string | | 374 | | | 375 | +// nostr-name.js is the vendored <nostr-name> element with the relay-local | | 376 | +// profile loader; rebuild it with npm run build:nostr-name. | | 377 | +// | | 378 | +//go:embed nostr-name.js | | 379 | +var nostrNameJS string | | 380 | + | | 381 | //go:embed blossom-encryption.js | | 382 | var blossomEncryptionJS string | | 383 | | | 384 | @@ -185,7 +191,7 @@ func repoView(query url.Values) string { | | 385 | // scripts are served at /scripts/<name> and cached by the service worker. | | 386 | // One version stamp covers them all, so a change to any file refreshes every | | 387 | // cached copy together. | | 388 | -var scripts = map[string]string{"bridge.js": bridgeJS, "tiny.js": tinyJS, "components.js": componentsJS, "blossom-encryption.js": blossomEncryptionJS, "blossom-manifests.js": blossomManifestsJS, "blossom-upload.js": blossomUploadJS, "file-messages.js": fileMessagesJS, "private-services.js": privateServicesJS, "file-workspace.js": fileWorkspaceJS} | | 389 | +var scripts = map[string]string{"bridge.js": bridgeJS, "tiny.js": tinyJS, "components.js": componentsJS, "nostr-name.js": nostrNameJS, "blossom-encryption.js": blossomEncryptionJS, "blossom-manifests.js": blossomManifestsJS, "blossom-upload.js": blossomUploadJS, "file-messages.js": fileMessagesJS, "private-services.js": privateServicesJS, "file-workspace.js": fileWorkspaceJS} | | 390 | | | 391 | var scriptsVersion = func() string { | | 392 | names := make([]string, 0, len(scripts)) | | 393 | diff --git a/internal/webui/wiki.html b/internal/webui/wiki.html | | 394 | index fce3c02..12c79c4 100644 | | 395 | --- a/internal/webui/wiki.html | | 396 | +++ b/internal/webui/wiki.html | | 397 | @@ -6,7 +6,7 @@ | | 398 | {{else if .Feed}}<table id="rows"> | | 399 | <thead><tr><th>page</th><th>author</th><th>versions</th><th>merge requests</th><th>updated</th></tr></thead> | | 400 | <tbody> | | 401 | -{{range .Feed}}{{$p := asMap .}}{{$v := asMap (index $p "version")}}<tr><td><a href="{{wikiURL (str (index $p "d"))}}">{{str (index $p "title")}}</a></td><td>{{template "nostrKey" (str (index $v "author"))}}</td><td>{{str (index $p "versions")}}</td><td>{{str (index $p "open_merges")}} open</td><td>{{when (index $v "created_at")}}</td></tr> | | 402 | +{{range .Feed}}{{$p := asMap .}}{{$v := asMap (index $p "version")}}<tr><td><a href="{{wikiURL (str (index $p "d"))}}">{{str (index $p "title")}}</a></td><td>{{template "nostrName" (str (index $v "author"))}}</td><td>{{str (index $p "versions")}}</td><td>{{str (index $p "open_merges")}} open</td><td>{{when (index $v "created_at")}}</td></tr> | | 403 | {{end}}</tbody> | | 404 | </table> | | 405 | {{else}}<p>No pages yet.</p> | | 406 | @@ -23,11 +23,11 @@ | | 407 | <h1>{{$w.Title}}</h1> | | 408 | {{template "alert" .Notice}}{{if not $w.Version}}<p>This name has no article of its own.</p> | | 409 | {{template "wikiRedirects" $w}}{{if .Actor}}{{template "wikiEditor" .}}{{end}} | | 410 | -{{else}}{{range $m := $w.OpenMerges}}<merge-request>{{template "nostrKey" (str (index $m "author"))}} proposes a new version of this page | <a href="{{wikiURL $w.D "merge" (str (index $m "id"))}}">compare</a>{{if eq $.Actor (str (index $m "destination"))}}<nostr-react event="{{str (index $m "id")}}" pubkey="{{str (index $m "author")}}" kind="818"><button name="reaction" value="+">Accept</button><button name="reaction" value="-">Reject</button></nostr-react>{{end}}<a href="/e/{{str (index $m "id")}}">Reply</a></merge-request> | | 411 | +{{else}}{{range $m := $w.OpenMerges}}<merge-request>{{template "nostrName" (str (index $m "author"))}} proposes a new version of this page | <a href="{{wikiURL $w.D "merge" (str (index $m "id"))}}">compare</a>{{if eq $.Actor (str (index $m "destination"))}}<nostr-react event="{{str (index $m "id")}}" pubkey="{{str (index $m "author")}}" kind="818"><button name="reaction" value="+">Accept</button><button name="reaction" value="-">Reject</button></nostr-react>{{end}}<a href="/e/{{str (index $m "id")}}">Reply</a></merge-request> | | 412 | {{end}}{{if .Query.Get "merge"}}{{template "wikiCompare" .}} | | 413 | {{else if .Query.Get "edit"}}{{template "wikiEditor" .}} | | 414 | {{else if .Query.Get "history"}}{{template "wikiHistory" $w}} | | 415 | -{{else}}<wiki-article><header>wiki | {{$w.D}} | by {{template "nostrKey" $w.Author}} | version {{$w.Number}} of {{$w.Count}} | {{$w.Forks}} fork{{if ne $w.Forks 1}}s{{end}}</header> | | 416 | +{{else}}<wiki-article><header>wiki | {{$w.D}} | by {{template "nostrName" $w.Author}} | version {{$w.Number}} of {{$w.Count}} | {{$w.Forks}} fork{{if ne $w.Forks 1}}s{{end}}</header> | | 417 | {{with asMap (index $w.Version "fork")}}<p><small>forked from <a href="{{wikiURL $w.D "version" (str (index . "e"))}}">{{shortID (index . "e")}}</a></small></p> | | 418 | {{end}}{{with asMap (index $w.Version "defer")}}<p><small>the author defers to <a href="{{wikiURL $w.D "version" (str (index . "e"))}}">{{shortID (index . "e")}}</a></small></p> | | 419 | {{end}}{{wikiHTML (index $w.Version "content")}}</wiki-article> | | 420 | @@ -50,13 +50,13 @@ | | 421 | {{/* wikiCompare shows the proposed version of a merge request above the current one. */}} | | 422 | {{define "wikiCompare"}}{{$w := wikiView .Event}}{{$m := asMap .Merge}}{{if not $m}}<p>The merge request could not be loaded.</p> | | 423 | {{else}}{{$r := asMap (index $m "merge")}}<div id="wiki-compare"> | | 424 | -<p id="merge-status">merge request <a href="/e/{{str (index $r "id")}}">{{shortID (index $r "id")}}</a> | from {{template "nostrKey" (str (index $r "author"))}} to {{template "nostrKey" (str (index $r "destination"))}} | {{str (index $r "status")}}{{with index $r "content"}} | {{.}}{{end}}</p> | | 425 | +<p id="merge-status">merge request <a href="/e/{{str (index $r "id")}}">{{shortID (index $r "id")}}</a> | from {{template "nostrName" (str (index $r "author"))}} to {{template "nostrName" (str (index $r "destination"))}} | {{str (index $r "status")}}{{with index $r "content"}} | {{.}}{{end}}</p> | | 426 | {{with asMap (index $m "proposed")}}<h2>Proposed version</h2> | | 427 | -<wiki-article><header>wiki | {{str (index . "d")}} | by {{template "nostrKey" (str (index . "author"))}} | {{when (index . "created_at")}}</header> | | 428 | +<wiki-article><header>wiki | {{str (index . "d")}} | by {{template "nostrName" (str (index . "author"))}} | {{when (index . "created_at")}}</header> | | 429 | {{wikiHTML (index . "content")}}</wiki-article> | | 430 | {{else}}<p>The proposed version is not available.</p> | | 431 | {{end}}{{with asMap (index $m "target")}}<h2>Current version</h2> | | 432 | -<wiki-article><header>wiki | {{str (index . "d")}} | by {{template "nostrKey" (str (index . "author"))}} | {{when (index . "created_at")}}</header> | | 433 | +<wiki-article><header>wiki | {{str (index . "d")}} | by {{template "nostrName" (str (index . "author"))}} | {{when (index . "created_at")}}</header> | | 434 | {{wikiHTML (index . "content")}}</wiki-article> | | 435 | {{else}}<p>The destination author has no version of this page.</p> | | 436 | {{end}}<p><a href="{{wikiURL $w.D}}">back to the page</a></p></div>{{end}}{{end}} | | 437 | @@ -65,7 +65,7 @@ | | 438 | {{define "wikiHistory"}}<table id="rows"> | | 439 | <thead><tr><th>author</th><th>version</th><th>notes</th><th>published</th></tr></thead> | | 440 | <tbody> | | 441 | -{{range .Versions}}<tr><td>{{template "nostrKey" (str (index . "author"))}}</td><td><a href="{{wikiURL $.D "version" (str (index . "id"))}}">{{shortID (index . "id")}}</a></td><td>{{with asMap (index . "fork")}}fork of {{shortID (index . "e")}}{{end}}{{with asMap (index . "defer")}} defers to {{shortID (index . "e")}}{{end}}{{with index . "likes"}} | {{str .}} likes{{end}}</td><td>{{when (index . "created_at")}}</td></tr> | | 442 | +{{range .Versions}}<tr><td>{{template "nostrName" (str (index . "author"))}}</td><td><a href="{{wikiURL $.D "version" (str (index . "id"))}}">{{shortID (index . "id")}}</a></td><td>{{with asMap (index . "fork")}}fork of {{shortID (index . "e")}}{{end}}{{with asMap (index . "defer")}} defers to {{shortID (index . "e")}}{{end}}{{with index . "likes"}} | {{str .}} likes{{end}}</td><td>{{when (index . "created_at")}}</td></tr> | | 443 | {{end}}</tbody> | | 444 | </table> | | 445 | <p><a href="{{wikiURL .D}}">back to the page</a></p>{{end}} | | 446 | diff --git a/internal/webui/wiki_test.go b/internal/webui/wiki_test.go | | 447 | index 8b7dc6b..8278f53 100644 | | 448 | --- a/internal/webui/wiki_test.go | | 449 | +++ b/internal/webui/wiki_test.go | | 450 | @@ -113,7 +113,7 @@ func TestWikiListRendersPagesAndSearch(t *testing.T) { | | 451 | } | | 452 | wantAll(t, "/wiki", body, | | 453 | `href="/wiki" aria-current="page"`, `id="wiki-search"`, `value="release"`, | | 454 | - `<a href="/wiki/release-notes-1-4">Release notes 1.4 <b></a>`, `<nostr-key hex="`+wikiOwner+`"`, | | 455 | + `<a href="/wiki/release-notes-1-4">Release notes 1.4 <b></a>`, `<nostr-name pubkey="`+wikiOwner+`"`, | | 456 | `<td>2</td><td>1 open</td>`, `2026-09-08 14:00 UTC`, `href="/wiki?cursor=release-notes-1-4&q=release"`, | | 457 | `<b>demo</b> » wiki`, `<a href="/wiki?new=1">new page</a>`) | | 458 | wantNone(t, "/wiki", body, "<wiki-compose") | | 459 | @@ -131,10 +131,10 @@ func TestWikiPageRendersArticleMergeBarAndPanel(t *testing.T) { | | 460 | } | | 461 | wantAll(t, path, body, | | 462 | `<title>Release notes 1.4 <b> | wiki | demo</title>`, `<h1>Release notes 1.4 <b></h1>`, | | 463 | - `<merge-request><nostr-key hex="`+wikiOther+`"`, `proposes a new version of this page`, `href="/wiki/release-notes-1-4?merge=`+wikiMerge+`">compare</a>`, | | 464 | + `<merge-request><nostr-name pubkey="`+wikiOther+`"`, `proposes a new version of this page`, `href="/wiki/release-notes-1-4?merge=`+wikiMerge+`">compare</a>`, | | 465 | `<nostr-react event="`+wikiMerge+`" pubkey="`+wikiOther+`" kind="818"><button name="reaction" value="+">Accept</button><button name="reaction" value="-">Reject</button></nostr-react>`, | | 466 | `<a href="/e/`+wikiMerge+`">Reply</a>`, | | 467 | - `<wiki-article><header>wiki | release-notes-1-4 | by <nostr-key hex="`+wikiOwner+`"`, `version 1 of 2 | 1 fork</header>`, | | 468 | + `<wiki-article><header>wiki | release-notes-1-4 | by <nostr-name pubkey="`+wikiOwner+`"`, `version 1 of 2 | 1 fork</header>`, | | 469 | `<h1>Highlights</h1>`, `<em>encrypted</em>`, `<a href="/wiki/files-and-private-repositories">Files and Private Repositories</a>`, `<script>alert(1)</script>`, | | 470 | `<h4>Page</h4>`, `<th>versions</th><td>2</td>`, `<th>merge requests</th><td>1 open</td>`, `<th>links</th><td>1</td>`, | | 471 | `<h4>Other versions</h4>`, `href="/wiki/release-notes-1-4?version=`+wikiOtherV+`">222222222222</a> | fork`, | | 472 | diff --git a/package-lock.json b/package-lock.json | | 473 | index e9afe27..7391875 100644 | | 474 | --- a/package-lock.json | | 475 | +++ b/package-lock.json | | 476 | @@ -1,14 +1,15 @@ | | 477 | { | | 478 | - "name": "tiny-conformance", | | 479 | + "name": "tiny", | | 480 | "lockfileVersion": 3, | | 481 | "requires": true, | | 482 | "packages": { | | 483 | "": { | | 484 | - "name": "tiny-conformance", | | 485 | + "name": "tiny", | | 486 | "devDependencies": { | | 487 | "@noble/hashes": "2.4.0", | | 488 | "esbuild": "0.28.2", | | 489 | "nostr-tools": "2.25.1", | | 490 | + "nostr-web-components": "^0.2.2", | | 491 | "vitest": "4.1.0", | | 492 | "ws": "8.21.0" | | 493 | } | | 494 | @@ -462,6 +463,46 @@ | | 495 | "dev": true, | | 496 | "license": "MIT" | | 497 | }, | | 498 | + "node_modules/@jsr/fiatjaf__lru-cache": { | | 499 | + "version": "0.0.1", | | 500 | + "resolved": "https://npm.jsr.io/~/11/@jsr/fiatjaf__lru-cache/0.0.1.tgz", | | 501 | + "integrity": "sha512-9MbsYMHWGe751L4E7XpxV3jWydd+N6RJv+c7tYtK03lST5xdcULn2QZorImZgCbP8qUu47n1RL7RrqvjZdmXPg==", | | 502 | + "dev": true | | 503 | + }, | | 504 | + "node_modules/@jsr/henrygd__semaphore": { | | 505 | + "version": "0.0.2", | | 506 | + "resolved": "https://npm.jsr.io/~/11/@jsr/henrygd__semaphore/0.0.2.tgz", | | 507 | + "integrity": "sha512-nrwZ8HaqU1Agb2ij8omIxaOCAsKkDHWcwS9hTRumPhZuptwh6/0BJExBd8+eClTYM7jBnZxK+cP4WJRTcHBvCA==", | | 508 | + "dev": true | | 509 | + }, | | 510 | + "node_modules/@jsr/nostr__tools": { | | 511 | + "version": "2.25.2", | | 512 | + "resolved": "https://npm.jsr.io/~/11/@jsr/nostr__tools/2.25.2.tgz", | | 513 | + "integrity": "sha512-Yhe1ppPZWWd52mSqdYDd+Zq9gm4e+B7CMQ62gGXPpkBvfCXXCiTUWlctEEVzam6TGZaeHXcgiYj+zzKW02bAig==", | | 514 | + "dev": true, | | 515 | + "dependencies": { | | 516 | + "@noble/ciphers": "2.1.1", | | 517 | + "@noble/curves": "2.0.1", | | 518 | + "@noble/hashes": "2.0.1", | | 519 | + "@scure/base": "2.0.0", | | 520 | + "@scure/bip32": "2.0.1", | | 521 | + "@scure/bip39": "2.0.1", | | 522 | + "nostr-wasm": "0.1.0" | | 523 | + } | | 524 | + }, | | 525 | + "node_modules/@jsr/nostr__tools/node_modules/@noble/hashes": { | | 526 | + "version": "2.0.1", | | 527 | + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", | | 528 | + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", | | 529 | + "dev": true, | | 530 | + "license": "MIT", | | 531 | + "engines": { | | 532 | + "node": ">= 20.19.0" | | 533 | + }, | | 534 | + "funding": { | | 535 | + "url": "https://paulmillr.com/funding/" | | 536 | + } | | 537 | + }, | | 538 | "node_modules/@noble/ciphers": { | | 539 | "version": "2.1.1", | | 540 | "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.1.1.tgz", | | 541 | @@ -517,6 +558,62 @@ | | 542 | "url": "https://paulmillr.com/funding/" | | 543 | } | | 544 | }, | | 545 | + "node_modules/@nostr/gadgets": { | | 546 | + "name": "@jsr/nostr__gadgets", | | 547 | + "version": "0.1.9", | | 548 | + "resolved": "https://npm.jsr.io/~/11/@jsr/nostr__gadgets/0.1.9.tgz", | | 549 | + "integrity": "sha512-BvAFJoH4WkYldOyBXZP2IwrqswK42RP3RRQcOWCE0TQETUam+spfcheGwDfkaZMCc/eshl1KiCgKWzC2t62fAw==", | | 550 | + "dev": true, | | 551 | + "dependencies": { | | 552 | + "@jsr/fiatjaf__lru-cache": "^0.0.1", | | 553 | + "@jsr/henrygd__semaphore": "^0.0.2", | | 554 | + "@jsr/nostr__tools": "^2.23.6", | | 555 | + "@noble/hashes": "2.0.1", | | 556 | + "idb-keyval": "^6.2.1" | | 557 | + } | | 558 | + }, | | 559 | + "node_modules/@nostr/gadgets/node_modules/@noble/hashes": { | | 560 | + "version": "2.0.1", | | 561 | + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", | | 562 | + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", | | 563 | + "dev": true, | | 564 | + "license": "MIT", | | 565 | + "engines": { | | 566 | + "node": ">= 20.19.0" | | 567 | + }, | | 568 | + "funding": { | | 569 | + "url": "https://paulmillr.com/funding/" | | 570 | + } | | 571 | + }, | | 572 | + "node_modules/@nostr/tools": { | | 573 | + "name": "@jsr/nostr__tools", | | 574 | + "version": "2.23.0", | | 575 | + "resolved": "https://npm.jsr.io/~/11/@jsr/nostr__tools/2.23.0.tgz", | | 576 | + "integrity": "sha512-yyRDsDTaIlRaRU1SiN4UPIapAvwPiMBVp5nfXyKS8VO1lxyxw0VdoApx6aNq8kSZznI8lAYb36IACRs6oJLeRA==", | | 577 | + "dev": true, | | 578 | + "dependencies": { | | 579 | + "@noble/ciphers": "2.1.1", | | 580 | + "@noble/curves": "2.0.1", | | 581 | + "@noble/hashes": "2.0.1", | | 582 | + "@scure/base": "2.0.0", | | 583 | + "@scure/bip32": "2.0.1", | | 584 | + "@scure/bip39": "2.0.1", | | 585 | + "nostr-wasm": "0.1.0" | | 586 | + } | | 587 | + }, | | 588 | + "node_modules/@nostr/tools/node_modules/@noble/hashes": { | | 589 | + "version": "2.0.1", | | 590 | + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", | | 591 | + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", | | 592 | + "dev": true, | | 593 | + "license": "MIT", | | 594 | + "engines": { | | 595 | + "node": ">= 20.19.0" | | 596 | + }, | | 597 | + "funding": { | | 598 | + "url": "https://paulmillr.com/funding/" | | 599 | + } | | 600 | + }, | | 601 | "node_modules/@oxc-project/types": { | | 602 | "version": "0.148.0", | | 603 | "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.148.0.tgz", | | 604 | @@ -1156,6 +1253,13 @@ | | 605 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" | | 606 | } | | 607 | }, | | 608 | + "node_modules/idb-keyval": { | | 609 | + "version": "6.3.0", | | 610 | + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.3.0.tgz", | | 611 | + "integrity": "sha512-um+2dgAWmYsu615EXpWVwSmapJhON0G43t3Ka/EVaohzPQXSMqKEqeDK/oIW3Ow+BXaF2PvSc+oBTFp793A5Ow==", | | 612 | + "dev": true, | | 613 | + "license": "Apache-2.0" | | 614 | + }, | | 615 | "node_modules/lightningcss": { | | 616 | "version": "1.33.0", | | 617 | "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", | | 618 | @@ -1502,6 +1606,17 @@ | | 619 | "dev": true, | | 620 | "license": "MIT" | | 621 | }, | | 622 | + "node_modules/nostr-web-components": { | | 623 | + "version": "0.2.2", | | 624 | + "resolved": "https://registry.npmjs.org/nostr-web-components/-/nostr-web-components-0.2.2.tgz", | | 625 | + "integrity": "sha512-v0FaFwkR7c0AO2NNkHNURcBEf89FjHW6xZrgmRkJ16AHb+iCMkyL7eJYsmQ91sRGy9g1aYoSNVqEma4e06UgUw==", | | 626 | + "dev": true, | | 627 | + "license": "Unlicense", | | 628 | + "dependencies": { | | 629 | + "@nostr/gadgets": "npm:@jsr/nostr__gadgets@^0.1.3", | | 630 | + "@nostr/tools": "npm:@jsr/nostr__tools@2.23.0" | | 631 | + } | | 632 | + }, | | 633 | "node_modules/obug": { | | 634 | "version": "2.1.4", | | 635 | "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", | | 636 | diff --git a/package.json b/package.json | | 637 | index cdcbfbd..ca84a1d 100644 | | 638 | --- a/package.json | | 639 | +++ b/package.json | | 640 | @@ -4,6 +4,7 @@ | | 641 | "type": "module", | | 642 | "scripts": { | | 643 | "build:signer": "node scripts/build-signer.mjs", | | 644 | + "build:nostr-name": "node scripts/build-nostr-name.mjs", | | 645 | "test:conformance": "node scripts/conformance/run.mjs", | | 646 | "test:webmcp": "node --test scripts/webmcp.test.mjs", | | 647 | "test:signer": "node --test scripts/signer.test.mjs", | | 648 | @@ -18,6 +19,7 @@ | | 649 | "@noble/hashes": "2.4.0", | | 650 | "esbuild": "0.28.2", | | 651 | "nostr-tools": "2.25.1", | | 652 | + "nostr-web-components": "^0.2.2", | | 653 | "vitest": "4.1.0", | | 654 | "ws": "8.21.0" | | 655 | } | | 656 | diff --git a/scripts/build-nostr-name.mjs b/scripts/build-nostr-name.mjs | | 657 | new file mode 100644 | | 658 | index 0000000..c902d5c | | 659 | --- /dev/null | | 660 | +++ b/scripts/build-nostr-name.mjs | | 661 | @@ -0,0 +1,13 @@ | | 662 | +import { build } from "esbuild"; | | 663 | + | | 664 | +await build({ | | 665 | + entryPoints: ["scripts/nostr-name-entry.mjs"], | | 666 | + bundle: true, | | 667 | + minify: true, | | 668 | + format: "iife", | | 669 | + platform: "browser", | | 670 | + target: ["es2020"], | | 671 | + outfile: "internal/webui/nostr-name.js", | | 672 | + legalComments: "eof", | | 673 | + banner: { js: '"use strict";' }, | | 674 | +}); | | 675 | diff --git a/scripts/nostr-name-entry.mjs b/scripts/nostr-name-entry.mjs | | 676 | new file mode 100644 | | 677 | index 0000000..bc40466 | | 678 | --- /dev/null | | 679 | +++ b/scripts/nostr-name-entry.mjs | | 680 | @@ -0,0 +1,5 @@ | | 681 | +// Vendored <nostr-name> from nostr-web-components, with the relay-local | | 682 | +// profile loader installed first. Built by scripts/build-nostr-name.mjs into | | 683 | +// internal/webui/nostr-name.js. | | 684 | +import "./nostr-name-setup.mjs"; | | 685 | +import "nostr-web-components/nostr-name"; | | 686 | diff --git a/scripts/nostr-name-setup.mjs b/scripts/nostr-name-setup.mjs | | 687 | new file mode 100644 | | 688 | index 0000000..22f655e | | 689 | --- /dev/null | | 690 | +++ b/scripts/nostr-name-setup.mjs | | 691 | @@ -0,0 +1,79 @@ | | 692 | +// Shared state for the vendored <nostr-name> element. Names resolve from this | | 693 | +// relay only: tiny is a personal relay, so the profiles that matter live here, | | 694 | +// and asking public indexers would leak who is being looked at. The element | | 695 | +// reads window.nostrSharedMetadataLoader when its module evaluates, so this | | 696 | +// file must be imported first. | | 697 | +import { SimplePool } from "@nostr/tools/pool"; | | 698 | +import { bareNostrUser, nostrUserFromEvent } from "@nostr/gadgets/metadata"; | | 699 | + | | 700 | +const pool = window.nostrSharedPool || new SimplePool(); | | 701 | +window.nostrSharedPool = pool; | | 702 | + | | 703 | +// The relay listens at the tenant root: wss://host for the default tenant, | | 704 | +// wss://host/r/<name> for the others. | | 705 | +const relayURL = () => { | | 706 | + const root = (location.pathname.match(/^\/r\/[^/]+/) || [""])[0]; | | 707 | + return (location.protocol === "https:" ? "wss://" : "ws://") + location.host + root; | | 708 | +}; | | 709 | + | | 710 | +const CACHE = "tiny:names:v1"; | | 711 | +const FRESH = 6 * 60 * 60; // seconds a profile is trusted before a refetch | | 712 | +const MISSING = 10 * 60; // seconds before asking again about a key with no profile | | 713 | +const BATCH = 50; // milliseconds to gather keys before one request | | 714 | +const LIMIT = 100; // authors per request | | 715 | + | | 716 | +const readCache = () => { | | 717 | + try { return JSON.parse(localStorage.getItem(CACHE) || "{}") || {}; } catch { return {}; } | | 718 | +}; | | 719 | +const writeCache = cache => { | | 720 | + try { localStorage.setItem(CACHE, JSON.stringify(cache)); } catch {} | | 721 | +}; | | 722 | + | | 723 | +const users = new Map(); // pubkey -> NostrUser, from cache or the relay | | 724 | +const pending = new Map(); // pubkey -> [resolve] | | 725 | +let timer = null; | | 726 | + | | 727 | +const userFor = (pubkey, entry) => entry?.event ? nostrUserFromEvent(entry.event) : bareNostrUser(pubkey); | | 728 | + | | 729 | +const flush = async () => { | | 730 | + timer = null; | | 731 | + const keys = [...pending.keys()].slice(0, LIMIT); | | 732 | + if (!keys.length) return; | | 733 | + const cache = readCache(); | | 734 | + const now = Math.floor(Date.now() / 1000); | | 735 | + let events = []; | | 736 | + try { | | 737 | + events = await pool.querySync([relayURL()], {kinds: [0], authors: keys}); | | 738 | + } catch {} | | 739 | + const newest = new Map(); | | 740 | + for (const event of events) { | | 741 | + if (!newest.has(event.pubkey) || newest.get(event.pubkey).created_at < event.created_at) newest.set(event.pubkey, event); | | 742 | + } | | 743 | + for (const key of keys) { | | 744 | + const event = newest.get(key) || cache[key]?.event || null; | | 745 | + cache[key] = {event, at: now}; | | 746 | + const user = userFor(key, cache[key]); | | 747 | + users.set(key, user); | | 748 | + for (const resolve of pending.get(key) || []) resolve(user); | | 749 | + pending.delete(key); | | 750 | + } | | 751 | + writeCache(cache); | | 752 | + if (pending.size) timer = setTimeout(flush, BATCH); | | 753 | +}; | | 754 | + | | 755 | +// load answers like @nostr/gadgets' loadNostrUser: a NostrUser from cache, | | 756 | +// refreshed from this relay when stale, with a placeholder while unknown. | | 757 | +const load = ({pubkey}) => { | | 758 | + const key = String(pubkey || "").toLowerCase(); | | 759 | + if (!/^[0-9a-f]{64}$/.test(key)) return Promise.resolve(bareNostrUser(key || "0".repeat(64))); | | 760 | + const cached = readCache()[key]; | | 761 | + const now = Math.floor(Date.now() / 1000); | | 762 | + if (cached && now - cached.at < (cached.event ? FRESH : MISSING)) return Promise.resolve(users.get(key) || userFor(key, cached)); | | 763 | + return new Promise(resolve => { | | 764 | + pending.set(key, [...(pending.get(key) || []), resolve]); | | 765 | + if (!timer) timer = setTimeout(flush, BATCH); | | 766 | + }); | | 767 | +}; | | 768 | + | | 769 | +window.nostrSharedMetadataLoader = window.nostrSharedMetadataLoader || (request => load(typeof request === "string" ? {pubkey: request} : request)); | | 770 | +window.tinyNames = Object.freeze({load, relayURL, cacheKey: CACHE}); | | 771 | diff --git a/scripts/rooms.test.mjs b/scripts/rooms.test.mjs | | 772 | index 9949e71..472fdb4 100644 | | 773 | --- a/scripts/rooms.test.mjs | | 774 | +++ b/scripts/rooms.test.mjs | | 775 | @@ -61,7 +61,7 @@ function setup({attributes = {}, result = {accepted: true}, change = false} = {} | | 776 | const list = new FakeNode("div"); list.id = "messages"; | | 777 | const members = new FakeNode("ul"); members.id = "members"; | | 778 | const agentRow = new FakeNode("li"); agentRow.dataset.agent = ""; | | 779 | - const agentKey = new FakeNode("nostr-key"); agentKey.setAttribute("hex", "b".repeat(64)); | | 780 | + const agentKey = new FakeNode("nostr-name"); agentKey.setAttribute("pubkey", "b".repeat(64)); | | 781 | agentRow.append(agentKey, " ", Object.assign(new FakeNode("small"), {childNodes: ["member | agent"]})); | | 782 | members.append(agentRow); | | 783 | document.body.append(content, list, members); | | 784 | @@ -183,11 +183,11 @@ test("room-live appends streamed messages once, summarizes reactions and reconne | | 785 | const node = s.list.children[0]; | | 786 | assert.equal(node.id, "msg-" + message.id); | | 787 | assert.equal(node.dataset.kind, "9"); | | 788 | - assert.equal(node.querySelector("header b nostr-key").getAttribute("hex"), message.pubkey); | | 789 | + assert.equal(node.querySelector("header b nostr-name").getAttribute("pubkey"), message.pubkey); | | 790 | const link = node.querySelector("p a"); | | 791 | assert.equal(link.href, "https://example.com/x"); | | 792 | assert.equal(node.querySelector("p").textContent, "see https://example.com/x, ok"); | | 793 | - assert.equal(node.querySelector("footer span nostr-key").getAttribute("hex"), "c".repeat(64)); | | 794 | + assert.equal(node.querySelector("footer span nostr-name").getAttribute("pubkey"), "c".repeat(64)); | | 795 | source.emit("message", {data: "not json"}); | | 796 | source.emit("message", {data: JSON.stringify({...message, id: "9".repeat(64), kind: 20001})}); | | 797 | assert.equal(s.list.children.length, 1); |
|
About| owner | c01503e8035f |
|---|
| visibility | public |
|---|
| head | 8191f1a31d1a |
|---|
Maintainers- c01503e8035f owner
- ba1b5beed5b5 agent hermes
Clonehttps://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git
|