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 ¶
const ( DefaultMaxInbound = 512 << 10 DefaultMaxOutbound = 64 << 10 DefaultDialTimeout = 15 * time.Second DefaultAuthTimeout = 15 * time.Second )
Defaults from the relay design (§3).
const KindAuth = 22242
KindAuth is the NIP-42 client authentication event kind.
Variables ¶
var ErrAuthRefused = errors.New("relay refused authentication")
ErrAuthRefused is a negative OK for this connection's AUTH event.
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).
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.
var ErrRejected = errors.New("relay rejected event")
ErrRejected is an explicit negative OK for a published event.
var ErrSubscriptionClosed = errors.New("relay closed the subscription")
ErrSubscriptionClosed is a CLOSED from the relay for a subscription.
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 ¶
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
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 ¶
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 ¶
Conn returns the current authenticated connection, or nil. A connection whose grant expired is never handed out, even before its transport ends.
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 ¶
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 ¶
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.
type RemoteError ¶
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).
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.