relay

package
v0.2.4 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: Unlicense Imports: 44 Imported by: 0

Documentation

Overview

Package relay is an embeddable Nostr relay engine: event storage (EventStore), WebSocket session handling (SessionHandler), NIP-11 negotiation, and a plugin registration mechanism (RegisterNIP / RegisterEventValidator) that NIP packages use to declare relay support without the relay package needing to import them. relay/client provides the corresponding client-side connection.

Index

Constants

View Source
const (
	// Cache directives
	CacheField        = "cache"
	CacheActionTopZap = "top-zapped"

	// NIP-50 constants
	ImpossibleID = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
)

Variables

View Source
var (
	ErrSessionClosed = errors.New("session closed")
	ErrRateLimited   = errors.New("rate-limited: too many concurrent tasks")
)
View Source
var (
	ErrEventDuplicated = errors.New("already have this event")
	ErrStoreClosed     = errors.New("store is closed")
	ErrEventNotFound   = errors.New("event not found")
)
View Source
var (
	ErrInviteClaimNotFound  = errors.New("relay: invite claim not found")
	ErrInviteClaimExpired   = errors.New("relay: invite claim expired")
	ErrInviteClaimExhausted = errors.New("relay: invite claim already used")
)
View Source
var (
	ErrSubscriptionClosed = errors.New("subscription closed")
)

Functions

func CheckSelfAuthored added in v0.2.3

func CheckSelfAuthored(ev *nip01.Event, selfPubkey string) (ok bool, message string)

CheckSelfAuthored reports whether ev is allowed given selfPubkey, the relay's own configured NIP-11 "self" identity (relay/nip11.Metadata.Self). Kinds NIP-43 requires to be relay-authored (role definitions, membership lists, add/remove-user, invite responses) MUST be signed by selfPubkey; every other kind passes unconditionally. selfPubkey == "" (no self identity configured) means every relay-authored kind is rejected outright -- there is no valid signer for them, so failing closed is the only sound default.

func IsPacketError

func IsPacketError(err error) bool

IsPacketError checks if the error is a wire.PacketError

func NewNIP05Handler added in v0.2.2

func NewNIP05Handler(store *EventStore, cfg *nip11.Metadata) http.Handler

NewNIP05Handler returns an http.Handler serving NIP-05 identity lookups (optionally filtered by a "name" query parameter) from the identity events already stored in store.

func RegisterEventValidator

func RegisterEventValidator(kind int, validator EventValidator)

RegisterEventValidator appends a validator for a specific event kind.

func RegisterLetteredNIP added in v0.2.3

func RegisterLetteredNIP(s string)

RegisterLetteredNIP declares support for a letter-suffixed NIP (e.g. "B0", "B7") whose ID isn't a plain number. See RegisterNIP.

func RegisterNIP

func RegisterNIP(n int)

RegisterNIP declares that this build supports the given numbered NIP. Feature packages call this from init(), typically alongside RegisterEventValidator, so the set of supported NIPs is derived from which packages are actually linked into the binary rather than asserted by config.

func RegisteredNIPs

func RegisteredNIPs() []nip11.NIPID

RegisteredNIPs returns the NIPs declared via RegisterNIP/RegisterLetteredNIP.

Types

type AuthedIdentity added in v0.2.3

type AuthedIdentity struct {
	Pubkey     string
	Membership MembershipStatus

	// Owner and Conditions are populated only when Membership ==
	// MembershipVirtual. Owner is the NIP-43-member pubkey this virtual
	// membership was derived from. Conditions is the already-verified
	// NIP-OA credential's parsed conditions, retained so optional per-event
	// kind enforcement (a later phase) never has to re-verify a signature
	// -- it's a nil pointer, not a zero-value struct, so plain NIP-43
	// active members and non-AA connections carry no incremental memory
	// for this field at all.
	Owner      string
	Conditions *nipOA.Conditions
}

AuthedIdentity records one pubkey authenticated on this connection via NIP-42, plus the membership status resolved for it at AUTH time.

Membership status is resolved once, when the identity is added, and never re-checked for the rest of the connection's life -- this follows NIP-AA's own revocation wording ("virtual membership is checked on each new connection, not cached across reconnects... the relay does not proactively expire mid-session"), and is also this design's main performance property: every REQ/EVENT membership check thereafter is a read of this connection's own (at most a handful of entries) identity list, never a lookup against the shared, cross-connection membership cache.

type CacheHandler

type CacheHandler struct{}

func (*CacheHandler) CanHandle

func (h *CacheHandler) CanHandle(rp *wire.RequestPacket) bool

func (*CacheHandler) Handle

func (h *CacheHandler) Handle(ctx context.Context, s *Session, rp *wire.RequestPacket) (bool, error)

