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
- Variables
- func CheckSelfAuthored(ev *nip01.Event, selfPubkey string) (ok bool, message string)
- func IsPacketError(err error) bool
- func NewNIP05Handler(store *EventStore, cfg *nip11.Metadata) http.Handler
- func RegisterEventValidator(kind int, validator EventValidator)
- func RegisterLetteredNIP(s string)
- func RegisterNIP(n int)
- func RegisteredNIPs() []nip11.NIPID
- type AuthedIdentity
- type CacheHandler
- type ClientInfo
- type DelegationConfig
- type EventDeleteTask
- type EventInsertTask
- type EventStore
- func (s *EventStore) ClearZapIndex(ctx context.Context) error
- func (s *EventStore) Close()
- func (s *EventStore) ConsumeInviteClaim(code string, now int64) (*InviteClaim, error)
- func (s *EventStore) CountEvents(ctx context.Context, filters *nip01.SubscriptionFilterGroup) (int64, error)
- func (s *EventStore) Db() *bolt.DB
- func (s *EventStore) DeleteAll(pes []*PotentialEvent) error
- func (s *EventStore) DeleteInviteClaim(code string) error
- func (s *EventStore) Execute(ctx context.Context, task Task)
- func (s *EventStore) ExecuteBatch(tasks []Task)
- func (s *EventStore) FetchAll() ([]*nip01.Event, error)
- func (s *EventStore) FetchProfileEvents(ctx context.Context, pubkeys []string) ([]*nip01.Event, error)
- func (s *EventStore) FindEvent(evsid uint64) (*nip01.Event, error)
- func (s *EventStore) FindEventBytes(evsid uint64) ([]byte, error)
- func (s *EventStore) FindEvents(ctx context.Context, filter *nip01.SubscriptionFilter) ([]*PotentialEvent, error)
- func (s *EventStore) GetInviteClaim(code string) (*InviteClaim, error)
- func (s *EventStore) GetMember(pubkey string) (*MemberRecord, error)
- func (s *EventStore) GetProfileMetrics(pubkey string) (*ProfileMetrics, error)
- func (s *EventStore) GetTopZapped(ctx context.Context, since, until uint64, limit int) ([]ZapStats, error)
- func (s *EventStore) IndexZap(tx *bolt.Tx, event *nip01.Event, evsid uint64) error
- func (s *EventStore) InsertEvents(ctx context.Context, events []*nip01.Event) error
- func (s *EventStore) ListInviteClaims() ([]*InviteClaim, error)
- func (s *EventStore) ListMemberRecords() ([]*MemberRecord, error)
- func (s *EventStore) ListMembers() ([]string, error)
- func (s *EventStore) Name() string
- func (s *EventStore) PutInviteClaim(claim *InviteClaim) error
- func (s *EventStore) PutMember(rec *MemberRecord) error
- func (s *EventStore) QueryEvents(ctx context.Context, filter *nip01.SubscriptionFilter) ([]*nip01.Event, error)
- func (s *EventStore) QueryNip77Items(ctx context.Context, filter *nip01.SubscriptionFilter) ([]nip77.Item, error)
- func (s *EventStore) ReindexZaps(ctx context.Context, progressCb func(count int)) (int, error)
- func (s *EventStore) RemoveMember(pubkey string) error
- func (s *EventStore) UpdateProfileMetrics(pubkey string, modifier func(*ProfileMetrics)) error
- type EventStoreConfig
- type EventStoreOption
- type EventValidator
- type InviteClaim
- type MemberRecord
- type MembershipRequestHandler
- type MembershipService
- func (m *MembershipService) Get(pubkey string) (*MemberRecord, error)
- func (m *MembershipService) HandleEvent(ctx context.Context, s *Session, ev *nip01.Event)
- func (m *MembershipService) IsMember(pubkey string) bool
- func (m *MembershipService) IssueInvite(ttl time.Duration, maxUses int, roles []string) (*InviteClaim, error)
- func (m *MembershipService) Join(pubkey string, roles []string) error
- func (m *MembershipService) Leave(pubkey string) error
- func (m *MembershipService) List() ([]*MemberRecord, error)
- func (m *MembershipService) LoadFromStore() error
- func (m *MembershipService) ReplaceFromEvent(ev *nip01.Event) error
- type MembershipStatus
- type NIP50Handler
- type PotentialEvent
- type ProfileMetrics
- type ProfileVerificationWorker
- type Relay
- type RequestHandler
- type Session
- func (s *Session) AuthedPubkey() string
- func (s *Session) Close()
- func (s *Session) HasMembership() bool
- func (s *Session) HasOwnerIdentity(owner string) bool
- func (s *Session) ID() int64
- func (s *Session) Identities() []AuthedIdentity
- func (s *Session) IdentityMembership(pubkey string) (AuthedIdentity, bool)
- func (s *Session) Info() *ClientInfo
- func (s *Session) ProcessPacket(ctx context.Context, p wire.Packet) error
- func (s *Session) Recv(ctx context.Context) error
- func (s *Session) Start(parent context.Context) error
- type SessionConfig
- type SessionContext
- type SessionHandler
- type SessionOption
- func WithLogger(logger zerolog.Logger) SessionOption
- func WithSessionAgentAuth(enabled bool, freshnessWindow time.Duration, kindEnforcement bool) SessionOption
- func WithSessionAllowedOrigins(origins ...string) SessionOption
- func WithSessionBufferSize(size int) SessionOption
- func WithSessionCacheConfig(window time.Duration, limit int) SessionOption
- func WithSessionCloseGrace(delta time.Duration) SessionOption
- func WithSessionConfig(cfg SessionConfig) SessionOption
- func WithSessionDelegation(d *DelegationConfig) SessionOption
- func WithSessionMaxConcurrentTasks(limit int) SessionOption
- func WithSessionMembership(inviteTTL time.Duration, inviteMaxUses int, publishAddRemove bool) SessionOption
- func WithSessionPingConfig(pingInterval, pongTimeout time.Duration) SessionOption
- func WithSessionPrivKey(key string) SessionOption
- func WithSessionSearchLimit(limit int) SessionOption
- func WithSessionTopZapped(enabled bool) SessionOption
- func WithSessionWriteTimeouts(control, data time.Duration) SessionOption
- type StandardRequestHandler
- type StoreQuery
- type Subscription
- type SubscriptionsMap
- type Task
- type VerificationJob
- type ZapStats
Constants ¶
const ( // Cache directives CacheField = "cache" CacheActionTopZap = "top-zapped" // NIP-50 constants ImpossibleID = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" )
Variables ¶
var ( ErrSessionClosed = errors.New("session closed") ErrRateLimited = errors.New("rate-limited: too many concurrent tasks") )
var ( ErrEventDuplicated = errors.New("already have this event") ErrStoreClosed = errors.New("store is closed") ErrEventNotFound = errors.New("event not found") )
var ( ErrInviteClaimNotFound = errors.New("relay: invite claim not found") ErrInviteClaimExpired = errors.New("relay: invite claim expired") ErrInviteClaimExhausted = errors.New("relay: invite claim already used") )
var (
ErrSubscriptionClosed = errors.New("subscription closed")
)
Functions ¶
func CheckSelfAuthored ¶ added in v0.2.3
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 ¶
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 ¶
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 DelegationConfig ¶
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) ExecuteBatch ¶
func (s *EventStore) ExecuteBatch(tasks []Task)
ExecuteBatch commits multiple tasks in a single transaction
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) 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 ¶
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 (*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 ¶
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 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
func (h *MembershipRequestHandler) Handle(ctx context.Context, s *Session, rp *wire.RequestPacket) (bool, error)
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
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 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
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.
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 (*Session) AuthedPubkey ¶ added in v0.2.2
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) HasMembership ¶ added in v0.2.3
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
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) 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 ¶
ProcessPacket dispatches the packet to the appropriate handler.
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 ¶
func (h *StandardRequestHandler) Handle(ctx context.Context, s *Session, rp *wire.RequestPacket) (bool, error)
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 ¶
Source Files
¶
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. |