clone

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

nip11: the information document and the supported-NIP list are a module, read by the router and the discovery record

Dami committed 06f1328
commit
06f1328510294cd50eadbcf8a4dd7c0fd4b3552c
parent
eb8640f806aa028930799270c737364cf64ce166
author
Dami <dami.etoile@gmail.com>
4 files changed, +75 -63
Msrc/landing.ts3+-
Asrc/nip11.ts67++++++++++++++++++++++++++++++
Msrc/nip66.ts3+-
Msrc/relay.ts65++---------------------------
M src/landing.ts +2 -1
A src/nip11.ts +67 -0
@@ -0,0 +1,67 @@
1+// The NIP-11 information document: what the relay says about itself to a2+// client that asks with accept: application/nostr+json. The rules, the3+// limits, the policy links and lists the owner set, retention, fuel, the4+// lease, succession, and the views, from the settings and the relay's state.5+import type { Relay } from "./relay.ts";6+import { now } from "./event.ts";7+import { SUCCESSION_WARN_DAYS } from "./settings.ts";8+import { nip11Views } from "./views.ts";9+10+// The less obvious numbers: 43 is added once the relay has an identity; 6211+// is request-to-vanish (store.vanish); 67 is the EOSE hint array at the end12+// of the subscription handler; 70 is the "-" tag check in the write gate13+// (gates.ts); 66 is the discovery record the relay signs about itself14+// (publishDiscovery); 77 is negentropy sync (handleSync).15+export const SUPPORTED_NIPS = [1, 5, 9, 11, 13, 17, 29, 40, 42, 45, 46, 50, 56, 59, 62, 66, 67, 70, 77, 86, 98];16+export const SOFTWARE = "https://bind.ws";17+export const VERSION = "0.1.0";18+19+export function nip11(relay: Relay, host: string) {20+ const p = relay.settings.policy;21+ const doc: Record<string, unknown> = {22+ name: p.name || relay.slug,23+ description: p.description,24+ supported_nips: SUPPORTED_NIPS,25+ software: SOFTWARE,26+ version: VERSION,27+ limitation: {28+ max_message_length: relay.maxMessage(),29+ max_subscriptions: p.maxSubs,30+ max_limit: p.maxLimit,31+ default_limit: p.maxLimit,32+ max_subid_length: 64,33+ // True whenever a fresh socket cannot REQ: reads for the authenticated or for members.34+ auth_required: p.reads !== "open",35+ payment_required: false,36+ restricted_writes: p.writes !== "open" || relay.settings.isUnclaimed(),37+ created_at_upper_limit: p.maxFuture || undefined,38+ min_pow_difficulty: p.minPow || undefined,39+ },40+ };41+ if (p.icon) doc.icon = p.icon;42+ if (p.banner) doc.banner = p.banner;43+ if (p.joinTerms && host) doc.terms_of_service = relay.webURL(host) + "/terms";44+ if (p.postingPolicy) doc.posting_policy = p.postingPolicy;45+ if (p.privacyPolicy) doc.privacy_policy = p.privacyPolicy;46+ if (p.tags.length) doc.tags = p.tags;47+ if (p.languageTags.length) doc.language_tags = p.languageTags;48+ if (p.relayCountries.length) doc.relay_countries = p.relayCountries;49+ if (p.contact) doc.contact = p.contact;50+ const retention = relay.settings.listRetention().map((r) => (r.kind === null ? { time: r.days * 86400 } : { kinds: [r.kind], time: r.days * 86400 }));51+ if (retention.length) doc.retention = retention;52+ if (relay.fuel.cfg.lightningAddress && relay.fuel.cfg.servicePubkey && host) {53+ const f = relay.fuel.status(now(), relay.eventBytes(), relay.mediaBytes());54+ (doc.limitation as Record<string, unknown>).payment_required = f.outOfFuel;55+ doc.payments_url = "https://" + host + "/";56+ }57+ if (p.owner) doc.pubkey = p.owner;58+ if (p.succession && relay.succession.warn) doc.succession_pending = new Date((relay.succession.warn.since + SUCCESSION_WARN_DAYS * 86400) * 1000).toISOString().slice(0, 10);59+ if (relay.settings.isLeased() && p.lease) doc.lease = { expires_at: p.lease.until, holder: p.lease.holder || undefined, claim_url: host ? "https://" + host + "/" : undefined };60+ if (host) doc.self_url = relay.relayURL(host);61+ if (relay.identity.pubkey) {62+ doc.self = relay.identity.pubkey;63+ doc.supported_nips = [...SUPPORTED_NIPS, 43];64+ doc.views = nip11Views(relay);65+ }66+ return doc;67+}
M src/nip66.ts +2 -1
M src/relay.ts +4 -61
@@ -10,9 +10,10 @@ import { Negentropy, bytesToHex, hexToBytes } from "./negentropy.ts";
1010 import { discovery } from "./nip66.ts";1111 import { Audit } from "./audit.ts";1212 import { ERR_DUPLICATE, ERR_TOO_BIG, Store, type Access } from "./store.ts";13-import { Settings, isReplaceable, isProtected, SUCCESSION_WARN_DAYS } from "./settings.ts";13+import { Settings, isReplaceable, isProtected } from "./settings.ts";1414 import { manage } from "./manage.ts";1515 import { Succession } from "./succession.ts";16+import { nip11 } from "./nip11.ts";1617 import { guestPass, readGate, writeGate } from "./gates.ts";1718 import { dashboard } from "./dashboard.ts";1819 import { Fuel, fuelConfig, acceptReceipt, fuelInvoice, type Fetcher, type LnurlParams } from "./fuel.ts";
@@ -38,7 +39,7 @@ import { SIGNER_JS } from "./gen/signer.ts";
3839 import { isPagePath, pages } from "./pages.ts";3940 import { notify, fuelLow, fuelText } from "./notify.ts";4041 import { card } from "./card.ts";41-import { PRESENCE_THROTTLE_S, markView, nip11Views, notePresence, serveView, viewsTick } from "./views.ts";42+import { markView, notePresence, serveView, viewsTick } from "./views.ts";4243 import type { Blob } from "./blossom.ts";4344 import { KIND_VANISH, KIND_AUTH, KIND_REPORT, KIND_NOSTR_CONNECT, KIND_GROUP_MEMBERS, KIND_GROUP_PINS, KIND_RELAY_DISCOVERY } from "./kinds.ts";4445
@@ -77,14 +78,6 @@ export interface Env {
7778 METRICS?: AnalyticsEngineDataset;7879 }7980 80-// The less obvious numbers: 43 is added by info() once the relay has an81-// identity; 62 is request-to-vanish (store.vanish); 67 is the EOSE hint82-// array at the end of the subscription handler; 70 is the "-" tag check in83-// gate(); 66 is the discovery record the relay signs about itself84-// (publishDiscovery); 77 is negentropy sync (handleSync).85-export const SUPPORTED_NIPS = [1, 5, 9, 11, 13, 17, 29, 40, 42, 45, 46, 50, 56, 59, 62, 66, 67, 70, 77, 86, 98];86-export const SOFTWARE = "https://bind.ws";87-export const VERSION = "0.1.0";8881 export const MAX_MESSAGE = 1024 * 1024;8982 const FAVICON = FAVICON_SVG;9083 const MAX_SYNC = 100_000;
@@ -763,7 +756,7 @@ export class Relay extends DurableObject<Env> {
763756 }764757 if (upgrade) return this.acceptWebSocket(req);765758 if (req.headers.get("accept")?.includes("application/nostr+json")) {766- return Response.json(this.info(url.host), {759+ return Response.json(nip11(this, url.host), {767760 headers: { "content-type": "application/nostr+json", "access-control-allow-origin": "*" },768761 });769762 }
@@ -840,56 +833,6 @@ export class Relay extends DurableObject<Env> {
840833 return Math.min(this.settings.policy.maxMessageKB * 1024, MAX_MESSAGE);841834 }842835 843- info(host: string) {844- const p = this.settings.policy;845- const doc: Record<string, unknown> = {846- name: p.name || this.slug,847- description: p.description,848- supported_nips: SUPPORTED_NIPS,849- software: SOFTWARE,850- version: VERSION,851- limitation: {852- max_message_length: this.maxMessage(),853- max_subscriptions: p.maxSubs,854- max_limit: p.maxLimit,855- default_limit: p.maxLimit,856- max_subid_length: 64,857- // True whenever a fresh socket cannot REQ: reads for the authenticated or for members.858- auth_required: p.reads !== "open",859- payment_required: false,860- restricted_writes: p.writes !== "open" || this.settings.isUnclaimed(),861- created_at_upper_limit: p.maxFuture || undefined,862- min_pow_difficulty: p.minPow || undefined,863- },864- };865- if (p.icon) doc.icon = p.icon;866- if (p.banner) doc.banner = p.banner;867- if (p.joinTerms && host) doc.terms_of_service = this.webURL(host) + "/terms";868- if (p.postingPolicy) doc.posting_policy = p.postingPolicy;869- if (p.privacyPolicy) doc.privacy_policy = p.privacyPolicy;870- if (p.tags.length) doc.tags = p.tags;871- if (p.languageTags.length) doc.language_tags = p.languageTags;872- if (p.relayCountries.length) doc.relay_countries = p.relayCountries;873- if (p.contact) doc.contact = p.contact;874- const retention = this.settings.listRetention().map((r) => (r.kind === null ? { time: r.days * 86400 } : { kinds: [r.kind], time: r.days * 86400 }));875- if (retention.length) doc.retention = retention;876- if (this.fuel.cfg.lightningAddress && this.fuel.cfg.servicePubkey && host) {877- const f = this.fuel.status(now(), this.eventBytes(), this.mediaBytes());878- (doc.limitation as Record<string, unknown>).payment_required = f.outOfFuel;879- doc.payments_url = "https://" + host + "/";880- }881- if (p.owner) doc.pubkey = p.owner;882- if (p.succession && this.succession.warn) doc.succession_pending = new Date((this.succession.warn.since + SUCCESSION_WARN_DAYS * 86400) * 1000).toISOString().slice(0, 10);883- if (this.settings.isLeased() && p.lease) doc.lease = { expires_at: p.lease.until, holder: p.lease.holder || undefined, claim_url: host ? "https://" + host + "/" : undefined };884- if (host) doc.self_url = this.relayURL(host);885- if (this.identity.pubkey) {886- doc.self = this.identity.pubkey;887- doc.supported_nips = [...SUPPORTED_NIPS, 43];888- doc.views = nip11Views(this);889- }890- return doc;891- }892-893836 // ---- websockets ----894837 895838 private acceptWebSocket(req: Request): Response {