Skip to content

Latest commit

 

History

History
173 lines (144 loc) · 12.6 KB

File metadata and controls

173 lines (144 loc) · 12.6 KB
title Develop and extend
audience developer

Develop and extend

Layout

src/
  index.ts      worker entry: hostname -> object, POST /lease
  edge.ts       what the edge provides, with and without Cloudflare: the client's address, the lease limit, the hostname map
  names.ts      valid names, reserved names, lease names
  pull.ts       copy another relay in: NIP-77 as the initiator, one round per connection
  jobs.ts       the alarm's job list: pulls, backfills, rebroadcasts, once or standing
  relay.ts      the Durable Object: sockets, the alarm, members and bans, jobs, usage
  gates.ts      the write gate and the read gate
  routes.ts     the HTTP doors, in the order they are tried, each saying whether a blocked address may use it
  nip11.ts      the information document and the supported-NIP list
  succession.ts an heir and a dead-man's switch
  kinds.ts      every event kind the relay treats specially, by NIP
  store.ts      SQLite schema and queries
  settings.ts   policy, members, bans, kind rules, retention, export/import
  roles.ts      who may do what: owner, moderator; one table for NIP-86 and NIP-29
  groups.ts     NIP-29, one group per relay: joins, leaves, moderation events
  audit.ts      the moderation log: one row per change from manage.ts or groups.ts
  presets.ts    the templates as one-click presets, some with a standing pull
  config.ts     the configuration document: parse, plan, apply, export
  connections.ts the Connect fold's app shortcuts: the library's parser, the owner's list, /connect.json
  manage.ts     NIP-86 methods over NIP-98
  domains.ts    custom hostnames: the Cloudflare client and the KV mapping
  dumps.ts      scheduled JSONL dumps to R2
  notify.ts     NIP-17 messages from the relay to its owner
  pages.ts      notes and articles as pages, the Atom feed
  sites.ts      NIP-5A manifest validation, label parsing and site serving
  site-mirror.ts proxy and alarm-driven copies of missing site blobs
  site-auth.ts  NIP-07 challenge and relay-signed site cookie
  grasp.ts      GRASP-01 Git Smart HTTP and atomic event visibility
  git-sqlite.ts compressed Git objects, refs and retry receipts in SQLite
  git-objects.ts bounded indexed object graphs for Git reconciliation
  git-catalog.ts repository identities, quota totals and SQL work accounting
  git-storage.ts owner-only Git storage diagnostics
  git-backup.ts verified portable SQLite Git snapshots
  grasp-state.ts NIP-34 authority events, purgatory and cleanup state
  grasp-policy.ts GRASP-01 NIP-34 repository and ref policy
  card.ts       the status card, signed and as SVG
  qr.ts         a QR encoder for the card and the console
  fuel.ts       meters, prices, receipts, LNURL
  identity.ts   relay keypair and NIP-43 roster
  nip66.ts      the relay's NIP-66 discovery record about itself
  blossom.ts    BUD-01/02/04/06/08/09 on R2
  nip96.ts      NIP-96 door to the same bucket and table
  bridge.ts     HTTP bridge
  invites.ts    invite codes and the invite page
  nip05.ts      names
  ratelimit.ts  token buckets
  negentropy.ts hll.ts event.ts filter.ts
  dashboard.ts  the console's page, from the three files in console/
  console/      console.html, console.css, console.js: the console as written
  gen/signer.ts generated: the NIP-46 client library the console loads from /signer.js
  gen/templates.ts generated from relay-templates/
  gen/connections.ts generated from connection-templates/
  landing.ts    the apex, rendered from config
  ui.ts         the shared look
test/
  unit/         pure functions on node: the QR encoder, bolt11 amounts, the edge
  object/       Durable Object tests in workerd, one file per feature or module
  helpers/      the socket client, the management call, media tokens, the QR reader
  conformance/  black-box suite for any relay URL, one file per NIP, files included
relay-templates/      one relay configuration per template, the presets the console offers
relay-config.schema.json  the configuration file's schema, served at the apex
connection-templates/ one app shortcut per template, the library the Connect tab offers
connection-template.schema.json  a connection template's schema, served at the apex
scripts/
  build/   build-signer.mjs signer-entry.js   bundle nostr-tools for the console's remote signing
           build-console.mjs                  fold src/console into src/gen/console.ts
           build-templates.mjs                fold relay-templates/ into src/gen/templates.ts
           build-connections.mjs              fold connection-templates/ into src/gen/connections.ts
  check/   check-console.mjs check-celld.mjs check-connections.mjs check-config.mjs   run by npm run typecheck; check-config also checks any file
  dev/     dev-signer.mjs seed.mjs stage.mjs junk.mjs shot.mjs zaptest.mjs   npm run dev:signer, dev:seed, dev:stage, dev:junk, dev:shot, dev:zaptest
  ops/     margin.mjs network.mjs relay.mjs    npm run margin; npm run test:network; npm run relay check|plan|push|pull

Every script has an npm name (package.json), and the docs use those names: a tutorial never says node and a path.
wrangler.jsonc        the Worker on Cloudflare
wrangler.celld.jsonc  the same Worker on celld (docs/16)

Run the tests

npm test                  # unit and Durable Object tests
npm run typecheck
npm run test:conformance  # against RELAY_URL, default ws://127.0.0.1:7447
npm run test:network -- plan  # print the finite production topology; run and cleanup are manual

The conformance suite needs a claimed relay. Against a dev server:

CLAIM=1 RELAY_URL=ws://dev.localhost:8787 npm run test:conformance

CI runs typecheck and the object tests on every push; the conformance suite against the Worker on celld dev is a separate workflow run on demand (Hosting without Cloudflare). Work lands through pull requests in small commits that each typecheck on their own; the branch checks and review carry the change to main.