type ClientInfo

type ClientInfo struct {
	RemoteAddr string

	HTTP struct {
		UserAgent string
		Origin    string
		Host      string
	}
}

type DelegationConfig

type DelegationConfig struct {
	Issuer     string
	Conditions string
	Token      string
}

type EventDeleteTask

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

func NewEventDeleteTask

func NewEventDeleteTask(pes []*PotentialEvent) *EventDeleteTask

func (*EventDeleteTask) Completed

func (edt *EventDeleteTask) Completed() <-chan struct{}

func (*EventDeleteTask) Done

func (edt *EventDeleteTask) Done(err error)

func (*EventDeleteTask) Errors

func (edt *EventDeleteTask) Errors() <-chan error

func (*EventDeleteTask) Execute

func (edt *EventDeleteTask) Execute(store *EventStore) error

type EventInsertTask

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

func NewEventInsertTask

func NewEventInsertTask(events []*nip01.Event) *EventInsertTask

func (*EventInsertTask) Completed

func (eit *EventInsertTask) Completed() <-chan struct{}

func (*EventInsertTask) Done

func (eit *EventInsertTask) Done(err error)

func (*EventInsertTask) Errors

func (eit *EventInsertTask) Errors() <-chan error

func (*EventInsertTask) Execute

func (eit *EventInsertTask) Execute(store *EventStore) error

type EventStore

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

func NewEventStore

func NewEventStore(path string, limitation *nip11.Limitation, opts ...EventStoreOption) (*EventStore, error)

func (*EventStore) ClearZapIndex added in v0.2.2

func (s *EventStore) ClearZapIndex(ctx context.Context) error

ClearZapIndex deletes and recreates the zap index bucket, discarding all indexed zap-receipt data without touching the underlying stored events. Pair with ReindexZaps to rebuild the index from scratch.

func (*EventStore) Close

func (s *EventStore) Close()

Close stops accepting new tasks and shuts down the store, waiting for every handleTasks/housekeeper goroutine to exit before closing db. Those goroutines race db.Update/db.View against closeCh, so closing db early can hit one already past that check — Execute on a mid-close bbolt handle hangs instead of erroring, blocking its caller forever.

func (*EventStore) ConsumeInviteClaim added in v0.2.3

func (s *EventStore) ConsumeInviteClaim(code string, now int64) (*InviteClaim, error)

ConsumeInviteClaim atomically loads, validates, increments Uses, and persists code's claim in a single bbolt transaction -- avoiding a check-then-act race between concurrent Join Requests racing to use the same claim. Returns ErrInviteClaimNotFound / ErrInviteClaimExpired / ErrInviteClaimExhausted for the respective failure, or the (post-increment) claim on success.

func (*EventStore) CountEvents

func (s *EventStore) CountEvents(ctx context.Context, filters *nip01.SubscriptionFilterGroup) (int64, error)

func (*EventStore) Db

func (s *EventStore) Db() *bolt.DB

Db returns the underlying bbolt handle, for an embedder's own buckets sharing this file. Never touch this package's own buckets through it — go through Execute/Task instead, which enforces dedup, replaceable-event handling, and index consistency. Unusable once Close has been called.

func (*EventStore) DeleteAll

func (s *EventStore) DeleteAll(pes []*PotentialEvent) error

func (*EventStore) DeleteInviteClaim added in v0.2.3

func (s *EventStore) DeleteInviteClaim(code string) error

DeleteInviteClaim removes code outright (distinct from ConsumeInviteClaim, which increments Uses on a valid claim) -- used for `ncli relay invites revoke`. A no-op, not an error, if code doesn't exist.

func (*EventStore) Execute

func (s *EventStore) Execute(ctx context.Context, task Task)

func (*EventStore) ExecuteBatch

func (s *EventStore) ExecuteBatch(tasks []Task)

ExecuteBatch commits multiple tasks in a single transaction

func (*EventStore) FetchAll

func (s *EventStore) FetchAll() ([]*nip01.Event, error)

func (*EventStore) FetchProfileEvents

func (s *EventStore) FetchProfileEvents(ctx context.Context, pubkeys []string) ([]*nip01.Event, error)

FetchProfileEvents retrieves the latest Kind 0 (Metadata) events for the given pubkeys. It bypasses the standard subscription query mechanism for performance.

func (*EventStore) FindEvent

func (s *EventStore) FindEvent(evsid uint64) (*nip01.Event, error)

func (*EventStore) FindEventBytes

func (s *EventStore) FindEventBytes(evsid uint64) ([]byte, error)

func (*EventStore) FindEvents

func (s *EventStore) FindEvents(ctx context.Context, filter *nip01.SubscriptionFilter) ([]*PotentialEvent, error)

