clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

feat/standalone-relay
tinyrelay / internal / replication / delivery.go
delivery.go 3.0K
1package replication23import (4 "context"5 "errors"6 "fmt"78 "github.com/FelineStateMachine/tinyrelay/internal/event"9 "github.com/FelineStateMachine/tinyrelay/internal/policy"10 "github.com/FelineStateMachine/tinyrelay/internal/storage"11)1213type DeliveryHandler func(context.Context, storage.Intent) error1415func NewDeliveryHandler(store *storage.Store, transport DeliveryTransport) DeliveryHandler {16 return func(ctx context.Context, intent storage.Intent) error {17 return deliver(ctx, store, transport, intent)18 }19}2021func deliver(ctx context.Context, store *storage.Store, transport DeliveryTransport, intent storage.Intent) error {22 if err := ValidateIntent(intent); err != nil {23 return err24 }25 result, err := store.Query(ctx, event.Filter{IDs: []string{intent.EventID}, Tags: map[string][]string{}}, storage.QueryOptions{Now: 0, Access: storage.Access{All: true}, Limit: 1})26 if err != nil {27 return fmt.Errorf("replication: load delivery event: %w", err)28 }29 if len(result.Events) == 0 {30 return nil31 }32 e := result.Events[0]33 private, privateErr := privateRepository(ctx, store, e)34 if privateErr != nil {35 return fmt.Errorf("replication: check repository privacy: %w", privateErr)36 }37 if privateKind(e.Kind) || hasProtectedTag(e) || private {38 return nil39 }40 if e.Kind != 10002 {41 if relayList, err := authorRelayList(ctx, store, e); err != nil {42 return fmt.Errorf("replication: load author relay list: %w", err)43 } else if relayList != nil {44 if err := sendAccepted(ctx, transport, intent.Target, *relayList); err != nil {45 return err46 }47 }48 }49 return sendAccepted(ctx, transport, intent.Target, e)50}5152func privateRepository(ctx context.Context, store *storage.Store, e event.Event) (bool, error) {53 return policy.PrivateRepository(ctx, store, e)54}5556func authorRelayList(ctx context.Context, store *storage.Store, e event.Event) (*event.Event, error) {57 result, err := store.Query(ctx, event.Filter{Authors: []string{e.PubKey}, Kinds: []int{10002}, Tags: map[string][]string{}}, storage.QueryOptions{Now: 0, Access: storage.Access{All: true}, Limit: 1})58 if err != nil {59 return nil, err60 }61 for _, candidate := range result.Events {62 if candidate.Kind == 10002 && !privateKind(candidate.Kind) && !hasProtectedTag(candidate) {63 return &candidate, nil64 }65 }66 return nil, nil67}6869func sendAccepted(ctx context.Context, transport DeliveryTransport, target string, e event.Event) error {70 response, err := transport.Send(ctx, target, e)71 if err != nil {72 return fmt.Errorf("replication: send event: %w", err)73 }74 if response.Accepted || isDuplicate(response.Message) {75 return nil76 }77 message := response.Message78 if message == "" {79 message = "target rejected event"80 }81 return errors.New(message)82}8384func hasProtectedTag(e event.Event) bool {85 for _, tag := range e.Tags {86 if len(tag) > 0 && tag[0] == "-" {87 return true88 }89 }90 return false91}9293func isDuplicate(message string) bool {94 const prefix = "duplicate:"95 return len(message) >= len(prefix) && message[:len(prefix)] == prefix96}