2022-01-02 08:44:18 -03:00
|
|
|
package nostr
|
2021-01-31 11:05:09 -03:00
|
|
|
|
|
|
|
import (
|
2021-02-20 17:44:05 -03:00
|
|
|
"crypto/rand"
|
|
|
|
"encoding/hex"
|
2021-01-31 11:05:09 -03:00
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2021-02-07 07:56:55 -03:00
|
|
|
"fmt"
|
2021-01-31 11:05:09 -03:00
|
|
|
"log"
|
2021-02-20 17:44:05 -03:00
|
|
|
"time"
|
2021-01-31 11:05:09 -03:00
|
|
|
|
2021-12-16 20:47:53 -03:00
|
|
|
"github.com/fiatjaf/bip340"
|
2021-01-31 11:05:09 -03:00
|
|
|
"github.com/gorilla/websocket"
|
|
|
|
)
|
|
|
|
|
2022-01-02 08:44:18 -03:00
|
|
|
const (
|
|
|
|
PublishStatusSent = 0
|
|
|
|
PublishStatusFailed = -1
|
|
|
|
PublishStatusSucceeded = 1
|
|
|
|
)
|
|
|
|
|
|
|
|
type PublishStatus struct {
|
|
|
|
Relay string
|
|
|
|
Status int
|
|
|
|
}
|
|
|
|
|
2021-01-31 11:05:09 -03:00
|
|
|
type RelayPool struct {
|
|
|
|
SecretKey *string
|
|
|
|
|
2022-01-02 08:44:18 -03:00
|
|
|
Relays map[string]RelayPoolPolicy
|
2022-01-12 10:54:45 -04:00
|
|
|
websockets map[string]*Connection
|
2021-02-20 17:44:05 -03:00
|
|
|
subscriptions map[string]*Subscription
|
2021-01-31 11:05:09 -03:00
|
|
|
|
|
|
|
Notices chan *NoticeMessage
|
|
|
|
}
|
|
|
|
|
2022-01-02 08:50:53 -03:00
|
|
|
type RelayPoolPolicy interface {
|
2022-02-08 16:27:33 -03:00
|
|
|
ShouldRead(Filters) bool
|
2022-01-02 08:50:53 -03:00
|
|
|
ShouldWrite(*Event) bool
|
2021-01-31 11:05:09 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
type SimplePolicy struct {
|
|
|
|
Read bool
|
|
|
|
Write bool
|
|
|
|
}
|
|
|
|
|
2022-02-08 16:27:33 -03:00
|
|
|
func (s SimplePolicy) ShouldRead(_ Filters) bool {
|
2022-01-02 08:50:53 -03:00
|
|
|
return s.Read
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s SimplePolicy) ShouldWrite(_ *Event) bool {
|
|
|
|
return s.Write
|
|
|
|
}
|
|
|
|
|
2021-01-31 11:05:09 -03:00
|
|
|
type NoticeMessage struct {
|
|
|
|
Message string
|
|
|
|
Relay string
|
|
|
|
}
|
|
|
|
|
|
|
|
// New creates a new RelayPool with no relays in it
|
2022-01-02 08:44:18 -03:00
|
|
|
func NewRelayPool() *RelayPool {
|
2021-01-31 11:05:09 -03:00
|
|
|
return &RelayPool{
|
2022-01-02 08:44:18 -03:00
|
|
|
Relays: make(map[string]RelayPoolPolicy),
|
2022-01-12 10:54:45 -04:00
|
|
|
websockets: make(map[string]*Connection),
|
2021-02-20 18:26:26 -03:00
|
|
|
subscriptions: make(map[string]*Subscription),
|
2021-01-31 11:05:09 -03:00
|
|
|
|
|
|
|
Notices: make(chan *NoticeMessage),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add adds a new relay to the pool, if policy is nil, it will be a simple
|
|
|
|
// read+write policy.
|
2022-01-02 08:50:53 -03:00
|
|
|
func (r *RelayPool) Add(url string, policy RelayPoolPolicy) error {
|
2021-01-31 11:05:09 -03:00
|
|
|
if policy == nil {
|
2022-01-02 08:50:53 -03:00
|
|
|
policy = SimplePolicy{Read: true, Write: true}
|
2021-01-31 11:05:09 -03:00
|
|
|
}
|
|
|
|
|
2022-01-02 08:44:18 -03:00
|
|
|
nm := NormalizeURL(url)
|
2021-01-31 11:05:09 -03:00
|
|
|
if nm == "" {
|
2021-02-07 07:56:55 -03:00
|
|
|
return fmt.Errorf("invalid relay URL '%s'", url)
|
2021-01-31 11:05:09 -03:00
|
|
|
}
|
|
|
|
|
2022-01-12 10:54:45 -04:00
|
|
|
socket, _, err := websocket.DefaultDialer.Dial(NormalizeURL(url), nil)
|
2021-01-31 11:05:09 -03:00
|
|
|
if err != nil {
|
2021-02-07 07:56:55 -03:00
|
|
|
return fmt.Errorf("error opening websocket to '%s': %w", nm, err)
|
2021-01-31 11:05:09 -03:00
|
|
|
}
|
|
|
|
|
2022-01-12 10:54:45 -04:00
|
|
|
conn := NewConnection(socket)
|
|
|
|
|
2022-01-02 08:50:53 -03:00
|
|
|
r.Relays[nm] = policy
|
2021-01-31 11:05:09 -03:00
|
|
|
r.websockets[nm] = conn
|
|
|
|
|
2021-02-20 17:44:05 -03:00
|
|
|
for _, sub := range r.subscriptions {
|
|
|
|
sub.addRelay(nm, conn)
|
|
|
|
}
|
|
|
|
|
2021-01-31 11:05:09 -03:00
|
|
|
go func() {
|
|
|
|
for {
|
2022-01-12 10:54:45 -04:00
|
|
|
typ, message, err := conn.socket.ReadMessage()
|
2021-01-31 11:05:09 -03:00
|
|
|
if err != nil {
|
|
|
|
log.Println("read error: ", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if typ == websocket.PingMessage {
|
|
|
|
conn.WriteMessage(websocket.PongMessage, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
if typ != websocket.TextMessage || len(message) == 0 || message[0] != '[' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-02-20 17:44:05 -03:00
|
|
|
var jsonMessage []json.RawMessage
|
|
|
|
err = json.Unmarshal(message, &jsonMessage)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(jsonMessage) < 2 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var label string
|
|
|
|
json.Unmarshal(jsonMessage[0], &label)
|
|
|
|
|
|
|
|
switch label {
|
|
|
|
case "NOTICE":
|
|
|
|
var content string
|
|
|
|
json.Unmarshal(jsonMessage[1], &content)
|
|
|
|
r.Notices <- &NoticeMessage{
|
|
|
|
Relay: nm,
|
|
|
|
Message: content,
|
|
|
|
}
|
|
|
|
case "EVENT":
|
|
|
|
if len(jsonMessage) < 3 {
|
2021-01-31 11:05:09 -03:00
|
|
|
continue
|
|
|
|
}
|
2021-02-20 17:44:05 -03:00
|
|
|
|
|
|
|
var channel string
|
|
|
|
json.Unmarshal(jsonMessage[1], &channel)
|
|
|
|
if subscription, ok := r.subscriptions[channel]; ok {
|
2022-01-02 08:44:18 -03:00
|
|
|
var event Event
|
2021-02-20 17:44:05 -03:00
|
|
|
json.Unmarshal(jsonMessage[2], &event)
|
2021-12-16 20:47:53 -03:00
|
|
|
|
|
|
|
// check signature of all received events, ignore invalid
|
2021-02-20 17:44:05 -03:00
|
|
|
ok, _ := event.CheckSignature()
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-12-16 20:47:53 -03:00
|
|
|
// check if the event matches the desired filter, ignore otherwise
|
|
|
|
if !subscription.filters.Match(&event) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-02-07 11:33:45 -03:00
|
|
|
if !subscription.stopped {
|
|
|
|
subscription.Events <- EventMessage{
|
|
|
|
Relay: nm,
|
|
|
|
Event: event,
|
|
|
|
}
|
2021-02-20 17:44:05 -03:00
|
|
|
}
|
|
|
|
}
|
2021-01-31 11:05:09 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
2021-02-07 07:56:55 -03:00
|
|
|
|
|
|
|
return nil
|
2021-01-31 11:05:09 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Remove removes a relay from the pool.
|
|
|
|
func (r *RelayPool) Remove(url string) {
|
2022-01-02 08:44:18 -03:00
|
|
|
nm := NormalizeURL(url)
|
2021-02-20 17:44:05 -03:00
|
|
|
|
|
|
|
for _, sub := range r.subscriptions {
|
|
|
|
sub.removeRelay(nm)
|
|
|
|
}
|
2021-01-31 11:05:09 -03:00
|
|
|
if conn, ok := r.websockets[nm]; ok {
|
|
|
|
conn.Close()
|
|
|
|
}
|
2021-02-20 17:44:05 -03:00
|
|
|
|
2021-01-31 11:05:09 -03:00
|
|
|
delete(r.Relays, nm)
|
|
|
|
delete(r.websockets, nm)
|
|
|
|
}
|
|
|
|
|
2022-02-08 16:27:33 -03:00
|
|
|
func (r *RelayPool) Sub(filters Filters) *Subscription {
|
2021-02-20 17:44:05 -03:00
|
|
|
random := make([]byte, 7)
|
|
|
|
rand.Read(random)
|
2021-01-31 11:05:09 -03:00
|
|
|
|
2022-01-04 09:56:53 -04:00
|
|
|
subscription := Subscription{filters: filters}
|
2021-02-20 17:44:05 -03:00
|
|
|
subscription.channel = hex.EncodeToString(random)
|
2022-01-12 10:54:45 -04:00
|
|
|
subscription.relays = make(map[string]*Connection)
|
2021-02-20 17:44:05 -03:00
|
|
|
for relay, policy := range r.Relays {
|
2022-01-02 08:50:53 -03:00
|
|
|
if policy.ShouldRead(filters) {
|
2021-02-20 17:44:05 -03:00
|
|
|
ws := r.websockets[relay]
|
|
|
|
subscription.relays[relay] = ws
|
2021-02-07 07:56:55 -03:00
|
|
|
}
|
2021-01-31 11:05:09 -03:00
|
|
|
}
|
2021-02-20 17:44:05 -03:00
|
|
|
subscription.Events = make(chan EventMessage)
|
2022-01-02 08:44:18 -03:00
|
|
|
subscription.UniqueEvents = make(chan Event)
|
2021-02-20 17:44:05 -03:00
|
|
|
r.subscriptions[subscription.channel] = &subscription
|
2021-01-31 11:05:09 -03:00
|
|
|
|
2022-01-04 09:56:53 -04:00
|
|
|
subscription.Sub()
|
2021-02-20 17:44:05 -03:00
|
|
|
return &subscription
|
2021-01-31 11:05:09 -03:00
|
|
|
}
|
|
|
|
|
2022-01-02 08:44:18 -03:00
|
|
|
func (r *RelayPool) PublishEvent(evt *Event) (*Event, chan PublishStatus, error) {
|
2021-02-20 18:54:34 -03:00
|
|
|
status := make(chan PublishStatus, 1)
|
2021-01-31 11:05:09 -03:00
|
|
|
|
2021-12-16 20:47:53 -03:00
|
|
|
if r.SecretKey == nil && (evt.PubKey == "" || evt.Sig == "") {
|
2021-02-20 17:44:05 -03:00
|
|
|
return nil, status, errors.New("PublishEvent needs either a signed event to publish or to have been configured with a .SecretKey.")
|
2021-01-31 11:05:09 -03:00
|
|
|
}
|
|
|
|
|
2021-12-16 20:47:53 -03:00
|
|
|
if evt.PubKey == "" {
|
|
|
|
secretKeyN, err := bip340.ParsePrivateKey(*r.SecretKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, status, fmt.Errorf("The pool's global SecretKey is invalid: %w", err)
|
|
|
|
}
|
|
|
|
evt.PubKey = fmt.Sprintf("%x", bip340.GetPublicKey(secretKeyN))
|
|
|
|
}
|
|
|
|
|
2021-01-31 11:05:09 -03:00
|
|
|
if evt.Sig == "" {
|
2021-02-07 07:56:55 -03:00
|
|
|
err := evt.Sign(*r.SecretKey)
|
|
|
|
if err != nil {
|
2021-02-20 17:44:05 -03:00
|
|
|
return nil, status, fmt.Errorf("Error signing event: %w", err)
|
2021-02-07 07:56:55 -03:00
|
|
|
}
|
2021-01-31 11:05:09 -03:00
|
|
|
}
|
|
|
|
|
2021-02-20 17:44:05 -03:00
|
|
|
for relay, conn := range r.websockets {
|
2022-01-02 08:50:53 -03:00
|
|
|
if !r.Relays[relay].ShouldWrite(evt) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-01-12 10:54:45 -04:00
|
|
|
go func(relay string, conn *Connection) {
|
2021-02-20 18:29:30 -03:00
|
|
|
err := conn.WriteJSON([]interface{}{"EVENT", evt})
|
2021-02-20 17:44:05 -03:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("error sending event to '%s': %s", relay, err.Error())
|
|
|
|
status <- PublishStatus{relay, PublishStatusFailed}
|
|
|
|
}
|
|
|
|
status <- PublishStatus{relay, PublishStatusSent}
|
|
|
|
|
2022-02-08 16:27:33 -03:00
|
|
|
subscription := r.Sub(Filters{Filter{IDs: []string{evt.ID}}})
|
2021-02-20 18:54:34 -03:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case event := <-subscription.UniqueEvents:
|
|
|
|
if event.ID == evt.ID {
|
|
|
|
status <- PublishStatus{relay, PublishStatusSucceeded}
|
|
|
|
break
|
|
|
|
} else {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
case <-time.After(5 * time.Second):
|
|
|
|
break
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
2021-02-20 17:44:05 -03:00
|
|
|
subscription.Unsub()
|
2021-02-20 18:54:34 -03:00
|
|
|
close(status)
|
2021-02-20 17:44:05 -03:00
|
|
|
}(relay, conn)
|
2021-01-31 11:05:09 -03:00
|
|
|
}
|
|
|
|
|
2021-02-20 17:44:05 -03:00
|
|
|
return evt, status, nil
|
2021-01-31 11:05:09 -03:00
|
|
|
}
|