func (*EventStore) GetInviteClaim added in v0.2.3

func (s *EventStore) GetInviteClaim(code string) (*InviteClaim, error)

GetInviteClaim returns the persisted claim, or (nil, nil) if code is not a known claim.

func (*EventStore) GetMember added in v0.2.3

func (s *EventStore) GetMember(pubkey string) (*MemberRecord, error)

GetMember returns the persisted record for pubkey, or (nil, nil) if pubkey is not currently a member -- absence is a normal, common outcome, not an error.

func (*EventStore) GetProfileMetrics

func (s *EventStore) GetProfileMetrics(pubkey string) (*ProfileMetrics, error)

GetProfileMetrics returns the persisted metrics or an empty ProfileMetrics struct.

func (*EventStore) GetTopZapped

func (s *EventStore) GetTopZapped(ctx context.Context, since, until uint64, limit int) ([]ZapStats, error)

GetTopZapped returns the top zapped public keys within the specified time window.

func (*EventStore) IndexZap

func (s *EventStore) IndexZap(tx *bolt.Tx, event *nip01.Event, evsid uint64) error

IndexZap indexes a zap receipt — NIP-57 (Kind 9735) or AltZap (Kind 5521). It extracts the amount and the receiver pubkey and stores them in a dedicated bucket.

func (*EventStore) InsertEvents

func (s *EventStore) InsertEvents(ctx context.Context, events []*nip01.Event) error

func (*EventStore) ListInviteClaims added in v0.2.3

func (s *EventStore) ListInviteClaims() ([]*InviteClaim, error)

ListInviteClaims returns every currently-stored invite claim. Used for `ncli relay invites list`, not the request hot path.

func (*EventStore) ListMemberRecords added in v0.2.3

func (s *EventStore) ListMemberRecords() ([]*MemberRecord, error)

ListMemberRecords returns every member's full persisted record. Used by `ncli relay members list`, not the request hot path -- unlike ListMembers (pubkeys only, for cache warm-up), callers here need Roles/JoinedAt too, so this reads each record directly in one pass rather than making callers pair it with a GetMember per pubkey.

func (*EventStore) ListMembers added in v0.2.3

func (s *EventStore) ListMembers() ([]string, error)

ListMembers returns every member pubkey currently persisted. Used for cold-start cache loading at relay construction, not the request hot path.

func (*EventStore) Name

func (s *EventStore) Name() string

func (*EventStore) PutInviteClaim added in v0.2.3

func (s *EventStore) PutInviteClaim(claim *InviteClaim) error

PutInviteClaim inserts or overwrites claim.

func (*EventStore) PutMember added in v0.2.3

func (s *EventStore) PutMember(rec *MemberRecord) error

PutMember inserts or overwrites rec's membership record.

func (*EventStore) QueryEvents

func (s *EventStore) QueryEvents(ctx context.Context, filter *nip01.SubscriptionFilter) ([]*nip01.Event, error)

QueryEvents finds events matching the filter.

func (*EventStore) QueryNip77Items

func (s *EventStore) QueryNip77Items(ctx context.Context, filter *nip01.SubscriptionFilter) ([]nip77.Item, error)

func (*EventStore) ReindexZaps

func (s *EventStore) ReindexZaps(ctx context.Context, progressCb func(count int)) (int, error)

ReindexZaps iterates over all events and populates the indexZaps bucket. It returns the count of indexed zaps and any error encountered.

func (*EventStore) RemoveMember added in v0.2.3

func (s *EventStore) RemoveMember(pubkey string) error

RemoveMember deletes pubkey's membership record. A no-op, not an error, if pubkey isn't currently a member.

func (*EventStore) UpdateProfileMetrics

func (s *EventStore) UpdateProfileMetrics(pubkey string, modifier func(*ProfileMetrics)) error

UpdateProfileMetrics performs a thread-safe read-modify-write on the metrics.

type EventStoreConfig added in v0.2.2

type EventStoreConfig struct {
	TaskQueueSize int
	WorkerCount   int
	BatchSize     int
	BatchInterval time.Duration

	// Logger receives store/migration logging. Defaults to zerolog.Nop()
	// (silent) so an EventStore never writes to the process-global logger
	// unless the caller opts in.
	Logger zerolog.Logger
}

type EventStoreOption

type EventStoreOption func(*EventStoreConfig)

EventStoreOption mutates the internal event store configuration.

func WithEventStoreBatchConfig

func WithEventStoreBatchConfig(size int, interval time.Duration) EventStoreOption

WithEventStoreBatchConfig sets the batch processing parameters.

func WithEventStoreLogger added in v0.2.2

func WithEventStoreLogger(logger zerolog.Logger) EventStoreOption

WithEventStoreLogger configures the logger used for store and migration logging. Defaults to zerolog.Nop() (silent).

func WithEventStoreQueueSize

func WithEventStoreQueueSize(size int) EventStoreOption

WithEventStoreQueueSize sets the buffered queue size for store tasks.

func WithEventStoreWorkerCount

func WithEventStoreWorkerCount(count int) EventStoreOption

WithEventStoreWorkerCount sets the number of background workers.

type EventValidator

type EventValidator func(context.Context, *nip01.Event) error

type InviteClaim added in v0.2.3

type InviteClaim struct {
	Code      string   `json:"code"`
	CreatedAt int64    `json:"created_at"`
	ExpiresAt int64    `json:"expires_at,omitempty"` // 0 = never expires
	MaxUses   int      `json:"max_uses,omitempty"`   // 0 = unlimited uses
	Uses      int      `json:"uses"`
	Roles     []string `json:"roles,omitempty"`
}

InviteClaim is the persisted state for one NIP-43 invite code.

type MemberRecord added in v0.2.3

type MemberRecord struct {
	Pubkey   string   `json:"pubkey"`
	Roles    []string `json:"roles,omitempty"`
	JoinedAt int64    `json:"joined_at"`
}

MemberRecord is the authoritative, persisted record for one NIP-43 member.

type MembershipRequestHandler added in v0.2.3

type MembershipRequestHandler struct{}

MembershipRequestHandler serves NIP-43's kind:28935 Invite Request: a client REQs for this kind, and the relay generates and signs a fresh invite code on the fly. Per spec, this is deliberately in the ephemeral kind range so relays "improve security by issuing a different claim for each request." Mirrors CacheHandler's shape (relay-signed, one-shot, synthetic response to a REQ, not an EVENT).

func (*MembershipRequestHandler) CanHandle added in v0.2.3

func (h *MembershipRequestHandler) CanHandle(rp *wire.RequestPacket) bool

func (*MembershipRequestHandler) Handle added in v0.2.3

type MembershipService added in v0.2.3

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

MembershipService resolves NIP-43 membership status for a pubkey and owns keeping the in-memory cache in sync with the authoritative store. A nil *MembershipService is a valid, common case -- NIP-43 not configured on this relay -- and every method reports the "not a member"/no-op answer unconditionally, so call sites never need their own nil check.

func NewMembershipService added in v0.2.3

func NewMembershipService(store *EventStore) *MembershipService

NewMembershipService constructs a MembershipService backed by store. Call LoadFromStore once, at relay construction, to pre-populate the in-memory cache before serving traffic.

func (*MembershipService) Get added in v0.2.3

func (m *MembershipService) Get(pubkey string) (*MemberRecord, error)

Get returns pubkey's persisted member record, or (nil, nil) if pubkey is not currently a member. Used by `ncli relay members show`, not the request hot path.

func (*MembershipService) HandleEvent added in v0.2.3

func (m *MembershipService) HandleEvent(ctx context.Context, s *Session, ev *nip01.Event)

HandleEvent processes a NIP-43 Join (kind 28934) or Leave (kind 28936) request, replying via s and mutating membership state as a side effect. Callers (processEvent) should invoke this only for those two kinds; it always replies (never falls through silently), since a client sending one of these kinds is unambiguously attempting to use this feature -- including when m is nil (NIP-43 membership isn't configured on this relay at all), which gets an explicit "restricted" reply rather than being silently accepted as an ordinary ephemeral event.

func (*MembershipService) IsMember added in v0.2.3

func (m *MembershipService) IsMember(pubkey string) bool

IsMember reports whether pubkey is a current, direct/active NIP-43 member.

func (*MembershipService) IssueInvite added in v0.2.3

func (m *MembershipService) IssueInvite(ttl time.Duration, maxUses int, roles []string) (*InviteClaim, error)

IssueInvite generates a fresh invite claim, persists it, and returns it. Shared by the client-facing REQ kind:28935 path (MembershipRequestHandler.Handle) and the admin "invites create" HTTP endpoint, so both produce claims the exact same way. ttl <= 0 falls back to defaultMembershipInviteTTL; maxUses <= 0 means unlimited uses.

func (*MembershipService) Join added in v0.2.3

func (m *MembershipService) Join(pubkey string, roles []string) error

Join enrolls pubkey as a member with the given roles, persisting it to the authoritative store and updating the in-memory cache.

func (*MembershipService) Leave added in v0.2.3

func (m *MembershipService) Leave(pubkey string) error

Leave removes pubkey's membership from the authoritative store and the in-memory cache.

func (*MembershipService) List added in v0.2.3

func (m *MembershipService) List() ([]*MemberRecord, error)

List returns every currently-enrolled member's full persisted record. Used by `ncli relay members list` (admin HTTP path), not the request hot path.

func (*MembershipService) LoadFromStore added in v0.2.3

func (m *MembershipService) LoadFromStore() error

LoadFromStore populates the in-memory cache from the authoritative store -- called once at relay construction (cold start), not on any request path.

func (*MembershipService) ReplaceFromEvent added in v0.2.3

func (m *MembershipService) ReplaceFromEvent(ev *nip01.Event) error

ReplaceFromEvent re-derives the authoritative member set from a manually-published kind:13534 event's member tags -- the belt-and-suspenders resync for an operator who publishes a raw membership-list event directly instead of going through Join/Leave.

type MembershipStatus added in v0.2.3

type MembershipStatus uint8

MembershipStatus records a single authenticated identity's NIP-43 standing on a connection.

const (
	// MembershipNone means the pubkey authenticated (NIP-42) but holds no
	// NIP-43 membership -- the common case on a relay that hasn't enabled
	// membership enforcement, or for a non-member on one that has.
	MembershipNone MembershipStatus = iota
	// MembershipActive means the pubkey is a direct, explicitly-enrolled
	// NIP-43 member.
	MembershipActive
	// MembershipVirtual means access was derived from an owner's active
	// membership (NIP-AA), not from the pubkey's own enrollment.
	MembershipVirtual
)

func (MembershipStatus) String added in v0.2.3

func (m MembershipStatus) String() string

type NIP50Handler

type NIP50Handler struct{}

NIP50Handler intercepts requests with "search" filter fields.

func (*NIP50Handler) CanHandle

func (h *NIP50Handler) CanHandle(rp *wire.RequestPacket) bool

func (*NIP50Handler) Handle

func (h *NIP50Handler) Handle(ctx context.Context, s *Session, rp *wire.RequestPacket) (bool, error)

type PotentialEvent

type PotentialEvent struct {
	Evsid     uint64
	CreatedAt uint64
	EventID   string
}

type ProfileMetrics

type ProfileMetrics struct {
	BaseScore      int64 `json:"base_score"`
	Nip05Score     int64 `json:"nip05_score"`
	Lud16Score     int64 `json:"lud16_score"`
	PictureScore   int64 `json:"picture_score"`
	LastVerifiedAt int64 `json:"last_verified_at"`
}

ProfileMetrics holds the parsed metrics for a profile, allowing unified scoring in search and other components.

func (*ProfileMetrics) TotalScore

func (p *ProfileMetrics) TotalScore() int64

TotalScore strictly evaluates the sum of all metrics to provide a single score metric.

type ProfileVerificationWorker

type ProfileVerificationWorker struct {
	TotalProcessed atomic.Uint64
	TotalErrors    atomic.Uint64
	TotalEnqueued  atomic.Uint64
	StartTime      time.Time
	// contains filtered or unexported fields
}

func NewProfileVerificationWorker

func NewProfileVerificationWorker(store *EventStore, searchService search.Service) *ProfileVerificationWorker

NewProfileVerificationWorker inherits its logger from store, which already carries whatever WithEventStoreLogger the caller configured on it.

func (*ProfileVerificationWorker) GetStats

func (w *ProfileVerificationWorker) GetStats() map[string]interface{}

GetStats returns thread-safe metadata about the worker workload

func (*ProfileVerificationWorker) QueueJob

func (w *ProfileVerificationWorker) QueueJob(event *nip01.Event)

func (*ProfileVerificationWorker) Start

func (w *ProfileVerificationWorker) Start(count int)

func (*ProfileVerificationWorker) Stop

func (w *ProfileVerificationWorker) Stop()

type Relay added in v0.2.2

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

Relay is an http.Handler; pass it straight to http.ListenAndServe.

func New added in v0.2.2

func New(path string, metadata *nip11.Metadata) (*Relay, error)

New opens an event store at path and returns a ready-to-serve relay (NIP-11 negotiation included, search disabled). This is the fast path for "just run a relay" — for storage tuning, a search service, or session options, build the store and handler directly with NewEventStore and NewSessionHandler.

func (*Relay) Close added in v0.2.2

func (r *Relay) Close() error

Close stops the verification worker and closes the underlying event store.

func (*Relay) ServeHTTP added in v0.2.2

func (r *Relay) ServeHTTP(w http.ResponseWriter, req *http.Request)

type RequestHandler

type RequestHandler interface {
	// CanHandle returns true if this handler should process the request.
	CanHandle(rp *wire.RequestPacket) bool
	// Handle processes the request. modifying the request or responding directly.
	// Returns true if the request was fully handled and no further processing is needed.
	Handle(ctx context.Context, s *Session, rp *wire.RequestPacket) (bool, error)
}

RequestHandler defines a strategy for handling a subscription request.

type Session

type Session struct {
	*SessionContext
	// contains filtered or unexported fields
}

func NewSession

func NewSession(id int64, conn *websocket.Conn, sc *SessionContext, maxMessageLength int64) *Session

func (*Session) AuthedPubkey added in v0.2.2

func (s *Session) AuthedPubkey() string

AuthedPubkey returns the first pubkey this session authenticated as via NIP-42, or "" if it has not authenticated. A connection may hold more than one authenticated identity (see addIdentity) -- this returns only the first, which is all plain NIP-42 (no NIP-43/NIP-AA) ever produces. Callers that need a specific pubkey's own status should use IdentityMembership; callers that need "does anything on this connection hold membership" should use HasMembership.

func (*Session) Close

func (s *Session) Close()

func (*Session) HasMembership added in v0.2.3

func (s *Session) HasMembership() bool

HasMembership reports whether any authenticated identity on this connection holds active or virtual NIP-43 membership. This is the REQ/COUNT gate: per NIP-AA, such requests pass "if at least one authenticated pubkey on the connection holds active or virtual membership."

func (*Session) HasOwnerIdentity added in v0.2.3

func (s *Session) HasOwnerIdentity(owner string) bool

HasOwnerIdentity reports whether any virtual identity on this connection was derived from owner. Used by owner-scoped session enumeration (a later phase), not the request/event hot path.

func (*Session) ID added in v0.2.2

func (s *Session) ID() int64

ID returns this session's server-assigned identifier.

func (*Session) Identities added in v0.2.3

func (s *Session) Identities() []AuthedIdentity

Identities returns a defensive copy of every identity authenticated on this connection (at most a handful of entries in practice) -- for admin/audit use, not the hot path.

func (*Session) IdentityMembership added in v0.2.3

func (s *Session) IdentityMembership(pubkey string) (AuthedIdentity, bool)

IdentityMembership looks up pubkey's own status among this connection's authenticated identities. This is the EVENT gate: per NIP-AA, the relay "MUST verify event.pubkey is authenticated on the connection AND holds active or virtual membership" -- never falling back to another identity's status. An event signed by a pubkey that never itself authenticated on this connection reports ok=false, even if some other pubkey on the same connection is a member.

func (*Session) Info added in v0.2.2

func (s *Session) Info() *ClientInfo

Info returns the client metadata (remote address, User-Agent, Origin, Host) captured when this session's connection was established.

func (*Session) ProcessPacket

func (s *Session) ProcessPacket(ctx context.Context, p wire.Packet) error

ProcessPacket dispatches the packet to the appropriate handler.

func (*Session) Recv

func (s *Session) Recv(ctx context.Context) error

func (*Session) Start

func (s *Session) Start(parent context.Context) error

type SessionConfig

type SessionConfig struct {
	PingInterval            time.Duration
	PongTimeout             time.Duration
	ControlWriteTimeout     time.Duration
	DataWriteTimeout        time.Duration
	CloseGracePeriod        time.Duration
	OutgoingBufferSize      int
	MaxConcurrentStoreTasks int

	// Default limits for cache and search
	DefaultCacheWindow time.Duration
	DefaultCacheLimit  int
	DefaultSearchLimit int

	// EnableTopZapped gates the signed "top zapped" cache response (see
	// handler_cache.go). Off by default: serving it requires signing with
	// PrivKey, so it must be turned on deliberately rather than activating
	// itself just because a PrivKey happens to be configured.
	EnableTopZapped bool

	// MembershipInviteTTL bounds how long a NIP-43 invite claim (kind
	// 28935) remains valid after being issued. <= 0 falls back to
	// defaultMembershipInviteTTL.
	MembershipInviteTTL time.Duration

	// MembershipInviteMaxUses caps how many times a single invite claim
	// may be consumed via a Join Request (kind 28934). 0 means unlimited.
	MembershipInviteMaxUses int

	// MembershipPublishAddRemove, if true, makes the relay also publish a
	// signed kind:8000/8001 event whenever NIP-43 membership changes via
	// Join/Leave Request. Off by default -- the authoritative state lives
	// in the store either way; this only affects whether other clients see
	// a corresponding advertised event.
	MembershipPublishAddRemove bool

	// AgentAuthEnabled turns on NIP-AA: a non-member AUTH event carrying a
	// valid NIP-OA "auth" tag grants virtual membership when the named
	// owner is an active NIP-43 member. Off by default -- requires NIP-43
	// membership to already be meaningful (a relay with no membership
	// concept has nothing for NIP-AA to derive access from).
	AgentAuthEnabled bool

	// AgentAuthFreshnessWindow bounds how far an AUTH event's created_at
	// may drift from now before NIP-AA rejects it (on top of NIP-42's own,
	// wider window). <= 0 falls back to nipAA.DefaultFreshnessWindow
	// (spec-recommended ±120s).
	AgentAuthFreshnessWindow time.Duration

	// AgentKindEnforcement, if true, additionally checks a virtual
	// member's retained credential's kind= clauses against every EVENT it
	// submits. Off by default -- per spec, kind= clauses are advisory only
	// unless a relay opts into this.
	AgentKindEnforcement bool

	// NIP-26 Delegation
	PrivKey    string
	Delegation *DelegationConfig

	// AllowedOrigins is the CORS allowlist checked against the WebSocket
	// handshake's Origin header. Defaults to {"*"} (allow any origin),
	// matching prior behavior; set explicitly via WithSessionAllowedOrigins
	// to restrict which browser origins may connect.
	AllowedOrigins []string

	// Logger receives all session lifecycle/verification logging. Defaults
	// to zerolog.Nop() (silent) so embedding a Session/SessionHandler never
	// writes to the process-global logger unless the caller opts in.
	Logger zerolog.Logger
}

SessionConfig holds runtime behaviour for websocket sessions.

type SessionContext

type SessionContext struct {
	SearchService      search.Service
	VerificationWorker *ProfileVerificationWorker
	// contains filtered or unexported fields
}

func NewSessionContext

func NewSessionContext(store *EventStore, info *ClientInfo, cfg *nip11.Metadata, searchService search.Service, vWorker *ProfileVerificationWorker, sessCfg *SessionConfig) *SessionContext

type SessionHandler

type SessionHandler struct {

	// VerificationWorker processes NIP-05/LUD-16 profile verification jobs.
	// NewSessionHandler constructs it but does not start it — call
	// VerificationWorker.Start(n) yourself before serving traffic, or use
	// relay.New, which starts it with a default worker count automatically.
	VerificationWorker *ProfileVerificationWorker
	// contains filtered or unexported fields
}

func NewSessionHandler

func NewSessionHandler(store *EventStore, relayMetadata *nip11.Metadata, searchService search.Service, opts ...SessionOption) *SessionHandler

NewSessionHandler constructs a SessionHandler ready to be used as an http.Handler. Note: the returned handler's VerificationWorker is constructed but not started — call VerificationWorker.Start(n) before serving traffic if profile verification should run, or use relay.New, which does this for you.

func (*SessionHandler) Membership added in v0.2.3

func (sh *SessionHandler) Membership() *MembershipService

Membership returns this handler's NIP-43 MembershipService, shared by every Session it serves. Never nil.

func (*SessionHandler) ServeHTTP

func (sh *SessionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

func (*SessionHandler) SessionCount added in v0.2.2

func (sh *SessionHandler) SessionCount() int

SessionCount returns the number of currently connected sessions.

func (*SessionHandler) SupportedNIPs

func (sh *SessionHandler) SupportedNIPs() nip11.NIPSet

SupportedNIPs derives the set of NIPs this SessionHandler actually implements from its wired-in configuration and services. Operators cannot override this list — it is computed, not configured.

type SessionOption

type SessionOption func(*SessionConfig)

SessionOption mutates a SessionConfig instance.

func WithLogger added in v0.2.2

func WithLogger(logger zerolog.Logger) SessionOption

WithLogger configures the logger used for session lifecycle, verification, and error logging. Defaults to zerolog.Nop() (silent).

func WithSessionAgentAuth added in v0.2.3

func WithSessionAgentAuth(enabled bool, freshnessWindow time.Duration, kindEnforcement bool) SessionOption

WithSessionAgentAuth configures NIP-AA: whether it's enabled, the AUTH freshness window (<= 0 for the spec-recommended default), and whether per-event kind= enforcement is active. Setting this alone, unlike WithSessionConfig, does not discard any other configured defaults.

func WithSessionAllowedOrigins added in v0.2.2

func WithSessionAllowedOrigins(origins ...string) SessionOption

WithSessionAllowedOrigins restricts the CORS allowlist checked against the WebSocket handshake's Origin header to the given origins. Without this option, any origin is allowed ("*"), matching prior behavior.

func WithSessionBufferSize

func WithSessionBufferSize(size int) SessionOption

WithSessionBufferSize sets the buffered capacity for outgoing replies.

func WithSessionCacheConfig

func WithSessionCacheConfig(window time.Duration, limit int) SessionOption

WithSessionCacheConfig sets the default window and limit for cache requests.

func WithSessionCloseGrace

func WithSessionCloseGrace(delta time.Duration) SessionOption

WithSessionCloseGrace configures how long we wait for the peer close frame.

func WithSessionConfig

func WithSessionConfig(cfg SessionConfig) SessionOption

WithSessionConfig replaces the default session configuration with the provided values.

func WithSessionDelegation added in v0.2.2

func WithSessionDelegation(d *DelegationConfig) SessionOption

WithSessionDelegation sets NIP-26 delegation for cache responses. Setting this alone, unlike WithSessionConfig, does not discard any other configured defaults.

func WithSessionMaxConcurrentTasks

func WithSessionMaxConcurrentTasks(limit int) SessionOption

WithSessionMaxConcurrentTasks limits concurrent store submissions per session.

func WithSessionMembership added in v0.2.3

func WithSessionMembership(inviteTTL time.Duration, inviteMaxUses int, publishAddRemove bool) SessionOption

WithSessionMembership configures NIP-43's invite-claim TTL/max-uses and whether the relay also publishes kind:8000/8001 add/remove events on membership changes. Setting this alone, unlike WithSessionConfig, does not discard any other configured defaults.

func WithSessionPingConfig

func WithSessionPingConfig(pingInterval, pongTimeout time.Duration) SessionOption

WithSessionPingConfig overrides the ping-pong intervals.

func WithSessionPrivKey added in v0.2.2

func WithSessionPrivKey(key string) SessionOption

WithSessionPrivKey sets the relay's own private key, used to sign cache responses (see handler_cache.go). Setting this alone, unlike WithSessionConfig, does not discard any other configured defaults.

func WithSessionSearchLimit

func WithSessionSearchLimit(limit int) SessionOption

WithSessionSearchLimit sets the default limit for search requests.

func WithSessionTopZapped added in v0.2.2

func WithSessionTopZapped(enabled bool) SessionOption

WithSessionTopZapped enables or disables the signed "top zapped" cache response (see handler_cache.go). Setting this alone, unlike WithSessionConfig, does not discard any other configured defaults.

func WithSessionWriteTimeouts

func WithSessionWriteTimeouts(control, data time.Duration) SessionOption

WithSessionWriteTimeouts configures write deadlines for control and data frames.

type StandardRequestHandler

type StandardRequestHandler struct{}

StandardRequestHandler handles standard NIP-01 subscriptions.

func (*StandardRequestHandler) CanHandle

func (h *StandardRequestHandler) CanHandle(rp *wire.RequestPacket) bool

func (*StandardRequestHandler) Handle

type StoreQuery

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

func NewStoreQuery

func NewStoreQuery(store *EventStore, filters *nip01.SubscriptionFilterGroup) (*StoreQuery, error)

func (*StoreQuery) Fetch

func (q *StoreQuery) Fetch(ctx context.Context, out chan<- *PotentialEvent, wg *sync.WaitGroup, keepOpen bool) error

type Subscription

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

func NewSubscription

func NewSubscription(id string, query *StoreQuery) (*Subscription, <-chan *PotentialEvent, <-chan error, <-chan bool)

func (*Subscription) Closed

func (sub *Subscription) Closed() <-chan interface{}

func (*Subscription) Start

func (sub *Subscription) Start(parent context.Context, wg *sync.WaitGroup)

func (*Subscription) Stop

func (sub *Subscription) Stop()

type SubscriptionsMap

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

func NewSubscriptions

func NewSubscriptions(cfg *nip11.Limitation) *SubscriptionsMap

func (*SubscriptionsMap) Add

func (s *SubscriptionsMap) Add(sub *Subscription) bool

func (*SubscriptionsMap) Close

func (s *SubscriptionsMap) Close(subID string) bool

func (*SubscriptionsMap) Get

func (s *SubscriptionsMap) Get(subID string) (*Subscription, bool)

func (*SubscriptionsMap) Size

func (s *SubscriptionsMap) Size() int

func (*SubscriptionsMap) StopAll

func (s *SubscriptionsMap) StopAll()

type Task

type Task interface {
	Execute(store *EventStore) error
	Done(err error)
}

type VerificationJob

type VerificationJob struct {
	Pubkey  string
	Nip05   string
	Lud16   string
	Picture string
}

type ZapStats

type ZapStats struct {
	Pubkey     string `json:"pubkey"`
	TotalMLoki uint64 `json:"total_mloki"`
}

ZapStats contains the aggregated zap amount for a pubkey.

Directories

Path Synopsis
Package client is a Nostr relay WebSocket client: dial a relay (Connection), publish and subscribe, and round-trip higher-level protocols built on top of the wire format (e.g.
Package client is a Nostr relay WebSocket client: dial a relay (Connection), publish and subscribe, and round-trip higher-level protocols built on top of the wire format (e.g.
Package migrations runs versioned schema migrations against the relay package's embedded bbolt key-value store, tracking the applied version in a dedicated bucket.
Package migrations runs versioned schema migrations against the relay package's embedded bbolt key-value store, tracking the applied version in a dedicated bucket.

Jump to

Keyboard shortcuts

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