relay

package
v0.98.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 18, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package relay is AMQ's Nostr relay transport (bead 611.15, relay design slice 1): one bounded WebSocket per relay and body identity, NIP-42 AUTH that presents exactly one enrolled NIP-OA tag, and publish that succeeds only on the relay's matching OK. It executes no requests, mints no credentials and knows nothing about bridge envelopes; callers own policy and ledgers.

Index

Constants

View Source
const (
	DefaultMaxInbound  = 512 << 10
	DefaultMaxOutbound = 64 << 10
	DefaultDialTimeout = 15 * time.Second
	DefaultAuthTimeout = 15 * time.Second
)

Defaults from the relay design (§3).

View Source
const KindAuth = 22242

KindAuth is the NIP-42 client authentication event kind.

Variables

View Source
var ErrAuthRefused = errors.New("relay refused authentication")

ErrAuthRefused is a negative OK for this connection's AUTH event.

View Source
var ErrGrantExpired = errors.New("enrolled grant expired")

ErrGrantExpired means the enrolled grant this connection authenticated with has passed its signed not-after: the connection is unusable from that instant (codex slice 1 review r2 #3).

View Source
var ErrOverflow = errors.New("relay subscription overflowed; resubscribe from your cursor")

ErrOverflow closes a subscription whose consumer fell behind. Events are never dropped silently: the consumer re-subscribes from its own cursor.

View Source
var ErrRejected = errors.New("relay rejected event")

ErrRejected is an explicit negative OK for a published event.

View Source
var ErrSubscriptionClosed = errors.New("relay closed the subscription")

ErrSubscriptionClosed is a CLOSED from the relay for a subscription.

View Source
var ErrUnknownDelivery = errors.New("relay publish result unknown")

ErrUnknownDelivery is a publish whose OK never arrived: the event may or may not have been accepted. It is never reported as non-delivery.

Functions

func Category

func Category(err error) string

Category maps an error to a fixed, AMQ-worded category for status files: relay-controlled text (OK reasons, close reasons) never reaches them (codex slice 1 review #9). Local configuration errors keep their text.

func StatusText added in v0.88.0

func StatusText(err error) string

StatusText is err's text for a status file: a wrapped relay refusal shows only its category, so relay-controlled words never reach the file (codex slice 1 review #9).

func ValidateURL

func ValidateURL(raw string) error

ValidateURL enforces the transport policy: wss with normal TLS, or ws to a loopback address, and never credentials in the URL.

Types

type Client

type Client struct {

	// OnConnect, if set, runs for each authenticated connection in its own
	// goroutine; its context ends when that connection does. Subscriptions
	// and outbox flushing live here, so nothing crosses a reconnect.
	OnConnect func(ctx context.Context, conn *Conn)
	// BeforeClose, if set, runs when Run's ctx ends while a connection is
	// authenticated, before the socket closes, with its own short deadline:
	// the last chance to publish (a graceful offline). It never runs after a
	// transport loss.
	BeforeClose func(ctx context.Context, conn *Conn)
	// contains filtered or unexported fields
}

Client keeps one authenticated connection alive: connect, AUTH, and on loss reconnect with capped jittered backoff (1–30 s). No challenge, OK waiter or authenticated flag crosses a reconnect.

func NewClient

func NewClient(config ConfigFunc) *Client

NewClient returns a client in state configured.

func (*Client) Conn

func (c *Client) Conn() *Conn

Conn returns the current authenticated connection, or nil. A connection whose grant expired is never handed out, even before its transport ends.

func (*Client) Run

func (c *Client) Run(ctx context.Context) error

Run connects and stays connected until ctx ends. It returns nil on cancellation; it never returns on transport failure, only backs off.

func (*Client) Status

func (c *Client) Status() Status

Status returns the current status.

type Config

type Config struct {
	// URL is the relay endpoint: wss://, or ws:// to a loopback host only
	// (in-process tests). No userinfo.
	URL string
	// Secret is the body key's secret scalar; it signs the AUTH event.
	Secret [32]byte
	// AuthTag is the one enrolled NIP-OA tag ["auth", owner, conditions,
	// sig] presented on AUTH.
	AuthTag []string
	// NotAfter is the enrolled grant's signed not-after (created_at must be
	// before it). From that instant the connection is closed and unusable,
	// and an AUTH that completes at or after it is refused. Zero means none.
	NotAfter time.Time
	// Bounds; zero means the default.
	MaxInbound  int64
	MaxOutbound int
	DialTimeout time.Duration
	AuthTimeout time.Duration
	// Now is the clock for AUTH created_at; nil means time.Now.
	Now func() time.Time
}

Config is one connection's identity and bounds.

type ConfigFunc

type ConfigFunc func() (Config, error)

ConfigFunc produces a fresh connection config for each attempt, so a renewed enrollment is picked up on reconnect and an expired one stops authentication rather than riding an old socket.

type Conn

type Conn struct {
	// contains filtered or unexported fields
}

Conn is one authenticated relay connection. A Conn never crosses a reconnect: its challenge, OK waiters and authenticated flag die with it.

func Connect

func Connect(ctx context.Context, cfg Config) (*Conn, error)

Connect dials the relay, awaits its AUTH challenge, answers it with a signed kind 22242 event carrying the relay and challenge tags plus the one NIP-OA tag, and returns only after the relay's positive OK for that event. Socket open is not authentication; a negative OK, malformed challenge, timeout or disconnect is an error and the connection is closed.

func (*Conn) Close

func (c *Conn) Close()

Close ends the connection.

func (*Conn) Done

func (c *Conn) Done() <-chan struct{}

Done is closed when the connection ends.

func (*Conn) Err

func (c *Conn) Err() error

Err is why the connection ended, once Done is closed.

func (*Conn) Expired

func (c *Conn) Expired() bool

Expired reports whether the connection's grant has passed its not-after.

func (*Conn) Publish

func (c *Conn) Publish(ctx context.Context, evt nostr.Event) error

Publish sends one already signed event and returns nil only on the relay's matching positive OK. A negative OK is ErrRejected; a missing OK is ErrUnknownDelivery. It never re-signs or retries.

func (*Conn) Subscribe

func (c *Conn) Subscribe(ctx context.Context, id string, filters ...nostr.Filter) (*Sub, error)

Subscribe sends REQ with the given id and filters.

type RemoteError

type RemoteError struct {
	Kind   error
	Reason string
}

RemoteError carries a relay's refusal. Kind is the stable category; Reason is relay-controlled text. Error() adds the reason as quoted relay words, so a log or doctor names why the relay refused (agent-message-queue-611.58); status files take Category or StatusText, which never carry it (codex slice 1 review #9).

func (*RemoteError) Error

func (e *RemoteError) Error() string

func (*RemoteError) Unwrap

func (e *RemoteError) Unwrap() error

type State

type State string

State is a relay surface's reported condition. Each is distinct: socket open is not authenticated, and authenticated is not proof that the relay materialized ownership or that any viewer is ready (relay design §2).

const (
	StateConfigured    State = "configured"
	StateAuthPending   State = "auth_pending"
	StateAuthenticated State = "authenticated"
	StateUnavailable   State = "unavailable"
)

type Status

type Status struct {
	State State
	// Err is the last connection or authentication failure, if any.
	Err string
	// Since is when State was entered.
	Since time.Time
}

Status is a point-in-time view of a Client.

type Sub

type Sub struct {
	ID     string
	Events <-chan nostr.Event
	EOSE   <-chan struct{}
	// contains filtered or unexported fields
}

Sub is one REQ on one connection. Events carries only events whose id and signature verify and that match the subscription's filters. EOSE is closed when the relay reports end of stored events, which is an observation, never proof of complete history.

func (*Sub) Close

func (s *Sub) Close()

Close sends CLOSE and ends the subscription.

func (*Sub) Done

func (s *Sub) Done() <-chan struct{}

Done is closed when the subscription ends: CLOSED from the relay, overflow, Close, or the connection ending.

func (*Sub) Err

func (s *Sub) Err() error

Err is why the subscription ended, once Done is closed.

Directories

Path Synopsis
Package relaytest is an in-process Nostr relay for AMQ relay tests.
Package relaytest is an in-process Nostr relay for AMQ relay tests.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL