khatru

package module
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Dec 9, 2023 License: Unlicense Imports: 25 Imported by: 29

README

khatru, a relay framework docs badge

Run Tests Go Reference Go Report Card

Khatru makes it easy to write very very custom relays:

  • custom event or filter acceptance policies
  • custom AUTH handlers
  • custom storage and pluggable databases
  • custom webpages and other HTTP handlers

Here's a sample:

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"

	"github.com/fiatjaf/khatru"
	"github.com/nbd-wtf/go-nostr"
)

func main() {
	// create the relay instance
	relay := khatru.NewRelay()

	// set up some basic properties (will be returned on the NIP-11 endpoint)
	relay.Info.Name = "my relay"
	relay.Info.PubKey = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
	relay.Info.Description = "this is my custom relay"
	relay.Info.Icon = "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fliquipedia.net%2Fcommons%2Fimages%2F3%2F35%2FSCProbe.jpg&f=1&nofb=1&ipt=0cbbfef25bce41da63d910e86c3c343e6c3b9d63194ca9755351bb7c2efa3359&ipo=images"

	// you must bring your own storage scheme -- if you want to have any
	store := make(map[string]*nostr.Event, 120)

	// set up the basic relay functions
	relay.StoreEvent = append(relay.StoreEvent,
		func(ctx context.Context, event *nostr.Event) error {
			store[event.ID] = event
			return nil
		},
	)
	relay.QueryEvents = append(relay.QueryEvents,
		func(ctx context.Context, filter nostr.Filter) (chan *nostr.Event, error) {
			ch := make(chan *nostr.Event)
			go func() {
				for _, evt := range store {
					if filter.Matches(evt) {
						ch <- evt
					}
				}
				close(ch)
			}()
			return ch, nil
		},
	)
	relay.DeleteEvent = append(relay.DeleteEvent,
		func(ctx context.Context, event *nostr.Event) error {
			delete(store, event.ID)
			return nil
		},
	)

	// there are many other configurable things you can set
	relay.RejectEvent = append(relay.RejectEvent,
		func(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
			if event.PubKey == "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52" {
				return true, "we don't allow this person to write here"
			}
			return false, "" // anyone else can
		},
	)
	relay.OnConnect = append(relay.OnConnect,
		func(ctx context.Context) {
			// request NIP-42 AUTH from everybody
			relay.RequestAuth(ctx)
		},
	)
	relay.OnAuth = append(relay.OnAuth,
		func(ctx context.Context, pubkey string) {
			// and when they auth we just log that for nothing
			log.Println(pubkey + " is authed!")
		},
	)
	// check the docs for more goodies!

	mux := relay.Router()
	// set up other http handlers
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("content-type", "text/html")
		fmt.Fprintf(w, `<b>welcome</b> to my relay!`)
	})

	// start the server
	fmt.Println("running on :3334")
	http.ListenAndServe(":3334", relay)
}
But I don't want to write my own database!

Fear no more. Using the https://github.com/fiatjaf/eventstore module you get a bunch of compatible databases out of the box and you can just plug them into your relay. For example, sqlite:

	db := sqlite3.SQLite3Backend{DatabaseURL: "/tmp/khatru-sqlite-tmp"}
	if err := db.Init(); err != nil {
		panic(err)
	}

	relay.StoreEvent = append(relay.StoreEvent, db.SaveEvent)
	relay.QueryEvents = append(relay.QueryEvents, db.QueryEvents)
	relay.CountEvents = append(relay.CountEvents, db.CountEvents)
	relay.DeleteEvent = append(relay.DeleteEvent, db.DeleteEvent)

Documentation

Index

Constants

View Source
const (
	AUTH_CONTEXT_KEY = iota
	WS_KEY
)

Variables

This section is empty.

Functions

func GetAuthed

func GetAuthed(ctx context.Context) string

func GetIP added in v0.2.1

func GetIP(ctx context.Context) string

func GetListeningFilters

func GetListeningFilters() nostr.Filters

func GetOpenSubscriptions added in v0.2.1

func GetOpenSubscriptions(ctx context.Context) []nostr.Filter

Types

type Listener

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

type Relay

type Relay struct {
	ServiceURL string

	RejectEvent               []func(ctx context.Context, event *nostr.Event) (reject bool, msg string)
	RejectFilter              []func(ctx context.Context, filter nostr.Filter) (reject bool, msg string)
	RejectCountFilter         []func(ctx context.Context, filter nostr.Filter) (reject bool, msg string)
	OverwriteDeletionOutcome  []func(ctx context.Context, target *nostr.Event, deletion *nostr.Event) (acceptDeletion bool, msg string)
	OverwriteResponseEvent    []func(ctx context.Context, event *nostr.Event)
	OverwriteFilter           []func(ctx context.Context, filter *nostr.Filter)
	OverwriteCountFilter      []func(ctx context.Context, filter *nostr.Filter)
	OverwriteRelayInformation []func(ctx context.Context, r *http.Request, info nip11.RelayInformationDocument) nip11.RelayInformationDocument
	StoreEvent                []func(ctx context.Context, event *nostr.Event) error
	DeleteEvent               []func(ctx context.Context, event *nostr.Event) error
	QueryEvents               []func(ctx context.Context, filter nostr.Filter) (chan *nostr.Event, error)
	CountEvents               []func(ctx context.Context, filter nostr.Filter) (int64, error)
	OnAuth                    []func(ctx context.Context, pubkey string)
	OnConnect                 []func(ctx context.Context)
	OnDisconnect              []func(ctx context.Context)
	OnEventSaved              []func(ctx context.Context, event *nostr.Event)

	// editing info will affect
	Info *nip11.RelayInformationDocument

	// Default logger, as set by NewServer, is a stdlib logger prefixed with "[khatru-relay] ",
	// outputting to stderr.
	Log *log.Logger

	// in case you call Server.Start
	Addr string

	// websocket options
	WriteWait      time.Duration // Time allowed to write a message to the peer.
	PongWait       time.Duration // Time allowed to read the next pong message from the peer.
	PingPeriod     time.Duration // Send pings to peer with this period. Must be less than pongWait.
	MaxMessageSize int64         // Maximum message size allowed from peer.
	// contains filtered or unexported fields
}

func NewRelay

func NewRelay() *Relay

func (*Relay) AddEvent

func (rl *Relay) AddEvent(ctx context.Context, evt *nostr.Event) error

func (*Relay) HandleNIP11

func (rl *Relay) HandleNIP11(w http.ResponseWriter, r *http.Request)

func (*Relay) HandleWebsocket

func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request)

func (*Relay) Router

func (rl *Relay) Router() *http.ServeMux

func (*Relay) ServeHTTP

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

ServeHTTP implements http.Handler interface.

func (*Relay) Shutdown

func (rl *Relay) Shutdown(ctx context.Context)

Shutdown sends a websocket close control message to all connected clients.

func (*Relay) Start

func (rl *Relay) Start(host string, port int, started ...chan bool) error

Start creates an http server and starts listening on given host and port.

type WebSocket

type WebSocket struct {

	// original request
	Request *http.Request

	// nip42
	Challenge       string
	AuthedPublicKey string
	Authed          chan struct{}
	// contains filtered or unexported fields
}

func GetConnection

func GetConnection(ctx context.Context) *WebSocket

func (*WebSocket) WriteJSON

func (ws *WebSocket) WriteJSON(any any) error

func (*WebSocket) WriteMessage

func (ws *WebSocket) WriteMessage(t int, b []byte) error

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

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