- commit
- 06f1328510294cd50eadbcf8a4dd7c0fd4b3552c
- parent
- eb8640f806aa028930799270c737364cf64ce166
- author
- Dami <dami.etoile@gmail.com>
| M | src/ | 3 | +- |
| A | src/ | 67 | ++++++++++++++++++++++++++++++ |
| M | src/ | 3 | +- |
| M | src/ | 65 | ++--------------------------- |
@@ -1,7 +1,8 @@
11 // The apex page: what bind.ws is, drawn as the system it is, with the22 // datasheet under it. Every number comes from the same config the relay runs33 // on, so the page cannot drift from the service.4-import { MAX_MESSAGE, VERSION, type Env } from "./relay.ts";4+import { MAX_MESSAGE, type Env } from "./relay.ts";5+import { VERSION } from "./nip11.ts";56 import { leaseDays } from "./names.ts";67 import { fuelConfig } from "./fuel.ts";78 import { DEFAULT_POLICY } from "./settings.ts";@@ -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+}@@ -5,6 +5,7 @@
55 // content is the NIP-11 document. Nothing here is measured, so there are66 // no rtt or geohash tags: those are a monitor's to add.77 import type { Relay } from "./relay.ts";8+import { nip11 } from "./nip11.ts";89 910 export interface Discovery {1011 url: string;@@ -29,7 +30,7 @@ export function relayTypes(writes: string, reads: string): string[] {
2930 // discovery builds the record's tags and content for the relay at `host`.3031 export function discovery(relay: Relay, host: string): Discovery {3132 const p = relay.settings.policy;32- const doc = relay.info(host);33+ const doc = nip11(relay, host);3334 const lim = (doc.limitation ?? {}) as Record<string, unknown>;3435 const url = new URL(relay.relayURL(host)).toString();3536 const tags: string[][] = [["-"], ["d", url], ["n", "clearnet"]];@@ -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 {