The production network exercise in Scripts and agents is an operator-run check of relay relationships. It creates five small, run-owned relays, uses finite one-shot jobs, samples only public events from the two named external relays and tears down its own relays. It is not a scheduled test, load test or capacity benchmark. Its private manifest supports manual cleanup after an interrupted run.

Add a management method

  1. Add an entry to METHODS in src/manage.ts: the action it needs (roles.ts), reads: true if it changes nothing, and run. The handler takes what it uses from the call: str(i) and num(i) for parameters, s for settings, reply({ result }) or reply({ error }, 400) to answer. supportedmethods, the permission check and the moderation log read the same entry.
  2. A method a moderator may call names an action in the moderator's set in roles.ts; anything else is the owner's.
  3. If it changes state that other code caches, update Settings and its in-memory sets.
  4. If the console should call it, add the control to src/console/console.html and a handler in console.js.
  5. Cover it in the test/object file for its feature, with rpc from test/helpers/relay.ts. If it can show a member, an event or a file, ask Settings.mayRead (or mayList for names and counts) with what whoAsks found in the header, and add the path to the door walk in test/object/exposure.test.ts, which knocks on every path as a stranger and as a signed-in non-member and fails on anything of a member's that comes back.

Add a NIP

Most NIPs land as their own module, wired in with one import and one row in routes.ts or one call from relay.ts. That is the pattern to follow:

NIP Module Wired from
29 groups groups.ts acceptGroup in relay.ts, after the write gate (gates.ts)
43 roster identity.ts publishMembership in relay.ts
66 discovery nip66.ts publishDiscovery in relay.ts, from publishMembership and the alarm
46 transport one kind in the write gate and the read gate gates.ts
86 methods manage.ts with roles.ts the RPC route
96 files nip96.ts over blossom.ts one row in routes.ts
11 extras settings.ts fields, nip11.ts the NIP-11 row in routes.ts

A draft NIP implemented ahead of upstream also gets a docs/20-nip-<name>.md file. Record the upstream commit and date it tracks, its user-facing usage, the relay-specific additions and limits, and what to watch upstream. Draft NIPs remain out of supported_nips unless the NIP explicitly defines relay advertising for them.

A NIP that changes the query surface touches store.ts. Add the number to SUPPORTED_NIPS in nip11.ts only when the NIP says relays advertise it, with a word in the comment there for the less obvious ones. Add a file named after the NIP in test/conformance so the behavior is checked from outside, and a Durable Object test in test/object, named after the module it exercises; a NIP number names a test file only where the feature has no other name (nip05, nip11, nip66).

GRASP-01 follows the same split: grasp-policy.ts keeps NIP-34 parsing and authorization pure, while the Git door owns Smart HTTP and the object-store seam. The accepted kind 30618 state is the complete ref map the door may serve. Keep the Git path separate from relay and NIP-5A routing, and record any new GRASP capability in the information document and the numbered draft specification. GRASP-02, GRASP-03, GRASP-05 and GRASP-06 are independently gated extensions with dependency checks. GRASP-02/03/05 are bounded previews and are not advertised until interoperability and conformance review; GRASP-08 remains outside this implementation.

The signer bundle

Remote signing needs NIP-44 and secp256k1, which browsers do not ship. scripts/build/signer-entry.js imports the pieces of nostr-tools the console uses and npm run build:signer bundles them with esbuild into src/gen/signer.ts, a string the relay serves at /signer.js. The console loads it only when someone picks a remote signer. The generated file is committed; npm run typecheck fails when it is stale.

The console

The console is three ordinary files in src/console: console.html (the body), console.css and console.js. npm run build:console folds them into src/gen/console.ts, the strings the relay serves; npm run dev runs it first, and npm run typecheck fails when the generated file is stale. It is a generated module rather than a text-module rule in wrangler.jsonc because celld deploy refuses a config with rules. scripts/check/check-console.mjs runs the script's synchronous start against a stub document, so a handler wired above the helper it calls fails typecheck instead of a live page. The shared shell in ui.ts provides fonts, colors, buttons, inputs and the sticker vocabulary; keep new pages inside it.

To look at what you changed: npm run dev, then npm run dev:signer, open http://<name>.localhost:8787/, paste the window.nostr snippet from that script into the devtools console, and sign in or claim. Playwright's CLI can drive the same loop: open the page, inject the snippet with eval, click, screenshot at a phone width and at a desktop width. scripts/dev/shot.mjs renders a page over the Chrome debugging protocol when Playwright is not around.

Rules that hold across pages: no middle dots, no purposeless subtext, tables scroll inside their card on narrow screens, decorative elements are not selectable, a multi-field form uses the labelled grid rather than one row of inputs, and the copy above a block is one sentence.

The Connect fold on the relay's page and the Connect tab in the owner's console are clients of /connect.json and the listconnectiontemplates, listconnections and setconnections methods. What they show is a library of files in connection-templates/, folded into src/gen/connections.ts by npm run build:connections the way the presets are; the file format, the placeholders, the owner's list and how to add a template are in Connection templates.

Screenshots

npm run dev:shot <url> <out.png> [width] [height] [--nostr] [--eval <js>] renders a page with phone or desktop emulation over the Chrome debugging protocol and prints the layout's scroll width, which should equal the viewport width. --nostr signs in as the dev signer's key (npm run dev:signer must be running), so the owner's console is in the picture; --eval runs a snippet after the page has loaded, such as opening a fold or a tab, before the shot. The pictures in docs/img/ come from a relay staged with npm run dev:stage.

Pull requests

Branch from main, push, open a pull request. CI must pass. Squash merge is the only merge mode; branches are deleted on merge.